一键导入
working-with-secrets
Guide to the secrets abstraction including the Store, SecretService registry, and how to add new named secret types
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Guide to the secrets abstraction including the Store, SecretService registry, and how to add new named secret types
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Add a new runtime implementation to the kdn runtime system
Guide to using the instances manager API for workspace management and project detection
Guide to understanding and working with the kdn runtime system architecture
Guide to configuring the Podman runtime including image setup, agent configuration, and containerfile generation
Conventional Commit Message Generator
Guide to the autoconf package — how secret detection, filtering, and the runner are wired together, and how to extend the system with new detector types
| name | working-with-secrets |
| description | Guide to the secrets abstraction including the Store, SecretService registry, and how to add new named secret types |
| argument-hint |
The secrets system uses a two-layer architecture: a Store that persists secrets securely, and a SecretService registry that describes how each named type maps to HTTP requests inside workspaces.
<storage-dir>/secrets.jsongithub) derive all their descriptor fields from a registered SecretServiceother type lets the user supply descriptor fields explicitly; only --host and --header are requiredpkg/secret/secret.go): Create(CreateParams) error, List() ([]ListItem, error), Get(name string) (ListItem, string, error), Remove(name string) errorpkg/secret/store.go): writes the value to the keychain, metadata to secrets.jsonpkg/secretservice/secretservice.go): describes a named type — description, host pattern, path, header name, header template, env varspkg/secretservice/registry.go): maps names to SecretService implementationspkg/secretservicesetup/register.go): loads definitions from the embedded secretservices.json and exposes ListAvailable() and RegisterAll()<storage-dir>/
secrets.json # metadata only — no secret values on disk
Keychain entry: service=kdn, user=<secret-name>, password=<secret-value>
The keychain backend is platform-specific: GNOME Keyring on Linux, Keychain on macOS, DPAPI on Windows (via github.com/zalando/go-keyring).
| Type | How descriptor fields are resolved |
|---|---|
Named (e.g. github) | Taken from the registered SecretService automatically |
other | Required: --host, --header; optional: --path, --headerTemplate, --env |
import "github.com/openkaiden/kdn/pkg/secret"
store := secret.NewStore(absStorageDir)
err := store.Create(secret.CreateParams{
Name: "my-token",
Type: "github",
Value: "ghp_xxxx",
Description: "Personal access token",
})
For other type, supply the required descriptor fields; Path, HeaderTemplate, and Envs are optional:
err := store.Create(secret.CreateParams{
Name: "my-api-key",
Type: secret.TypeOther,
Value: "secret123",
Hosts: []string{"api.example.com"},
Header: "Authorization",
Envs: []string{"MY_API_KEY"}, // optional
})
Create checks for a duplicate name before touching the keychain. ErrSecretAlreadyExists is returned if the name is already taken.
To retrieve all stored secrets (metadata only — no secret values):
items, err := store.List()
// items is []secret.ListItem{Name, Type, Description, Hosts, Path, Header, HeaderTemplate, Envs}
To retrieve a single secret's metadata and value (used when resolving secrets at workspace creation time):
item, value, err := store.Get("my-token")
// item is secret.ListItem (metadata); value is the keychain secret
Get returns ErrSecretNotFound if no secret with the given name exists.
To remove a secret:
err := store.Remove("my-token")
Remove deletes the value from the keychain and the metadata entry from secrets.json. If the keychain entry is missing (e.g. manually deleted), the metadata is still cleaned up. ErrSecretNotFound is returned when no secret with the given name exists.
Add an entry to pkg/secretservicesetup/secretservices.json:
{
"name": "my-service",
"hostsPatterns": ["api.my-service.com"],
"headerName": "Authorization",
"headerTemplate": "Bearer ${value}",
"envVars": ["MY_SERVICE_TOKEN"]
}
All fields:
| Field | Required | Description |
|---|---|---|
name | yes | Identifier used as --type value |
hostsPatterns | yes | List of regex patterns matched against the request host |
headerName | yes | HTTP header to set |
headerTemplate | yes | Header value template; ${value} is replaced with the secret value |
path | no | URL path prefix restriction |
envVars | no | Environment variable names to populate with the secret value |
No code changes required. Once added, the type is immediately:
kdn secret create --type my-service--type shell completionsecretservicesetup.ListAvailable()Commands get valid type names from secretservicesetup.ListAvailable() rather than a hardcoded list. The built-in other type is appended separately:
import (
"github.com/openkaiden/kdn/pkg/secret"
"github.com/openkaiden/kdn/pkg/secretservicesetup"
)
registeredTypes := secretservicesetup.ListAvailable()
sort.Strings(registeredTypes)
validTypes := append(registeredTypes, secret.TypeOther)
Register shell completion from this list:
cmd.RegisterFlagCompletionFunc("type", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return validTypes, cobra.ShellCompDirectiveNoFileComp
})
Inject a fakeKeyring to avoid touching the real system keychain:
// fakeKeyring is unexported in pkg/secret — use the package-internal
// newStoreWithKeyring constructor (available only within the package).
// From outside the package, test via the Store interface with a real temp dir
// and rely on the keychain failing (or use build tags to swap the backend).
For command-level tests that bypass NewSecretCreateCmd(), populate validTypes directly on the struct:
c := &secretCreateCmd{
secretType: "github",
value: "ghp_token",
validTypes: []string{"github", secret.TypeOther},
}
For commands that call store.List(), implement a fake that satisfies the full Store interface:
type fakeListStore struct {
items []secret.ListItem
err error
}
func (f *fakeListStore) Create(_ secret.CreateParams) error { return nil }
func (f *fakeListStore) List() ([]secret.ListItem, error) { return f.items, f.err }
func (f *fakeListStore) Remove(_ string) error { return f.err }
func (f *fakeListStore) Get(name string) (secret.ListItem, string, error) {
for _, item := range f.items {
if item.Name == name {
return item, "", f.err
}
}
return secret.ListItem{}, "", f.err
}