// Command mciasgrpcctl is the MCIAS gRPC admin CLI. // // It connects to a running mciassrv gRPC listener and provides subcommands for // managing accounts, roles, tokens, Postgres credentials, and policy rules via // the gRPC API. // // Usage: // // mciasgrpcctl [global flags] [args] // // Global flags: // // --server gRPC server address (default: mcias.metacircular.net:9443) // --token Bearer token for authentication (or set MCIAS_TOKEN env var) // --cacert Path to CA certificate for TLS verification (optional) // // Commands: // // health // pubkey // // auth login --username NAME [--totp CODE] // auth logout // // account list // account create --username NAME --password PASS [--type human|system] // account get --id UUID // account update --id UUID --status active|inactive // account delete --id UUID // // role list --id UUID // role set --id UUID --roles role1,role2,... // role grant --id UUID --role ROLE // role revoke --id UUID --role ROLE // // token validate --token TOKEN // token issue --id UUID // token revoke --jti JTI // // pgcreds get --id UUID // pgcreds set --id UUID --host HOST [--port PORT] --db DB --user USER --password PASS // // policy list // policy create --description STR --json FILE [--priority N] [--not-before RFC3339] [--expires-at RFC3339] // policy get --id ID // policy update --id ID [--priority N] [--enabled true|false] [--not-before RFC3339] [--expires-at RFC3339] [--clear-not-before] [--clear-expires-at] // policy delete --id ID package main import ( "context" "crypto/tls" "crypto/x509" "encoding/json" "fmt" "os" "strconv" "strings" "time" "github.com/spf13/cobra" "golang.org/x/term" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" mciasv1 "git.wntrmute.dev/mc/mcias/gen/mcias/v1" ) // controller holds the shared gRPC connection and token for all subcommands. type controller struct { conn *grpc.ClientConn token string } // authCtx returns a context with the Bearer token injected as gRPC metadata. // Security: token is placed in the "authorization" key per the gRPC convention // that mirrors the HTTP Authorization header. Value is never logged. func (c *controller) authCtx() context.Context { ctx := context.Background() if c.token == "" { return ctx } // Security: metadata key "authorization" matches the server-side // extractBearerFromMD expectation; value is "Bearer ". return metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+c.token) } // callCtx returns an authCtx with a 30-second deadline. func (c *controller) callCtx() (context.Context, context.CancelFunc) { return context.WithTimeout(c.authCtx(), 30*time.Second) } func main() { var ( serverAddr string tokenFlag string caCert string ) ctl := &controller{} root := &cobra.Command{ Use: "mciasgrpcctl", Short: "MCIAS gRPC admin CLI", PersistentPreRun: func(cmd *cobra.Command, args []string) { // Resolve token from flag or environment. ctl.token = tokenFlag if ctl.token == "" { ctl.token = os.Getenv("MCIAS_TOKEN") } // Skip gRPC connection for completion commands. if cmd.Name() == "completion" || cmd.Parent() != nil && cmd.Parent().Name() == "completion" { return } // Also skip for help commands. if cmd.Name() == "help" { return } conn, err := newGRPCConn(serverAddr, caCert) if err != nil { fatalf("connect to gRPC server: %v", err) } ctl.conn = conn }, PersistentPostRun: func(cmd *cobra.Command, args []string) { if ctl.conn != nil { _ = ctl.conn.Close() } }, } root.PersistentFlags().StringVar(&serverAddr, "server", "mcias.metacircular.net:9443", "gRPC server address (host:port)") root.PersistentFlags().StringVar(&tokenFlag, "token", "", "bearer token (or set MCIAS_TOKEN)") root.PersistentFlags().StringVar(&caCert, "cacert", "", "path to CA certificate for TLS") root.AddCommand(healthCmd(ctl)) root.AddCommand(pubkeyCmd(ctl)) root.AddCommand(authCmd(ctl)) root.AddCommand(accountCmd(ctl)) root.AddCommand(roleCmd(ctl)) root.AddCommand(tokenCmd(ctl)) root.AddCommand(pgcredsCmd(ctl)) root.AddCommand(policyCmd(ctl)) if err := root.Execute(); err != nil { os.Exit(1) } } // ---- health / pubkey ---- func healthCmd(ctl *controller) *cobra.Command { return &cobra.Command{ Use: "health", Short: "Check server health", Run: func(cmd *cobra.Command, args []string) { adminCl := mciasv1.NewAdminServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() resp, err := adminCl.Health(ctx, &mciasv1.HealthRequest{}) if err != nil { fatalf("health: %v", err) } printJSON(map[string]string{"status": resp.Status}) }, } } func pubkeyCmd(ctl *controller) *cobra.Command { return &cobra.Command{ Use: "pubkey", Short: "Get server public key", Run: func(cmd *cobra.Command, args []string) { adminCl := mciasv1.NewAdminServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() resp, err := adminCl.GetPublicKey(ctx, &mciasv1.GetPublicKeyRequest{}) if err != nil { fatalf("pubkey: %v", err) } printJSON(map[string]string{ "kty": resp.Kty, "crv": resp.Crv, "use": resp.Use, "alg": resp.Alg, "x": resp.X, }) }, } } // ---- auth subcommands ---- func authCmd(ctl *controller) *cobra.Command { cmd := &cobra.Command{ Use: "auth", Short: "Authentication commands", } cmd.AddCommand(authLoginCmd(ctl)) cmd.AddCommand(authLogoutCmd(ctl)) return cmd } // authLoginCmd authenticates with the gRPC server using username and password, // then prints the resulting bearer token to stdout. The password is always // prompted interactively; it is never accepted as a command-line flag to // prevent it from appearing in shell history, ps output, and process argument // lists. // // Security: terminal echo is disabled during password entry // (golang.org/x/term.ReadPassword); the raw byte slice is zeroed after use. func authLoginCmd(ctl *controller) *cobra.Command { var ( username string totpCode string ) cmd := &cobra.Command{ Use: "login", Short: "Obtain a bearer token (password prompted interactively)", Run: func(cmd *cobra.Command, args []string) { if username == "" { fatalf("auth login: --username is required") } // Security: always prompt interactively; never accept password as a flag. // This prevents the credential from appearing in shell history, ps output, // and /proc/PID/cmdline. fmt.Fprint(os.Stderr, "Password: ") raw, err := term.ReadPassword(int(os.Stdin.Fd())) //nolint:gosec // uintptr==int on all target platforms fmt.Fprintln(os.Stderr) if err != nil { fatalf("read password: %v", err) } passwd := string(raw) // Zero the raw byte slice once copied into the string. for i := range raw { raw[i] = 0 } authCl := mciasv1.NewAuthServiceClient(ctl.conn) // Login is a public RPC — no auth context needed. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() resp, err := authCl.Login(ctx, &mciasv1.LoginRequest{ Username: username, Password: passwd, TotpCode: totpCode, }) if err != nil { fatalf("auth login: %v", err) } // Print token to stdout so it can be captured by scripts, e.g.: // export MCIAS_TOKEN=$(mciasgrpcctl auth login --username alice) fmt.Println(resp.Token) if resp.ExpiresAt != nil { fmt.Fprintf(os.Stderr, "expires: %s\n", resp.ExpiresAt.AsTime().UTC().Format(time.RFC3339)) } }, } cmd.Flags().StringVar(&username, "username", "", "username (required)") cmd.Flags().StringVar(&totpCode, "totp", "", "TOTP code (required if TOTP is enrolled)") return cmd } func authLogoutCmd(ctl *controller) *cobra.Command { return &cobra.Command{ Use: "logout", Short: "Revoke the current bearer token", Run: func(cmd *cobra.Command, args []string) { authCl := mciasv1.NewAuthServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() if _, err := authCl.Logout(ctx, &mciasv1.LogoutRequest{}); err != nil { fatalf("auth logout: %v", err) } fmt.Println("logged out") }, } } // ---- account subcommands ---- func accountCmd(ctl *controller) *cobra.Command { cmd := &cobra.Command{ Use: "account", Short: "Account management commands", } cmd.AddCommand(accountListCmd(ctl)) cmd.AddCommand(accountCreateCmd(ctl)) cmd.AddCommand(accountGetCmd(ctl)) cmd.AddCommand(accountUpdateCmd(ctl)) cmd.AddCommand(accountDeleteCmd(ctl)) return cmd } func accountListCmd(ctl *controller) *cobra.Command { return &cobra.Command{ Use: "list", Short: "List all accounts", Run: func(cmd *cobra.Command, args []string) { cl := mciasv1.NewAccountServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() resp, err := cl.ListAccounts(ctx, &mciasv1.ListAccountsRequest{}) if err != nil { fatalf("account list: %v", err) } printJSON(resp.Accounts) }, } } func accountCreateCmd(ctl *controller) *cobra.Command { var ( username string password string accountType string ) cmd := &cobra.Command{ Use: "create", Short: "Create an account", Run: func(cmd *cobra.Command, args []string) { if username == "" { fatalf("account create: --username is required") } cl := mciasv1.NewAccountServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() resp, err := cl.CreateAccount(ctx, &mciasv1.CreateAccountRequest{ Username: username, Password: password, AccountType: accountType, }) if err != nil { fatalf("account create: %v", err) } printJSON(resp.Account) }, } cmd.Flags().StringVar(&username, "username", "", "username (required)") cmd.Flags().StringVar(&password, "password", "", "password (required for human accounts)") cmd.Flags().StringVar(&accountType, "type", "human", "account type: human or system") return cmd } func accountGetCmd(ctl *controller) *cobra.Command { var id string cmd := &cobra.Command{ Use: "get", Short: "Get an account by UUID", Run: func(cmd *cobra.Command, args []string) { if id == "" { fatalf("account get: --id is required") } cl := mciasv1.NewAccountServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() resp, err := cl.GetAccount(ctx, &mciasv1.GetAccountRequest{Id: id}) if err != nil { fatalf("account get: %v", err) } printJSON(resp.Account) }, } cmd.Flags().StringVar(&id, "id", "", "account UUID (required)") return cmd } func accountUpdateCmd(ctl *controller) *cobra.Command { var ( id string status string ) cmd := &cobra.Command{ Use: "update", Short: "Update an account status", Run: func(cmd *cobra.Command, args []string) { if id == "" { fatalf("account update: --id is required") } if status == "" { fatalf("account update: --status is required") } cl := mciasv1.NewAccountServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() _, err := cl.UpdateAccount(ctx, &mciasv1.UpdateAccountRequest{ Id: id, Status: status, }) if err != nil { fatalf("account update: %v", err) } fmt.Println("account updated") }, } cmd.Flags().StringVar(&id, "id", "", "account UUID (required)") cmd.Flags().StringVar(&status, "status", "", "new status: active or inactive (required)") return cmd } func accountDeleteCmd(ctl *controller) *cobra.Command { var id string cmd := &cobra.Command{ Use: "delete", Short: "Delete an account", Run: func(cmd *cobra.Command, args []string) { if id == "" { fatalf("account delete: --id is required") } cl := mciasv1.NewAccountServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() _, err := cl.DeleteAccount(ctx, &mciasv1.DeleteAccountRequest{Id: id}) if err != nil { fatalf("account delete: %v", err) } fmt.Println("account deleted") }, } cmd.Flags().StringVar(&id, "id", "", "account UUID (required)") return cmd } // ---- role subcommands ---- func roleCmd(ctl *controller) *cobra.Command { cmd := &cobra.Command{ Use: "role", Short: "Role management commands", } cmd.AddCommand(roleListCmd(ctl)) cmd.AddCommand(roleSetCmd(ctl)) cmd.AddCommand(roleGrantCmd(ctl)) cmd.AddCommand(roleRevokeCmd(ctl)) return cmd } func roleListCmd(ctl *controller) *cobra.Command { var id string cmd := &cobra.Command{ Use: "list", Short: "List roles for an account", Run: func(cmd *cobra.Command, args []string) { if id == "" { fatalf("role list: --id is required") } cl := mciasv1.NewAccountServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() resp, err := cl.GetRoles(ctx, &mciasv1.GetRolesRequest{Id: id}) if err != nil { fatalf("role list: %v", err) } printJSON(resp.Roles) }, } cmd.Flags().StringVar(&id, "id", "", "account UUID (required)") return cmd } func roleSetCmd(ctl *controller) *cobra.Command { var ( id string rolesFlag string ) cmd := &cobra.Command{ Use: "set", Short: "Set roles for an account", Run: func(cmd *cobra.Command, args []string) { if id == "" { fatalf("role set: --id is required") } var roles []string if rolesFlag != "" { for _, r := range strings.Split(rolesFlag, ",") { r = strings.TrimSpace(r) if r != "" { roles = append(roles, r) } } } cl := mciasv1.NewAccountServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() _, err := cl.SetRoles(ctx, &mciasv1.SetRolesRequest{Id: id, Roles: roles}) if err != nil { fatalf("role set: %v", err) } fmt.Printf("roles set: %v\n", roles) }, } cmd.Flags().StringVar(&id, "id", "", "account UUID (required)") cmd.Flags().StringVar(&rolesFlag, "roles", "", "comma-separated list of roles") return cmd } func roleGrantCmd(ctl *controller) *cobra.Command { var ( id string role string ) cmd := &cobra.Command{ Use: "grant", Short: "Grant a role to an account", Run: func(cmd *cobra.Command, args []string) { if id == "" { fatalf("role grant: --id is required") } if role == "" { fatalf("role grant: --role is required") } cl := mciasv1.NewAccountServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() _, err := cl.GrantRole(ctx, &mciasv1.GrantRoleRequest{Id: id, Role: role}) if err != nil { fatalf("role grant: %v", err) } fmt.Printf("role granted: %s\n", role) }, } cmd.Flags().StringVar(&id, "id", "", "account UUID (required)") cmd.Flags().StringVar(&role, "role", "", "role name (required)") return cmd } func roleRevokeCmd(ctl *controller) *cobra.Command { var ( id string role string ) cmd := &cobra.Command{ Use: "revoke", Short: "Revoke a role from an account", Run: func(cmd *cobra.Command, args []string) { if id == "" { fatalf("role revoke: --id is required") } if role == "" { fatalf("role revoke: --role is required") } cl := mciasv1.NewAccountServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() _, err := cl.RevokeRole(ctx, &mciasv1.RevokeRoleRequest{Id: id, Role: role}) if err != nil { fatalf("role revoke: %v", err) } fmt.Printf("role revoked: %s\n", role) }, } cmd.Flags().StringVar(&id, "id", "", "account UUID (required)") cmd.Flags().StringVar(&role, "role", "", "role name (required)") return cmd } // ---- token subcommands ---- func tokenCmd(ctl *controller) *cobra.Command { cmd := &cobra.Command{ Use: "token", Short: "Token management commands", } cmd.AddCommand(tokenValidateCmd(ctl)) cmd.AddCommand(tokenIssueCmd(ctl)) cmd.AddCommand(tokenRevokeCmd(ctl)) return cmd } func tokenValidateCmd(ctl *controller) *cobra.Command { var tok string cmd := &cobra.Command{ Use: "validate", Short: "Validate a JWT", Run: func(cmd *cobra.Command, args []string) { if tok == "" { fatalf("token validate: --token is required") } cl := mciasv1.NewTokenServiceClient(ctl.conn) // ValidateToken is public — no auth context needed. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() resp, err := cl.ValidateToken(ctx, &mciasv1.ValidateTokenRequest{Token: tok}) if err != nil { fatalf("token validate: %v", err) } printJSON(map[string]interface{}{ "valid": resp.Valid, "subject": resp.Subject, "roles": resp.Roles, }) }, } cmd.Flags().StringVar(&tok, "token", "", "JWT to validate (required)") return cmd } func tokenIssueCmd(ctl *controller) *cobra.Command { var id string cmd := &cobra.Command{ Use: "issue", Short: "Issue a service token", Run: func(cmd *cobra.Command, args []string) { if id == "" { fatalf("token issue: --id is required") } cl := mciasv1.NewTokenServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() resp, err := cl.IssueServiceToken(ctx, &mciasv1.IssueServiceTokenRequest{AccountId: id}) if err != nil { fatalf("token issue: %v", err) } printJSON(map[string]string{"token": resp.Token}) }, } cmd.Flags().StringVar(&id, "id", "", "system account UUID (required)") return cmd } func tokenRevokeCmd(ctl *controller) *cobra.Command { var jti string cmd := &cobra.Command{ Use: "revoke", Short: "Revoke a token by JTI", Run: func(cmd *cobra.Command, args []string) { if jti == "" { fatalf("token revoke: --jti is required") } cl := mciasv1.NewTokenServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() _, err := cl.RevokeToken(ctx, &mciasv1.RevokeTokenRequest{Jti: jti}) if err != nil { fatalf("token revoke: %v", err) } fmt.Println("token revoked") }, } cmd.Flags().StringVar(&jti, "jti", "", "JTI of the token to revoke (required)") return cmd } // ---- pgcreds subcommands ---- func pgcredsCmd(ctl *controller) *cobra.Command { cmd := &cobra.Command{ Use: "pgcreds", Short: "Postgres credential management commands", } cmd.AddCommand(pgcredsGetCmd(ctl)) cmd.AddCommand(pgcredsSetCmd(ctl)) return cmd } func pgcredsGetCmd(ctl *controller) *cobra.Command { var id string cmd := &cobra.Command{ Use: "get", Short: "Get Postgres credentials for an account", Run: func(cmd *cobra.Command, args []string) { if id == "" { fatalf("pgcreds get: --id is required") } cl := mciasv1.NewCredentialServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() resp, err := cl.GetPGCreds(ctx, &mciasv1.GetPGCredsRequest{Id: id}) if err != nil { fatalf("pgcreds get: %v", err) } if resp.Creds == nil { fatalf("pgcreds get: no credentials returned") } printJSON(map[string]interface{}{ "host": resp.Creds.Host, "port": resp.Creds.Port, "database": resp.Creds.Database, "username": resp.Creds.Username, "password": resp.Creds.Password, }) }, } cmd.Flags().StringVar(&id, "id", "", "account UUID (required)") return cmd } func pgcredsSetCmd(ctl *controller) *cobra.Command { var ( id string host string port int dbName string username string password string ) cmd := &cobra.Command{ Use: "set", Short: "Set Postgres credentials for an account", Run: func(cmd *cobra.Command, args []string) { if id == "" || host == "" || dbName == "" || username == "" || password == "" { fatalf("pgcreds set: --id, --host, --db, --user, and --password are required") } cl := mciasv1.NewCredentialServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() _, err := cl.SetPGCreds(ctx, &mciasv1.SetPGCredsRequest{ Id: id, Creds: &mciasv1.PGCreds{ Host: host, Port: int32(port), //nolint:gosec // G115: port validated as [1,65535] by flag parsing Database: dbName, Username: username, Password: password, }, }) if err != nil { fatalf("pgcreds set: %v", err) } fmt.Println("credentials stored") }, } cmd.Flags().StringVar(&id, "id", "", "account UUID (required)") cmd.Flags().StringVar(&host, "host", "", "Postgres host (required)") cmd.Flags().IntVar(&port, "port", 5432, "Postgres port") cmd.Flags().StringVar(&dbName, "db", "", "Postgres database name (required)") cmd.Flags().StringVar(&username, "user", "", "Postgres username (required)") cmd.Flags().StringVar(&password, "password", "", "Postgres password (required)") return cmd } // ---- policy subcommands ---- func policyCmd(ctl *controller) *cobra.Command { cmd := &cobra.Command{ Use: "policy", Short: "Policy rule management commands", } cmd.AddCommand(policyListCmd(ctl)) cmd.AddCommand(policyCreateCmd(ctl)) cmd.AddCommand(policyGetCmd(ctl)) cmd.AddCommand(policyUpdateCmd(ctl)) cmd.AddCommand(policyDeleteCmd(ctl)) return cmd } func policyListCmd(ctl *controller) *cobra.Command { return &cobra.Command{ Use: "list", Short: "List all policy rules", Run: func(cmd *cobra.Command, args []string) { cl := mciasv1.NewPolicyServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() resp, err := cl.ListPolicyRules(ctx, &mciasv1.ListPolicyRulesRequest{}) if err != nil { fatalf("policy list: %v", err) } printJSON(resp.Rules) }, } } func policyCreateCmd(ctl *controller) *cobra.Command { var ( description string jsonFile string priority int notBefore string expiresAt string ) cmd := &cobra.Command{ Use: "create", Short: "Create a policy rule", Run: func(cmd *cobra.Command, args []string) { if description == "" { fatalf("policy create: --description is required") } if jsonFile == "" { fatalf("policy create: --json is required (path to rule body JSON file)") } // G304: path comes from a CLI flag supplied by the operator. ruleBytes, err := os.ReadFile(jsonFile) //nolint:gosec if err != nil { fatalf("policy create: read %s: %v", jsonFile, err) } // Validate that the file contains valid JSON before sending. var ruleBody interface{} if err := json.Unmarshal(ruleBytes, &ruleBody); err != nil { fatalf("policy create: invalid JSON in %s: %v", jsonFile, err) } if notBefore != "" { if _, err := time.Parse(time.RFC3339, notBefore); err != nil { fatalf("policy create: --not-before must be RFC3339: %v", err) } } if expiresAt != "" { if _, err := time.Parse(time.RFC3339, expiresAt); err != nil { fatalf("policy create: --expires-at must be RFC3339: %v", err) } } cl := mciasv1.NewPolicyServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() resp, err := cl.CreatePolicyRule(ctx, &mciasv1.CreatePolicyRuleRequest{ Description: description, RuleJson: string(ruleBytes), Priority: int32(priority), //nolint:gosec // priority is a small positive integer NotBefore: notBefore, ExpiresAt: expiresAt, }) if err != nil { fatalf("policy create: %v", err) } printJSON(resp.Rule) }, } cmd.Flags().StringVar(&description, "description", "", "rule description (required)") cmd.Flags().StringVar(&jsonFile, "json", "", "path to JSON file containing the rule body (required)") cmd.Flags().IntVar(&priority, "priority", 100, "rule priority (lower = evaluated first)") cmd.Flags().StringVar(¬Before, "not-before", "", "earliest activation time (RFC3339, optional)") cmd.Flags().StringVar(&expiresAt, "expires-at", "", "expiry time (RFC3339, optional)") return cmd } func policyGetCmd(ctl *controller) *cobra.Command { var idStr string cmd := &cobra.Command{ Use: "get", Short: "Get a policy rule by ID", Run: func(cmd *cobra.Command, args []string) { if idStr == "" { fatalf("policy get: --id is required") } id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { fatalf("policy get: --id must be an integer") } cl := mciasv1.NewPolicyServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() resp, err := cl.GetPolicyRule(ctx, &mciasv1.GetPolicyRuleRequest{Id: id}) if err != nil { fatalf("policy get: %v", err) } printJSON(resp.Rule) }, } cmd.Flags().StringVar(&idStr, "id", "", "rule ID (required)") return cmd } func policyUpdateCmd(ctl *controller) *cobra.Command { var ( idStr string priority int enabled string notBefore string expiresAt string clearNotBefore bool clearExpiresAt bool ) cmd := &cobra.Command{ Use: "update", Short: "Update a policy rule", Run: func(cmd *cobra.Command, args []string) { if idStr == "" { fatalf("policy update: --id is required") } id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { fatalf("policy update: --id must be an integer") } req := &mciasv1.UpdatePolicyRuleRequest{ Id: id, ClearNotBefore: clearNotBefore, ClearExpiresAt: clearExpiresAt, } if priority >= 0 { v := int32(priority) //nolint:gosec // priority is a small positive integer req.Priority = &v } if enabled != "" { switch enabled { case "true": b := true req.Enabled = &b case "false": b := false req.Enabled = &b default: fatalf("policy update: --enabled must be true or false") } } if !clearNotBefore && notBefore != "" { if _, err := time.Parse(time.RFC3339, notBefore); err != nil { fatalf("policy update: --not-before must be RFC3339: %v", err) } req.NotBefore = notBefore } if !clearExpiresAt && expiresAt != "" { if _, err := time.Parse(time.RFC3339, expiresAt); err != nil { fatalf("policy update: --expires-at must be RFC3339: %v", err) } req.ExpiresAt = expiresAt } cl := mciasv1.NewPolicyServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() resp, err := cl.UpdatePolicyRule(ctx, req) if err != nil { fatalf("policy update: %v", err) } printJSON(resp.Rule) }, } cmd.Flags().StringVar(&idStr, "id", "", "rule ID (required)") cmd.Flags().IntVar(&priority, "priority", -1, "new priority (-1 = no change)") cmd.Flags().StringVar(&enabled, "enabled", "", "true or false") cmd.Flags().StringVar(¬Before, "not-before", "", "earliest activation time (RFC3339)") cmd.Flags().StringVar(&expiresAt, "expires-at", "", "expiry time (RFC3339)") cmd.Flags().BoolVar(&clearNotBefore, "clear-not-before", false, "remove not_before constraint") cmd.Flags().BoolVar(&clearExpiresAt, "clear-expires-at", false, "remove expires_at constraint") return cmd } func policyDeleteCmd(ctl *controller) *cobra.Command { var idStr string cmd := &cobra.Command{ Use: "delete", Short: "Delete a policy rule", Run: func(cmd *cobra.Command, args []string) { if idStr == "" { fatalf("policy delete: --id is required") } id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { fatalf("policy delete: --id must be an integer") } cl := mciasv1.NewPolicyServiceClient(ctl.conn) ctx, cancel := ctl.callCtx() defer cancel() if _, err := cl.DeletePolicyRule(ctx, &mciasv1.DeletePolicyRuleRequest{Id: id}); err != nil { fatalf("policy delete: %v", err) } fmt.Println("policy rule deleted") }, } cmd.Flags().StringVar(&idStr, "id", "", "rule ID (required)") return cmd } // ---- gRPC connection ---- // newGRPCConn dials the gRPC server with TLS. // If caCertPath is empty, the system CA pool is used. // Security: TLS 1.2+ is enforced by the crypto/tls defaults on the client side. // The connection is insecure-skip-verify-free; operators can supply a custom CA // for self-signed certs without disabling certificate validation. func newGRPCConn(serverAddr, caCertPath string) (*grpc.ClientConn, error) { tlsCfg := &tls.Config{ MinVersion: tls.VersionTLS12, } if caCertPath != "" { // G304: path comes from a CLI flag supplied by the operator, not // from untrusted input. File inclusion is intentional. pemData, err := os.ReadFile(caCertPath) //nolint:gosec if err != nil { return nil, fmt.Errorf("read CA cert: %w", err) } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(pemData) { return nil, fmt.Errorf("no valid certificates found in %s", caCertPath) } tlsCfg.RootCAs = pool } creds := credentials.NewTLS(tlsCfg) conn, err := grpc.NewClient(serverAddr, grpc.WithTransportCredentials(creds)) if err != nil { return nil, fmt.Errorf("dial %s: %w", serverAddr, err) } return conn, nil } // ---- helpers ---- // printJSON pretty-prints a value as JSON to stdout. func printJSON(v interface{}) { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") if err := enc.Encode(v); err != nil { fatalf("encode output: %v", err) } } // fatalf prints an error message to stderr and exits with code 1. func fatalf(format string, args ...interface{}) { fmt.Fprintf(os.Stderr, "mciasgrpcctl: "+format+"\n", args...) os.Exit(1) }