Documentation
¶
Overview ¶
Package secrets provides secure secret storage operations using AES-256-GCM encryption with OS keychain integration for master key management.
The package uses a hybrid approach for secret storage:
- Master encryption key is stored in the OS keychain (or env var fallback)
- Secrets are encrypted and stored as individual files
This allows for storing large secrets (e.g., auth tokens) that wouldn't fit in the OS keychain directly, while still leveraging secure key storage.
Basic usage:
store, err := secrets.New()
if err != nil {
log.Fatal(err)
}
// Store a secret
err = store.Set(ctx, "my-api-key", []byte("secret-value"))
// Retrieve a secret
value, err := store.Get(ctx, "my-api-key")
// List all secrets
names, err := store.List(ctx)
// Delete a secret
err = store.Delete(ctx, "my-api-key")
Index ¶
- Constants
- Variables
- func DeleteMasterKeyFromKeyring(kr Keyring) error
- func Export(exportData *secretcrypto.ExportFormat, opts ExportOptions) ([]byte, error)
- func FilterSecrets(names []string, includeInternal bool) []string
- func FilterSecretsFor(names []string, includeInternal bool, prefix string) []string
- func FilterUserSecrets(names []string) []string
- func FilterUserSecretsFor(names []string, prefix string) []string
- func GetMasterKeyFromKeyring(kr Keyring) ([]byte, error)
- func InternalSecretPrefixFor(binaryName string) string
- func IsEncrypted(data []byte) bool
- func IsInternalSecret(name string) bool
- func IsInternalSecretFor(name, prefix string) bool
- func IsValidName(name string) bool
- func SecretType(name string) string
- func SetMasterKeyInKeyring(kr Keyring, key []byte) error
- func ValidateName(name string) error
- func ValidateSecretName(name string, includeInternal bool) error
- func ValidateSecretNameFor(name string, includeInternal bool, prefix string) error
- func ValidateUserSecretName(name string) error
- type CorruptedSecretError
- type ExportOptions
- type ImportOptions
- type ImportResult
- type InvalidNameError
- type Keyring
- type KeyringCall
- type KeyringError
- type KeyringSetCall
- type MockKeyring
- type MockStore
- func (m *MockStore) Delete(_ context.Context, name string) error
- func (m *MockStore) Exists(_ context.Context, name string) (bool, error)
- func (m *MockStore) Get(_ context.Context, name string) ([]byte, error)
- func (m *MockStore) KeyringBackend() string
- func (m *MockStore) List(_ context.Context) ([]string, error)
- func (m *MockStore) Reset()
- func (m *MockStore) Rotate(_ context.Context) error
- func (m *MockStore) Set(_ context.Context, name string, value []byte) error
- type OSKeyring
- type Option
- type SetCall
- type Store
Constants ¶
const ( // KeyringBackendOS indicates the OS keyring was used. KeyringBackendOS = "os" // KeyringBackendEnv indicates the environment variable keyring was used. KeyringBackendEnv = "env" // KeyringBackendFile indicates the file-based keyring was used. KeyringBackendFile = "file" )
const ( // KeyringService is the service name used in the OS keyring. KeyringService = "scafctl" // KeyringMasterKeyAccount is the account name for the master encryption key. KeyringMasterKeyAccount = "master-key" // EnvSecretKey is the environment variable name for the fallback secret key. EnvSecretKey = "SCAFCTL_SECRET_KEY" //nolint:gosec // This is the env var name, not a credential )
const ( // MinNameLength is the minimum length for a secret name. MinNameLength = 1 // MaxNameLength is the maximum length for a secret name. MaxNameLength = 255 )
const ( // InternalSecretPrefix is the default prefix used for internal secrets (e.g. auth tokens). InternalSecretPrefix = "scafctl." //nolint:gosec // Not a credential, just a naming prefix )
Variables ¶
var ( // ErrNotFound is returned when a secret does not exist. ErrNotFound = errors.New("secret not found") // ErrInvalidName is returned when a secret name is invalid. ErrInvalidName = errors.New("invalid secret name") // ErrCorrupted is returned when a secret file is corrupted and cannot be decrypted. ErrCorrupted = errors.New("secret is corrupted") // ErrKeyringAccess is returned when the OS keyring cannot be accessed. ErrKeyringAccess = errors.New("cannot access keyring") )
Sentinel errors for the secrets package.
var ErrKeyNotFound = errors.New("key not found in keyring")
ErrKeyNotFound is returned when a key is not found in the keyring.
Functions ¶
func DeleteMasterKeyFromKeyring ¶
DeleteMasterKeyFromKeyring removes the master encryption key from the keyring.
func Export ¶ added in v0.6.0
func Export(exportData *secretcrypto.ExportFormat, opts ExportOptions) ([]byte, error)
Export serializes a secret collection to bytes in the specified format, optionally encrypting the output.
func FilterSecrets ¶ added in v0.5.0
FilterSecrets filters secret names, optionally including internal secrets. When includeInternal is false, internal secrets are excluded using the default prefix.
func FilterSecretsFor ¶ added in v0.7.0
FilterSecretsFor filters secret names using the given internal-secret prefix.
func FilterUserSecrets ¶ added in v0.5.0
FilterUserSecrets filters out internal secrets from a list using the default prefix.
func FilterUserSecretsFor ¶ added in v0.7.0
FilterUserSecretsFor filters out secrets matching the given prefix.
func GetMasterKeyFromKeyring ¶
GetMasterKeyFromKeyring retrieves the master encryption key from the keyring. The key is stored as base64-encoded string in the keyring.
func InternalSecretPrefixFor ¶ added in v0.7.0
InternalSecretPrefixFor returns the internal-secret prefix for the given binary name. Falls back to the default prefix when binaryName is empty.
func IsEncrypted ¶ added in v0.6.0
IsEncrypted returns true if the data starts with the encrypted header marker.
func IsInternalSecret ¶ added in v0.5.0
IsInternalSecret returns true if the secret name belongs to the default internal namespace.
func IsInternalSecretFor ¶ added in v0.7.0
IsInternalSecretFor returns true if the secret name starts with the given prefix.
func IsValidName ¶
IsValidName returns true if the secret name is valid.
func SecretType ¶ added in v0.5.0
SecretType returns "internal" or "user" based on the secret name prefix.
func SetMasterKeyInKeyring ¶
SetMasterKeyInKeyring stores the master encryption key in the keyring. The key is stored as base64-encoded string.
func ValidateName ¶
ValidateName checks if a secret name is valid according to the naming rules. Returns nil if valid, or an InvalidNameError with details if invalid.
func ValidateSecretName ¶ added in v0.5.0
ValidateSecretName validates a secret name using the default internal prefix.
func ValidateSecretNameFor ¶ added in v0.7.0
ValidateSecretNameFor validates a secret name, optionally allowing internal secrets identified by the given prefix.
func ValidateUserSecretName ¶ added in v0.5.0
ValidateUserSecretName validates a secret name for user operations. Returns an error if the name is invalid or uses the reserved internal prefix.
Types ¶
type CorruptedSecretError ¶
type CorruptedSecretError struct {
Name string `json:"name" yaml:"name" doc:"The name of the corrupted secret" example:"my-secret"`
Reason string `json:"reason" yaml:"reason" doc:"The reason the secret is corrupted" example:"invalid version byte"`
Cause error `json:"-" yaml:"-" doc:"The underlying error"`
}
CorruptedSecretError provides details about a corrupted secret.
func NewCorruptedSecretError ¶
func NewCorruptedSecretError(name, reason string, cause error) *CorruptedSecretError
NewCorruptedSecretError creates a new CorruptedSecretError with the given name, reason, and cause.
func (*CorruptedSecretError) Error ¶
func (e *CorruptedSecretError) Error() string
Error implements the error interface.
func (*CorruptedSecretError) Is ¶
func (e *CorruptedSecretError) Is(target error) bool
Is reports whether the target error is ErrCorrupted.
func (*CorruptedSecretError) Unwrap ¶
func (e *CorruptedSecretError) Unwrap() error
Unwrap returns the underlying cause for use with errors.Is and errors.As.
type ExportOptions ¶ added in v0.6.0
type ExportOptions struct {
// Format is the output format: "json" or "yaml" (default: "yaml").
Format string
// Encrypt enables password-based encryption of the output.
Encrypt bool
// Password for encryption. Required when Encrypt is true.
Password string `json:"-"` // nosec G117 -- not serialized, used only as function parameter
}
ExportOptions configures secret export behavior.
type ImportOptions ¶ added in v0.6.0
type ImportOptions struct {
// Password for decrypting encrypted imports. Required when data is encrypted.
Password string `json:"-"` // nosec G117 -- not serialized, used only as function parameter
}
ImportOptions configures secret import behavior.
type ImportResult ¶ added in v0.6.0
type ImportResult struct {
// Secrets contains the user secrets from the import (internal secrets filtered out).
Secrets []secretcrypto.ExportedSecret
// SkippedInternal is the number of internal secrets that were filtered out.
SkippedInternal int
// Version is the version string from the import file.
Version string
// VersionMismatch is true if the import version differs from the current export version.
VersionMismatch bool
}
ImportResult holds the result of parsing an import file.
func Import ¶ added in v0.6.0
func Import(data []byte, opts ImportOptions) (*ImportResult, error)
Import reads secrets from raw bytes, auto-detecting format (encrypted vs plaintext, JSON vs YAML), filtering out internal secrets, and returning a structured result.
type InvalidNameError ¶
type InvalidNameError struct {
Name string `json:"name" yaml:"name" doc:"The invalid secret name" example:".invalid-name"`
Reason string `json:"reason" yaml:"reason" doc:"The reason the name is invalid" example:"cannot start with '.'"`
}
InvalidNameError provides details about an invalid secret name.
func NewInvalidNameError ¶
func NewInvalidNameError(name, reason string) *InvalidNameError
NewInvalidNameError creates a new InvalidNameError with the given name and reason.
func (*InvalidNameError) Error ¶
func (e *InvalidNameError) Error() string
Error implements the error interface.
func (*InvalidNameError) Is ¶
func (e *InvalidNameError) Is(target error) bool
Is reports whether the target error is ErrInvalidName.
type Keyring ¶
type Keyring interface {
// Get retrieves a value from the keyring.
// Returns an error if the key does not exist or cannot be accessed.
Get(service, account string) (string, error)
// Set stores a value in the keyring.
// Creates or updates the existing value.
Set(service, account, value string) error
// Delete removes a value from the keyring.
// Returns an error if the key does not exist or cannot be deleted.
Delete(service, account string) error
}
Keyring defines the interface for keyring operations. This interface abstracts the OS keychain to allow for testing and alternative implementations.
func NewDefaultKeyring ¶
func NewDefaultKeyring() Keyring
NewDefaultKeyring creates the default keyring with the following fallback order:
- OS keyring (most secure - uses OS keychain)
- Environment variable SCAFCTL_SECRET_KEY (explicit user intent, e.g. CI)
- File-based key storage (auto-fallback, less secure but "just works")
type KeyringCall ¶
KeyringCall records a call to Get or Delete.
type KeyringError ¶
type KeyringError struct {
Operation string `json:"operation" yaml:"operation" doc:"The keyring operation that failed" example:"get"`
Cause error `json:"-" yaml:"-" doc:"The underlying error"`
}
KeyringError wraps a keyring access error with additional context.
func NewKeyringError ¶
func NewKeyringError(operation string, cause error) *KeyringError
NewKeyringError creates a new KeyringError with the given operation and cause.
func (*KeyringError) Error ¶
func (e *KeyringError) Error() string
Error implements the error interface.
func (*KeyringError) Is ¶
func (e *KeyringError) Is(target error) bool
Is reports whether the target error is ErrKeyringAccess.
func (*KeyringError) Unwrap ¶
func (e *KeyringError) Unwrap() error
Unwrap returns the underlying cause for use with errors.Is and errors.As.
type KeyringSetCall ¶
KeyringSetCall records a call to Set.
type MockKeyring ¶
type MockKeyring struct {
// Data holds the mock keyring data
Data map[string]string
// Error injection fields
GetErr error
SetErr error
DeleteErr error
// Call tracking
GetCalls []KeyringCall
SetCalls []KeyringSetCall
DeleteCalls []KeyringCall
// contains filtered or unexported fields
}
MockKeyring is a mock implementation of the Keyring interface for testing. It provides configurable behavior including error injection.
func NewMockKeyring ¶
func NewMockKeyring() *MockKeyring
NewMockKeyring creates a new MockKeyring with an empty data map.
func (*MockKeyring) Delete ¶
func (m *MockKeyring) Delete(service, account string) error
Delete removes a value from the mock keyring.
func (*MockKeyring) Get ¶
func (m *MockKeyring) Get(service, account string) (string, error)
Get retrieves a value from the mock keyring.
func (*MockKeyring) Set ¶
func (m *MockKeyring) Set(service, account, value string) error
Set stores a value in the mock keyring.
type MockStore ¶
type MockStore struct {
// Data holds the mock secret data
Data map[string][]byte
// Error injection fields - set these to make operations return errors
GetErr error
SetErr error
DeleteErr error
ListErr error
ExistsErr error
RotateErr error
// Call tracking
GetCalls []string
SetCalls []SetCall
DeleteCalls []string
ListCalls int
ExistsCalls []string
RotateCalls int
// contains filtered or unexported fields
}
MockStore is a mock implementation of the Store interface for testing. It provides configurable behavior including error injection and call tracking.
func NewMockStore ¶
func NewMockStore() *MockStore
NewMockStore creates a new MockStore with an empty data map.
func (*MockStore) KeyringBackend ¶ added in v0.2.0
KeyringBackend returns an empty string for mock stores.
type OSKeyring ¶
type OSKeyring struct{}
OSKeyring implements Keyring using the OS keychain via go-keyring.
type Option ¶
type Option func(*config)
Option configures the Store.
func WithKeyring ¶
WithKeyring sets a custom keyring implementation. This is primarily useful for testing or for environments where the OS keyring is not available.
func WithLogger ¶
WithLogger sets the logger for the Store. If not set, a discard logger is used.
func WithRequireSecureKeyring ¶ added in v0.6.0
WithRequireSecureKeyring makes store initialization fail if the OS keyring is unavailable and scafctl would have to fall back to an insecure file-based or environment-variable-based master key. Enable this in production or shared environments to prevent silent degradation of secret protection.
func WithSecretsDir ¶
WithSecretsDir overrides the default secrets directory. If empty, the XDG-compliant default will be used:
- Linux: ~/.local/share/scafctl/secrets/ (XDG_DATA_HOME)
- macOS: ~/.local/share/scafctl/secrets/
- Windows: %LOCALAPPDATA%\scafctl\secrets\
This can also be overridden by the SCAFCTL_SECRETS_DIR environment variable.
type Store ¶
type Store interface {
// Get retrieves a secret by name.
// Returns ErrNotFound if the secret does not exist.
// Returns ErrCorrupted if the secret file is corrupted and cannot be decrypted.
// Returns ErrInvalidName if the name is invalid.
Get(ctx context.Context, name string) ([]byte, error)
// Set stores a secret. Creates or overwrites existing.
// Returns ErrInvalidName if the name is invalid.
Set(ctx context.Context, name string, value []byte) error
// Delete removes a secret.
// No error is returned if the secret does not exist.
// Returns ErrInvalidName if the name is invalid.
Delete(ctx context.Context, name string) error
// List returns the names of all stored secrets.
List(ctx context.Context) ([]string, error)
// Exists checks if a secret exists.
// Returns ErrInvalidName if the name is invalid.
Exists(ctx context.Context, name string) (bool, error)
// Rotate rotates the master encryption key by re-encrypting all secrets.
// This operation is atomic - if any step fails, all secrets are preserved
// with the original key. The new key is generated, all secrets are
// re-encrypted, and the keyring is updated.
Rotate(ctx context.Context) error
// KeyringBackend returns the identifier of the keyring backend used for
// master key storage. Possible values: "os", "env", "file", or "" if unknown.
KeyringBackend() string
}
Store provides secure secret storage operations. All operations are safe for concurrent use.
func New ¶
New creates a new Store with the given options. If no keyring is provided, the default keyring (OS keychain with env var fallback) is used. If no secrets directory is provided, the platform-specific default is used.
The master encryption key is retrieved from the keyring on initialization. If no master key exists:
- If there are existing secrets, they are deleted (orphaned) and a new key is generated
- If there are no existing secrets, a new key is generated
Options:
- WithSecretsDir: Override the secrets directory
- WithKeyring: Provide a custom keyring implementation
- WithLogger: Set a logger for diagnostic output
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package secretcrypto provides password-based encryption and decryption for secret export/import operations using PBKDF2 key derivation and AES-256-GCM.
|
Package secretcrypto provides password-based encryption and decryption for secret export/import operations using PBKDF2 key derivation and AES-256-GCM. |