Documentation
¶
Overview ¶
Package profile owns connection profile persistence: the JSON record, UUIDv7 scope generation and validation, XDG path resolution, versioned AES-256-GCM at-rest encryption, secret-reference handling, and atomic hardened 0700/0600 saves.
It has no Bubble Tea dependency; list rendering and driver display labels belong to the workbench connection feature.
At-rest encryption ¶
Literal passwords are stored in a versioned envelope ("enc:v2:" + base64(nonce|AES-256-GCM ciphertext|tag)) whose authenticated data binds the ciphertext to the profile's scope ID and the field identity ("pass" or the exact extra key). Connection targets that carry credentials (URL userinfo passwords, the libpq/pgx password query parameter, mysql: DSNs) are encrypted the same way under the "target" field identity; non-credential targets stay plaintext and readable. Pre-v2 "enc:" records (no binding) still load and are rewritten to the v2 envelope on the next successful save, and legacy plaintext credential-bearing targets are migrated to the encrypted form on the next successful save. Secret references (${ENV_VAR}, file://) are stored verbatim. A value that cannot be decrypted (tampering, wrong or missing key, unknown envelope) is never surfaced as a literal and never rewritten: Load retains it in the profile with an Undecryptable marker and reports secretFail, and Save refuses while the field still holds the retained blob. The key and the ciphertext both live under the same user-owned 0700 config directory, so the encryption protects against accidental disclosure and backups — not against an attacker with account access.
Index ¶
Constants ¶
const (
// MaxProfiles caps the persisted profile list.
MaxProfiles = 20
)
Variables ¶
This section is empty.
Functions ¶
func IsSecretRef ¶
IsSecretRef returns true if pass is a reference (${ENV_VAR} or file:///path) rather than a literal password.
func ResolveSecretRef ¶
ResolveSecretRef expands ${ENV_VAR} and file:///path references.
func Save ¶
Save persists profiles atomically with 0600 permissions in a 0700 directory, skipping invalid records and encrypting literal passwords into the versioned v2 envelope bound to each profile's scope and field identity. Secret references are stored verbatim. The profiles target must be a regular file (never a symlink), the directory and file permissions are forced to 0700/0600 even when pre-existing, the temporary file is fsynced before the atomic rename, and the containing directory is fsynced after it; a failure at any point leaves the original file intact.
Save refuses a record while any field still holds a retained undecryptable blob (see Load): the ciphertext is never wrapped as a literal or silently rewritten. Re-entering the field clears the refusal.
Types ¶
type PostgreSQLTLS ¶
type PostgreSQLTLS string
PostgreSQLTLS is the persisted PostgreSQL TLS mode.
const ( PostgreSQLTLSVerifyFull PostgreSQLTLS = "verify-full" PostgreSQLTLSEncrypt PostgreSQLTLS = "require" PostgreSQLTLSDisabled PostgreSQLTLS = "disable" )
PostgreSQL TLS modes, as stored in profiles and the connection form.
type Profile ¶
type Profile struct {
ID string `json:"id"`
// Plugin is the selected plugin instance. Driver remains the database
// family for form rules and display.
Plugin string `json:"plugin,omitempty"`
Driver Driver `json:"driver"`
Name string `json:"name"`
// Target is the driver's opener target body. It is persisted as-is
// unless it carries credentials (URL userinfo password, a password
// query parameter, or a mysql: DSN), in which case it is stored as a
// v2 envelope bound to the "target" field identity and restored only
// after authenticated decryption.
Target string `json:"target"`
Host string `json:"host,omitempty"`
Port string `json:"port,omitempty"`
User string `json:"user,omitempty"`
Pass string `json:"pass,omitempty"`
MySQLTLS MySQLTLS `json:"mysqlTLS,omitempty"`
PostgreSQLTLS PostgreSQLTLS `json:"postgresTLS,omitempty"`
ReadOnly bool `json:"readOnly,omitempty"`
// Extras carries driver-specific form values beyond the fixed fields
// (empty for the built-in drivers).
Extras map[string]string `json:"extras,omitempty"`
// Undecryptable maps a field identity ("pass" or an exact extra key)
// to the stored enc: value that failed to decrypt at load. It is
// in-memory only (json:"-"; the wire format is unchanged) and marks
// a fail-closed load: the retained ciphertext is never surfaced as a
// literal, and Save refuses to rewrite it until the user re-enters it.
Undecryptable map[string]string `json:"-"`
}
Profile is one persisted connection record. The JSON keys are stable wire format; do not rename them.
func Load ¶
Load reads persisted profiles, assigning a fresh UUIDv7 scope to every legacy profile whose ID is empty, invalid, or duplicated by an earlier profile, and marking legacy plaintext credential-bearing targets for encryption. migrated reports whether any profile was reassigned or a legacy credential target was found, so callers can persist the corrected file immediately. An unreadable or corrupt file yields an empty list; a symlink or non-regular profiles target is refused (treated as unreadable) rather than followed.
secretFail reports that at least one stored enc: value could not be decrypted (tampering, wrong or missing key). Such values are retained verbatim in the returned profiles with an Undecryptable marker — never surfaced as literals — and Save refuses to rewrite them until the user re-enters the field, so a transient key problem can never silently destroy the stored ciphertext.