//go:build linux // Package command implements the deliberately small dwh-auth operator CLI. // It is intentionally independent of flag.CommandLine: command-line values // are never included in diagnostics, and the grammar is closed by default. package command import ( "context" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/json" "errors" "fmt" "io" "os" "path/filepath" "strings" "time" "unicode" "unicode/utf8" "github.com/aritmolab/thothii/tools/dwh-auth/internal/credential" "github.com/aritmolab/thothii/tools/dwh-auth/internal/record" "github.com/aritmolab/thothii/tools/dwh-auth/internal/registry" "github.com/aritmolab/thothii/tools/dwh-auth/internal/securefile" ) const ( exitOK = 0 exitUsage = 2 exitNotFound = 3 exitIntegrity = 4 maxReasonBytes = 160 commandServeMsg = "serve is not available in this release" ) var ( nowUTC = func() time.Time { return time.Now().UTC() } generateCredential = func() (credential.Material, error) { return credential.Generate(rand.Reader) } addRecord = func(store *registry.Store, value record.Record) error { return store.Add(value) } closeStore = func(store *registry.Store) error { return store.Close() } ) // Run executes one dwh-auth invocation and returns its stable process exit // status. It writes only redacted, deterministic diagnostics to stderr. func Run(ctx context.Context, args []string, _ io.Reader, stdout, stderr io.Writer) int { if ctx == nil { ctx = context.Background() } if stdout == nil || stderr == nil { return exitIntegrity } if len(args) > 0 && args[0] == "serve" { return runServe(args[1:], stderr) } root, rest, err := parseRoot(args) if err != nil { writeLine(stderr, "unsafe invocation") return exitUsage } if len(rest) == 0 { writeLine(stderr, "unsafe invocation") return exitUsage } switch rest[0] { case "key": return runKey(ctx, root, rest[1:], stdout, stderr) case "check": return runCheck(ctx, root, rest[1:], stdout, stderr) default: writeLine(stderr, "unsafe invocation") return exitUsage } } func parseRoot(args []string) (string, []string, error) { if len(args) < 2 || args[0] != "--registry-root" || args[1] == "" || !filepath.IsAbs(args[1]) { return "", nil, errors.New("invalid registry root") } return filepath.Clean(args[1]), args[2:], nil } func runKey(ctx context.Context, root string, args []string, stdout, stderr io.Writer) int { if len(args) == 0 { writeLine(stderr, "unsafe invocation") return exitUsage } switch args[0] { case "create": return runCreate(ctx, root, args[1:], stdout, stderr) case "import": return runImport(ctx, root, args[1:], stdout, stderr) case "list": return runList(ctx, root, args[1:], stdout, stderr) case "status": return runStatus(ctx, root, args[1:], stdout, stderr) case "revoke": return runRevoke(ctx, root, args[1:], stdout, stderr) default: writeLine(stderr, "unsafe invocation") return exitUsage } } type parsed struct { installationID string description string expiresAt string output string fromFile string keyID string reason string json bool legacyRaw bool socket string } func parseFlags(args []string, allowed map[string]bool) (parsed, error) { var value parsed seen := make(map[string]bool) for i := 0; i < len(args); i++ { name := args[i] if !strings.HasPrefix(name, "--") || name == "--" || strings.Contains(name, "=") { return parsed{}, errors.New("invalid option") } if !allowed[name] || seen[name] { return parsed{}, errors.New("invalid option") } seen[name] = true if name == "--json" || name == "--legacy-raw" { switch name { case "--json": value.json = true case "--legacy-raw": value.legacyRaw = true } continue } if i+1 >= len(args) || strings.HasPrefix(args[i+1], "--") { return parsed{}, errors.New("missing option value") } i++ switch name { case "--installation-id": value.installationID = args[i] case "--description": value.description = args[i] case "--expires-at": value.expiresAt = args[i] case "--output": value.output = args[i] case "--from-file": value.fromFile = args[i] case "--key-id": value.keyID = args[i] case "--reason": value.reason = args[i] case "--socket": value.socket = args[i] } } return value, nil } func runCreate(ctx context.Context, root string, args []string, stdout, stderr io.Writer) int { options, err := parseFlags(args, map[string]bool{ "--installation-id": true, "--description": true, "--expires-at": true, "--output": true, }) if err != nil || options.installationID == "" || options.output == "" || !filepath.IsAbs(options.output) || !validInstallationID(options.installationID) || !validMetadata(options.description) || containsCanonicalCredential(options.description) { writeLine(stderr, "unsafe invocation") return exitUsage } createdAt := nowUTC() expiresAt, err := parseExpiry(options.expiresAt, createdAt) if err != nil { writeLine(stderr, "unsafe invocation") return exitUsage } if err := ctx.Err(); err != nil { writeLine(stderr, "operation cancelled") return exitIntegrity } material, err := generateCredential() if err != nil { writeLine(stderr, "integrity failure") return exitIntegrity } outputFile, err := securefile.CreateSecret(options.output) if err != nil { writeLine(stderr, "integrity failure") return exitIntegrity } if err := writeAndSync(outputFile, material.Value); err != nil { _ = outputFile.Close() if cleanupOutput(options.output) != nil { writeCleanupFailure(stderr, options.output) return exitIntegrity } writeLine(stderr, "integrity failure") return exitIntegrity } if err := outputFile.Close(); err != nil { if cleanupOutput(options.output) != nil { writeCleanupFailure(stderr, options.output) return exitIntegrity } writeLine(stderr, "integrity failure") return exitIntegrity } store, err := registry.Open(root) if err != nil { if cleanupOutput(options.output) != nil { writeCleanupFailure(stderr, options.output) return exitIntegrity } writeLine(stderr, "integrity failure") return exitIntegrity } value := record.Record{ SchemaVersion: record.SchemaVersion, Kind: record.KindV1, KeyID: material.KeyID, InstallationID: options.installationID, Description: options.description, SecretSHA256: base64.RawURLEncoding.EncodeToString(material.Digest[:]), CreatedAt: createdAt, ExpiresAt: expiresAt, } addErr := addRecord(store, value) if addErr != nil { present, snapshotErr := snapshotContainsKey(store, material.KeyID) closeErr := closeStore(store) if snapshotErr == nil && !present && closeErr == nil { if cleanupOutput(options.output) != nil { writeCleanupFailure(stderr, options.output) return exitIntegrity } } else { writeRecoveryFailure(stderr, options.output) return exitIntegrity } if errors.Is(addErr, registry.ErrConflict) || errors.Is(addErr, registry.ErrRevoked) { writeLine(stderr, "credential already exists") return exitIntegrity } writeLine(stderr, "integrity failure") return exitIntegrity } if err := closeStore(store); err != nil { writeRecoveryFailure(stderr, options.output) return exitIntegrity } _, _ = fmt.Fprintf(stdout, "created key_id=%s installation_id=%s output=%s\n", material.KeyID, options.installationID, filepath.Clean(options.output)) return exitOK } func runImport(ctx context.Context, root string, args []string, stdout, stderr io.Writer) int { options, err := parseFlags(args, map[string]bool{ "--legacy-raw": true, "--installation-id": true, "--from-file": true, }) if err != nil || !options.legacyRaw || options.installationID != record.LegacyKeyID || options.fromFile == "" || !filepath.IsAbs(options.fromFile) { writeLine(stderr, "unsafe invocation") return exitUsage } if err := ctx.Err(); err != nil { writeLine(stderr, "operation cancelled") return exitIntegrity } value, err := securefile.ReadSecret(options.fromFile, credential.MaxHeaderBytes) if err != nil { writeLine(stderr, "integrity failure") return exitIntegrity } sum := sha256.Sum256(value) digest := record.Digest(sum) if !credential.VerifyLegacy(value, digest) { writeLine(stderr, "unsafe invocation") return exitUsage } store, err := registry.Open(root) if err != nil { writeLine(stderr, "integrity failure") return exitIntegrity } now := time.Now().UTC() addErr := store.Add(record.Record{ SchemaVersion: record.SchemaVersion, Kind: record.KindLegacyRaw, KeyID: record.LegacyKeyID, InstallationID: record.LegacyKeyID, SecretSHA256: base64.RawURLEncoding.EncodeToString(digest[:]), CreatedAt: now, }) closeErr := store.Close() if closeErr != nil { writeLine(stderr, "integrity failure") return exitIntegrity } if addErr != nil { switch { case errors.Is(addErr, registry.ErrConflict), errors.Is(addErr, registry.ErrRevoked): writeLine(stderr, "credential already exists") return exitIntegrity default: writeLine(stderr, "integrity failure") return exitIntegrity } } _, _ = fmt.Fprintf(stdout, "imported key_id=%s installation_id=%s\n", record.LegacyKeyID, record.LegacyKeyID) return exitOK } func runList(ctx context.Context, root string, args []string, stdout, stderr io.Writer) int { options, err := parseFlags(args, map[string]bool{"--json": true}) if err != nil { writeLine(stderr, "unsafe invocation") return exitUsage } store, err := openCheckedStore(ctx, root) if err != nil { writeStoreError(stderr, err) return storeExit(err) } values, listErr := store.List() closeErr := store.Close() if listErr != nil || closeErr != nil { writeLine(stderr, "integrity failure") return exitIntegrity } if options.json { writeJSON(stdout, values) return exitOK } for _, value := range values { _, _ = fmt.Fprintf(stdout, "key_id=%s installation_id=%s state=%s\n", value.KeyID, value.InstallationID, value.State) } return exitOK } func runStatus(ctx context.Context, root string, args []string, stdout, stderr io.Writer) int { options, err := parseFlags(args, map[string]bool{"--key-id": true, "--json": true}) if err != nil || options.keyID == "" || !validKeyID(options.keyID) { writeLine(stderr, "unsafe invocation") return exitUsage } store, err := openCheckedStore(ctx, root) if err != nil { writeStoreError(stderr, err) return storeExit(err) } values, listErr := store.List() closeErr := store.Close() if listErr != nil || closeErr != nil { writeLine(stderr, "integrity failure") return exitIntegrity } for _, value := range values { if value.KeyID != options.keyID { continue } if options.json { writeJSON(stdout, value) } else { _, _ = fmt.Fprintf(stdout, "key_id=%s installation_id=%s state=%s\n", value.KeyID, value.InstallationID, value.State) } return exitOK } writeLine(stderr, "not found") return exitNotFound } func runRevoke(ctx context.Context, root string, args []string, stdout, stderr io.Writer) int { options, err := parseFlags(args, map[string]bool{"--key-id": true, "--reason": true}) if err != nil || options.keyID == "" || !validKeyID(options.keyID) || options.reason == "" || !validMetadata(options.reason) || containsCanonicalCredential(options.reason) { writeLine(stderr, "unsafe invocation") return exitUsage } if err := ctx.Err(); err != nil { writeLine(stderr, "operation cancelled") return exitIntegrity } store, err := registry.Open(root) if err != nil { writeLine(stderr, "integrity failure") return exitIntegrity } err = store.Revoke(options.keyID, options.reason, nowUTC()) closeErr := store.Close() if errors.Is(err, registry.ErrNotFound) { writeLine(stderr, "not found") return exitNotFound } if err != nil || closeErr != nil { writeLine(stderr, "integrity failure") return exitIntegrity } _, _ = fmt.Fprintf(stdout, "revoked key_id=%s\n", options.keyID) return exitOK } func runCheck(ctx context.Context, root string, args []string, stdout, stderr io.Writer) int { options, err := parseFlags(args, map[string]bool{"--json": true}) if err != nil { writeLine(stderr, "unsafe invocation") return exitUsage } if err := ctx.Err(); err != nil { writeLine(stderr, "operation cancelled") return exitIntegrity } store, err := registry.Open(root) if err != nil { writeLine(stderr, "integrity failure") return exitIntegrity } checkErr := store.Check() closeErr := store.Close() if checkErr != nil || closeErr != nil { writeLine(stderr, "integrity failure") return exitIntegrity } if options.json { _, _ = io.WriteString(stdout, "{\"status\":\"ok\"}\n") } else { writeLine(stdout, "ok") } return exitOK } func runServe(args []string, stderr io.Writer) int { if len(args) != 4 || args[0] != "--registry-root" || args[2] != "--socket" || !filepath.IsAbs(args[1]) || !filepath.IsAbs(args[3]) { writeLine(stderr, "unsafe invocation") return exitUsage } // The Unix-socket service is intentionally introduced by Task 4. Keeping // this exact grammar reserved prevents accidental plaintext or network fallback. writeLine(stderr, commandServeMsg) return exitIntegrity } func snapshotContainsKey(store *registry.Store, keyID string) (bool, error) { values, err := store.List() if err != nil { return false, err } for _, value := range values { if value.KeyID == keyID { return true, nil } } return false, nil } func openCheckedStore(ctx context.Context, root string) (*registry.Store, error) { if err := ctx.Err(); err != nil { return nil, err } store, err := registry.Open(root) if err != nil { return nil, err } if err := store.Check(); err != nil { _ = store.Close() return nil, err } return store, nil } func writeStoreError(stderr io.Writer, err error) { if errors.Is(err, registry.ErrNotFound) { writeLine(stderr, "not found") return } writeLine(stderr, "integrity failure") } func storeExit(err error) int { if errors.Is(err, registry.ErrNotFound) { return exitNotFound } return exitIntegrity } func writeJSON(writer io.Writer, value any) { data, err := json.Marshal(value) if err != nil { return } _, _ = writer.Write(append(data, '\n')) } func writeLine(writer io.Writer, value string) { _, _ = io.WriteString(writer, value+"\n") } func writeAndSync(file *os.File, value []byte) error { for len(value) > 0 { n, err := file.Write(value) if err != nil { return err } if n == 0 { return io.ErrShortWrite } value = value[n:] } return file.Sync() } func cleanupOutput(path string) error { info, err := os.Lstat(path) if errors.Is(err, os.ErrNotExist) { return nil } if err != nil || !info.Mode().IsRegular() { return errors.New("cleanup target is not a regular file") } if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) { return err } return nil } func writeCleanupFailure(stderr io.Writer, path string) { _, _ = fmt.Fprintf(stderr, "cleanup failed path=%s\n", filepath.Clean(path)) } func writeRecoveryFailure(stderr io.Writer, path string) { _, _ = fmt.Fprintf(stderr, "publication uncertain path=%s\n", filepath.Clean(path)) } func parseExpiry(value string, createdAt time.Time) (*time.Time, error) { if value == "" { return nil, nil } parsed, err := time.Parse(time.RFC3339, value) if err != nil || parsed.Location() != time.UTC || !parsed.After(createdAt) { return nil, errors.New("expiry must be UTC RFC3339") } return &parsed, nil } func validInstallationID(value string) bool { if len(value) < 1 || len(value) > 63 { return false } for i := range value { c := value[i] if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (i > 0 && c == '-') { continue } return false } return true } func validMetadata(value string) bool { if !utf8.ValidString(value) || len([]rune(value)) > maxReasonBytes { return false } for _, r := range value { if unicode.IsControl(r) { return false } } return true } func containsCanonicalCredential(value string) bool { const credentialLength = len(credential.Prefix) + 1 + credential.KeyIDEncodedLength + 1 + credential.SecretEncodedLength needle := credential.Prefix + "." for offset := 0; offset < len(value); { index := strings.Index(value[offset:], needle) if index < 0 { return false } index += offset if index+credentialLength <= len(value) { candidate := []byte(value[index : index+credentialLength]) sum := sha256.Sum256(candidate) if _, ok := credential.VerifyV1(candidate, record.Digest(sum)); ok { return true } } offset = index + len(needle) } return false } func validKeyID(value string) bool { if value == record.LegacyKeyID { return true } if len(value) != credential.KeyIDEncodedLength { return false } decoded, err := base64.RawURLEncoding.DecodeString(value) return err == nil && len(decoded) == 12 && base64.RawURLEncoding.EncodeToString(decoded) == value }