Skip to content

feat: scaffold Bonbon (AWS Account Access Manager) connector path#121

Draft
btipling wants to merge 1 commit into
mainfrom
bt/baton-bonbon-scaffold
Draft

feat: scaffold Bonbon (AWS Account Access Manager) connector path#121
btipling wants to merge 1 commit into
mainfrom
bt/baton-bonbon-scaffold

Conversation

@btipling

Copy link
Copy Markdown
Contributor

Summary

  • Adds an opt-in, plain-Go path for AWS Account Access Manager (codename: Bonbon) alongside the existing IAM / IdC / Orgs syncs in baton-aws. Two resource types — bonbon_application (TRAIT_APP) and bonbon_role (TRAIT_ROLE) — both annotated OptInRequired{} because the AWS service itself is still in private preview.
  • Hand-rolls a SigV4 REST/JSON client (pkg/connector/bonbon/client.go) against the 11 documented account-access operations because the service is not in aws-sdk-go-v2. Reuses the existing baton-aws AssumeRole + external-id credential chain — operators already onboarded for baton-aws do not need new auth wiring.
  • Gated end-to-end by --global-bonbon-enabled (default false). Region is restricted at parse time to us-east-1 / us-west-2; outside-set values are rejected with an explicit "private preview region" error. Grants on bonbon_role map to CreateEntitlement / DeleteEntitlement with idempotent semantics on both ends.
  • In-memory testserver (test/bonbon-testserver/) serves the 11 routes against a seeded application + entitlement graph. TestBonbonFullSync and TestBonbonGrantRevoke drive the production builders end-to-end through SigV4 → HTTP → JSON.

Architecture note

The plan favored a standalone ConductorOne/baton-bonbon repo. The current scaffold lives under baton-aws/pkg/connector/bonbon/ because the gh App installation cannot create repos under the ConductorOne org — re-confirmed at scaffold time. The directory is self-contained and copy-movable to a new repo with a single import-path rename when a maintainer creates the repo.

Test Plan

  • gofmt -l ./... clean across new and touched files
  • go vet ./... clean
  • go test ./...TestBonbonFullSync, TestBonbonGrantRevoke, TestValidateRegion, plus added region-validation cases in TestConfigs
  • golangci-lint run clean
  • Manual smoke against a real Bonbon-enabled account (private preview) — requires a Bonbon-enabled test account; documented in docs/bonbon.md. Connector remains DRAFT until this is done.

Risks

  • The SigV4 path is exercised end-to-end by tests but the testserver does not validate signatures. A real-account smoke is the only signal that the signing scope (account-access / region) is correct.
  • AlreadyCreatedException HTTP status is not pinned by the service-2.json. The error parser reads __type / X-Amzn-ErrorType rather than relying on status, but the actual status code wants real-account confirmation before GA.
  • connectorbuilder.NewSyncTestRunner referenced in the plan is not in the currently vendored baton-sdk — direct builder-method tests are used instead, matching the existing sso_group_test.go pattern in baton-aws. Open question for the eventual move to a standalone repo.

Open questions

  1. Repo home. Plan recommends a standalone baton-bonbon repo. Blocked at scaffold time by gh App permissions; the directory is self-contained for migration once a maintainer creates the repo.
  2. AlreadyCreatedException HTTP code. Service-2.json declares the shape but does not pin a status. Parser reads __type / header; real-account verification wanted before GA.
  3. Entitlement tags. Bonbon supports tags on entitlements. Scaffold ignores them; product-shaped open question.

🛰️ Built with stargate.

Adds an opt-in, plain-Go path for AWS Account Access Manager
(codename: Bonbon) alongside the existing IAM / IdC / Orgs syncs. The
service ships only as REST/JSON over SigV4 and is not in
aws-sdk-go-v2, so the path hand-rolls the request/response shapes
plus an aws/signer/v4 client and reuses the existing AssumeRole +
external-id credential chain — operators already onboarded for
baton-aws do not need new auth wiring.

The two resource types (bonbon_application, bonbon_role) are both
annotated OptInRequired{} because the service itself is still in
private preview; the connector is also gated by
--global-bonbon-enabled and the region is constrained to the
preview regions (us-east-1, us-west-2). Grants on bonbon_role map
to CreateEntitlement / DeleteEntitlement with idempotent semantics
on both ends.

In-memory testserver covers the 11 documented operations against a
seeded application + entitlement graph; TestBonbonFullSync and
TestBonbonGrantRevoke drive the production builders end-to-end. The
plan's reference to connectorbuilder.NewSyncTestRunner is not
available in the currently vendored baton-sdk — left as an open
question for the follow-up move to a standalone baton-bonbon repo.
Comment on lines +302 to +315
// applicationsForProvisioning lists applications via a live API call —
// Grant/Revoke do not receive a SyncOpAttrs and therefore have no session to
// read from. CreateEntitlement/DeleteEntitlement run rarely enough that the
// extra pagination is acceptable.
func (o *roleResourceType) applicationsForProvisioning(ctx context.Context) ([]string, error) {
resp, err := o.client.ListApplications(ctx, &ListApplicationsRequest{})
if err != nil {
return nil, WrapForRetry(fmt.Errorf("bonbon: ListApplications (provision): %w", err))
}
out := make([]string, 0, len(resp.Applications))
for _, a := range resp.Applications {
out = append(out, a.ApplicationArn)
}
return out, nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Bug: applicationsForProvisioning only fetches the first page from ListApplications — if resp.NextToken is non-empty, subsequent applications are silently dropped. Grant/Revoke operations would fail with "no Bonbon application discovered" for any application beyond the first page. Paginate until NextToken is empty, matching the pattern in walkEntitlements.

Comment on lines +71 to +90
func (o *roleResourceType) walkEntitlements(ctx context.Context, applicationArn string) ([]EntitlementSummary, error) {
out := make([]EntitlementSummary, 0)
var nextToken string
for {
resp, err := o.client.ListEntitlements(ctx, &ListEntitlementsRequest{
ApplicationArn: applicationArn,
Filter: EntitlementFilter{},
NextToken: nextToken,
})
if err != nil {
return nil, WrapForRetry(fmt.Errorf("bonbon: ListEntitlements: %w", err))
}
out = append(out, resp.Entitlements...)
if resp.NextToken == "" {
break
}
nextToken = resp.NextToken
}
return out, nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: walkEntitlements buffers all pages of entitlements into memory inside the sync path (called from List and Grants). For large entitlement sets this risks OOM and bypasses SDK-driven pagination checkpointing and context cancellation. The session caching mitigates repeat calls, but the initial walk still loads everything at once. Consider restructuring so the SDK drives pagination one page at a time, or at minimum add a ctx.Err() check inside the loop.

Comment on lines +125 to +128
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("bonbon: http: %w", err)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: When httpClient.Do returns both a non-nil response and an error (e.g. redirect policy violations), the response body leaks. Add a nil check before returning:

Suggested change
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("bonbon: http: %w", err)
}
resp, err := c.httpClient.Do(req)
if err != nil {
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
return fmt.Errorf("bonbon: http: %w", err)
}

Comment on lines +31 to +34
awsCfg, err := c.getCallingConfig(ctx, c.globalRegion)
if err != nil {
l.Error("baton-aws: bonbon disabled — failed to load AWS calling config", zap.Error(err))
return nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: When the user explicitly sets --global-bonbon-enabled=true, silently returning nil here makes credential/config errors invisible at sync time — the bonbon resource types simply vanish from the output. Consider returning an error so the connector fails visibly rather than producing a partial sync that omits the opted-in resource types.

@github-actions

Copy link
Copy Markdown
Contributor

Connector PR Review: feat: scaffold Bonbon (AWS Account Access Manager) connector path

Blocking Issues: 1 | Suggestions: 3 | Threads Resolved: 0
Review mode: full
View review run

Review Summary

New opt-in Bonbon (AWS Account Access Manager) connector path adding two resource types (bonbon_application, bonbon_role), a hand-rolled SigV4 REST client, provisioning via CreateEntitlement/DeleteEntitlement, and an in-memory testserver. The implementation is well-structured with good idempotency handling and session caching. One pagination bug in the provisioning path needs fixing; three additional suggestions for robustness.

Security Issues

None found.

Correctness Issues

  • pkg/connector/bonbon/role.go:302-315applicationsForProvisioning only fetches the first page of ListApplications, silently dropping any applications beyond page 1. Grant/Revoke operations would fail for those applications.

Suggestions

  • pkg/connector/bonbon/role.go:71-90walkEntitlements buffers all entitlement pages into memory inside the sync path; risks OOM for large datasets and bypasses SDK pagination checkpointing.
  • pkg/connector/bonbon/client.go:125-128 — Response body leak when httpClient.Do returns both a non-nil response and an error (e.g. redirect policy violations).
  • pkg/connector/bonbon_wiring.go:31-34 — Silently disables bonbon on credential/config errors when the user explicitly enabled it; partial sync with missing resource types may be confusing.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Correctness Issues

In `pkg/connector/bonbon/role.go`:
- Around lines 302-315: `applicationsForProvisioning` only calls `ListApplications` once and
  does not paginate. If `resp.NextToken` is non-empty, applications beyond the first page are
  silently dropped. Fix by adding a pagination loop that continues calling `ListApplications`
  with the returned `NextToken` until it is empty, matching the pattern used in `walkEntitlements`
  (lines 71-90 of the same file).

## Suggestions

In `pkg/connector/bonbon/role.go`:
- Around lines 71-90: `walkEntitlements` fetches all pages of entitlements into memory inside
  a loop called from `List()` and `Grants()`. At minimum, add a `ctx.Err()` check inside the
  loop body (after each `ListEntitlements` call) so the sync engine can cancel a long-running
  walk. Ideally, restructure so the SDK drives pagination one page at a time.

In `pkg/connector/bonbon/client.go`:
- Around lines 125-128: After `c.httpClient.Do(req)`, if `err != nil`, check whether `resp`
  is also non-nil and close `resp.Body` before returning. Replace the error block with:
  `if err != nil { if resp != nil && resp.Body != nil { resp.Body.Close() }; return ... }`.

In `pkg/connector/bonbon_wiring.go`:
- Around lines 31-34 and 52-54: When `bc.Enabled` is true but `getCallingConfig` or
  `bonbon.ResourceSyncers` returns an error, the function logs the error and returns nil.
  This silently drops the bonbon resource types from the sync. Consider returning an error
  from `bonbonResourceSyncers` and propagating it in `ResourceSyncers` so the connector
  fails visibly when an explicitly enabled feature cannot initialize.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issues found — see review comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant