Files
incredigo/cmd/incredigo/main.go
T
leetcrypt 5c4727d1e6 rotate: safety-spine scaffold + mandatory backup gate (NO rotation)
Design-phase foundation for v2 rotation. Changes NO credential at any service.

- internal/rotate: Rotator interface + registry + PlanAll (zero drivers
  registered, so every credential plans as "(none)")
- internal/rotate.Snapshot: the MANDATORY backup gate — seals the gopass
  prefix into an authenticated bundle, re-opens it, and confirms the entry
  count matches before returning; any failure blocks rotation
- internal/sink.CountBundleRecords: read-only bundle completeness check
- cmd: `incredigo rotate` runs the backup gate + prints the plan; it is
  dry-run only and refuses --execute (design phase)
- tests: backup gate happy path, no-overwrite, tamper detection; race-clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:39:45 -07:00

513 lines
15 KiB
Go

// Command incredigo is a local-first, encrypted, RAM-only credential custody tool.
//
// v1 subcommands: scan, migrate, status, export, import.
// See DESIGN.md for the full architecture and threat model.
package main
import (
"bytes"
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"text/tabwriter"
"time"
"github.com/spf13/cobra"
"golang.org/x/term"
"incredigo/internal/audit"
"incredigo/internal/discover"
"incredigo/internal/policy"
"incredigo/internal/rotate"
"incredigo/internal/sink"
"incredigo/internal/vault"
)
var (
flagSources []string
flagPaths []string
flagPrefix string
flagDedupe bool
flagForce bool
flagJSON bool
flagDryRun bool
flagAuditLog string
flagConfig string
flagSealer string
)
func main() {
root := &cobra.Command{
Use: "incredigo",
Short: "Local-first, encrypted, RAM-only credential custody",
SilenceUsage: true,
SilenceErrors: true,
}
root.PersistentFlags().BoolVar(&flagDryRun, "dry-run", false, "do not write anything")
root.PersistentFlags().StringVar(&flagAuditLog, "audit-log", "", "append-only redacted audit log path")
root.PersistentFlags().StringVar(&flagConfig, "config", "", "policy.yaml path")
root.PersistentFlags().StringVar(&flagSealer, "sealer", "age", "backup sealer: age (default, authenticated), hmac (authenticated, openssl-only), or openssl (unauthenticated)")
root.AddCommand(scanCmd(), migrateCmd(), statusCmd(), exportCmd(), importCmd(), rotateCmd())
if err := root.Execute(); err != nil {
fmt.Fprintln(os.Stderr, "incredigo:", err)
os.Exit(1)
}
}
// applyPaths registers any --path targets with the file scanner and, when an
// explicit --source filter is in play, makes sure "file" is included so --path
// just works alongside it.
func applyPaths() {
for _, p := range flagPaths {
discover.AddPath(p)
}
if len(flagPaths) == 0 || len(flagSources) == 0 {
return
}
for _, s := range flagSources {
if s == "file" {
return
}
}
flagSources = append(flagSources, "file")
}
// withVault sets up the RAM vault and guarantees it is purged on exit.
func withVault(fn func(ctx context.Context, v *vault.Vault) error) error {
v := vault.New()
defer v.Purge()
return fn(context.Background(), v)
}
func scanCmd() *cobra.Command {
c := &cobra.Command{
Use: "scan",
Short: "Discover credentials (no secrets printed)",
RunE: func(cmd *cobra.Command, args []string) error {
applyPaths()
return withVault(func(ctx context.Context, v *vault.Vault) error {
creds, err := discover.ScanAll(ctx, v, flagSources...)
if err != nil {
return err
}
log, _ := audit.Open(flagAuditLog, time.Now)
defer log.Close()
w := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
fmt.Fprintln(w, "SOURCE\tKIND\tIDENTITY\tLOCATION")
for _, cr := range creds {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", cr.Source, cr.Kind, cr.Identity, cr.Location)
log.Write(audit.Entry{Action: "scan", Source: cr.Source,
Kind: string(cr.Kind), Identity: cr.Identity, Location: cr.Location, Outcome: "ok"})
}
w.Flush()
fmt.Fprintf(os.Stderr, "\n%d credential(s) found across %d source(s)\n",
len(creds), countSources(creds))
return nil
})
},
}
c.Flags().StringSliceVar(&flagSources, "source", nil, "limit to these scanners (comma-separated)")
c.Flags().StringArrayVar(&flagPaths, "path", nil, "extra file or directory to harvest (repeatable)")
c.Flags().BoolVar(&flagJSON, "json", false, "json output")
return c
}
func migrateCmd() *cobra.Command {
c := &cobra.Command{
Use: "migrate",
Short: "Migrate discovered credentials into gopass (plaintext never hits disk)",
RunE: func(cmd *cobra.Command, args []string) error {
applyPaths()
return withVault(func(ctx context.Context, v *vault.Vault) error {
gp := &sink.Gopass{Prefix: flagPrefix}
if !gp.Available() {
return fmt.Errorf("gopass not found on PATH")
}
creds, err := discover.ScanAll(ctx, v, flagSources...)
if err != nil {
return err
}
log, _ := audit.Open(flagAuditLog, time.Now)
defer log.Close()
var done, skipped int
for _, cr := range creds {
sp := gp.StorePath(cr)
if flagDedupe && gp.Exists(ctx, sp) {
skipped++
fmt.Printf("skip %s (exists)\n", sp)
log.Write(audit.Entry{Action: "migrate", Source: cr.Source, Kind: string(cr.Kind),
Identity: cr.Identity, Outcome: "skipped"})
continue
}
if flagDryRun {
fmt.Printf("would %s\n", sp)
continue
}
if err := gp.Insert(ctx, v, cr, flagForce); err != nil {
log.Write(audit.Entry{Action: "migrate", Source: cr.Source, Kind: string(cr.Kind),
Identity: cr.Identity, Outcome: err.Error()})
return err
}
done++
fmt.Printf("store %s\n", sp)
log.Write(audit.Entry{Action: "migrate", Source: cr.Source, Kind: string(cr.Kind),
Identity: cr.Identity, Outcome: "ok"})
}
fmt.Fprintf(os.Stderr, "\nmigrated %d, skipped %d\n", done, skipped)
return nil
})
},
}
c.Flags().StringSliceVar(&flagSources, "source", nil, "limit to these scanners")
c.Flags().StringArrayVar(&flagPaths, "path", nil, "extra file or directory to harvest (repeatable)")
c.Flags().StringVar(&flagPrefix, "prefix", "imported/", "gopass path prefix")
c.Flags().BoolVar(&flagDedupe, "dedupe", false, "skip entries that already exist")
c.Flags().BoolVar(&flagForce, "force", false, "overwrite existing entries")
return c
}
func statusCmd() *cobra.Command {
c := &cobra.Command{
Use: "status",
Short: "Report credential age vs expiry policy",
RunE: func(cmd *cobra.Command, args []string) error {
applyPaths()
pol, err := policy.Load(flagConfig)
if err != nil {
return err
}
return withVault(func(ctx context.Context, v *vault.Vault) error {
creds, err := discover.ScanAll(ctx, v, flagSources...)
if err != nil {
return err
}
now := time.Now()
w := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
fmt.Fprintln(w, "STATE\tAGE\tSOURCE\tIDENTITY")
for _, cr := range creds {
st, age := pol.Evaluate(cr, now)
fmt.Fprintf(w, "%s\t%dd\t%s\t%s\n", strings.ToUpper(string(st)),
int(age.Hours()/24), cr.Source, cr.Identity)
}
w.Flush()
return nil
})
},
}
c.Flags().StringSliceVar(&flagSources, "source", nil, "limit to these scanners")
c.Flags().StringArrayVar(&flagPaths, "path", nil, "extra file or directory to harvest (repeatable)")
return c
}
func exportCmd() *cobra.Command {
var out string
c := &cobra.Command{
Use: "export",
Short: "Write a sealed backup bundle of the gopass prefix",
RunE: func(cmd *cobra.Command, args []string) error {
gp := &sink.Gopass{Prefix: flagPrefix}
if !gp.Available() {
return fmt.Errorf("gopass not found on PATH")
}
sealer, err := sealerFor(flagSealer)
if err != nil {
return err
}
if flagDryRun {
paths, err := gp.ListPaths(context.Background(), flagPrefix)
if err != nil {
return err
}
for _, p := range paths {
fmt.Printf("would export %s\n", p)
}
fmt.Fprintf(os.Stderr, "\n%d entr(ies) under %q\n", len(paths), flagPrefix)
return nil
}
f, err := os.OpenFile(out, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
if err != nil {
return fmt.Errorf("create %s (use a fresh path; export refuses to overwrite): %w", out, err)
}
defer f.Close()
return withVault(func(ctx context.Context, v *vault.Vault) error {
h, err := readPassphrase(v, true)
if err != nil {
return err
}
pass, err := v.Open(h)
if err != nil {
return err
}
// gopass show -> framed plaintext -> pipe -> Sealer -> file.
pr, pw := io.Pipe()
var n int
go func() {
var gerr error
n, gerr = gp.ExportTo(ctx, pw, flagPrefix)
pw.CloseWithError(gerr)
}()
if err := sealer.Seal(ctx, pr, f, pass); err != nil {
return err
}
log, _ := audit.Open(flagAuditLog, time.Now)
defer log.Close()
log.Write(audit.Entry{Action: "export", Outcome: "ok",
Location: out, Source: sealer.Name()})
fmt.Fprintf(os.Stderr, "sealed %d entr(ies) with %s -> %s\n", n, sealer.Name(), out)
return nil
})
},
}
c.Flags().StringVar(&out, "out", "backup.incredigo.age", "output bundle path (refuses to overwrite)")
c.Flags().StringVar(&flagPrefix, "prefix", "imported/", "gopass prefix to export")
return c
}
func importCmd() *cobra.Command {
var in string
c := &cobra.Command{
Use: "import",
Short: "Restore a sealed bundle into gopass",
RunE: func(cmd *cobra.Command, args []string) error {
gp := &sink.Gopass{Prefix: flagPrefix}
if !gp.Available() {
return fmt.Errorf("gopass not found on PATH")
}
sealer, err := sealerFor(flagSealer)
if err != nil {
return err
}
f, err := os.Open(in)
if err != nil {
return err
}
defer f.Close()
return withVault(func(ctx context.Context, v *vault.Vault) error {
h, err := readPassphrase(v, false)
if err != nil {
return err
}
pass, err := v.Open(h)
if err != nil {
return err
}
// file -> Sealer.Open -> pipe -> framed plaintext -> gopass insert.
pr, pw := io.Pipe()
go func() {
pw.CloseWithError(sealer.Open(ctx, f, pw, pass))
}()
n, err := gp.ImportFrom(ctx, pr, flagForce)
if err != nil {
return err
}
log, _ := audit.Open(flagAuditLog, time.Now)
defer log.Close()
log.Write(audit.Entry{Action: "import", Outcome: "ok",
Location: in, Source: sealer.Name()})
fmt.Fprintf(os.Stderr, "restored %d entr(ies) from %s\n", n, in)
return nil
})
},
}
c.Flags().StringVar(&in, "in", "backup.incredigo.age", "input bundle path")
c.Flags().BoolVar(&flagForce, "force", false, "overwrite existing gopass entries")
return c
}
// rotateCmd is the rotation entry point. ROTATION IS NOT IMPLEMENTED (design
// phase, see docs/ROTATION.md). The command runs the mandatory backup gate and
// prints the rotation plan, but cannot and does not change any credential: there
// are zero registered drivers, and --execute is refused.
func rotateCmd() *cobra.Command {
var prefix, backupOut string
var execute bool
c := &cobra.Command{
Use: "rotate",
Short: "Plan rotation + take a verified backup (DESIGN PHASE — rotates nothing)",
Long: "Rotation is not implemented yet (see docs/ROTATION.md). This command runs the\n" +
"MANDATORY backup gate (sealed, verified snapshot of the gopass prefix) and prints\n" +
"the rotation plan. It never changes a credential.",
RunE: func(cmd *cobra.Command, args []string) error {
if execute {
return fmt.Errorf("rotate --execute refused: no rotation drivers are implemented (design phase) — see docs/ROTATION.md")
}
applyPaths()
gp := &sink.Gopass{Prefix: prefix}
if !gp.Available() {
return fmt.Errorf("gopass not found on PATH")
}
sealer, err := sealerFor(flagSealer)
if err != nil {
return err
}
if backupOut == "" {
dir := filepath.Join(os.Getenv("HOME"), ".incredigo", "rotation-backups")
if err := os.MkdirAll(dir, 0o700); err != nil {
return err
}
backupOut = filepath.Join(dir, time.Now().UTC().Format("2006-01-02T15-04-05Z")+".age")
}
return withVault(func(ctx context.Context, v *vault.Vault) error {
h, err := readPassphrase(v, true)
if err != nil {
return err
}
pass, err := v.Open(h)
if err != nil {
return err
}
// MANDATORY backup gate — rotation may never run without this.
n, err := rotate.Snapshot(ctx, gp, prefix, sealer, pass, backupOut)
if err != nil {
return fmt.Errorf("backup gate failed — rotation would be blocked: %w", err)
}
fmt.Fprintf(os.Stderr, "✓ backup gate: %d entr(ies) sealed + verified -> %s\n\n", n, backupOut)
// Non-destructive plan over discoverable credentials.
creds, err := discover.ScanAll(ctx, v, flagSources...)
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
fmt.Fprintln(w, "DRIVER\tSOURCE\tIDENTITY")
for _, p := range rotate.PlanAll(creds) {
drv := p.Driver
if drv == "" {
drv = "(none)"
}
fmt.Fprintf(w, "%s\t%s\t%s\n", drv, p.Credential.Source, p.Credential.Identity)
}
w.Flush()
log, _ := audit.Open(flagAuditLog, time.Now)
defer log.Close()
log.Write(audit.Entry{Action: "rotate-plan", Outcome: "ok", Location: backupOut})
fmt.Fprintf(os.Stderr, "\nDESIGN PHASE: %d rotation driver(s) registered — nothing rotated. See docs/ROTATION.md\n",
len(rotate.Drivers()))
return nil
})
},
}
c.Flags().StringVar(&prefix, "prefix", "imported/", "gopass prefix to back up and plan")
c.Flags().StringVar(&backupOut, "backup-out", "", "backup bundle path (default ~/.incredigo/rotation-backups/<ts>.age)")
c.Flags().BoolVar(&execute, "execute", false, "(reserved) perform rotation — refused in design phase")
c.Flags().StringArrayVar(&flagPaths, "path", nil, "extra file or directory to include in the plan")
c.Flags().StringSliceVar(&flagSources, "source", nil, "limit discovery to these scanners")
return c
}
// sealerFor selects the backup sealer. age is the authenticated default; openssl
// is an unauthenticated fallback (see internal/sink/openssl.go).
func sealerFor(name string) (sink.Sealer, error) {
switch strings.ToLower(strings.TrimSpace(name)) {
case "", "age":
return &sink.Age{}, nil
case "hmac":
return &sink.HMACSealer{}, nil
case "openssl":
return &sink.OpenSSL{}, nil
default:
return nil, fmt.Errorf("unknown sealer %q (want: age, hmac, openssl)", name)
}
}
// readPassphrase obtains a backup passphrase into the locked vault and returns its
// handle. Sources, in order: $INCREDIGO_PASSPHRASE (automation), an interactive
// no-echo TTY prompt, or one line from non-terminal stdin (piped). On a TTY,
// confirm re-prompts to guard against typos in a backup passphrase.
func readPassphrase(v *vault.Vault, confirm bool) (*vault.Handle, error) {
if env, ok := os.LookupEnv("INCREDIGO_PASSPHRASE"); ok {
if env == "" {
return nil, fmt.Errorf("INCREDIGO_PASSPHRASE is set but empty")
}
return v.Store([]byte(env)), nil
}
fd := int(os.Stdin.Fd())
if !term.IsTerminal(fd) {
// piped stdin: read a single line as the passphrase
line, err := readLine(os.Stdin)
if err != nil {
return nil, err
}
if len(line) == 0 {
return nil, fmt.Errorf("empty passphrase on stdin")
}
return v.Store(line), nil
}
fmt.Fprint(os.Stderr, "Passphrase: ")
pw1, err := term.ReadPassword(fd)
fmt.Fprintln(os.Stderr)
if err != nil {
return nil, err
}
if len(pw1) == 0 {
return nil, fmt.Errorf("empty passphrase")
}
if confirm {
fmt.Fprint(os.Stderr, "Confirm passphrase: ")
pw2, err := term.ReadPassword(fd)
fmt.Fprintln(os.Stderr)
if err != nil {
wipe(pw1)
return nil, err
}
eq := bytes.Equal(pw1, pw2)
wipe(pw2)
if !eq {
wipe(pw1)
return nil, fmt.Errorf("passphrases do not match")
}
}
return v.Store(pw1), nil // Store wipes pw1
}
// readLine reads bytes up to (not including) the first newline.
func readLine(r io.Reader) ([]byte, error) {
var out []byte
b := make([]byte, 1)
for {
n, err := r.Read(b)
if n > 0 {
if b[0] == '\n' {
return out, nil
}
out = append(out, b[0])
}
if err == io.EOF {
return out, nil
}
if err != nil {
return out, err
}
}
}
func wipe(b []byte) {
for i := range b {
b[i] = 0
}
}
func countSources(creds []discover.Credential) int {
seen := map[string]struct{}{}
for _, c := range creds {
seen[c.Source] = struct{}{}
}
return len(seen)
}