Skip to content

feat(secrets): add optional SOPS age workflow #7

Description

@wax911

Background

stackctl must support the existing local-stack SOPS + age workflow, but sops and age must remain optional runtime dependencies. The base CLI must work without them. They become mandatory only when a stackctl secrets ... command is used or when stackctl doctor --check-secrets is requested.

Goals

Implement:

  • stackctl secrets encrypt
  • stackctl secrets decrypt
  • stackctl secrets deploy
  • stackctl secrets clean
  • stackctl secrets doctor
  • optional stackctl secrets rotate-keys

Dependency behavior

Before any secrets operation mutates files or deploys stacks, check for required commands:

  • sops
  • age

If either is missing, exit gracefully with an actionable message:

ERROR: stackctl secrets decrypt requires sops and age.

Missing:
  - sops
  - age

Install:
  macOS: brew install sops age
  Linux: install sops and age from your package manager or upstream releases

Then verify:
  stackctl doctor --check-secrets

No partial secret workflow should continue when required tools are missing.

Required behavior

  • Discover services from configured .env.example locations.
  • Resolve default encrypted file names from config, e.g. .env.enc.
  • Resolve profile encrypted file names, e.g. .env.dev.enc or .env.prod.enc.
  • Support explicit service target by basename or repo-relative path.
  • Encrypt .env or profile-selected plaintext input to encrypted dotenv output.
  • Decrypt encrypted dotenv output into active .env using restrictive file permissions.
  • Use temp file + atomic move for decrypted outputs.
  • For secrets deploy:
    1. decrypt target env files
    2. determine affected stacks
    3. generate/render/deploy affected stacks
    4. remove plaintext .env files created by this run
  • For secrets clean:
    • remove plaintext .env files only when corresponding encrypted files exist
    • use shred -u when available
    • fall back to rm -f with a warning when shred is unavailable

CLI shape

stackctl secrets encrypt
stackctl secrets encrypt postgres
stackctl secrets encrypt --profile dev

stackctl secrets decrypt
stackctl secrets decrypt postgres
stackctl secrets decrypt --profile prod

stackctl secrets deploy
stackctl secrets deploy postgres
stackctl secrets deploy --profile prod

stackctl secrets clean
stackctl secrets doctor

Plaintext safety

  • .env is always treated as plaintext and should be gitignored.
  • .env.<profile> is allowed only when env.allowPlaintextProfiles is true.
  • Warn when plaintext profile files appear to contain sensitive keys such as SECRET, TOKEN, PASSWORD, PRIVATE, KEY, CLIENT_SECRET, DATABASE_URL, MONGO_URI, or REDIS_URL.

Acceptance criteria

  • Secrets commands fail cleanly when sops or age is missing.
  • Base non-secret commands work when sops and age are missing.
  • Encrypt/decrypt command construction is unit tested through a fake process runner.
  • Temp-file and atomic-move behavior is tested.
  • Cleanup behavior is tested with both shred available and unavailable.
  • Profile-specific encrypted file resolution is tested.
  • secrets deploy --dry-run prints decrypt/render/deploy/cleanup plan without mutation.

Non-goals

  • Do not implement secret editing UI in this issue.
  • Do not commit plaintext env files.
  • Do not make sops or age a hard install dependency for the entire CLI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions