Skip to content

[Prototype] IGA-5308: Vend Datadog keys through custom actions - #56

Closed
highb wants to merge 2 commits into
highb/IGA-5308/emit-multipass-api-key-v2-jsonfrom
highb/IGA-5308/custom-action-vending
Closed

highb wants to merge 2 commits into
highb/IGA-5308/emit-multipass-api-key-v2-jsonfrom
highb/IGA-5308/custom-action-vending

Conversation

@highb

@highb highb commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Squire (Together/DeepSeek-V4.1-Flash-1017):

What this is

A prototype, for comparison with the IssueCredential path in #55. Geoff asked
whether credential vending could go through baton custom actions instead, so this
does exactly that for both Datadog credential kinds. It is not a proposal to
replace #55
— the point is to have both mechanisms in hand and compare them.

Stacked on #55; base branch is highb/IGA-5308/emit-multipass-api-key-v2-json.
It depends on baton-sdk #1218 for CredentialIssueOptionDescriptor.output, the
same dependency #55 has.

Modelled on the AWS connector's issueFederationTokenSchema:
pkg/connector/sts_actions.go.

How it works

Two global actions, registered with actions.RegisterWithSecrets:

Action Gate Required args
issue_organization_api_key allow-org-api-key-deletion request_id
issue_service_account_application_key sync-secrets + sync-service-account-application-keys request_id, service_account_id, optional scopes

Each gate matches the one that gates the corresponding IssueCredential kind, so
an action cannot mint a credential the connector has no way to revoke.

The secret return is exactly the Field the descriptor already advertises
(is_secret, content_type application/vnd.multipass.api_key_v2+json, the
tags, and a string field). The public returns are key_id,
secret_resource_type_id and secret_resource_id — identifiers only, so C1 can
revoke through DeleteResourceV2 without ever holding the key.

The minting and plaintext helpers are shared with Issue, not copied, so both
paths produce the same api_key_v2 document under the same name.

Why this might not be the shape

Worth stating plainly, since the comparison is the point:

  • Delivery is one-shot and stateless. IssueCredential produces a synced
    secret resource with a SecretTrait and a revoke path wired into the SDK. An
    action returns bytes once and the caller owns them; nothing is registered for
    later sync or revocation.
  • No IssueCredential contract checks. Scopes, expiry, resource mode and the
    deleter requirement are enforced by the SDK on the Issue path. Here they are
    re-implemented per action, which is more surface to keep in step.
  • request_id idempotency is duplicated from Issue, not shared.

What it gains: the caller chooses at invocation time, and the secret never
becomes a resource in the connector's own inventory.

Tests

  • TestVendingActionsDeclareTheSameSecretReturnAsIssue — the secret return
    equals the descriptor Field byte for byte; public returns are the three
    identifiers.
  • TestVendingActionsGatedOnOperatorGrants — no grant, no action;
    each grant enables only its own action.
  • TestVendingActionVendsThroughTheActionManager — drives the real action
    manager with a real age recipient: the provider is called, the response
    carries the identifiers and never the key, and the returned EncryptedData
    is ciphertext holding no plaintext.
  • TestVendingActionRefusesDuplicateRequestID — a repeated request is refused
    rather than minting a second key.

go build ./..., go vet ./..., go test ./... and golangci-lint run (CI's
v2.14.0) pass. baton_capabilities.json is unchanged: these are actions, not
connector capabilities, so no regeneration was needed.

Known CI

verify-versions-match fails for the same reason as #55: .versions.yaml on
main pins baton-sdk v0.43.0, and both branches need the commit that adds the
credential-issue output field.

Geoff asked whether vending could go through baton custom actions instead of
IssueCredential. This is the prototype to compare against the Issue path.

Two global actions register with RegisterWithSecrets, one per credential
kind, each gated on the same operator grant that gates its Issue
counterpart. Their secret return is exactly the Field the descriptor
advertises, and the public returns are identifiers only, so C1 can show and
revoke a vended key without ever holding it.

The mint and plaintext helpers are shared with Issue rather than copied, so
both paths deliver the same api_key_v2 document under the same name.

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

linear-code Bot commented Nov 4, 2026

Copy link
Copy Markdown

IGA-5308

applicationKeyBuilder.Delete takes the bare key id as resourceID and the
service account as parentResourceID. The action returned a composite
"<service-account>/<key>" id instead, so DeleteResourceV2 with it would
have looked up a key that does not exist. The response now carries the
bare id, plus the parent type and id the deleter needs.

Also share the minting with Issue rather than copying it: the duplicate
guard, the service-account re-check, the create call and the
discard-on-encode-failure now live in mintOrganizationAPIKey and
mintServiceAccountApplicationKey, called by both paths.

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

highb commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Squire (claude-opus-5-5): Closing. These hand-written vending actions were a prototype for an approach that has been dropped. The revised plan exposes existing IssueCredential implementations as custom actions through a baton-sdk adapter, so connector-specific vending actions aren't needed.

@highb highb closed this Nov 4, 2026
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