// Command agentvault runs deterministic Vault flows for agents in a single // invocation, so credentials are never plumbed through a shell. It authenticates // with the same Vault AppRole as agentpr (role_id only, no secret_id). // // agentvault seed-outpost --outpost --dest-path // agentvault seed-oauth --path --client-id package main import ( "fmt" "os" "strings" "git.unkin.net/unkin/agent-tools/internal/agent" "github.com/spf13/cobra" ) var version = "dev" func main() { if err := newRootCmd().Execute(); err != nil { os.Exit(1) } } // newRootCmd builds the agentvault command tree. Separated from main so tests // can execute it against httptest servers. func newRootCmd() *cobra.Command { root := &cobra.Command{ Use: "agentvault", Short: "Run deterministic Vault flows as the agent AppRole.", Long: "agentvault performs self-contained Vault flows for agents: it logs in with the\nagent AppRole and moves secret material between systems without ever printing it.", Version: version, SilenceUsage: true, } root.SetVersionTemplate("{{.Version}}\n") root.AddCommand(newSeedOutpostCmd(), newSeedOAuthCmd(), newVersionCmd()) return root } func newSeedOutpostCmd() *cobra.Command { opts := agent.SeedOutpostOptions{} cmd := &cobra.Command{ Use: "seed-outpost", Short: "Copy an Authentik outpost token into Vault KV", Long: "Read the Authentik API token from Vault KV, resolve the named outpost's\n" + "token_identifier, fetch its key and write it to a Vault KV path. Re-running\n" + "writes a new KV version. The token value is never printed or logged.", SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { opts.VaultAddr = agent.VaultAddr() opts.RoleID = agent.RoleID() res, err := agent.SeedOutpost(opts) if err != nil { return err } out := cmd.OutOrStdout() _, _ = fmt.Fprintf(out, "outpost: %s\n", res.Outpost) _, _ = fmt.Fprintf(out, "token_identifier: %s\n", res.TokenIdentifier) _, _ = fmt.Fprintf(out, "dest: %s/%s\n", res.KVMount, res.DestPath) _, _ = fmt.Fprintf(out, "version: %d\n", res.Version) return nil }, } f := cmd.Flags() f.StringVar(&opts.Outpost, "outpost", "", "Authentik outpost name (required)") f.StringVar(&opts.DestPath, "dest-path", "", "KV-v2 path to write the token to, e.g. kubernetes/namespace/authentik/default/outpost-token (required)") f.StringVar(&opts.DestKey, "dest-key", agent.DefaultDestKey, "Field to write the token under") f.StringVar(&opts.KVMount, "kv-mount", agent.DefaultKVMount, "KV-v2 mount holding both the API token and the destination") f.StringVar(&opts.TokenPath, "token-path", agent.DefaultOutpostTokenPath, "KV-v2 path of the Authentik API token") f.StringVar(&opts.AuthentikURL, "authentik-url", agent.AuthentikURL(), "Authentik base URL") _ = cmd.MarkFlagRequired("outpost") _ = cmd.MarkFlagRequired("dest-path") return cmd } func newSeedOAuthCmd() *cobra.Command { opts := agent.SeedOAuthOptions{} cmd := &cobra.Command{ Use: "seed-oauth", Short: "Seed an oauth2-proxy credential set into Vault KV", Long: "Make a Vault KV-v2 path hold a complete oauth2-proxy credential set: the\n" + "given client_id, plus a client_secret and a 32-byte cookie_secret that are\n" + "generated only when missing (or with --rotate). Existing keys are preserved\n" + "and nothing is written when the secret is already correct. Secret values are\n" + "never printed or logged.", SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { opts.VaultAddr = agent.VaultAddr() opts.RoleID = agent.RoleID() res, err := agent.SeedOAuth(opts) if err != nil { return err } out := cmd.OutOrStdout() _, _ = fmt.Fprintf(out, "path: %s/%s\n", res.KVMount, res.Path) _, _ = fmt.Fprintf(out, "keys: %s\n", strings.Join(res.KeyNames(), ", ")) for _, k := range res.Keys { _, _ = fmt.Fprintf(out, " %-14s %s\n", k.Name+":", k.Action) } if res.Changed { _, _ = fmt.Fprintf(out, "version: %d\n", res.Version) } else { _, _ = fmt.Fprintf(out, "version: unchanged\n") } return nil }, } f := cmd.Flags() f.StringVar(&opts.Path, "path", "", "KV-v2 path holding the credentials, e.g. kubernetes/namespace/repospawner/default/oauth-credentials (required)") f.StringVar(&opts.ClientID, "client-id", "", "OIDC client id to store (required)") f.StringVar(&opts.KVMount, "kv-mount", agent.DefaultKVMount, "KV-v2 mount holding the path") f.BoolVar(&opts.Rotate, "rotate", false, "Regenerate client_secret and cookie_secret even when they already exist") _ = cmd.MarkFlagRequired("path") _ = cmd.MarkFlagRequired("client-id") return cmd } func newVersionCmd() *cobra.Command { return &cobra.Command{ Use: "version", Short: "Print the version", Run: func(cmd *cobra.Command, args []string) { fmt.Println(version) }, SilenceUsage: true, } }