Skip to content

feat(credentials): issue service principal client secrets#50

Open
c1-squire-dev[bot] wants to merge 11 commits into
mainfrom
paul.querna/credential-issuer-databricks
Open

feat(credentials): issue service principal client secrets#50
c1-squire-dev[bot] wants to merge 11 commits into
mainfrom
paul.querna/credential-issuer-databricks

Conversation

@c1-squire-dev

@c1-squire-dev c1-squire-dev Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds released baton-sdk v0.20.2 CredentialIssuerV2 support for Databricks service principals.

  • advertises the precise OAuth CLIENT_SECRET issuance option
  • creates account-level service-principal secrets with optional absolute expiry
  • returns one-time secret material only through the SDK encrypted-result contract
  • syncs issued secrets through the same stable resource constructor
  • marks service-principal secret discovery as opt-in through the standard resource-type selection contract
  • deletes secrets through ResourceDeleterV2 using the secret ID and parent service-principal ID
  • gates account-only issuance and secret listing when the connector has workspace-only configuration
  • reports provider 404 deletion as confirmed absence with ResourceDoesNotExist and rejects expiry below Databricks one-second granularity before mutation
  • bypasses HTTP caching for secret lists so post-revocation resync cannot retain stale credentials
  • removes response-body debug logging because the create endpoint returns live secret material

Lifecycle proof

TestServicePrincipalSecretCredentialLifecycle exercises the connectorbuilder boundary end to end:

  1. IssueCredential creates and returns a secret resource.
  2. Sync returns the same external ID and parent resource ID.
  3. DeleteResourceV2 calls the provider DELETE endpoint.
  4. Resync confirms the secret is absent.

Capability tests also prove workspace-only mode does not advertise credential issuance.

Dependency

Uses the managed, released baton-sdk v0.20.2 from main; .versions.yaml, go.mod, and vendoring agree.

Verification

  • go test -count=1 ./...
  • go build ./cmd/baton-databricks
  • clean golangci-lint run --allow-parallel-runners
  • go generate ./pkg/config with no diff
  • managed SDK version equality check
  • git diff --check origin/main...HEAD

Grounded in the Databricks Account Service Principal Secrets create/list/delete API and connector source per D-NHI-012.

Comment thread pkg/connector/service_principal_secrets.go
Comment thread go.mod Outdated
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

@/tmp/summary.md

@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.

No blocking issues found.

@c1-squire-dev

c1-squire-dev Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Updated to finalized SDK commit a675375c: lifetime is now an issuance constraint, output uses the structured SDK contract, and 403/404 from the account-level secret-list endpoint now degrades to an empty page as requested. Restored .versions.yaml to the managed base version. go test ./... passes locally; tagged SDK bump remains gated on #941 release.

@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.

No blocking issues found.

@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.

No blocking issues found.

@c1-squire-dev

c1-squire-dev Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main and pushed d9436a2 (per D-NHI-012). This revision adds the provider-backed account DELETE endpoint (/api/2.0/accounts/{account_id}/servicePrincipals/{service_principal_id}/credentials/secrets/{secret_id}), registers ResourceDeleterV2 using the synced secret ID plus parent service-principal ID, and gates CAPABILITY_CREDENTIAL_ISSUE when account configuration is absent. The connectorbuilder-level lifecycle test now proves IssueCredential returned resource == synced resource (external ID and parent), DeleteResourceV2 invokes provider deletion, and a cache-bypassing resync is empty. Full go test ./..., build, clean golangci-lint (0 issues), generation, and changed-file diff checks pass. .versions.yaml remains managed at v0.19.1 while go.mod intentionally retains the SDK pseudo-version; verify-versions-match is the expected release gate.

@c1-squire-dev
c1-squire-dev Bot force-pushed the paul.querna/credential-issuer-databricks branch from 8a3d4f6 to d9436a2 Compare July 22, 2026 16:22
Comment thread go.mod Outdated

@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.

No blocking issues found.

@c1-squire-dev

c1-squire-dev Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Cleared the real lint failure in this release-gated draft. The test server now encodes its synthetic response as a JSON map rather than marshaling a typed struct with a Secret field, avoiding G117 without suppressing the rule. Local go test ./pkg/connector/... and golangci-lint run pass. The remaining versions check is intentionally blocked on the tagged baton-sdk release.

@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.

No blocking issues found.

pquerna and others added 7 commits July 23, 2026 17:29
Prototype OAuth client-secret issuance, sync, TTL, and secret-safe HTTP handling.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Move lifetime to issuance constraints, use structured outputs, and gracefully omit unreadable service-principal secrets on 403/404.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Move profiles and status from deprecated trait fields to resource attributes and read provisioning metadata from the canonical resource profile.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Add account-scoped deletion, exact issue/list/delete parity coverage, and workspace-only issuance capability gating per D-NHI-012.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Encode the synthetic Databricks response as a JSON map so gosec does not treat the test fixture type as a marshaled production secret.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Use the v0.20.2 expiry, request ID, resource-mode, and resource-type contract while dropping prototype-only vendored API changes per D-NHI-012.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
@c1-squire-dev
c1-squire-dev Bot force-pushed the paul.querna/credential-issuer-databricks branch from 4b7d9b5 to ee41f3d Compare July 23, 2026 17:35
@c1-squire-dev
c1-squire-dev Bot marked this pull request as ready for review July 23, 2026 17:35
@c1-squire-dev
c1-squire-dev Bot requested a review from a team July 23, 2026 17:35
@c1-squire-dev

c1-squire-dev Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Release gate cleared in ee41f3d (per D-NHI-012): rebased onto managed main, replaced the prototype pseudo-version with released baton-sdk v0.20.2, and reconciled the final API (CredentialIssueOptions, absolute ExpiresAt, required RequestId, discoverable ResourceMode, and declared secret resource type). Prototype-only vendored SDK deltas were removed; vendor now matches the managed release. The prior SDK-pin suggestion is resolved. Full tests, build, clean lint, generation, version equality, and diff checks pass locally. The trait-to-resource profile/status migration remains unchanged from the previously green and repeatedly reviewed implementation. Marked ready for review; not merging.

Comment thread pkg/connector/service_principal_secrets.go
Comment thread pkg/connector/service_principal_secrets.go

@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.

No blocking issues found.

Mark service-principal secrets OptInRequired while preserving entitlement skipping and account-scoped issuance registration per D-NHI-012.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
@c1-squire-dev

c1-squire-dev Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Closed the opt-in contract gap at b707d6d (per D-NHI-012). service_principal_secret now carries both v2.OptInRequired and v2.SkipEntitlementsAndGrants. Metadata tests prove its ResourceTypeCapability.opt_in_required=true, preserve the skip annotation, and prove the service-principal builder implements/advertises CredentialIssuerV2 only with account configuration. No connector-specific flag was added: SDK selection already flows through the standard --sync-resource-types / ConnectorOpts.SyncResourceTypeIDs surface, while OptInRequired is surfaced in resource-type capability metadata for default selection behavior. Full tests, build, lint (0 issues), and diff checks pass locally.

Comment thread pkg/connector/service_principal_secrets.go Outdated
Comment thread pkg/connector/service_principal_secrets.go
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Connector PR Review: feat(credentials): issue service principal client secrets

Blocking Issues: 0 | Suggestions: 2 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 0dbdbc7.
Review mode: full
View review run: https://github.com/ConductorOne/baton-databricks/actions/runs/30043630585

Review Summary

Scanned the full PR diff for security and correctness. The change adds CredentialIssuerV2 client-secret issuance and ResourceDeleterV2 deletion for Databricks account service principals, plus a coordinated SDK migration moving profiles and status from trait-level options to resource-level (WithResourceProfile, WithResourceStatus) with all read sites updated consistently. Secret handling is sound: created secret material is zeroed before building the synced resource, list requests bypass the HTTP cache (GetNoCache) so revoked secrets cannot linger, response-body debug logging was removed, and 403/404 on secret listing degrades gracefully with a Warn. Issuance is correctly gated to account-configured connectors. No blocking issues found; two minor suggestions below.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • pkg/connector/service_principal_secrets.go:108 - sub-second positive expiry rounds to lifetime 0s, which Databricks may treat as no-expiry or reject.
  • pkg/connector/service_principal_secrets.go:73-82 - Delete is not idempotent; a 404 on an already-removed secret surfaces as an error rather than a no-op.
Prompt for AI agents

Verify each finding against the current code and only fix it if needed.

In pkg/connector/service_principal_secrets.go:

  • Around line 108: lifetime is computed as fmt.Sprintf with int64(remaining/time.Second). For a positive sub-second remaining (e.g. 500ms) this yields 0s, which Databricks may interpret as no-expiry or reject. Round up to at least 1 second (math.Ceil on remaining.Seconds(), guarding the result is at least 1), or reject lifetimes that would round to 0.
  • Around line 73-82: Delete returns any error from deleteSecret directly, so deleting an already-deleted secret (HTTP 404) surfaces as a failure. If the API returns a distinguishable not-found error (a databricks.APIError with StatusCode 404), treat it as success and return nil to make deletion idempotent.

@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.

No blocking issues found.

Skip account-only listing without account configuration, make provider 404 deletion idempotent, and reject sub-second expiry before mutation per D-NHI-012.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
@c1-squire-dev

c1-squire-dev Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Holistic review follow-up at 98143f5 (per D-NHI-012): all four new threads are addressed and resolved. Workspace-only connectors now skip account secret listing before any request; duplicate 404-delete suggestions are implemented as a narrowly typed idempotent no-op; sub-second absolute expiry is rejected before provider mutation rather than rounded beyond the approved bound. Released SDK v0.20.2 resource-mode/type validation, opt-in discovery, account issuance gating, list/delete parity, and non-404 error visibility remain intact. Full tests, build, lint, generation/docs, managed version equality, and diff checks pass locally. PR remains ready and unmerged.

Emit ResourceDoesNotExist for provider 404 and ceil positive expiry to the provider one-second minimum per D-NHI-012.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
@c1-squire-dev

c1-squire-dev Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Final contract correction at 30fd9f5: positive sub-second expiry is ceiled to Databricks minimum 1s, and typed provider 404 deletion returns the released SDK v2.ResourceDoesNotExist annotation so C1 receives explicit confirmed-absence semantics. Tests assert both the exact provider lifetime and marker. Workspace-only listing guard, opt-in sync metadata, account issuance gate, discoverable lifecycle parity, and non-404 error propagation are unchanged. Full tests/build/lint pass; PR remains ready and unmerged.

Reject sub-second remaining validity before provider mutation so issued credentials cannot exceed approved ExpiresAt per D-NHI-012.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
@c1-squire-dev

c1-squire-dev Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Final security disposition at 4486cc5: positive sub-second remaining lifetime is rejected before provider mutation. This preserves the released v0.20.2 absolute ExpiresAt bound; rounding up could issue validity beyond the approved timestamp. Dedicated coverage proves no provider create call occurs. The typed 404 v2.ResourceDoesNotExist marker remains intact. Full tests/build/lint pass; PR remains ready and unmerged.

@github-actions

Copy link
Copy Markdown
Contributor

Connector PR Review: feat(credentials): issue service principal client secrets

Blocking Issues: 0 | Suggestions: 1 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 0dbdbc759d17.
Review mode: full
View review run

Review Summary

Scanned the full PR diff for security and correctness. This adds CredentialIssuerV2 / ResourceDeleterV2 support for account-level Databricks service-principal OAuth client secrets, and migrates trait-level profile/status to resource-level (WithResourceProfile / WithResourceStatus) across users, groups, roles, service-principals, and workspaces. The credential path is well-guarded: issuance is opt-in and gated on account configuration, secret material is returned only via the SDK encrypted PlaintextData contract and cleared from the synced resource, list bypasses HTTP caching to avoid stale post-revocation credentials, delete is idempotent on a provider 404, list degrades gracefully on 403/404, and response-body debug logging was removed. Entity and argument order in create/list/delete are correct, and the profile/status migration is internally consistent (every reader now uses resource.GetProfile(); no leftover trait readers). No blocking issues found.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • Profile/status trait-to-resource migration (pkg/connector/users.go, plus groups/roles/service-principals/workspaces): internally coherent, but confirm the consuming platform/SDK version reads resource-level status/profile rather than the old user-trait status and trait profiles, which are now unset. (confidence: medium)
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In pkg/connector/users.go (and correspondingly groups.go, roles.go, service-principals.go, workspaces.go):
- Around the userResource status/profile block: user status and profile were moved from
  trait-level (WithStatus/WithUserProfile) to resource-level (WithResourceStatus/WithResourceProfile),
  and readers switched to resource.GetProfile(). This is internally consistent. Confirm the target
  ConductorOne platform / baton-sdk version consumes resource-level status and profile; if any
  downstream still reads user-trait status or the group/role trait profiles, those fields are now
  unset and status/profile would appear empty. No code change needed if the platform reads
  resource-level fields.

@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.

No blocking issues found.

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