Skip to content

feat: machine-credential options (ApiKey/Keypair/Token) + CredentialManagerService.IssueCredential (NHI vending, additive)#941

Open
c1-squire-dev[bot] wants to merge 2 commits into
mainfrom
paul.querna/nhi-machine-cred-issue
Open

feat: machine-credential options (ApiKey/Keypair/Token) + CredentialManagerService.IssueCredential (NHI vending, additive)#941
c1-squire-dev[bot] wants to merge 2 commits into
mainfrom
paul.querna/nhi-machine-cred-issue

Conversation

@c1-squire-dev

@c1-squire-dev c1-squire-dev Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🔗 Preview: C1

Summary

Additive baton-sdk surface that unblocks service-account / machine-credential vending fleet-wide for the pqprime NHI program. Implements R1 (machine-credential CredentialOptions arms) and R3 (CredentialManagerService.IssueCredential) from the SA-vending surface grounding (§5), which was verified against SDK HEAD (v0.13.x). Everything is additive and wire-compatible — buf breaking is clean.

R1 — machine-credential CredentialOptions arms

  • CredentialOptions gains three additive oneof arms (the oneof is open; existing 100..103 are unchanged):
    • ApiKey { repeated string scopes; google.protobuf.Duration ttl } = 104
    • Keypair { string algorithm; int32 bits } = 105
    • Token { repeated string scopes; google.protobuf.Duration ttl; string audience } = 106
  • Mirrored on the decrypted server-side LocalCredentialOptions (same shape — these arms carry no encrypted payload).
  • CapabilityDetailCredentialOption gains API_KEY / KEYPAIR / TOKEN values so the surface is advertisable.
  • crypto.ConvertCredentialOptions passes the new arms through verbatim, exactly like the existing no_password / sso arms (no change to the crypto path — encryption keys off the connector returning []PlaintextData, not the option variant).

Keypair = client-side generation semantics: the connector generates the keypair locally, registers the public key with the platform, and returns the private material as PlaintextData, which the builder encrypts before it leaves the connector — the platform only ever sees the encrypted form (mirrors the Snowflake key-pair auth pattern).

R3 — CredentialManagerService.IssueCredential

  • New additive RPC that mints a new secret for an existing identity, distinct from RotateCredential's replace-in-place semantics. It returns the new secret as a first-class Resource (so it can carry SecretTrait with an identity_id back-ref) plus the encrypted material, modeling N coexisting keys per identity (e.g. cloud SA key Update import paths to use new sdk repo #1 and Add ViewSync() to the connectorstore reader interface #2).
    • IssueCredentialRequest { ResourceId identity_id = 1; CredentialOptions credential_options = 2; repeated EncryptionConfig encryption_configs = 3 }
    • IssueCredentialResponse { Resource secret = 1; repeated EncryptedData encrypted_data = 2; repeated google.protobuf.Any annotations = 3 }
  • Advertisement: CAPABILITY_CREDENTIAL_ISSUE + CredentialDetailsCredentialIssue { supported_credential_options[]; preferred_credential_option } on CredentialDetails, wired into the builder's capability assembly and validation.
  • Go: a new, small CredentialIssuer interface (Issue + IssueCapabilityDetails) so existing CredentialManager implementations don't break — a connector opts in only by implementing it. Builder dispatch reuses the existing []PlaintextData → Encrypt fan-out loop verbatim.

Grounding

Based on the SA-vending baton-sdk surface analysis (pqprime NHI), §5 R1 + R3. Every new field has a named consumer in that mapping; no reserved slots, no V1Identifier. R2 (AccountInfo.account_type) was intentionally left out of scope.

Tests

  • Oneof round-trip for ApiKey / Keypair / Token through ConvertCredentialOptions.
  • IssueCredential builder dispatch: mock issuer returns a Resource + plaintexts → asserts the encryption fan-out and response shape (including the returned secret resource and the converted options reaching the issuer).
  • Capability advertisement: CAPABILITY_CREDENTIAL_ISSUE surfaced connector-wide and per-resource-type, plus the credential-issue details.

Build, full test suite, buf lint, buf format, and buf breaking --against main all green.

Known follow-ups (not in this PR)

Two committed consumer tracks ride this surface:

  1. c1 provisioning handler — planned (plans/nhi/sa-lifecycle/layer-work-map.md L5): an "issue credential" task that calls IssueCredential, persists the returned secret resource (emitting SecretTrait), and routes encrypted_data through the existing Vault decryption path.
  2. baton-http declarative framework — needs a follow-up to capture platform-returned secrets (ClickHouse Cloud key vending is the first declarative consumer).

Five connectors are queued on this surface: mongo-atlas, clickhouse-cloud, aws, databricks, snowflake.


🛡️ Built with pqprime


Exposed endpoints

🏰 Squire environment: gentle-jellyfish-87640
Task: 126ac54f-6872-4833-933f-c48553c2a3ba

…anagerService.IssueCredential (NHI vending, additive)

Additive baton-sdk surface that unblocks service-account / key vending
fleet-wide for the NHI program. Grounded against the SA-vending surface
analysis (§5 R1 + R3).

R1 — machine-credential CredentialOptions arms:
  - CredentialOptions.{ApiKey=104, Keypair=105, Token=106} additive oneof arms
    (open oneof; existing 100..103 unchanged), mirrored on the decrypted
    server-side LocalCredentialOptions.
  - CapabilityDetailCredentialOption gains API_KEY/KEYPAIR/TOKEN values.
  - ConvertCredentialOptions passes the new arms through verbatim (they carry
    no encrypted payload, same shape as no_password/sso).

R3 — CredentialManagerService.IssueCredential:
  - New additive RPC: mint a NEW secret for an EXISTING identity, returning the
    secret as a first-class Resource + EncryptedData[], distinct from
    rotate-in-place. Supports N coexisting keys per identity.
  - CAPABILITY_CREDENTIAL_ISSUE + CredentialDetailsCredentialIssue advertisement.
  - New opt-in CredentialIssuer Go interface (Issue + IssueCapabilityDetails);
    existing CredentialManager implementations are untouched. Builder dispatch
    reuses the existing PlaintextData -> Encrypt fan-out verbatim.

All changes are additive and wire-compatible; `buf breaking` is clean. Keypair
uses client-side generation semantics (connector generates locally, returns
private material as PlaintextData, platform sees only the encrypted form).

Tests: oneof round-trip for the new arms, IssueCredential builder dispatch
(mock issuer -> encryption fan-out + response shape), capability advertisement.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
@c1-squire-dev c1-squire-dev Bot requested a review from a team June 10, 2026 20:42
Comment thread pkg/connectorbuilder/credentials.go Outdated
@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

General PR Review: feat: machine-credential options (ApiKey/Keypair/Token) + CredentialManagerService.IssueCredential (NHI vending, additive)

Blocking Issues: 0 | Suggestions: 0 | Threads Resolved: 0
Review mode: incremental since b50b8f8
View review run

Review Summary

The new commit adds a dedicated IssueCredentialType task constant in pkg/types/tasks/tasks.go and switches IssueCredential in pkg/connectorbuilder/credentials.go to use it, directly addressing the previous review finding about conflated issue/rotate telemetry. The fix is clean and correct — no new issues found.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

None.

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

… task type

IssueCredential was recording task success/failure metrics under
tasks.RotateCredentialsType, conflating issue and rotate telemetry. Add a
dedicated IssueCredentialType ("issue_credential") task type and use it so the
two operations are distinguishable in metrics and logs. Appended to the iota to
avoid renumbering existing TaskType values.

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

@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 Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Note from the vending-platform design (consumer context for reviewers): the vending lifecycle records will bind IssueCredentialResponse.secret.resource_id as the durable link between a minted credential and its owning identity's credential inventory — so the id's stability and true-external-id discipline are load-bearing downstream, not cosmetic. Full direction doc: pqprime plans/nhi/sa-lifecycle/actions-first-decision.md (O2 hybrid: actions create identities; secrets mint exclusively via this CredentialManager surface).

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