Added list command and ability to combine certificate usages.
This commit is contained in:
53
ca.go
53
ca.go
@@ -465,6 +465,11 @@ func issueSingleCertificate(def CertificateDefinition) error {
|
||||
return fmt.Errorf("certificate name must be specified and contain only letters, numbers, dash, or underscore")
|
||||
}
|
||||
|
||||
// Check if the certificate is in database, fail if it is.
|
||||
if caState.FindByName(def.Name, false) != nil {
|
||||
return fmt.Errorf("certificate %s already exists and is valid.", def.Name)
|
||||
}
|
||||
|
||||
// Initialize Subject if not specified
|
||||
if def.Subject == "" {
|
||||
def.Subject = def.Name
|
||||
@@ -528,19 +533,24 @@ func issueSingleCertificate(def CertificateDefinition) error {
|
||||
}
|
||||
}
|
||||
|
||||
switch def.Type {
|
||||
case "client":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
|
||||
case "server":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
|
||||
case "server-only":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
|
||||
case "code-signing":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}
|
||||
case "email":
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}
|
||||
default:
|
||||
return fmt.Errorf("unknown certificate type. Use one of: client, server, server-only, code-signing, email")
|
||||
// Split usage types by comma
|
||||
types := strings.SplitSeq(def.Type, ",")
|
||||
certTmpl.ExtKeyUsage = []x509.ExtKeyUsage{}
|
||||
|
||||
// Collect selected usage types
|
||||
for certType := range types {
|
||||
switch certType {
|
||||
case "client":
|
||||
certTmpl.ExtKeyUsage = append(certTmpl.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
|
||||
case "server":
|
||||
certTmpl.ExtKeyUsage = append(certTmpl.ExtKeyUsage, x509.ExtKeyUsageServerAuth)
|
||||
case "code-signing":
|
||||
certTmpl.ExtKeyUsage = append(certTmpl.ExtKeyUsage, x509.ExtKeyUsageCodeSigning)
|
||||
case "email":
|
||||
certTmpl.ExtKeyUsage = append(certTmpl.ExtKeyUsage, x509.ExtKeyUsageEmailProtection)
|
||||
default:
|
||||
return fmt.Errorf("unknown certificate type. Use one of: client, server, code-signing, email")
|
||||
}
|
||||
}
|
||||
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, &priv.PublicKey, caKey)
|
||||
@@ -614,25 +624,26 @@ func ProvisionCertificates(filePath string, overwrite bool, dryRun bool, verbose
|
||||
|
||||
// Loop through all certificate definitions
|
||||
// to render templates and fill missing fields from defaults
|
||||
for i := range certDefs.Certificates {
|
||||
for _, def := range certDefs.Certificates {
|
||||
// Fill missing fields from defaults, if provided
|
||||
certDefs.Certificates[i].FillDefaultValues(certDefs.Defaults)
|
||||
def.FillDefaultValues(certDefs.Defaults)
|
||||
// Render templates in the definition using the variables map
|
||||
// with added definition name.
|
||||
variables := certDefs.Variables
|
||||
if variables == nil {
|
||||
variables = make(map[string]string)
|
||||
}
|
||||
variables["Name"] = certDefs.Certificates[i].Name
|
||||
err = certDefs.Certificates[i].RenderTemplates(variables)
|
||||
variables["Name"] = def.Name
|
||||
err = def.RenderTemplates(variables)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to render templates for certificate %s: %v", certDefs.Certificates[i].Name, err)
|
||||
return fmt.Errorf("failed to render templates for certificate %s: %v", def.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
n := len(certDefs.Certificates)
|
||||
// No errors so far, now we can issue certificates
|
||||
for i := range certDefs.Certificates {
|
||||
fmt.Printf("[%d/%d] Issuing %s... ", i+1, len(certDefs.Certificates), certDefs.Certificates[i].Name)
|
||||
for i, def := range certDefs.Certificates {
|
||||
fmt.Printf("[%d/%d] Issuing %s... ", i+1, n, def.Name)
|
||||
|
||||
if dryRun {
|
||||
fmt.Printf("(dry run)\n")
|
||||
@@ -640,7 +651,7 @@ func ProvisionCertificates(filePath string, overwrite bool, dryRun bool, verbose
|
||||
continue
|
||||
}
|
||||
|
||||
err = issueSingleCertificate(certDefs.Certificates[i])
|
||||
err = issueSingleCertificate(def)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
errors++
|
||||
|
29
main.go
29
main.go
@@ -16,6 +16,9 @@ var verbose bool
|
||||
|
||||
func main() {
|
||||
|
||||
// list command flags
|
||||
var listRevoked bool
|
||||
|
||||
// issue command flags
|
||||
var name string
|
||||
var subject string
|
||||
@@ -60,6 +63,29 @@ func main() {
|
||||
}
|
||||
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",
|
||||
@@ -82,7 +108,8 @@ func main() {
|
||||
|
||||
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, server-only, code-signing, email")
|
||||
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.")
|
||||
|
Reference in New Issue
Block a user