package cmd import ( "crypto/x509" "fmt" "log" "math/big" "os" "github.com/joho/godotenv" "github.com/kelseyhightower/envconfig" "github.com/spf13/cobra" "github.com/spf13/viper" "gitlab.com/urkob/go-cert-gen/internal/cert" "gitlab.com/urkob/go-cert-gen/internal/io" "gitlab.com/urkob/go-cert-gen/pkg/ca" "gitlab.com/urkob/go-cert-gen/pkg/client" pkgio "gitlab.com/urkob/go-cert-gen/pkg/io" "gitlab.com/urkob/go-cert-gen/pkg/util" ) var envConfig struct { ViperConfig string `required:"true" split_words:"true"` ViperConfigType string `required:"true" split_words:"true"` Env string `required:"true" split_words:"true"` } var writer pkgio.WriterIface func intEnvConfig(envFilePath string) { if envFilePath != "" { err := godotenv.Load(envFilePath) if err != nil { log.Fatalf("environment variable ENV is empty and an error occurred while loading the .env file: %s\n", err) } } err := envconfig.Process("", &envConfig) if err != nil { log.Fatalf("envconfig.Process: %s\n", err) } } var rootCmd = &cobra.Command{ Use: "go-gen-cert", Short: "go-gen-cert is a CLI tool to generate TLS certificates for your gRPC client/server communication", Run: func(cmd *cobra.Command, args []string) { caCfg := &ca.CaConfig{ SerialNumber: big.NewInt(viper.GetInt64("ca.serial_number")), Subject: ca.CaSubject{ Organization: viper.GetString("ca.subject.organization"), CommonName: viper.GetString("ca.subject.common_name"), }, KeyUsage: x509.KeyUsage(viper.GetInt("ca.key_usage")), ExtKeyUsage: getExtKeyUsage(viper.GetIntSlice("ca.ext_key_usage")), Duration: viper.GetDuration("ca.duration"), } rootCA, err := cert.NewRootCA(caCfg) if err != nil { log.Fatalf("cert.NewRootCA: %s", err) } subjectKeyId, err := util.GetBytes(viper.Get("client.subject_key_id")) if err != nil { log.Fatalf("cert.NewRootCA: %s", err) } clientCfg := &client.ClientCertConfig{ Serial: &big.Int{}, Subject: client.Subject{ Organization: viper.GetString("client.subject.organization"), Country: viper.GetString("client.subject.country"), Province: viper.GetString("client.subject.province"), Locality: viper.GetString("client.subject.locality"), StreetAddress: viper.GetString("client.subject.street_address"), PostalCode: viper.GetString("client.subject.postal_code"), }, Duration: viper.GetDuration("client.duration"), SubjectKeyId: subjectKeyId, ExtKeyUsage: getExtKeyUsage(viper.GetIntSlice("client.ext_key_usage")), KeyUsage: x509.KeyUsage(viper.GetInt("client.key_usage")), } clientCert, err := rootCA.WithClientCert(clientCfg) if err != nil { log.Fatalf("rootCA.WithClientCert: %s", err) } outputPath, err := exportPem("root-ca.pem", rootCA.PEM()) if err != nil { log.Fatalf("exportPem: %s\n", err) } log.Printf("file created successfully: %s\n", outputPath) outputPath, err = exportPem("root-key.pem", rootCA.Key()) if err != nil { log.Fatalf("exportPem: %s\n", err) } log.Printf("file created successfully: %s\n", outputPath) outputPath, err = exportPem("client-cert.pem", clientCert.PEM()) if err != nil { log.Fatalf("exportPem: %s\n", err) } log.Printf("file created successfully: %s\n", outputPath) outputPath, err = exportPem("client-key.pem", clientCert.Key()) if err != nil { log.Fatalf("exportPem: %s\n", err) } log.Printf("file created successfully: %s\n", outputPath) }, } func getExtKeyUsage(intKeyUsageSlice []int) []x509.ExtKeyUsage { if intKeyUsageSlice == nil || len(intKeyUsageSlice) <= 0 { return []x509.ExtKeyUsage{} } extKeyUsage := make([]x509.ExtKeyUsage, 0, len(intKeyUsageSlice)) for _, v := range intKeyUsageSlice { extKeyUsage = append(extKeyUsage, x509.ExtKeyUsage(v)) } return extKeyUsage } func exportPem(filename string, data []byte) (string, error) { outputPath, err := writer.WriteFile(filename, data) if err != nil { return "", fmt.Errorf("rootCA.WithClientCert: %s", err) } return outputPath, nil } func init() { envFile := "" if os.Getenv("ENV") != "prod" { envFile = "./.env" } intEnvConfig(envFile) cobra.OnInitialize(initConfig) } func initConfig() { if envConfig.ViperConfig != "" { viper.AddConfigPath(util.RootDir()) viper.SetConfigName(envConfig.ViperConfig) viper.SetConfigType(envConfig.ViperConfigType) } else { home, err := os.UserHomeDir() cobra.CheckErr(err) viper.AddConfigPath(home) viper.SetConfigType("yaml") viper.SetConfigName(".cobra") } if err := viper.ReadInConfig(); err != nil { log.Fatalf("viper.ReadInConfig: %s", err) } log.Println("viper.GetString('export_dir)", viper.GetString("export_dir")) writer = io.NewWriter(viper.GetString("export_dir")) } func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }