Skip to content

feat: add macOS keychain secret backend for profile refs#108

Merged
rmanibus merged 1 commit into
mainfrom
feat/rfc0011-macos-keychain-backend
Mar 15, 2026
Merged

feat: add macOS keychain secret backend for profile refs#108
rmanibus merged 1 commit into
mainfrom
feat/rfc0011-macos-keychain-backend

Conversation

@rmanibus
Copy link
Copy Markdown
Contributor

Summary

What Changed

  • Added keychain backend implementation:
    • internal/secretref/keychain_backend.go
    • internal/secretref/keychain_backend_darwin.go
    • internal/secretref/keychain_backend_stub.go
  • Updated resolver registration:
    • internal/secretref/secretref.go now includes keychain in NewDefaultResolver()
  • Added tests:
    • unit tests in internal/secretref/keychain_backend_test.go
    • integration test in internal/secretref/keychain_backend_integration_darwin_test.go
      • opt-in via CLOUDSTIC_TEST_KEYCHAIN=1
  • Updated resolver error test to keep unsupported-scheme coverage independent from keychain.

Notes

  • keychain://<service>/<account> parsing uses the last path segment as account and the preceding path as service.
  • Backend errors are mapped to typed resolver errors (not_found, backend_unavailable, invalid_ref).

Validation

  • go test ./internal/secretref -count=1
  • go test ./...
  • golangci-lint run ./...
  • go test ./internal/secretref -run TestKeychainBackend_Integration -count=1 -v
  • CLOUDSTIC_TEST_KEYCHAIN=1 go test ./internal/secretref -run TestKeychainBackend_Integration -count=1 -v

Tracking

@rmanibus rmanibus added the enhancement New feature or request label Mar 15, 2026
@rmanibus rmanibus merged commit b753395 into main Mar 15, 2026
4 checks passed
@rmanibus rmanibus deleted the feat/rfc0011-macos-keychain-backend branch March 15, 2026 18:48
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 15, 2026

Codecov Report

❌ Patch coverage is 74.35897% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/secretref/keychain_backend.go 75.75% 5 Missing and 3 partials ⚠️
internal/secretref/keychain_backend_stub.go 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds keychain://... secret reference support to the internal/secretref resolver so profile secret refs can be resolved via macOS Keychain (with a non-macOS stub backend), aligning with RFC 0011 / issue #88.

Changes:

  • Register keychain in NewDefaultResolver() alongside env.
  • Implement a Keychain backend with macOS security-based lookup plus non-darwin stub behavior.
  • Add unit + opt-in darwin integration tests for keychain resolution and error mapping.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
internal/secretref/secretref.go Registers keychain in the default resolver.
internal/secretref/secretref_test.go Keeps unsupported-scheme coverage independent of the new keychain backend.
internal/secretref/keychain_backend.go Core keychain backend, path parsing, and error-kind mapping.
internal/secretref/keychain_backend_darwin.go macOS implementation using security find-generic-password.
internal/secretref/keychain_backend_stub.go Non-macOS stub that reports backend unavailable.
internal/secretref/keychain_backend_test.go Unit tests for path parsing, success path, and typed error mapping.
internal/secretref/keychain_backend_integration_darwin_test.go Opt-in integration test using security CLI to add/delete and resolve a real keychain item.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +23 to +40
add := exec.Command("security", "add-generic-password", "-U", "-s", service, "-a", account, "-w", secret)
if out, err := add.CombinedOutput(); err != nil {
t.Fatalf("add-generic-password failed: %v\n%s", err, out)
}
t.Cleanup(func() {
del := exec.Command("security", "delete-generic-password", "-s", service, "-a", account)
_, _ = del.CombinedOutput()
})

b := NewKeychainBackend()
got, err := b.Resolve(context.Background(), Ref{
Raw: "keychain://" + service + "/" + account,
Scheme: "keychain",
Path: service + "/" + account,
})
if err != nil {
t.Fatalf("Resolve: %v", err)
}
Comment on lines 102 to +107
// NewDefaultResolver builds the baseline resolver with env:// support.
func NewDefaultResolver() *Resolver {
return NewResolver(map[string]Backend{"env": NewEnvBackend(nil)})
return NewResolver(map[string]Backend{
"env": NewEnvBackend(nil),
"keychain": NewKeychainBackend(),
})
}
return "", fmt.Errorf("security find-generic-password failed: %s", msg)
}
return strings.TrimSpace(string(out)), nil
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFC 0011: Implement macOS Keychain backend for profile secrets

2 participants