Repository navigation
Secrets contract, c op / c secrets commands, and c secrets init - #102
smebberson wants to merge 50 commits into
Conversation
…crets init. - Contract lib: secrets.services + sibling secrets.shared (segment-less ids, no gsmSegment), unique-owner sharedKeys across services ∪ shared, dotted owner.KEY disambiguation, opItem references (services.<name>/dev, one hop - each item id appears exactly once), lazy opItem resolution. - Commands: c op get/set, c secrets get/push/add/verify/ls/consumers/init. Values only ever display as truncated hashes; --plaintext is TTY-gated and agent-blocked with a discouraged --force. - init drafts the contract from existing declarations (warm session required): value-verified shared.opItem binding and legacy-duplicate removal by hash, sharedKeys authored automatically, plain-language review notes. - verify reconciles structure + op/GSM/k8s hashes (storage visibility, op:EXTRA stale fields) + functions drift (STALE). - Friendly pre-flights: assertOpSession, assertGcloudAuth, assertKubectl.
- New bin/lib/c-secrets-manifests.js: generates SecretProviderClasses, SecretSyncs and the secret-sync ServiceAccount for deployed environments, and local Secrets (op references) for local, directly into the compiled folder. A conflicting committed file is a hard error (one true source); without a contract, manifest behaviour is unchanged. - Wired into c kc manifests/apply/start before the file checks; ensureServiceFilesExist accepts already-compiled (generated) paths. - Local Secrets follow the exact pipeline committed templates use: op inject, base64-encoded data, 0600 permissions. - The op helpers (whoami/inject) are now session-aware: the op-session alias' session is wired in when present, falling back to the shell environment. - README: Commands summary + a Secrets configuration section. CHANGELOG: v6.2.0 entry.
- Exported opItemFields from c-secrets-io: without it, verify's op:EXTRA scan silently no-oped (the per-item catch swallowed the undefined function), so stale-field findings could never surface. - ensureServiceFilesExist now accepts an existing compiled file for contract-generated manifests (no committed source exists). The original version of this check failed to apply and compiled envs errored on secretsync.serviceaccount.
allanchau
left a comment
There was a problem hiding this comment.
@smebberson A couple of changes here
Signs in via op signin --raw and stores the raw token at ~/.local/state/idearium/op-session (0600, enforced on overwrite) where every c command reads it. No account-specific OP_SESSION_<id> env var names anywhere.
Spawns op with --session <token> (array form, no shell) and inherits stdio, pre-flighting the session with assertOpSession for a friendly error. Exit code propagates for scripting.
runOp and the c kc injection pipeline now pass the c-managed session via --session in the args array instead of injecting an OP_SESSION_idearium env var, and every error hint points at c op session. Env intake is unchanged (still checked first) so eval $(op signin) keeps working.
setOpItemInCjs and addKeyToServiceInCjs matched the first name: { block
anywhere in c.js, so a service named after a docker location (site,
static) silently edited the wrong block. Block location is now scoped
to secrets -> services, and every edit (register, addKey, setOpItem) is
re-parsed from disk and verified against the parsed contract - a miss
or syntax break restores the original text and throws.
resolveServiceKey resolves opRef: null for unbound services and shared entries - rendering embedded a literal null that survived op inject and was base64-encoded into the Secret. Generation now fails with a remedy (c op set for services, shared.opItem for shared keys) instead.
resolveOwnerItem always created for unbound services, so the read path left stray empty items behind failed reads. resolveOwnerItem now takes create (default true, shared never creates), and c op get passes create false per its stated reads-never-create design.
A bare -l parsed to true then NaN into randomBytes (raw crypto error), and c secrets add had the same latent bug. Both commands now share generateValueForProgram, which fails with a friendly error for a bare or invalid -l/-t before generating.
The per-key k8s hash was printed with a hard-coded ok: true - only the key set was reconciled, so verify reported VERIFIED even when a synced Secret's value had drifted. Phase 1 now records each key's GSM hash by secret id and phase 2 compares, flagging drift (or sync lag) instead.
Segment-ness now derives from the owner name (shared is reserved for the shared block), so the flag carried no information. init's dead segmentless emission went with it, and c secrets add rejects a new service named shared up front.
Commander's git-style dispatch re-parses subcommand args at every layer before spawning, silently dropping -- and unknown options (breaking e.g. c op cmd run -- sh -c ... and c kc cmd commands carrying kubectl flags). The op and kc subtrees are now dispatched with the verbatim argv, and each carries its cmd passthrough down the same way.
Each phase prints a heading with a one-line explanation and its output streams as checks run. Data is prefetched in bulk (one item list, one item fetch per distinct 1Password item, one GSM list, GSM values with bounded concurrency, one kubectl fetch per synced secret), and function bindings are also checked against the GSM list, so a binding referencing a deleted secret is reported (MISSING) alongside contract drift (STALE).
|
@allanchau, great job on surfacing those issues. I've resolved them all and reworked the |
allanchau
left a comment
There was a problem hiding this comment.
@smebberson A few more things were picked up, but nothing major.
allanchau
left a comment
There was a problem hiding this comment.
@smebberson I think this looks good now.
|
@allanchau, this is the final thing I've adding - environment binding. It means everything will be named |
allanchau
left a comment
There was a problem hiding this comment.
@smebberson Just a small change here.
Codifies secrets management per the ras-rsc GSM unification program (
docs/secrets-gsm-unification.md).c kc manifests|apply|startnow generateSecretProviderClassesandSecretSyncs(Kubernetes manifests files that manage syncing from Google Secret Manager to Kubernetes), the secret-sync ServiceAccount and local Secrets (1Password references) from the contract — written straight to the compiled folder. A conflicting committed file is a hard error; no contract means manifest behaviour is unchanged. README (Commands + Secrets configuration) and CHANGELOG added.What's here
bin/lib/c-secrets.js):secrets.services+ siblingsecrets.shared(segment-less ids;gsmSegmentremoved from the schema), unique-ownersharedKeysresolution across services ∪ shared with dottedowner.KEYdisambiguation, andopItemreferences (services.<name>/dev, one hop) so each 1Password item id appears exactly once. LazyopItemresolution: services resolve-or-create by title;sharednever auto-creates.c op get|set,c secrets get|push|add|verify|ls|consumers|init. Positional identity args, no defaults; values display as truncated hashes only (--plaintextis TTY-gated, agent-env-blocked, discouraged--force); stdin-pipe upserts;pushauto-verifies the GSM readback by hash;addis ordered-idempotent-rollback across c.js + 1Password + GSM and never touches the cluster;consumersis the deletion gate.c secrets init(requires a warm 1Password session): drafts the contract from existing declarations — SecretProviderClasses, local templates,--set-secrets,env.*.yaml, docker build args — with value-verifiedshared.opItembinding and legacy-duplicate removal by hash,sharedKeysauthored automatically, and plain-language review notes.c secrets verify: structure + op/GSM/k8s hash reconciliation with storage visibility (stored in the site item), stale-field detection (op:EXTRA), and deployed-functions drift (STALE).assertOpSession,assertGcloudAuth,assertKubectl— fail fast with actionable errors before any work.Verified live against ras-rsc: the warm init draft converges on the hand-authored contract in the unification doc (shared auto-bound by value hash, legacy duplicate dropped, bare sharedKeys), and
verify betais hash-green across all keys with only the two known STALE legacy references.Status
6.2.0-beta.1and6.2.0-beta.2published with thebetadist-tag. ras-rsc has adopted the contract end-to-end (see idearium/ras-rsc#454) — init's warm draft converged on the hand-authored contract, generation proved byte-identical against the committed manifests, andverify betais hash-green.Stays draft until the ras-rsc production conversion (Step D) proves the tooling in both environments — then merges and promotes to
6.2.0.Related PRs:
better-secretsskill rewrite