Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds cert-utility. #889

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ myblob
ts_chain.pem
enc-keyset.cfg
chain.crt.pem
.DS_Store
184 changes: 184 additions & 0 deletions cmd/certificate_maker/certificate_maker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright 2024 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// Package main implements a certificate creation utility for Timestamp Authority.
// It supports creating root and leaf certificates using (AWS, GCP, Azure).
package main

import (
"context"
"encoding/json"
"fmt"
"os"
"time"

"github.com/sigstore/timestamp-authority/pkg/certmaker"
"github.com/spf13/cobra"
"go.uber.org/zap"
)

// CLI flags and env vars for config.
// Supports AWS KMS, Google Cloud KMS, and Azure Key Vault configurations.
var (
logger *zap.Logger
version string

rootCmd = &cobra.Command{
Use: "tsa-certificate-maker",
Short: "Create certificate chains for Timestamp Authority",
Long: `A tool for creating root, intermediate, and leaf certificates for Timestamp Authority with timestamping capabilities`,
Version: version,
}

createCmd = &cobra.Command{
Use: "create",
Short: "Create certificate chain",
RunE: runCreate,
}

kmsType string
kmsRegion string
kmsKeyID string
kmsTenantID string
kmsCredsFile string
rootTemplatePath string
leafTemplatePath string
rootKeyID string
leafKeyID string
rootCertPath string
leafCertPath string
intermediateKeyID string
intermediateTemplate string
intermediateCert string
kmsVaultToken string
kmsVaultAddr string

rawJSON = []byte(`{
"level": "debug",
"encoding": "json",
"outputPaths": ["stdout"],
"errorOutputPaths": ["stderr"],
"initialFields": {"service": "tsa-certificate-maker"},
"encoderConfig": {
"messageKey": "message",
"levelKey": "level",
"levelEncoder": "lowercase",
"timeKey": "timestamp",
"timeEncoder": "iso8601"
}
}`)
)

func init() {
logger = initLogger()

rootCmd.AddCommand(createCmd)

createCmd.Flags().StringVar(&kmsType, "kms-type", "", "KMS provider type (awskms, gcpkms, azurekms, hashivault)")
createCmd.Flags().StringVar(&kmsRegion, "aws-region", "", "AWS KMS region")
createCmd.Flags().StringVar(&kmsKeyID, "kms-key-id", "", "KMS key identifier")
createCmd.Flags().StringVar(&kmsTenantID, "azure-tenant-id", "", "Azure KMS tenant ID")
createCmd.Flags().StringVar(&kmsCredsFile, "gcp-credentials-file", "", "Path to credentials file for GCP KMS")
createCmd.Flags().StringVar(&rootTemplatePath, "root-template",
"pkg/certmaker/templates/root-template.json", "Path to root certificate template")
createCmd.Flags().StringVar(&leafTemplatePath, "leaf-template",
"pkg/certmaker/templates/leaf-template.json", "Path to leaf certificate template")
createCmd.Flags().StringVar(&rootKeyID, "root-key-id", "", "KMS key identifier for root certificate")
createCmd.Flags().StringVar(&leafKeyID, "leaf-key-id", "", "KMS key identifier for leaf certificate")
createCmd.Flags().StringVar(&rootCertPath, "root-cert", "root.pem", "Output path for root certificate")
createCmd.Flags().StringVar(&leafCertPath, "leaf-cert", "leaf.pem", "Output path for leaf certificate")
createCmd.Flags().StringVar(&intermediateKeyID, "intermediate-key-id", "", "KMS key identifier for intermediate certificate")
createCmd.Flags().StringVar(&intermediateTemplate, "intermediate-template", "pkg/certmaker/templates/intermediate-template.json", "Path to intermediate certificate template")
createCmd.Flags().StringVar(&intermediateCert, "intermediate-cert", "intermediate.pem", "Output path for intermediate certificate")
createCmd.Flags().StringVar(&kmsVaultToken, "vault-token", "", "HashiVault token")
createCmd.Flags().StringVar(&kmsVaultAddr, "vault-address", "", "HashiVault server address")
}

func runCreate(_ *cobra.Command, _ []string) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// Build KMS config from flags and environment
config := certmaker.KMSConfig{
Type: getConfigValue(kmsType, "KMS_TYPE"),
Region: getConfigValue(kmsRegion, "AWS_REGION"),
RootKeyID: getConfigValue(rootKeyID, "KMS_ROOT_KEY_ID"),
IntermediateKeyID: getConfigValue(intermediateKeyID, "KMS_INTERMEDIATE_KEY_ID"),
LeafKeyID: getConfigValue(leafKeyID, "KMS_LEAF_KEY_ID"),
Options: make(map[string]string),
}

// Handle KMS provider options
switch config.Type {
case "gcpkms":
if credsFile := getConfigValue(kmsCredsFile, "GCP_CREDENTIALS_FILE"); credsFile != "" {
// Check if credentials file exists before trying to use it
if _, err := os.Stat(credsFile); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("failed to initialize KMS: credentials file not found: %s", credsFile)
}
return fmt.Errorf("failed to initialize KMS: error accessing credentials file: %w", err)
}
config.Options["credentials-file"] = credsFile
}
case "azurekms":
if tenantID := getConfigValue(kmsTenantID, "AZURE_TENANT_ID"); tenantID != "" {
config.Options["tenant-id"] = tenantID
}
case "hashivault":
if token := getConfigValue(kmsVaultToken, "VAULT_TOKEN"); token != "" {
config.Options["token"] = token
}
if addr := getConfigValue(kmsVaultAddr, "VAULT_ADDR"); addr != "" {
config.Options["address"] = addr
}
}

km, err := certmaker.InitKMS(ctx, config)
if err != nil {
return fmt.Errorf("failed to initialize KMS: %w", err)
}

// Validate template paths
if err := certmaker.ValidateTemplatePath(rootTemplatePath); err != nil {
return fmt.Errorf("root template error: %w", err)
}
if err := certmaker.ValidateTemplatePath(leafTemplatePath); err != nil {
return fmt.Errorf("leaf template error: %w", err)
}

return certmaker.CreateCertificates(km, config, rootTemplatePath, leafTemplatePath, rootCertPath, leafCertPath, intermediateKeyID, intermediateTemplate, intermediateCert)
}

func main() {
if err := rootCmd.Execute(); err != nil {
logger.Fatal("Command failed", zap.Error(err))
}
}

func getConfigValue(flagValue, envVar string) string {
if flagValue != "" {
return flagValue
}
return os.Getenv(envVar)
}

func initLogger() *zap.Logger {
var cfg zap.Config
if err := json.Unmarshal(rawJSON, &cfg); err != nil {
panic(err)
}
return zap.Must(cfg.Build())
}
Loading