package cmd import ( "crypto/rand" "encoding/base64" "errors" "fmt" "io" "os" "path/filepath" "reflect" "strings" "github.com/Pingu-Studio/MaidnCLI/internal/bootstrap" "github.com/Pingu-Studio/MaidnCLI/internal/config" "github.com/Pingu-Studio/MaidnCLI/internal/openbao" "github.com/spf13/cobra" ) var appSecretConfigPath, appSecretFile, appSecretTokenFile, appSecretIdentity, appSecretGrantEnvironment string var appSecretShared, appSecretDeleteYes, appSecretGenerate bool var appSecretGrantShared, appSecretGrantSecrets []string var loadAppSecretConfig = config.Load var saveAppSecretConfig = config.Save var storeAppSecret = openbao.StoreManagedSecret var listAppSecrets = openbao.ListManagedSecrets var appSecretStatus = openbao.ManagedSecretStatus var deleteAppSecret = openbao.DeleteManagedSecret var appSecretCmd = &cobra.Command{ Use: "secret", Short: "Manage application and shared OpenBao secret values.", } var appSecretSetCmd = &cobra.Command{ Use: "set ", Short: "Store a value from stdin or --file.", Args: cobra.ExactArgs(2), RunE: runAppSecretSet, } var appSecretListCmd = &cobra.Command{ Use: "list ", Short: "List secret names without values.", Args: cobra.ExactArgs(1), RunE: runAppSecretList, } var appSecretDeleteCmd = &cobra.Command{ Use: "delete ", Short: "Permanently delete a secret after explicit confirmation.", Args: cobra.ExactArgs(2), RunE: runAppSecretDelete, } var appSecretGrantCmd = &cobra.Command{ Use: "grant ", Short: "Add a declarative SecretGrant to the private bootstrap config.", Args: cobra.ExactArgs(2), RunE: runAppSecretGrant, } var appSecretStatusCmd = &cobra.Command{ Use: "status ", Short: "Report whether a secret exists without reading its value.", Args: cobra.ExactArgs(2), RunE: runAppSecretStatus, } func init() { appCmd.AddCommand(appSecretCmd) appSecretCmd.AddCommand(appSecretSetCmd, appSecretListCmd, appSecretDeleteCmd, appSecretGrantCmd, appSecretStatusCmd) appSecretCmd.PersistentFlags().StringVar(&appSecretConfigPath, "config", "", "Path to private bootstrap config YAML") _ = appSecretCmd.MarkPersistentFlagRequired("config") appSecretCmd.PersistentFlags().StringVar(&appSecretTokenFile, "token-file", "", "Path to restricted OpenBao token file for secret CRUD") appSecretCmd.PersistentFlags().StringVar(&appSecretIdentity, "identity", "", "Encrypted operational identity: admin or e2e:") appSecretSetCmd.Flags().StringVar(&appSecretFile, "file", "", "Read the secret value from this file instead of stdin") appSecretSetCmd.Flags().BoolVar(&appSecretGenerate, "generate", false, "Generate a random secret value without printing it") for _, command := range []*cobra.Command{appSecretSetCmd, appSecretListCmd, appSecretDeleteCmd, appSecretStatusCmd} { command.Flags().BoolVar(&appSecretShared, "shared", false, "Use shared// instead of apps//") } appSecretDeleteCmd.Flags().BoolVar(&appSecretDeleteYes, "yes", false, "Confirm permanent deletion") appSecretGrantCmd.Flags().StringVar(&appSecretGrantEnvironment, "environment", "", "Runtime environment: staging or production") appSecretGrantCmd.Flags().StringSliceVar(&appSecretGrantSecrets, "secret", nil, "Application secret name granted to this consumer (repeat for each)") appSecretGrantCmd.Flags().StringSliceVar(&appSecretGrantShared, "shared", nil, "Shared secret group allowed by this grant") } func runAppSecretSet(cmd *cobra.Command, args []string) error { path, kubeconfig, err := appSecretTarget(args) if err != nil { return err } value, err := readAppSecretValue(cmd) if err != nil { return err } tokenFile, cleanup, err := appSecretTokenPath() if err != nil { return err } defer cleanup() if err := storeAppSecret(kubeconfig, tokenFile, path, value); err != nil { return err } fmt.Fprintf(cmd.OutOrStdout(), "stored %s\n", path) return nil } func runAppSecretList(cmd *cobra.Command, args []string) error { kubeconfig, err := appSecretKubeconfigFromConfig() if err != nil { return err } tokenFile, cleanup, err := appSecretTokenPath() if err != nil { return err } defer cleanup() values, err := listAppSecrets(kubeconfig, tokenFile, appSecretShared, args[0]) if err != nil { return err } for _, value := range values { fmt.Fprintln(cmd.OutOrStdout(), value) } return nil } func runAppSecretDelete(cmd *cobra.Command, args []string) error { if !appSecretDeleteYes { return errors.New("delete requires --yes") } path, kubeconfig, err := appSecretTarget(args) if err != nil { return err } tokenFile, cleanup, err := appSecretTokenPath() if err != nil { return err } defer cleanup() if err := deleteAppSecret(kubeconfig, tokenFile, path); err != nil { return err } fmt.Fprintf(cmd.OutOrStdout(), "deleted %s\n", path) return nil } func runAppSecretGrant(cmd *cobra.Command, args []string) error { cfg, err := loadRequiredAppSecretConfig() if err != nil { return err } grant := config.SecretGrant{Application: args[0], Consumer: args[1], Environment: appSecretGrantEnvironment, Secrets: appSecretGrantSecrets, Shared: appSecretGrantShared} if err := config.ValidateSecretGrants([]config.SecretGrant{grant}); err != nil { return err } for _, existing := range cfg.SecretGrants { if existing.Application == grant.Application && existing.Consumer == grant.Consumer && existing.Environment == grant.Environment { if reflect.DeepEqual(existing, grant) { fmt.Fprintln(cmd.OutOrStdout(), "grant already declared") return nil } return errors.New("secret grant already exists with a different definition") } } cfg.SecretGrants = append(cfg.SecretGrants, grant) if err := config.ValidateSecretGrants(cfg.SecretGrants); err != nil { return err } if err := saveAppSecretConfig(appSecretConfigPath, cfg); err != nil { return fmt.Errorf("save declarative secret grant: %w", err) } fmt.Fprintln(cmd.OutOrStdout(), "grant declared; reconcile OpenBao through the reviewed bootstrap workflow") return nil } func runAppSecretStatus(cmd *cobra.Command, args []string) error { path, kubeconfig, err := appSecretTarget(args) if err != nil { return err } tokenFile, cleanup, err := appSecretTokenPath() if err != nil { return err } defer cleanup() present, err := appSecretStatus(kubeconfig, tokenFile, path) if err != nil { return err } if present { fmt.Fprintf(cmd.OutOrStdout(), "%s: present\n", path) } else { fmt.Fprintf(cmd.OutOrStdout(), "%s: absent\n", path) } return nil } func appSecretTarget(args []string) (string, string, error) { if len(args) != 2 { return "", "", errors.New("secret target requires an application or shared group and a secret name") } path, err := openbao.ManagedSecretPath(appSecretShared, args[0], args[1]) if err != nil { return "", "", err } kubeconfig, err := appSecretKubeconfigFromConfig() return path, kubeconfig, err } func loadRequiredAppSecretConfig() (config.Config, error) { if appSecretConfigPath == "" { return config.Config{}, errors.New("--config is required") } return loadAppSecretConfig(appSecretConfigPath) } func appSecretKubeconfigFromConfig() (string, error) { cfg, err := loadRequiredAppSecretConfig() if err != nil { return "", err } return filepath.Join(cfg.Git.CloneParent, cfg.Talos.RepoDirName, cfg.Talos.GeneratedDir, "kubeconfig"), nil } func readAppSecretValue(cmd *cobra.Command) ([]byte, error) { if appSecretGenerate { if appSecretFile != "" { return nil, errors.New("--generate and --file cannot be used together") } value := make([]byte, 32) if _, err := rand.Read(value); err != nil { return nil, err } return []byte(base64.RawURLEncoding.EncodeToString(value)), nil } if appSecretFile != "" { value, err := os.ReadFile(appSecretFile) if err != nil { return nil, fmt.Errorf("read secret file: %w", err) } return value, nil } value, err := io.ReadAll(cmd.InOrStdin()) if err != nil { return nil, fmt.Errorf("read secret stdin: %w", err) } return value, nil } func appSecretTokenPath() (string, func(), error) { if appSecretTokenFile != "" { return appSecretTokenFile, func() {}, nil } if appSecretIdentity == "" { return "", nil, errors.New("--token-file or --identity is required") } cfg, err := loadRequiredAppSecretConfig() if err != nil { return "", nil, err } path := "" if appSecretIdentity == "admin" { path = "cicd/app-secret-admin" } else if strings.HasPrefix(appSecretIdentity, "e2e:") { path = "cicd/e2e-" + strings.TrimPrefix(appSecretIdentity, "e2e:") } else { return "", nil, errors.New("--identity must be admin or e2e:") } secrets, err := bootstrap.ReadOperationalSecrets(cfg.SOPS.OperationalSecretsPath, cfg.SOPS.AgeKeyPath) if err != nil { return "", nil, errors.New("read encrypted app-secret identity") } token := secrets[path]["token"] if token == "" { return "", nil, errors.New("configured app-secret identity is absent") } file, err := os.CreateTemp("", "maidn-openbao-token-*") if err != nil { return "", nil, err } if _, err := file.WriteString(token + "\n"); err != nil { file.Close() os.Remove(file.Name()) return "", nil, err } if err := file.Close(); err != nil { os.Remove(file.Name()) return "", nil, err } return file.Name(), func() { _ = os.Remove(file.Name()) }, nil }