package main import ( "fmt" "os" "github.com/spf13/cobra" ) // Global flags available to all commands var overwrite bool var dryRun bool var verbose bool func main() { // list command flags var listRevoked bool // issue command flags var name string var subject string var certType string var validity string var san []string // provision command flags var provisionFile string // crl command flags var crlFile string var crlValidityDays int // revoke command flags var revokeName string var revokeSerial string var revokeReasonStr string var rootCmd = &cobra.Command{ Use: "lab-ca", Short: "Certificate Authority Utility", Long: "lab-ca - Certificate Authority Utility", Run: func(cmd *cobra.Command, args []string) { printMainHelp() }, } // Define persistent flags (global for all commands) rootCmd.PersistentFlags().BoolVar(&overwrite, "overwrite", false, "Allow overwriting existing files") rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Print detailed information about each processed certificate") rootCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Validate and show what would be created, but do not write files (batch mode)") rootCmd.PersistentFlags().StringVar(&caConfigPath, "config", "ca_config.hcl", "Path to CA configuration file") // lab-ca initca command var initCmd = &cobra.Command{ Use: "initca", Short: "Generate a new CA certificate and key", Run: func(cmd *cobra.Command, args []string) { InitCA() }, } rootCmd.AddCommand(initCmd) // lab-ca list command var listCmd = &cobra.Command{ Use: "list", Short: "List issued certificates", Run: func(cmd *cobra.Command, args []string) { err := LoadCA() if err != nil { fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) os.Exit(1) } for _, certDef := range caState.Certificates { if certDef.RevokedAt != "" { continue } fmt.Printf("Certificate %s\n", certDef.Name) fmt.Printf("\tSubject: %s\n\tType: %s\n\tIssued at: %s\n", certDef.Subject, certDef.Type, certDef.Issued) } }, } listCmd.Flags().BoolVar(&listRevoked, "revoked", false, "List all certificates, including revoked ones") rootCmd.AddCommand(listCmd) // lab-ca issue command var issueCmd = &cobra.Command{ Use: "issue", Short: "Issue a new certificate (client, server, server-only, code-signing, email)", Run: func(cmd *cobra.Command, args []string) { err := IssueCertificate(CertificateDefinition{ Name: name, Subject: subject, Type: certType, Validity: validity, SAN: san, }, overwrite, dryRun, verbose) if err != nil { fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) os.Exit(1) } }, } issueCmd.Flags().StringVar(&name, "name", "", "Name for the certificate and key files (used as subject if --subject is omitted)") issueCmd.Flags().StringVar(&subject, "subject", "", "Subject Common Name for the certificate (optional, defaults to --name)") issueCmd.Flags().StringVar(&certType, "type", "server", "Certificate type: client, server, code-signing, email.\nCombine by specifying more than one separated by comma.") issueCmd.Flags().StringArrayVar(&san, "san", nil, "Subject Alternative Name (SAN). Use multiple times for multiple values.\nFormat: dns:example.com, ip:1.2.3.4, email:user@example.com") issueCmd.Flags().StringVar(&validity, "validity", "1y", "Certificate validity (e.g. 2y, 6m, 30d). Overrides config file for this certificate.") issueCmd.MarkFlagRequired("name") rootCmd.AddCommand(issueCmd) // lab-ca provision command var provisionCmd = &cobra.Command{ Use: "provision", Short: "Provision certificates from a batch file (HCL)", Run: func(cmd *cobra.Command, args []string) { err := ProvisionCertificates(provisionFile, overwrite, false, verbose) if err != nil { fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) os.Exit(1) } }, } provisionCmd.Flags().StringVar(&provisionFile, "file", "", "Path to HCL file with certificate definitions (required)") provisionCmd.MarkFlagRequired("file") rootCmd.AddCommand(provisionCmd) // lab-ca revoke command var revokeCmd = &cobra.Command{ Use: "revoke", Short: "Revoke a certificate by name or serial number", Run: func(cmd *cobra.Command, args []string) { if err := LoadCA(); err != nil { fmt.Printf("ERROR: %v\n", err) os.Exit(1) } if (revokeName == "" && revokeSerial == "") || (revokeName != "" && revokeSerial != "") { fmt.Println("ERROR: You must specify either --name or --serial (but not both)") os.Exit(1) } serial := "" if revokeName != "" { found := false for _, rec := range caState.Certificates { if rec.Name == revokeName { serial = rec.Serial found = true break } } if !found { fmt.Printf("ERROR: Certificate with name '%s' not found\n", revokeName) os.Exit(1) } } else { serial = revokeSerial } reasonMap := map[string]int{ "unspecified": 0, "keyCompromise": 1, "caCompromise": 2, "affiliationChanged": 3, "superseded": 4, "cessationOfOperation": 5, "certificateHold": 6, "removeFromCRL": 8, } reasonCode, ok := reasonMap[revokeReasonStr] if !ok { fmt.Printf("ERROR: Unknown revocation reason '%s'. Valid reasons: ", revokeReasonStr) for k := range reasonMap { fmt.Printf("%s ", k) } fmt.Println() os.Exit(1) } if err := caState.RevokeCertificate(serial, reasonCode); err != nil { fmt.Printf("ERROR: %v\n", err) os.Exit(1) } fmt.Printf("Certificate with serial %s revoked (reason: %s, code %d)\n", serial, revokeReasonStr, reasonCode) }, } revokeCmd.Flags().StringVar(&revokeName, "name", "", "Certificate name to revoke (mutually exclusive with --serial)") revokeCmd.Flags().StringVar(&revokeSerial, "serial", "", "Certificate serial number to revoke (mutually exclusive with --name)") revokeCmd.Flags().StringVar(&revokeReasonStr, "reason", "cessationOfOperation", "Revocation reason (unspecified, keyCompromise, caCompromise, affiliationChanged, superseded, cessationOfOperation, certificateHold, removeFromCRL)") rootCmd.AddCommand(revokeCmd) // lab-ca crl command var crlCmd = &cobra.Command{ Use: "crl", Short: "Generate a Certificate Revocation List (CRL)", Run: func(cmd *cobra.Command, args []string) { if err := LoadCA(); err != nil { fmt.Printf("ERROR: %v\n", err) os.Exit(1) } if crlValidityDays <= 0 { crlValidityDays = 30 // default to 30 days } err := caState.GenerateCRL(crlFile, crlValidityDays) if err != nil { fmt.Printf("ERROR generating CRL: %v\n", err) os.Exit(1) } fmt.Printf("CRL written to %s (valid for %d days)\n", crlFile, crlValidityDays) }, } crlCmd.Flags().StringVar(&crlFile, "crl-file", "crl.pem", "Output path for CRL file (default: crl.pem)") crlCmd.Flags().IntVar(&crlValidityDays, "validity-days", 30, "CRL validity in days (default: 30)") rootCmd.AddCommand(crlCmd) // lab-ca version command var versionCmd = &cobra.Command{ Use: "version", Short: "Show version information", Run: func(cmd *cobra.Command, args []string) { fmt.Printf("lab-ca version: %s\n", getVersionDescription()) }, } rootCmd.AddCommand(versionCmd) if err := rootCmd.Execute(); err != nil { os.Exit(1) } } func getVersionDescription() string { if Version == "" { return "no version information was compiled in" } return Version } func printMainHelp() { fmt.Printf("lab-ca - Certificate Authority Utility\n") fmt.Printf("Version: %s\n", getVersionDescription()) fmt.Println() fmt.Println("Usage:") fmt.Println(" lab-ca [options]") fmt.Println() fmt.Println("Available commands:") fmt.Println(" initca Generate a new CA certificate and key") fmt.Println(" list List issued certificates") fmt.Println(" issue Issue a new certificate") fmt.Println(" provision Provision certificates from a batch file (HCL)") fmt.Println(" revoke Revoke a certificate by name or serial number") fmt.Println(" crl Generate a Certificate Revocation List (CRL)") fmt.Println(" version Show version information") fmt.Println() fmt.Println("Use 'lab-ca --help' for more information about a command.") }