Skip to content

feat: prototype Snowflake named key pair issuance - #138

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

feat: prototype Snowflake named key pair issuance#138
c1-squire-dev[bot] wants to merge 9 commits into
mainfrom
paul.querna/credential-issuer-snowflake

Conversation

@c1-squire-dev

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

Copy link
Copy Markdown
Contributor

Summary

  • model Snowflake named key pairs as discoverable secret resources under service users
  • issue RSA key pairs through the baton-sdk v0.20.2 credential issuer contract, returning the private key once and registering only the public key
  • map requested expiry to Snowflake DAYS_TO_EXPIRY
  • delete issued key pairs through Snowflake ALTER USER ... REMOVE KEY PAIR
  • expose credential issuance only when secret synchronization is enabled

Why named key pairs

Snowflake's named key-pair API avoids the legacy RSA_PUBLIC_KEY / RSA_PUBLIC_KEY_2 two-slot limit and gives each issued credential a stable identifier, fingerprint, status, usage timestamps, and expiration.

Safety properties

  • service users only (SERVICE or LEGACY_SERVICE)
  • RSA 2048/3072/4096 only
  • private key generated locally and returned once
  • only the public key is sent to Snowflake
  • issued credentials advertise discoverable resource mode and a registered deletion path
  • deletion validates the key-pair resource belongs to the supplied parent user
  • secret resource is linked to its canonical user identity

Dependency

  • github.com/conductorone/baton-sdk v0.20.2
  • baton-admin-managed .versions.yaml remains unchanged at v0.20.2

Testing

  • go test -count=1 ./...
  • golangci-lint run --allow-parallel-runners
  • go build ./cmd/baton-snowflake
  • generated config and capabilities verified; baton_capabilities.json refreshed

Comment thread pkg/snowflake/user.go
Comment on lines +160 to +170
func (r *ListNamedKeyPairsRawResponse) GetKeyPairs() ([]NamedKeyPair, error) {
keyPairs := make([]NamedKeyPair, 0, len(r.Data))
for _, row := range r.Data {
keyPair := &NamedKeyPair{}
if err := r.ResultSetMetadata.ParseRow(keyPair, row); err != nil {
return nil, err
}
keyPairs = append(keyPairs, *keyPair)
}
return keyPairs, 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: ParseRow requires every field mapped in namedKeyPairStructFieldToColumnMap to be present in the result set with an exact type match — string columns must be text and CreatedOn/LastUsedOn/ExpiresAt must be timestamp_ltz, otherwise it returns row type ... not found / type errors and the whole named_key_pair List fails. SHOW USER KEY PAIRS output may not include every mapped column (e.g. role_scope, rotated_to, comment) or may type timestamps differently. The same class of fragility is already worked around for DESCRIBE USER via ignoredUserStructFieldsForDescribeOperation. Consider verifying the actual column set against a live response and adding an ignore-list / graceful degradation, plus a table-driven parse test. (medium confidence)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed and retained through the rebase: optional SHOW columns are tolerated via IsOptionalField, SHOW-family timestamps accept both timestamp_ltz and text, and TestListNamedKeyPairsRawResponseGetKeyPairsToleratesOptionalColumns covers the reduced/text-timestamp response. go test -count=1 ./... passes at 1082ff7.

@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Connector PR Review: feat: prototype Snowflake named key pair issuance

Blocking Issues: 0 | Suggestions: 0 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 00badb892142.
Review mode: incremental since b06fd241
View review run: https://github.com/ConductorOne/baton-snowflake/actions/runs/30043662629

Review Summary

The new commit gates the named_key_pair resource type behind opt-in by adding the OptInRequired annotation (getOptInSkipEntitlementsAnnotation) and refreshing baton_capabilities.json, alongside a new connector_capabilities_test.go asserting the resource is only exposed and credential issuance only enabled when secret sync is on. The full PR diff was scanned for security and correctness: SQL statements escape double-quoted identifiers via escapeDoubleQuotedIdentifier, the interpolated public key is connector-generated base64, response bodies are closed via defer closeResponseBody, and errors are wrapped with %w. 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.

@c1-squire-dev

c1-squire-dev Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Updated to finalized SDK commit a675375c: explicit JOSE RSA profiles (2048/3072/4096), issuance-level whole-day lifetime, and dynamic service-user eligibility. Also hardened SHOW USER KEY PAIRS parsing for optional columns/text timestamps and added the requested regression test. Restored .versions.yaml to managed base. 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.

Comment thread go.mod Outdated

require (
github.com/conductorone/baton-sdk v0.19.0
github.com/conductorone/baton-sdk v0.19.2-0.20260722080606-a675375c27d2

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: This bumps baton-sdk to the unreleased prototype pseudo-version v0.19.2-0.20260722080606-a675375c27d2, but .versions.yaml (baton-admin managed, "DO NOT EDIT") still pins v0.19.0. The two disagree, and the new credential-issue code depends on SDK APIs (connectorbuilder.CredentialIssuerV2, rs.SyncOpAttrs/SyncOpResults, CredentialIssueInput/Output) that a released v0.19.0 likely does not contain — so .versions.yaml cannot simply win. Reconcile before merge by moving to a released SDK that actually contains these APIs and updating both files together. Shipping against the prototype pseudo-version remains a release blocker (previously reported, still open).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Resolved at 1082ff7: the branch is rebased onto main, go.mod and the baton-admin-managed .versions.yaml both use released baton-sdk v0.20.2, and prototype vendor changes were replaced with the released v0.20.2 vendor tree. The connector was also updated for the final v0.20.2 issuance/delete contract; tests, lint, build, and generated capabilities pass locally.

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

pquerna and others added 7 commits July 23, 2026 17:29
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>
Use explicit JOSE RSA profiles, issuance-level lifetimes, dynamic service-user eligibility, and resilient named-key row parsing with regression coverage.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Centralize Snowflake service-user type constants and simplify the RSA public-key assertion.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Reuse service-user, RSA key-type, and result-column constants and wrap the eligibility signature.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Regenerate credential issuance capabilities and document named key-pair issuance constraints.

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>
@c1-squire-dev
c1-squire-dev Bot force-pushed the paul.querna/credential-issuer-snowflake branch from 7384b20 to 1082ff7 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
}
keyPairs, err := b.client.ListUserKeyPairs(ctx, parent.GetResource())
if err != nil {
return nil, nil, fmt.Errorf("baton-snowflake: list named key pairs: %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: Unlike rsaBuilder.List, which swallows the 422 "no permission to describe user" error and returns nil, nil, nil to degrade gracefully, this returns a hard error. If the named key-pair feature or DESCRIBE permission is unavailable on the account, every service user's sync will fail here. Consider matching the rsa builder's isUnprocessableEntityError skip so an unavailable endpoint degrades gracefully (R7). (medium confidence)

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

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

Followed up on the latest automated review suggestion in b06fd24: named key-pair listing now mirrors the legacy RSA builder and skips Snowflake 422 feature/permission-unavailable responses with a debug log, while continuing to propagate unexpected errors. Added TestNamedKeyPairBuilderListSkipsUnavailableFeature; go test -count=1 ./... and golangci-lint run --allow-parallel-runners pass locally.

@c1-squire-dev
c1-squire-dev Bot dismissed github-actions[bot]’s stale review July 23, 2026 17:41

Stale automated changes-requested review: its SDK version blocker is fixed on the current head, and the latest automated full review reports zero blocking issues.

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

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 in 75e5f0b: named_key_pair now carries both OptInRequired and SkipEntitlementsAndGrants while remaining behind --sync-secrets. Added capability-registration tests proving (1) the advertised named-key resource is opt-in, syncable, and deletable but not itself an issuer, (2) user issuance advertises discoverable named_key_pair output only with secret sync enabled, and (3) named_key_pair plus user issuance details are absent when secret sync is disabled. Refreshed baton_capabilities.json. go test -count=1 ./..., golangci-lint, build, and generated capability validation pass locally.

@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