Documentation
¶
Overview ¶
Package workflows provides high-level orchestration for Kanuka commands.
Workflows coordinate multiple operations across packages (configs, secrets, audit) to implement complete user-facing features. Each workflow handles a single command's business logic, independent of CLI concerns like flag parsing, spinners, and output formatting.
Design Philosophy ¶
The cmd/ package should be a thin layer that:
- Parses command-line flags and arguments
- Calls the appropriate workflow function
- Formats the result for display
Workflows handle everything else:
- Loading configuration (user and project)
- Validating prerequisites and permissions
- Performing the core operation
- Recording audit trail entries
Available Workflows ¶
Each command has a corresponding workflow:
- Encrypt: Encrypts .env files using the project's symmetric key
- Decrypt: Decrypts .kanuka files back to .env files
- Init: Initializes a new Kanuka project
- Register: Registers a new user with an existing project
- Revoke: Removes a user's access to a project
- Rotate: Rotates the project's symmetric key
Error Handling ¶
Workflows return typed errors from the internal/errors package, allowing the CLI layer to provide appropriate user-facing messages without string matching. Use errors.Is() to check for specific error conditions:
result, err := workflows.Encrypt(ctx, opts)
if errors.Is(err, kerrors.ErrProjectNotInitialized) {
// Show user-friendly initialization message
}
Context Usage ¶
All workflow functions accept a context.Context as their first parameter. This enables cancellation, timeouts, and passing request-scoped values.
Index ¶
- Constants
- func CheckUserConfigComplete() (bool, error)
- func CheckUserExistsForRegistration(userEmail string) (targetUUID string, alreadyHasAccess bool, err error)
- func FormatDate(ts string) string
- func FormatDateTime(ts string) string
- func FormatDetails(e audit.Entry) string
- func FormatDetailsOneline(e audit.Entry) string
- func GetDevicesForUser(userEmail string) ([]configs.DeviceConfig, error)
- func IsCIUserRegistered() (bool, error)
- type AccessOptions
- type AccessResult
- type AccessSummary
- type CIInitOptions
- type CIInitResult
- type CheckResult
- type CheckStatus
- type CleanOptions
- type CleanResult
- type CreateOptions
- type CreatePreCheckResult
- type CreateResult
- type DecryptOptions
- type DecryptResult
- type DoctorOptions
- type DoctorResult
- type DoctorSummary
- type EncryptOptions
- type EncryptResult
- type ExportOptions
- type ExportResult
- type FileStatus
- type FileStatusInfo
- type FileToRevoke
- type ImportMode
- type ImportOptions
- type ImportPreCheckResult
- type ImportResult
- type InitOptions
- type InitResult
- type LogOptions
- type LogResult
- type OrphanEntry
- type RegisterMode
- type RegisterOptions
- type RegisterResult
- type RegisteredFile
- type RevokeOptions
- type RevokeResult
- type RotateOptions
- type RotateResult
- type StatusOptions
- type StatusResult
- type StatusSummary
- type SyncOptions
- type SyncResult
- type UserAccessInfo
- type UserStatus
Constants ¶
const CIUserEmail = "41898282+github-actions[bot]@users.noreply.github.com"
CIUserEmail is the email address used for the GitHub Actions CI user. This is the official GitHub Actions bot email.
const CIWorkflowPath = ".github/workflows/kanuka-decrypt.yml"
CIWorkflowPath is the path where the GitHub Actions workflow will be created.
Variables ¶
This section is empty.
Functions ¶
func CheckUserConfigComplete ¶
CheckUserConfigComplete checks if the user configuration has email and UUID set.
func CheckUserExistsForRegistration ¶
func CheckUserExistsForRegistration(userEmail string) (targetUUID string, alreadyHasAccess bool, err error)
CheckUserExistsForRegistration checks if a user can be registered (exists in project config). Returns the target user UUID and whether they already have access.
func FormatDate ¶
FormatDate formats a timestamp string to YYYY-MM-DD format.
func FormatDateTime ¶
FormatDateTime formats a timestamp string to YYYY-MM-DD HH:MM:SS format.
func FormatDetails ¶
FormatDetails formats the details for a log entry in verbose format.
func FormatDetailsOneline ¶
FormatDetailsOneline formats the details for a log entry in oneline format.
func GetDevicesForUser ¶
func GetDevicesForUser(userEmail string) ([]configs.DeviceConfig, error)
GetDevicesForUser returns devices for a user email (for interactive prompts).
func IsCIUserRegistered ¶ added in v1.3.0
IsCIUserRegistered checks if the CI user is already registered in the project.
Types ¶
type AccessResult ¶
type AccessResult struct {
// ProjectName is the name of the project.
ProjectName string
// Users contains information about each user.
Users []UserAccessInfo
// Summary contains counts of users by status.
Summary AccessSummary
}
AccessResult contains the outcome of an access operation.
func Access ¶
func Access(ctx context.Context, opts AccessOptions) (*AccessResult, error)
Access lists all users with access to the project's secrets.
It discovers users from the public_keys and secrets directories and determines their access status:
- active: user has public key AND encrypted symmetric key (can decrypt)
- pending: user has public key but NO encrypted symmetric key (run 'sync')
- orphan: encrypted symmetric key exists but NO public key (inconsistent)
Returns ErrProjectNotInitialized if the project has no .kanuka directory. Returns ErrInvalidProjectConfig if the project config is malformed.
type AccessSummary ¶
type AccessSummary struct {
// Active is the count of users with full access.
Active int
// Pending is the count of users awaiting access.
Pending int
// Orphan is the count of orphaned entries.
Orphan int
}
AccessSummary holds counts of users by status.
type CIInitOptions ¶ added in v1.3.0
CIInitOptions configures the ci-init workflow.
type CIInitResult ¶ added in v1.3.0
type CIInitResult struct {
CIUserUUID string
CIUserEmail string
WorkflowCreated bool
WorkflowPath string
PrivateKeyPEM []byte
GitHubRepoURL string
}
CIInitResult contains the outcome of a ci-init operation.
func CIInit ¶ added in v1.3.0
func CIInit(ctx context.Context, opts CIInitOptions) (*CIInitResult, error)
CIInit sets up GitHub Actions CI integration for the project. It generates a new keypair for CI, registers the CI user, and creates a workflow template. The private key is returned in the result and should be displayed securely to the user.
type CheckResult ¶
type CheckResult struct {
Name string `json:"name"`
Status CheckStatus `json:"status"`
Message string `json:"message"`
Suggestion string `json:"suggestion,omitempty"`
}
CheckResult holds the result of a single health check.
type CheckStatus ¶
type CheckStatus int
CheckStatus represents the result status of a health check.
const ( // CheckPass means the check passed. CheckPass CheckStatus = iota // CheckWarning means the check found a non-critical issue. CheckWarning // CheckError means the check found a critical issue. CheckError )
func (CheckStatus) MarshalJSON ¶
func (s CheckStatus) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler for CheckStatus.
func (CheckStatus) String ¶
func (s CheckStatus) String() string
String returns a string representation of CheckStatus.
type CleanOptions ¶
type CleanOptions struct {
// DryRun previews what would be removed without making changes.
DryRun bool
// Force skips the confirmation prompt (handled by caller).
Force bool
}
CleanOptions configures the clean workflow.
type CleanResult ¶
type CleanResult struct {
// Orphans is the list of orphaned entries found.
Orphans []OrphanEntry
// RemovedCount is the number of files removed (0 if dry-run).
RemovedCount int
// DryRun indicates whether this was a dry-run.
DryRun bool
}
CleanResult contains the outcome of a clean operation.
func Clean ¶
func Clean(ctx context.Context, opts CleanOptions) (*CleanResult, error)
Clean removes orphaned keys and inconsistent state.
An orphan is a .kanuka file in .kanuka/secrets/ that has no corresponding public key in .kanuka/public_keys/. This can happen if:
- A public key was manually deleted
- A revoke operation was interrupted
- Files were corrupted or partially restored
Returns ErrProjectNotInitialized if the project has no .kanuka directory.
type CreateOptions ¶
type CreateOptions struct {
// Email is the user's email address for identification.
Email string
// DeviceName is a custom device name (auto-generated from hostname if empty).
DeviceName string
// Force overwrites existing keys if true.
Force bool
}
CreateOptions configures the create workflow.
type CreatePreCheckResult ¶
type CreatePreCheckResult struct {
// NeedsEmail indicates whether an email needs to be provided.
NeedsEmail bool
// ExistingEmail is the email from the user config (if any).
ExistingEmail string
}
CreatePreCheckResult contains information needed before prompting for email.
func CreatePreCheck ¶
func CreatePreCheck(ctx context.Context) (*CreatePreCheckResult, error)
CreatePreCheck validates the project state before prompting for user input.
Returns ErrProjectNotInitialized if the project has no .kanuka directory. Returns ErrInvalidProjectConfig if the project config is malformed.
type CreateResult ¶
type CreateResult struct {
// Email is the validated email address used.
Email string
// DeviceName is the device name (provided or auto-generated).
DeviceName string
// UserUUID is the user's unique identifier.
UserUUID string
// PublicKeyPath is where the public key was saved in the project.
PublicKeyPath string
// KanukaKeyDeleted indicates if an existing .kanuka key was removed.
KanukaKeyDeleted bool
// DeletedKanukaKeyPath is the path of the deleted key (if any).
DeletedKanukaKeyPath string
}
CreateResult contains the outcome of a create operation.
func Create ¶
func Create(ctx context.Context, opts CreateOptions) (*CreateResult, error)
Create creates a new RSA key pair for accessing the project's encrypted secrets.
This command generates a unique cryptographic identity for the user on this device, identified by their email address. Each device gets its own key pair.
The workflow:
- Generates an RSA key pair (stored locally in ~/.local/share/kanuka/keys/)
- Copies the public key to the project's .kanuka/public_keys/ directory
- Registers the device in the project configuration
Returns ErrProjectNotInitialized if the project has no .kanuka directory. Returns ErrInvalidProjectConfig if the project config is malformed. Returns ErrInvalidEmail if the email format is invalid. Returns ErrDeviceNameTaken if the device name is already in use. Returns ErrPublicKeyExists if a public key already exists (unless Force is true).
type DecryptOptions ¶
type DecryptOptions struct {
// FilePatterns specifies files to decrypt. If empty, all .kanuka files are decrypted.
FilePatterns []string
// DryRun previews which files would be decrypted without making changes.
DryRun bool
// PrivateKeyData contains the private key bytes when reading from stdin.
// If nil, the private key is loaded from disk.
PrivateKeyData []byte
}
DecryptOptions configures the decrypt workflow.
type DecryptResult ¶
type DecryptResult struct {
// DecryptedFiles lists the .env files that were created.
DecryptedFiles []string
// SourceFiles lists the .kanuka files that were decrypted.
SourceFiles []string
// ProjectPath is the root path of the project.
ProjectPath string
// DryRun indicates whether this was a dry-run (no files modified).
DryRun bool
// ExistingFiles lists files that already exist and would be overwritten.
ExistingFiles []string
}
DecryptResult contains the outcome of a decrypt operation.
func Decrypt ¶
func Decrypt(ctx context.Context, opts DecryptOptions) (*DecryptResult, error)
Decrypt decrypts .kanuka files back to .env files.
It loads the user's encrypted symmetric key from the project, decrypts it using the user's private key, then decrypts each .kanuka file with NaCl secretbox. The decrypted files are written alongside the encrypted files with the .kanuka extension removed.
Returns ErrProjectNotInitialized if the project has no .kanuka directory. Returns ErrNoAccess if the user doesn't have a key file for this project. Returns ErrKeyDecryptFailed if the private key cannot decrypt the symmetric key. Returns ErrNoFilesFound if no .kanuka files match the specified patterns.
type DoctorResult ¶
type DoctorResult struct {
Checks []CheckResult `json:"checks"`
Summary DoctorSummary `json:"summary"`
Suggestions []string `json:"suggestions,omitempty"`
}
DoctorResult holds the complete result of the doctor workflow.
func Doctor ¶
func Doctor(ctx context.Context, opts DoctorOptions) (*DoctorResult, error)
Doctor runs health checks on the Kanuka project.
The doctor workflow checks:
- Project configuration validity
- User configuration validity
- Private key existence and permissions
- Public key and encrypted symmetric key consistency
- Gitignore configuration for .env files
- Unencrypted .env files
type DoctorSummary ¶
type DoctorSummary struct {
Passed int `json:"passed"`
Warnings int `json:"warnings"`
Errors int `json:"errors"`
}
DoctorSummary holds counts of checks by status.
type EncryptOptions ¶
type EncryptOptions struct {
// FilePatterns specifies files to encrypt. If empty, all .env files are encrypted.
FilePatterns []string
// DryRun previews which files would be encrypted without making changes.
DryRun bool
// PrivateKeyData contains the private key bytes when reading from stdin.
// If nil, the private key is loaded from disk.
PrivateKeyData []byte
}
EncryptOptions configures the encrypt workflow.
type EncryptResult ¶
type EncryptResult struct {
// EncryptedFiles lists the .kanuka files that were created.
EncryptedFiles []string
// SourceFiles lists the .env files that were encrypted.
SourceFiles []string
// ProjectPath is the root path of the project.
ProjectPath string
// DryRun indicates whether this was a dry-run (no files modified).
DryRun bool
}
EncryptResult contains the outcome of an encrypt operation.
func Encrypt ¶
func Encrypt(ctx context.Context, opts EncryptOptions) (*EncryptResult, error)
Encrypt encrypts environment files using the project's symmetric key.
It loads the user's encrypted symmetric key from the project, decrypts it using the user's private key, then encrypts each .env file with NaCl secretbox. The encrypted files are written alongside the originals with a .kanuka extension.
Returns ErrProjectNotInitialized if the project has no .kanuka directory. Returns ErrNoAccess if the user doesn't have a key file for this project. Returns ErrKeyDecryptFailed if the private key cannot decrypt the symmetric key. Returns ErrNoFilesFound if no .env files match the specified patterns.
type ExportOptions ¶
type ExportOptions struct {
// OutputPath is the path for the output archive.
// If empty, defaults to kanuka-secrets-YYYY-MM-DD.tar.gz.
OutputPath string
}
ExportOptions configures the export workflow.
type ExportResult ¶
type ExportResult struct {
// ConfigIncluded indicates whether config.toml was included.
ConfigIncluded bool
// PublicKeyCount is the number of public keys included.
PublicKeyCount int
// UserKeyCount is the number of user .kanuka files included.
UserKeyCount int
// SecretFileCount is the number of encrypted secret files included.
SecretFileCount int
// TotalFilesCount is the total number of files in the archive.
TotalFilesCount int
// OutputPath is the path to the created archive.
OutputPath string
}
ExportResult contains the outcome of an export operation.
func Export ¶
func Export(ctx context.Context, opts ExportOptions) (*ExportResult, error)
Export creates a tar.gz archive containing all encrypted secrets for backup.
The archive includes:
- .kanuka/config.toml (project configuration)
- .kanuka/public_keys/*.pub (user public keys)
- .kanuka/secrets/*.kanuka (encrypted symmetric keys for users)
- All *.kanuka files in the project (encrypted secret files)
The archive does NOT include:
- Private keys (these stay on each user's machine)
- Plaintext .env files (only encrypted versions are included)
Returns ErrProjectNotInitialized if the project has no .kanuka directory. Returns ErrInvalidProjectConfig if the project config is malformed. Returns ErrNoFilesFound if no files are found to export.
type FileStatus ¶
type FileStatus string
FileStatus represents the encryption status of a secret file.
const ( // StatusCurrent means the encrypted file is newer than the plaintext. StatusCurrent FileStatus = "current" // StatusStale means the plaintext was modified after encryption. StatusStale FileStatus = "stale" // StatusUnencrypted means plaintext exists with no encrypted version. StatusUnencrypted FileStatus = "unencrypted" // StatusEncryptedOnly means encrypted exists with no plaintext. StatusEncryptedOnly FileStatus = "encrypted_only" )
type FileStatusInfo ¶
type FileStatusInfo struct {
// Path is the relative path of the file.
Path string
// Status is the encryption status of the file.
Status FileStatus
// PlaintextMtime is the modification time of the plaintext file (if any).
PlaintextMtime string
// EncryptedMtime is the modification time of the encrypted file (if any).
EncryptedMtime string
}
FileStatusInfo holds information about a file's encryption status.
type FileToRevoke ¶
FileToRevoke represents a file to be revoked.
type ImportMode ¶
type ImportMode int
ImportMode represents the import strategy.
const ( // ImportModeMerge adds new files from archive, keeps existing files. ImportModeMerge ImportMode = iota // ImportModeReplace deletes existing .kanuka directory and extracts all from archive. ImportModeReplace )
type ImportOptions ¶
type ImportOptions struct {
// ArchivePath is the path to the tar.gz archive.
ArchivePath string
// ProjectPath is the path to the project directory.
// If empty, uses the current working directory.
ProjectPath string
// Mode is the import strategy (merge or replace).
Mode ImportMode
// DryRun previews the import without making changes.
DryRun bool
}
ImportOptions configures the import workflow.
type ImportPreCheckResult ¶
type ImportPreCheckResult struct {
// ArchiveFiles is the list of files in the archive.
ArchiveFiles []string
// KanukaExists indicates whether a .kanuka directory already exists.
KanukaExists bool
// ProjectPath is the resolved project path.
ProjectPath string
}
ImportPreCheckResult contains information from validating the archive.
func ImportPreCheck ¶
func ImportPreCheck(ctx context.Context, archivePath string) (*ImportPreCheckResult, error)
ImportPreCheck validates the archive and checks the project state.
Returns ErrFileNotFound if the archive doesn't exist. Returns ErrInvalidFileType if the archive is not a valid gzip file. Returns ErrInvalidArchive if the archive structure is invalid.
type ImportResult ¶
type ImportResult struct {
// FilesAdded is the count of new files added (merge mode).
FilesAdded int
// FilesSkipped is the count of files skipped because they exist (merge mode).
FilesSkipped int
// FilesReplaced is the count of files extracted (replace mode).
FilesReplaced int
// TotalFiles is the total number of files in the archive.
TotalFiles int
// DryRun indicates whether this was a dry-run.
DryRun bool
// Mode is the import mode used.
Mode ImportMode
}
ImportResult contains the outcome of an import operation.
func Import ¶
func Import(ctx context.Context, opts ImportOptions) (*ImportResult, error)
Import restores secrets from a tar.gz archive.
The archive should contain:
- .kanuka/config.toml (project configuration)
- .kanuka/public_keys/*.pub (user public keys)
- .kanuka/secrets/*.kanuka (encrypted symmetric keys)
- *.kanuka files (encrypted secret files)
Returns ErrFileNotFound if the archive doesn't exist. Returns ErrInvalidFileType if the archive is not a valid gzip file. Returns ErrInvalidArchive if the archive structure is invalid.
type InitOptions ¶
type InitOptions struct {
// ProjectName is the name for the project. If empty, uses the directory name.
ProjectName string
// Verbose enables verbose logging output.
Verbose bool
}
InitOptions configures the init workflow.
type InitResult ¶
type InitResult struct {
// ProjectName is the name of the initialized project.
ProjectName string
// ProjectUUID is the unique identifier assigned to the project.
ProjectUUID string
// DeviceName is the name assigned to this device for the project.
DeviceName string
// ProjectPath is the root path of the project.
ProjectPath string
}
InitResult contains the outcome of an init operation.
func Init ¶
func Init(ctx context.Context, opts InitOptions) (*InitResult, error)
Init initializes a new Kānuka secrets store in the current directory.
It creates the .kanuka directory structure, generates cryptographic keys, and registers the current user as the first project member.
Returns ErrProjectAlreadyInitialized if a .kanuka directory already exists. Returns errors from key generation or configuration if they fail.
type LogOptions ¶
type LogOptions struct {
// Limit is the maximum number of entries to return. 0 means no limit.
Limit int
// Reverse orders entries from most recent to oldest when true.
Reverse bool
// User filters entries by user email.
User string
// Operations filters entries by operation types (comma-separated).
Operations string
// Since filters entries after this date (YYYY-MM-DD format).
Since string
// Until filters entries before this date (YYYY-MM-DD format).
Until string
}
LogOptions configures the log workflow.
type LogResult ¶
type LogResult struct {
// Entries are the filtered audit log entries.
Entries []audit.Entry
// TotalEntriesBeforeFilter is the count of entries before filtering.
TotalEntriesBeforeFilter int
}
LogResult contains the outcome of a log operation.
type OrphanEntry ¶
type OrphanEntry struct {
// UUID is the user UUID from the orphaned file.
UUID string
// FilePath is the absolute path to the orphaned file.
FilePath string
// RelativePath is the path relative to the project root.
RelativePath string
}
OrphanEntry represents an orphaned .kanuka file with no corresponding public key.
type RegisterMode ¶
type RegisterMode string
RegisterMode indicates how the user is being registered.
const ( // RegisterModeEmail registers a user by looking up their email in the project config. RegisterModeEmail RegisterMode = "email" // RegisterModePubkeyText registers a user with provided public key text. RegisterModePubkeyText RegisterMode = "pubkey_text" // RegisterModeFile registers a user from a public key file. RegisterModeFile RegisterMode = "file" )
type RegisterOptions ¶
type RegisterOptions struct {
// Mode specifies how the user is being registered.
Mode RegisterMode
// UserEmail is the email of the user to register (required for email and pubkey_text modes).
UserEmail string
// PublicKeyText contains the public key content (for pubkey_text mode).
PublicKeyText string
// FilePath is the path to the public key file (for file mode).
FilePath string
// DryRun previews registration without making changes.
DryRun bool
// PrivateKeyData contains the private key bytes when reading from stdin.
PrivateKeyData []byte
// Force skips confirmation when updating existing user's access.
Force bool
// Verbose enables verbose output.
Verbose bool
// Debug enables debug output.
Debug bool
}
RegisterOptions configures the register workflow.
type RegisterResult ¶
type RegisterResult struct {
// DisplayName is the user-friendly name of who was registered.
DisplayName string
// TargetUserUUID is the UUID of the registered user.
TargetUserUUID string
// FilesCreated lists files that were created.
FilesCreated []RegisteredFile
// FilesUpdated lists files that were updated.
FilesUpdated []RegisteredFile
// DryRun indicates whether this was a dry-run (no changes made).
DryRun bool
// UserAlreadyHadAccess indicates if user already had access before this registration.
UserAlreadyHadAccess bool
// PubKeyPath is the path where the public key is/would be stored.
PubKeyPath string
// KanukaFilePath is the path where the .kanuka key is/would be stored.
KanukaFilePath string
// Mode indicates which registration mode was used.
Mode RegisterMode
}
RegisterResult contains the outcome of a register operation.
func Register ¶
func Register(ctx context.Context, opts RegisterOptions) (*RegisterResult, error)
Register grants a user access to the project's encrypted secrets.
It encrypts the project's symmetric key with the target user's public key, allowing them to decrypt secrets. The caller must have access to the project's secrets before they can grant access to others.
Returns ErrProjectNotInitialized if the project has no .kanuka directory. Returns ErrUserNotFound if the specified user is not in the project config. Returns ErrNoAccess if the current user doesn't have access to the project. Returns ErrPublicKeyNotFound if the target user's public key cannot be found.
type RegisteredFile ¶
RegisteredFile represents a file that was created or updated.
type RevokeOptions ¶
type RevokeOptions struct {
// UserEmail is the email of the user whose access is being revoked.
UserEmail string
// FilePath is an alternative way to specify revocation by .kanuka file path.
FilePath string
// DeviceName specifies a specific device to revoke (requires UserEmail).
DeviceName string
// DryRun previews revocation without making changes.
DryRun bool
// PrivateKeyData contains the private key bytes when reading from stdin.
PrivateKeyData []byte
// Verbose enables verbose output.
Verbose bool
// Debug enables debug output.
Debug bool
}
RevokeOptions configures the revoke workflow.
type RevokeResult ¶
type RevokeResult struct {
// DisplayName is the user-friendly name of who was revoked.
DisplayName string
// RevokedFiles lists the files that were deleted.
RevokedFiles []string
// UUIDsRevoked lists the UUIDs that were removed from config.
UUIDsRevoked []string
// RemainingUsers is the count of users still in the project.
RemainingUsers int
// SecretsReEncrypted is the count of secrets re-encrypted.
SecretsReEncrypted int
// DryRun indicates whether this was a dry-run (no changes made).
DryRun bool
// FilesToDelete lists files that would be deleted (for dry-run).
FilesToDelete []FileToRevoke
// AllUsers lists all users currently in the project (for dry-run info).
AllUsers []string
// KanukaFilesCount is the number of .kanuka secret files (for dry-run info).
KanukaFilesCount int
}
RevokeResult contains the outcome of a revoke operation.
func Revoke ¶
func Revoke(ctx context.Context, opts RevokeOptions) (*RevokeResult, error)
Revoke revokes a user's access to project secrets.
It removes the user's encrypted symmetric key and public key files, updates the project configuration, and re-encrypts all secrets with a new key so the revoked user cannot decrypt future secrets.
Returns ErrProjectNotInitialized if the project has no .kanuka directory. Returns ErrUserNotFound if the specified user is not in the project. Returns ErrDeviceNotFound if the specified device is not found. Returns ErrSelfRevoke if attempting to revoke the current user.
type RotateOptions ¶
type RotateOptions struct {
// Force skips the confirmation prompt (handled by caller).
// This field is informational; the actual prompting is done in the cmd layer.
Force bool
// PrivateKeyData contains the private key bytes when reading from stdin.
// If nil, the private key is loaded from disk.
PrivateKeyData []byte
}
RotateOptions configures the rotate workflow.
type RotateResult ¶
type RotateResult struct {
// UserUUID is the UUID of the user whose keys were rotated.
UserUUID string
// ProjectUUID is the UUID of the project.
ProjectUUID string
// PrivateKeyPath is where the new private key was saved.
PrivateKeyPath string
// PublicKeyPath is where the new public key was saved (user directory).
PublicKeyPath string
// ProjectPublicKeyPath is where the new public key was copied (project directory).
ProjectPublicKeyPath string
}
RotateResult contains the outcome of a rotate operation.
func Rotate ¶
func Rotate(ctx context.Context, opts RotateOptions) (*RotateResult, error)
Rotate generates a new keypair and replaces the user's current keys for this project.
This command is useful for key rotation when a private key may have been compromised. The workflow:
- Loads the user's current private key
- Decrypts the symmetric key with the old private key
- Generates a new RSA keypair
- Re-encrypts the symmetric key with the new public key
- Saves the new private key and updates the public key in both locations
Returns ErrProjectNotInitialized if the project has no .kanuka directory. Returns ErrNoAccess if the user doesn't have a key file for this project. Returns ErrPrivateKeyNotFound if the old private key cannot be loaded. Returns ErrKeyDecryptFailed if the private key cannot decrypt the symmetric key.
type StatusResult ¶
type StatusResult struct {
// ProjectName is the name of the project.
ProjectName string
// Files contains the status of each discovered file.
Files []FileStatusInfo
// Summary contains counts of files by status.
Summary StatusSummary
}
StatusResult contains the outcome of a status operation.
func Status ¶
func Status(ctx context.Context, opts StatusOptions) (*StatusResult, error)
Status checks the encryption status of all secret files in the project.
It discovers all .env and .kanuka files and determines their status:
- current: encrypted file is newer than plaintext (up to date)
- stale: plaintext modified after encryption (needs re-encryption)
- unencrypted: plaintext exists with no encrypted version
- encrypted_only: encrypted exists with no plaintext
Returns ErrProjectNotInitialized if the project has no .kanuka directory. Returns ErrInvalidProjectConfig if the project config is malformed.
type StatusSummary ¶
type StatusSummary struct {
// Current is the count of files that are up to date.
Current int
// Stale is the count of files where plaintext was modified after encryption.
Stale int
// Unencrypted is the count of files that have no encrypted version.
Unencrypted int
// EncryptedOnly is the count of files that only have an encrypted version.
EncryptedOnly int
}
StatusSummary holds counts of files by status.
type SyncOptions ¶
type SyncOptions struct {
// DryRun previews sync without making changes.
DryRun bool
// PrivateKeyData contains the private key bytes when reading from stdin.
// If nil, the private key is loaded from disk.
PrivateKeyData []byte
}
SyncOptions configures the sync workflow.
type SyncResult ¶
type SyncResult struct {
// SecretsProcessed is the number of secret files re-encrypted.
SecretsProcessed int
// UsersProcessed is the number of users who received the new key.
UsersProcessed int
// UsersExcluded is the number of users excluded from the new key.
UsersExcluded int
// DryRun indicates whether this was a dry-run.
DryRun bool
}
SyncResult contains the outcome of a sync operation.
func Sync ¶
func Sync(ctx context.Context, opts SyncOptions) (*SyncResult, error)
Sync re-encrypts all secrets with a new symmetric key.
This is useful for:
- Periodic security key rotation
- After adding new team members
- If you suspect a key may have been compromised
All users with access will receive the new symmetric key, encrypted with their public key.
Returns ErrProjectNotInitialized if the project has no .kanuka directory. Returns ErrPrivateKeyNotFound if the private key cannot be loaded. Returns ErrKeyDecryptFailed if the symmetric key cannot be decrypted.
type UserAccessInfo ¶
type UserAccessInfo struct {
// UUID is the user's unique identifier.
UUID string
// Email is the user's email address.
Email string
// DeviceName is the user's device name.
DeviceName string
// Status is the user's access status.
Status UserStatus
}
UserAccessInfo holds information about a user's access to the project.
type UserStatus ¶
type UserStatus string
UserStatus represents the access status of a user.
const ( // UserStatusActive means the user has both public key and encrypted symmetric key. UserStatusActive UserStatus = "active" // UserStatusPending means the user has public key but no encrypted symmetric key. UserStatusPending UserStatus = "pending" // UserStatusOrphan means the user has encrypted symmetric key but no public key. UserStatusOrphan UserStatus = "orphan" )