Skip to content

Commit

Permalink
feat: add option to use config file (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
fritterhoff authored Jan 10, 2025
1 parent 7d65fca commit 0a58c49
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 42 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@
--csr "-----BEGIN CERTIFICATE REQUEST-----\nfoo-bar\n-----END CERTIFICATE REQUEST-----"
```

Beside using arguments you can also create a config file `cert-generator.yaml`:

```yaml
requester_email: ""
validator_email: ""
validator_totp_seed: ""
requester_totp_seed: ""
validator_password: ""
requester_password: ""
```
## Automatic Domain Validation using AXFR
In case you want to (re)validate several domains using DNS Challenges, you may use this module. To use this module, you must have a DNS server/provider that supports standard AXFR Updates to your zones. Right now, we consider all domains to be revalidated that expire in the next 30 days. Domains with a validity of more than 30 days get ignored by the tool.
Expand Down
131 changes: 100 additions & 31 deletions cmd/genCert.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,108 @@ import (

"github.com/hm-edu/harica/client"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)

type GenCertConfig struct {
domains []string
csr string
transactionType string
requesterEmail string
requesterPassword string
requesterTOTPSeed string
validatorEmail string
validatorPassword string
validatorTOTPSeed string
Domains []string `mapstructure:"domains"`
Csr string `mapstructure:"csr"`
TransactionType string `mapstructure:"transaction_type"`
RequesterEmail string `mapstructure:"requester_email"`
RequesterPassword string `mapstructure:"requester_password"`
RequesterTOTPSeed string `mapstructure:"requester_totp_seed"`
ValidatorEmail string `mapstructure:"validator_email"`
ValidatorPassword string `mapstructure:"validator_password"`
ValidatorTOTPSeed string `mapstructure:"validator_totp_seed"`
}

var (
genCertConfig GenCertConfig
configPath string
keyMapping = map[string]string{
"domains": "domains",
"csr": "csr",
"transaction-type": "transaction_type",
"requester-email": "requester_email",
"requester-password": "requester_password",
"requester-totp-seed": "requester_totp-seed",
"validator-email": "validator_email",
"validator-password": "validator_password",
"validator-totp-seed": "validator_totp_seed",
}
)

// genCertCmd represents the genCert command
var genCertCmd = &cobra.Command{
Use: "gen-cert",
PreRun: func(cmd *cobra.Command, args []string) {
viper.SetConfigType("yaml")
viper.SetConfigName("cert-generator")
viper.AddConfigPath("/etc/harica/") // path to look for the config file in
viper.AddConfigPath("$HOME/harica/") // call multiple times to add many search paths
viper.AddConfigPath("/opt/harica/")
viper.AddConfigPath(".") // optionally look for config in the working directory
if configPath != "" {
viper.SetConfigFile(configPath)
}
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
slog.Info("No configuration file found")
} else {
slog.Error("Error reading config file", slog.Any("error", err))
os.Exit(1)
}
} else {
slog.Info("Using config file:", slog.Any("config", viper.ConfigFileUsed()))
}

// Unmarshal the config into a struct.
err := viper.Unmarshal(&genCertConfig)
if err != nil {
slog.Error("Error reading config file", slog.Any("error", err))
os.Exit(1)
}

cmd.Flags().VisitAll(func(f *pflag.Flag) {
if !f.Changed && viper.IsSet(f.Name) {
val := viper.Get(f.Name)
err = cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
if err != nil {
slog.Error("Failed to set flag", slog.Any("error", err))
os.Exit(1)
}
} else if v, ok := keyMapping[f.Name]; !f.Changed && ok && viper.IsSet(v) {
val := viper.Get(v)
err = cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
if err != nil {
slog.Error("Failed to set flag", slog.Any("error", err))
os.Exit(1)
}
}
})
},
Run: func(cmd *cobra.Command, args []string) {

requester, err := client.NewClient(genCertConfig.requesterEmail, genCertConfig.requesterPassword, genCertConfig.requesterTOTPSeed, client.WithDebug(debug))
requester, err := client.NewClient(genCertConfig.RequesterEmail, genCertConfig.RequesterPassword, genCertConfig.RequesterTOTPSeed, client.WithDebug(debug))
if err != nil {
slog.Error("failed to create requester client", slog.Any("error", err))
os.Exit(1)
}
validator, err := client.NewClient(genCertConfig.validatorEmail, genCertConfig.validatorPassword, genCertConfig.validatorTOTPSeed, client.WithDebug(debug))
validator, err := client.NewClient(genCertConfig.ValidatorEmail, genCertConfig.ValidatorPassword, genCertConfig.ValidatorTOTPSeed, client.WithDebug(debug))
if err != nil {
slog.Error("failed to create validator client", slog.Any("error", err))
os.Exit(1)
}

orgs, err := requester.CheckMatchingOrganization(genCertConfig.domains)
orgs, err := requester.CheckMatchingOrganization(genCertConfig.Domains)
if err != nil || len(orgs) == 0 {
slog.Error("failed to check matching organization", slog.Any("error", err))
os.Exit(1)
}
slog.Info("matching organizations", slog.Any("organizations", orgs))

transaction, err := requester.RequestCertificate(genCertConfig.domains, genCertConfig.csr, genCertConfig.transactionType, orgs[0])
transaction, err := requester.RequestCertificate(genCertConfig.Domains, genCertConfig.Csr, genCertConfig.TransactionType, orgs[0])
if err != nil {
slog.Error("failed to request certificate", slog.Any("error", err))
os.Exit(1)
Expand Down Expand Up @@ -83,22 +143,31 @@ var genCertCmd = &cobra.Command{

func init() {
rootCmd.AddCommand(genCertCmd)
genCertCmd.Flags().StringSliceVar(&genCertConfig.domains, "domains", []string{}, "Domains to request certificate for")
genCertCmd.Flags().StringVar(&genCertConfig.csr, "csr", "", "CSR to request certificate with")
genCertCmd.Flags().StringVarP(&genCertConfig.transactionType, "transaction-type", "t", "DV", "Transaction type to request certificate with")
genCertCmd.Flags().StringVar(&genCertConfig.requesterEmail, "requester-email", "", "Email of requester")
genCertCmd.Flags().StringVar(&genCertConfig.requesterPassword, "requester-password", "", "Password of requester")
genCertCmd.Flags().StringVar(&genCertConfig.requesterTOTPSeed, "requester-totp-seed", "", "TOTP seed of requester")
genCertCmd.Flags().StringVar(&genCertConfig.validatorEmail, "validator-email", "", "Email of validator")
genCertCmd.Flags().StringVar(&genCertConfig.validatorPassword, "validator-password", "", "Password of validator")
genCertCmd.Flags().StringVar(&genCertConfig.validatorTOTPSeed, "validator-totp-seed", "", "TOTP seed of validator")

genCertCmd.MarkFlagRequired("domains") //nolint:errcheck
genCertCmd.MarkFlagRequired("csr") //nolint:errcheck
genCertCmd.MarkFlagRequired("requester-email") //nolint:errcheck
genCertCmd.MarkFlagRequired("requester-password") //nolint:errcheck
genCertCmd.MarkFlagRequired("requester-totp-seed") //nolint:errcheck
genCertCmd.MarkFlagRequired("validator-email") //nolint:errcheck
genCertCmd.MarkFlagRequired("validator-password") //nolint:errcheck
genCertCmd.MarkFlagRequired("validator-totp-seed") //nolint:errcheck
genCertCmd.Flags().StringSlice("domains", []string{}, "Domains to request certificate for")
genCertCmd.Flags().String("csr", "", "CSR to request certificate with")
genCertCmd.Flags().StringP("transaction-type", "t", "DV", "Transaction type to request certificate with")
genCertCmd.Flags().String("requester-email", "", "Email of requester")
genCertCmd.Flags().String("requester-password", "", "Password of requester")
genCertCmd.Flags().String("requester-totp-seed", "", "TOTP seed of requester")
genCertCmd.Flags().String("validator-email", "", "Email of validator")
genCertCmd.Flags().String("validator-password", "", "Password of validator")
genCertCmd.Flags().String("validator-totp-seed", "", "TOTP seed of validator")

for k, v := range keyMapping {
err := viper.BindPFlag(v, genCertCmd.Flags().Lookup(k))
if err != nil {
slog.Error("Failed to bind flag", slog.Any("error", err))
os.Exit(1)
}
}

for _, s := range []string{"domains", "csr", "requester-email", "requester-password", "validator-email", "validator-password", "validator-totp-seed"} {
err := genCertCmd.MarkFlagRequired(s)
if err != nil {
slog.Error("Failed to mark flag required", slog.Any("error", err))
os.Exit(1)
}
}

genCertCmd.Flags().StringVar(&configPath, "config", "", "config file (default is cert-generator.yaml)")
}
22 changes: 19 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,36 @@ require (
github.com/miekg/dns v1.1.62
github.com/pquerna/otp v1.4.0
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.19.0
golang.org/x/net v0.34.0
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.22.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
Loading

0 comments on commit 0a58c49

Please sign in to comment.