diff --git a/CHANGELOG.md b/CHANGELOG.md index 60bbcab..fd8c668 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +# v1.7.0 + +## Features + +### CLI + +- `stores import csv`: supports interactive credential input, as well as input via flags and environmental + variables. [docs](docs/kfutil_stores_import_csv.md) + +## Fixes + +### CLI + +- `stores import csv`: providing a `Password(/StorePassword)` does not crash CLI. +- `stores import csv`: results CSV retains input header ordering. +- `stores import csv`: Handle `BOM` characters in an input CSV file. +- `store-types create`: URL encode `-b` parameter when passed. +- `store-types create`: Initialize logger before fetching store-type definitions. +- `stores rot`: Re-enabled and improved logging. + # v1.6.2 ## Fixes diff --git a/README.md b/README.md index f7f7d47..28efd8d 100644 --- a/README.md +++ b/README.md @@ -298,10 +298,10 @@ set of defined certificates are present in each store that meets a certain set o ```bash echo "Generating cert template file certs_template.csv" -kfutil stores rot generate-template-rot --type certs +kfutil stores rot generate-template --type certs # edit the certs_template.csv file echo "Generating stores template file stores_template.csv" -kfutil stores rot generate-template-rot --type stores +kfutil stores rot generate-template --type stores # edit the stores_template.csv file kfutil stores rot audit --add-certs certs_template.csv --stores stores_template.csv #This will audit the stores and generate a report file # review/edit the report file generated `rot_audit.csv` @@ -317,7 +317,7 @@ For full documentation, see [stores rot generate template](docs/kfutil_stores_ro This will write the file `certs_template.csv` to the current directory. ```bash -kfutil stores generate-template-rot --type certs +kfutil stores rot generate-template --type certs ``` #### Generate Certificate Store List Template @@ -327,7 +327,7 @@ For full documentation, see [stores rot generate template](docs/kfutil_stores_ro This will write the file `stores_template.csv` to the current directory. For full documentation ```bash -kfutil stores generate-template-rot --type stores +kfutil stores rot generate-template --type stores ``` #### Run Root of Trust Audit diff --git a/cmd/constants.go b/cmd/constants.go index 55779dd..e919bbf 100644 --- a/cmd/constants.go +++ b/cmd/constants.go @@ -30,10 +30,14 @@ const ( FlagGitRef = "git-ref" FlagGitRepo = "repo" FlagFromFile = "from-file" - DebugFuncEnter = "entered: %s" - DebugFuncExit = "exiting: %s" - DebugFuncCall = "calling: %s" + DebugFuncEnter = "entered:" + DebugFuncExit = "exiting:" + DebugFuncCall = "calling:" MinHttpTimeout = 3 + + EnvStoresImportCSVServerUsername = "KFUTIL_CSV_SERVER_USERNAME" + EnvStoresImportCSVServerPassword = "KFUTIL_CSV_SERVER_PASSWORD" + EnvStoresImportCSVStorePassword = "KFUTIL_CSV_STORE_PASSWORD" ) var ProviderTypeChoices = []string{ diff --git a/cmd/helpers.go b/cmd/helpers.go index 52e0cdb..b5086c7 100644 --- a/cmd/helpers.go +++ b/cmd/helpers.go @@ -23,6 +23,7 @@ import ( "net/http" "os" "path/filepath" + "slices" "strconv" "time" @@ -30,8 +31,7 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/cobra" - - stdlog "log" + //stdlog "log" ) func boolToPointer(b bool) *bool { @@ -132,7 +132,7 @@ func csvToMap(filename string) ([]map[string]string, error) { // Populate the map with data from the row for i, column := range header { - rowMap[column] = row[i] + rowMap[column] = stripAllBOMs(row[i]) } // Append the map to the data slice @@ -190,12 +190,23 @@ func informDebug(debugFlag bool) { } func initLogger() { - stdlog.SetOutput(io.Discard) - zerolog.TimeFieldFormat = zerolog.TimeFormatUnix - zerolog.SetGlobalLevel(zerolog.Disabled) // default to disabled - log.Logger = log.With().Caller().Logger() + // Configure zerolog to include caller information + log.Logger = log.With().Caller().Logger().Output( + zerolog.ConsoleWriter{ + Out: os.Stdout, + TimeFormat: time.RFC3339, + FormatCaller: func(caller interface{}) string { + if c, ok := caller.(string); ok { + return c // This will include the full file path and line number + } + return "" + }, + }, + ) log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}) + initStdLogger() + } func intToPointer(i int) *int { @@ -282,10 +293,10 @@ func logGlobals() { } -func mapToCSV(data []map[string]string, filename string) error { - file, err := os.Create(filename) - if err != nil { - return err +func mapToCSV(data []map[string]string, filename string, inputHeader []string) error { + file, fErr := os.Create(filename) + if fErr != nil { + return fErr } defer file.Close() @@ -293,16 +304,21 @@ func mapToCSV(data []map[string]string, filename string) error { defer writer.Flush() // Write the header using keys from the first map - var header []string - if len(data) > 0 { + var header = inputHeader + if len(header) <= 0 && len(data) > 0 { for key := range data[0] { - header = append(header, key) - } - if err := writer.Write(header); err != nil { - return err + header = append(header, stripAllBOMs(key)) } } + errorColFound := slices.Contains(header, "Errors") + if !errorColFound { + header = append(header, "Errors") + } + if hErr := writer.Write(header); hErr != nil { + return hErr + } + // Write map data to CSV for _, row := range data { var record []string diff --git a/cmd/logging.go b/cmd/logging.go new file mode 100644 index 0000000..45b3605 --- /dev/null +++ b/cmd/logging.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "strings" + + "github.com/rs/zerolog/log" +) + +// zerologWriter implements io.Writer and forwards standard log output to zerolog +type zerologWriter struct{} + +func (w zerologWriter) Write(p []byte) (n int, err error) { + // Clean up the log message (remove timestamp, etc.) + msg := string(p) + msg = strings.TrimSpace(msg) + + // Check if it's a debug message + if strings.Contains(msg, "[DEBUG]") { + msg = strings.Replace(msg, "[DEBUG]", "", 1) + log.Debug().Msg(strings.TrimSpace(msg)) + } else if strings.Contains(msg, "[ERROR]") { + msg = strings.Replace(msg, "[ERROR]", "", 1) + log.Error().Msg(strings.TrimSpace(msg)) + } else if strings.Contains(msg, "[INFO]") { + msg = strings.Replace(msg, "[INFO]", "", 1) + log.Info().Msg(strings.TrimSpace(msg)) + + } else if strings.Contains(msg, "[WARN]") { + msg = strings.Replace(msg, "[WARN]", "", 1) + log.Warn().Msg(strings.TrimSpace(msg)) + + } else if strings.Contains(msg, "[FATAL]") { + msg = strings.Replace(msg, "[FATAL]", "", 1) + log.Fatal().Msg(strings.TrimSpace(msg)) + + } else if strings.Contains(msg, "[TRACE]") { + msg = strings.Replace(msg, "[TRACE]", "", 1) + log.Trace().Msg(strings.TrimSpace(msg)) + } else { + // Default to info level + log.Info().Msg(msg) + } + + return len(p), nil +} diff --git a/cmd/login.go b/cmd/login.go index eea4638..f7d9283 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -17,8 +17,6 @@ package cmd import ( "bufio" "fmt" - "io" - stdlog "log" "os" "path" "strings" @@ -70,7 +68,7 @@ WARNING: This will write the environmental credentials to disk and will be store if debugErr != nil { return debugErr } - stdlog.SetOutput(io.Discard) + //stdlog.SetOutput(io.Discard) informDebug(debugFlag) logGlobals() @@ -237,14 +235,32 @@ WARNING: This will write the environmental credentials to disk and will be store } if authType == "oauth" { - log.Debug().Msg("attempting to authenticate via OAuth") + log.Debug(). + Str("profile", profile). + Str("configFile", configFile). + Str("host", outputServer.Host). + Str("authType", authType). + Str("accessToken", hashSecretValue(kfcOAuth.AccessToken)). + Str("clientID", kfcOAuth.ClientID). + Str("clientSecret", hashSecretValue(kfcOAuth.ClientSecret)). + Str("apiPath", kfcOAuth.CommandAPIPath). + Msg("attempting to authenticate via OAuth") aErr := kfcOAuth.Authenticate() if aErr != nil { log.Error().Err(aErr) return aErr } } else if authType == "basic" { - log.Debug().Msg("attempting to authenticate via Basic Auth") + log.Debug(). + Str("profile", profile). + Str("configFile", configFile). + Str("host", outputServer.Host). + Str("authType", authType). + Str("username", kfcBasicAuth.Username). + Str("domain", kfcBasicAuth.Domain). + Str("password", hashSecretValue(kfcBasicAuth.Password)). + Str("apiPath", kfcBasicAuth.CommandAPIPath). + Msg("attempting to authenticate via Basic Auth") aErr := kfcBasicAuth.Authenticate() if aErr != nil { log.Error().Err(aErr) diff --git a/cmd/logout.go b/cmd/logout.go index f203824..11e1141 100644 --- a/cmd/logout.go +++ b/cmd/logout.go @@ -16,8 +16,6 @@ package cmd import ( "fmt" - "io" - stdlog "log" "os" "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers" @@ -39,7 +37,7 @@ var logoutCmd = &cobra.Command{ if debugErr != nil { return debugErr } - stdlog.SetOutput(io.Discard) + //stdlog.SetOutput(io.Discard) informDebug(debugFlag) logGlobals() diff --git a/cmd/root.go b/cmd/root.go index 22ec1cb..82f0e72 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,7 +17,6 @@ package cmd import ( _ "embed" "fmt" - "io" stdlog "log" "os" "strings" @@ -135,6 +134,13 @@ func getServerConfigFromEnv() (*auth_providers.Server, error) { apiPath, aOk := os.LookupEnv(auth_providers.EnvKeyfactorAPIPath) clientId, cOk := os.LookupEnv(auth_providers.EnvKeyfactorClientID) clientSecret, csOk := os.LookupEnv(auth_providers.EnvKeyfactorClientSecret) + audience, _ := os.LookupEnv(auth_providers.EnvKeyfactorAuthAudience) + scopesCSV, _ := os.LookupEnv(auth_providers.EnvKeyfactorAuthScopes) + var scopes []string + if scopesCSV != "" { + scopes = strings.Split(scopesCSV, ",") + } + tokenUrl, tOk := os.LookupEnv(auth_providers.EnvKeyfactorAuthTokenURL) skipVerify, svOk := os.LookupEnv(auth_providers.EnvKeyfactorSkipVerify) var skipVerifyBool bool @@ -161,24 +167,44 @@ func getServerConfigFromEnv() (*auth_providers.Server, error) { } if isBasicAuth { - log.Debug(). - Str("username", username). - Str("password", hashSecretValue(password)). - Str("domain", domain). - Str("hostname", hostname). + + log.Debug().Str("hostname", hostname). Str("apiPath", apiPath). Bool("skipVerify", skipVerifyBool). - Msg("call: basicAuthNoParamsConfig.Authenticate()") + Msg("setting up basic auth client base configuration") basicAuthNoParamsConfig.WithCommandHostName(hostname). WithCommandAPIPath(apiPath). WithSkipVerify(skipVerifyBool) - bErr := basicAuthNoParamsConfig. + log.Debug(). + Str("username", username). + Str("password", hashSecretValue(password)). + Str("domain", domain). + Msg("setting up basic auth configuration") + _ = basicAuthNoParamsConfig. WithUsername(username). WithPassword(password). - WithDomain(domain). - Authenticate() - log.Debug().Msg("complete: basicAuthNoParamsConfig.Authenticate()") + WithDomain(domain) + + log.Debug(). + Str("username", basicAuthNoParamsConfig.Username). + Str("password", hashSecretValue(password)). + Str("domain", basicAuthNoParamsConfig.Domain). + Str("hostname", basicAuthNoParamsConfig.CommandHostName). + Str("apiPath", basicAuthNoParamsConfig.CommandAPIPath). + Bool("skipVerify", basicAuthNoParamsConfig.CommandAuthConfig.SkipVerify). + Msg(fmt.Sprintf("%s basicAuthNoParamsConfig.Authenticate()", DebugFuncCall)) + + bErr := basicAuthNoParamsConfig.Authenticate() + log.Debug(). + Str("username", basicAuthNoParamsConfig.Username). + Str("password", hashSecretValue(password)). + Str("domain", basicAuthNoParamsConfig.Domain). + Str("hostname", basicAuthNoParamsConfig.CommandHostName). + Str("apiPath", basicAuthNoParamsConfig.CommandAPIPath). + Bool("skipVerify", basicAuthNoParamsConfig.CommandAuthConfig.SkipVerify). + Msg("complete: basicAuthNoParamsConfig.Authenticate()") + if bErr != nil { log.Error().Err(bErr).Msg("unable to authenticate with provided credentials") return nil, bErr @@ -187,16 +213,36 @@ func getServerConfigFromEnv() (*auth_providers.Server, error) { return basicAuthNoParamsConfig.GetServerConfig(), nil } else if isOAuth { log.Debug(). - Str("clientId", clientId). - Str("clientSecret", hashSecretValue(clientSecret)). - Str("tokenUrl", tokenUrl). Str("hostname", hostname). Str("apiPath", apiPath). Bool("skipVerify", skipVerifyBool). - Msg("call: oAuthNoParamsConfig.Authenticate()") + Msg("setting up oAuth client base configuration") _ = oAuthNoParamsConfig.CommandAuthConfig.WithCommandHostName(hostname). WithCommandAPIPath(apiPath). WithSkipVerify(skipVerifyBool) + + log.Debug(). + Str("clientId", clientId). + Str("clientSecret", hashSecretValue(clientSecret)). + Str("tokenUrl", tokenUrl). + Str("audience", audience). + Strs("scopes", scopes). + Msg("setting up oAuth configuration") + _ = oAuthNoParamsConfig.WithClientId(clientId). + WithClientSecret(clientSecret). + WithTokenUrl(tokenUrl). + WithAudience(audience). + WithScopes(scopes) + + log.Debug(). + Str("clientId", oAuthNoParamsConfig.ClientID). + Str("clientSecret", hashSecretValue(oAuthNoParamsConfig.ClientSecret)). + Str("tokenUrl", oAuthNoParamsConfig.TokenURL). + Str("hostname", oAuthNoParamsConfig.CommandHostName). + Str("apiPath", oAuthNoParamsConfig.CommandAPIPath). + Bool("skipVerify", oAuthNoParamsConfig.SkipVerify). + Str("caCert", oAuthNoParamsConfig.CommandCACert). + Msg(fmt.Sprintf("%s oAuthNoParamsConfig.Authenticate()", DebugFuncCall)) oErr := oAuthNoParamsConfig.Authenticate() log.Debug().Msg("complete: oAuthNoParamsConfig.Authenticate()") if oErr != nil { @@ -738,7 +784,7 @@ var RootCmd = &cobra.Command{ // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { - stdlog.SetOutput(io.Discard) + //stdlog.SetOutput(io.Discard) err := RootCmd.Execute() if err != nil { os.Exit(1) @@ -881,3 +927,9 @@ func init() { RootCmd.AddCommand(makeDocsCmd) } + +func initStdLogger() { + // Redirect standard library's log to zerolog + stdlog.SetOutput(zerologWriter{}) + stdlog.SetFlags(0) // Remove timestamp from standard logger +} diff --git a/cmd/rot.go b/cmd/rot.go index 2a7478c..f47dc32 100644 --- a/cmd/rot.go +++ b/cmd/rot.go @@ -14,1406 +14,1953 @@ package cmd -//import ( -// "bufio" -// "encoding/csv" -// "encoding/json" -// "errors" -// "fmt" -// "log" -// "os" -// "strconv" -// "strings" -// -// "github.com/Keyfactor/keyfactor-go-client/v3/api" -// "github.com/spf13/cobra" -//) -// -//type templateType string -//type StoreCSVEntry struct { -// ID string `json:"id"` -// Type string `json:"type"` -// Machine string `json:"address"` -// Path string `json:"path"` -// Thumbprints map[string]bool `json:"thumbprints,omitempty"` -// Serials map[string]bool `json:"serials,omitempty"` -// Ids map[int]bool `json:"ids,omitempty"` -//} -//type ROTCert struct { -// ID int `json:"id,omitempty"` -// ThumbPrint string `json:"thumbprint,omitempty"` -// CN string `json:"cn,omitempty"` -// Locations []api.CertificateLocations `json:"locations,omitempty"` -//} -//type ROTAction struct { -// StoreID string `json:"store_id,omitempty"` -// StoreType string `json:"store_type,omitempty"` -// StorePath string `json:"store_path,omitempty"` -// Thumbprint string `json:"thumbprint,omitempty"` -// CertID int `json:"cert_id,omitempty" mapstructure:"CertID,omitempty"` -// AddCert bool `json:"add,omitempty" mapstructure:"AddCert,omitempty"` -// RemoveCert bool `json:"remove,omitempty" mapstructure:"RemoveCert,omitempty"` -//} -// -//const ( -// tTypeCerts templateType = "certs" -// reconcileDefaultFileName string = "rot_audit.csv" -//) -// -//var ( -// AuditHeader = []string{ -// "Thumbprint", -// "CertID", -// "SubjectName", -// "Issuer", -// "StoreID", -// "StoreType", -// "Machine", -// "Path", -// "AddCert", -// "RemoveCert", -// "Deployed", -// "AuditDate", -// } -// ReconciledAuditHeader = []string{ -// "Thumbprint", -// "CertID", -// "SubjectName", -// "Issuer", -// "StoreID", -// "StoreType", -// "Machine", -// "Path", -// "AddCert", -// "RemoveCert", -// "Deployed", -// "ReconciledDate", -// } -// StoreHeader = []string{ -// "StoreID", -// "StoreType", -// "StoreMachine", -// "StorePath", -// "ContainerId", -// "ContainerName", -// "LastQueriedDate", -// } -// CertHeader = []string{"Thumbprint", "SubjectName", "Issuer", "CertID", "Locations", "LastQueriedDate"} -//) -// -//// String is used both by fmt.Print and by Cobra in help text -//func (e *templateType) String() string { -// return string(*e) -//} -// -//// Set must have pointer receiver, so it doesn't change the value of a copy -//func (e *templateType) Set(v string) error { -// switch v { -// case "certs", "stores", "actions": -// *e = templateType(v) -// return nil -// default: -// return errors.New(`must be one of "certs", "stores", or "actions"`) -// } -//} -// -//// Type is only used in help text -//func (e *templateType) Type() string { -// return "string" -//} -// -//func templateTypeCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { -// return []string{ -// "certs\tGenerates template CSV for certificate input to be used w/ `--add-certs` or `--remove-certs`", -// "stores\tGenerates template CSV for certificate input to be used w/ `--stores`", -// "actions\tGenerates template CSV for certificate input to be used w/ `--actions`", -// }, cobra.ShellCompDirectiveDefault -//} -// -//func generateAuditReport( -// addCerts map[string]string, -// removeCerts map[string]string, -// stores map[string]StoreCSVEntry, -// outpath string, -// kfClient *api.Client, -//) ([][]string, map[string][]ROTAction, error) { -// log.Println("[DEBUG] generateAuditReport called") -// var ( -// data [][]string -// ) -// -// data = append(data, AuditHeader) -// var csvFile *os.File -// var fErr error -// if outpath == "" { -// csvFile, fErr = os.Create(reconcileDefaultFileName) -// outpath = reconcileDefaultFileName -// } else { -// csvFile, fErr = os.Create(outpath) -// } -// -// if fErr != nil { -// fmt.Printf("%s", fErr) -// log.Fatalf("[ERROR] creating audit file: %s", fErr) -// } -// csvWriter := csv.NewWriter(csvFile) -// cErr := csvWriter.Write(AuditHeader) -// if cErr != nil { -// fmt.Printf("%s", cErr) -// log.Fatalf("[ERROR] writing audit header: %s", cErr) -// } -// actions := make(map[string][]ROTAction) -// -// for _, cert := range addCerts { -// certLookupReq := api.GetCertificateContextArgs{ -// IncludeMetadata: boolToPointer(true), -// IncludeLocations: boolToPointer(true), -// CollectionId: nil, -// Thumbprint: cert, -// Id: 0, -// } -// certLookup, err := kfClient.GetCertificateContext(&certLookupReq) -// if err != nil { -// fmt.Printf("[ERROR] looking up certificate %s: %s\n", cert, err) -// log.Printf("[ERROR] looking up cert: %s\n%v", cert, err) -// continue -// } -// certID := certLookup.Id -// certIDStr := strconv.Itoa(certID) -// for _, store := range stores { -// if _, ok := store.Thumbprints[cert]; ok { -// // Cert is already in the store do nothing -// row := []string{ -// cert, -// certIDStr, -// certLookup.IssuedDN, -// certLookup.IssuerDN, -// store.ID, -// store.Type, -// store.Machine, -// store.Path, -// "false", -// "false", -// "true", -// getCurrentTime(""), -// } -// data = append(data, row) -// wErr := csvWriter.Write(row) -// if wErr != nil { -// fmt.Printf("[ERROR] writing audit file row: %s\n", wErr) -// log.Printf("[ERROR] writing audit row: %s", wErr) -// } -// } else { -// // Cert is not deployed to this store and will need to be added -// row := []string{ -// cert, -// certIDStr, -// certLookup.IssuedDN, -// certLookup.IssuerDN, -// store.ID, -// store.Type, -// store.Machine, -// store.Path, -// "true", -// "false", -// "false", -// getCurrentTime(""), -// } -// data = append(data, row) -// wErr := csvWriter.Write(row) -// if wErr != nil { -// fmt.Printf("[ERROR] writing audit file row: %s\n", wErr) -// log.Printf("[ERROR] writing audit row: %s", wErr) -// } -// actions[cert] = append( -// actions[cert], ROTAction{ -// Thumbprint: cert, -// CertID: certID, -// StoreID: store.ID, -// StoreType: store.Type, -// StorePath: store.Path, -// AddCert: true, -// RemoveCert: false, -// }, -// ) -// } -// } -// } -// for _, cert := range removeCerts { -// certLookupReq := api.GetCertificateContextArgs{ -// IncludeMetadata: boolToPointer(true), -// IncludeLocations: boolToPointer(true), -// CollectionId: nil, -// Thumbprint: cert, -// Id: 0, -// } -// certLookup, err := kfClient.GetCertificateContext(&certLookupReq) -// if err != nil { -// log.Printf("[ERROR] looking up cert: %s", err) -// continue -// } -// certID := certLookup.Id -// certIDStr := strconv.Itoa(certID) -// for _, store := range stores { -// if _, ok := store.Thumbprints[cert]; ok { -// // Cert is deployed to this store and will need to be removed -// row := []string{ -// cert, -// certIDStr, -// certLookup.IssuedDN, -// certLookup.IssuerDN, -// store.ID, -// store.Type, -// store.Machine, -// store.Path, -// "false", -// "true", -// "true", -// getCurrentTime(""), -// } -// data = append(data, row) -// wErr := csvWriter.Write(row) -// if wErr != nil { -// fmt.Printf("%s", wErr) -// log.Printf("[ERROR] writing row to CSV: %s", wErr) -// } -// actions[cert] = append( -// actions[cert], ROTAction{ -// Thumbprint: cert, -// CertID: certID, -// StoreID: store.ID, -// StoreType: store.Type, -// StorePath: store.Path, -// AddCert: false, -// RemoveCert: true, -// }, -// ) -// } else { -// // Cert is not deployed to this store do nothing -// row := []string{ -// cert, -// certIDStr, -// certLookup.IssuedDN, -// certLookup.IssuerDN, -// store.ID, -// store.Type, -// store.Machine, -// store.Path, -// "false", -// "false", -// "false", -// getCurrentTime(""), -// } -// data = append(data, row) -// wErr := csvWriter.Write(row) -// if wErr != nil { -// fmt.Printf("%s", wErr) -// log.Printf("[ERROR] writing row to CSV: %s", wErr) -// } -// } -// } -// } -// csvWriter.Flush() -// ioErr := csvFile.Close() -// if ioErr != nil { -// fmt.Println(ioErr) -// log.Printf("[ERROR] closing audit file: %s", ioErr) -// } -// fmt.Printf("Audit report written to %s\n", outpath) -// return data, actions, nil -//} -// -//func reconcileRoots(actions map[string][]ROTAction, kfClient *api.Client, reportFile string, dryRun bool) error { -// log.Printf("[DEBUG] Reconciling roots") -// if len(actions) == 0 { -// log.Printf("[INFO] No actions to take, roots are up-to-date.") -// return nil -// } -// rFileName := fmt.Sprintf("%s_reconciled.csv", strings.Split(reportFile, ".csv")[0]) -// csvFile, fErr := os.Create(rFileName) -// if fErr != nil { -// fmt.Printf("[ERROR] creating reconciled report file: %s", fErr) -// } -// csvWriter := csv.NewWriter(csvFile) -// cErr := csvWriter.Write(ReconciledAuditHeader) -// if cErr != nil { -// fmt.Printf("%s", cErr) -// log.Fatalf("[ERROR] writing audit header: %s", cErr) -// } -// for thumbprint, action := range actions { -// -// for _, a := range action { -// if a.AddCert { -// log.Printf("[INFO] Adding cert %s to store %s(%s)", thumbprint, a.StoreID, a.StorePath) -// if !dryRun { -// cStore := api.CertificateStore{ -// CertificateStoreId: a.StoreID, -// Overwrite: true, -// } -// var stores []api.CertificateStore -// stores = append(stores, cStore) -// schedule := &api.InventorySchedule{ -// Immediate: boolToPointer(true), -// } -// addReq := api.AddCertificateToStore{ -// CertificateId: a.CertID, -// CertificateStores: &stores, -// InventorySchedule: schedule, -// } -// log.Printf("[DEBUG] Adding cert %s to store %s", thumbprint, a.StoreID) -// log.Printf("[TRACE] Add request: %+v", addReq) -// addReqJSON, _ := json.Marshal(addReq) -// log.Printf("[TRACE] Add request JSON: %s", addReqJSON) -// _, err := kfClient.AddCertificateToStores(&addReq) -// if err != nil { -// fmt.Printf( -// "[ERROR] adding cert %s (%d) to store %s (%s): %s\n", -// a.Thumbprint, -// a.CertID, -// a.StoreID, -// a.StorePath, -// err, -// ) -// continue -// } -// } else { -// log.Printf("[INFO] DRY RUN: Would have added cert %s from store %s", thumbprint, a.StoreID) -// } -// } else if a.RemoveCert { -// if !dryRun { -// log.Printf("[INFO] Removing cert from store %s", a.StoreID) -// cStore := api.CertificateStore{ -// CertificateStoreId: a.StoreID, -// Alias: a.Thumbprint, -// } -// var stores []api.CertificateStore -// stores = append(stores, cStore) -// schedule := &api.InventorySchedule{ -// Immediate: boolToPointer(true), -// } -// removeReq := api.RemoveCertificateFromStore{ -// CertificateId: a.CertID, -// CertificateStores: &stores, -// InventorySchedule: schedule, -// } -// _, err := kfClient.RemoveCertificateFromStores(&removeReq) -// if err != nil { -// fmt.Printf( -// "[ERROR] removing cert %s (ID: %d) from store %s (%s): %s\n", -// a.Thumbprint, -// a.CertID, -// a.StoreID, -// a.StorePath, -// err, -// ) -// } -// } else { -// fmt.Printf("DRY RUN: Would have removed cert %s from store %s\n", thumbprint, a.StoreID) -// log.Printf("[INFO] DRY RUN: Would have removed cert %s from store %s", thumbprint, a.StoreID) -// } -// } -// } -// } -// return nil -//} -// -//func readCertsFile(certsFilePath string, kfclient *api.Client) (map[string]string, error) { -// // Read in the cert CSV -// csvFile, _ := os.Open(certsFilePath) -// reader := csv.NewReader(bufio.NewReader(csvFile)) -// certEntries, _ := reader.ReadAll() -// var certs = make(map[string]string) -// for _, entry := range certEntries { -// switch entry[0] { -// case "CertID", "thumbprint", "id", "CertId", "Thumbprint": -// continue // Skip header -// } -// certs[entry[0]] = entry[0] -// } -// return certs, nil -//} -// -//func isRootStore( -// st *api.GetCertificateStoreResponse, -// invs *[]api.CertStoreInventoryV1, -// minCerts int, -// maxKeys int, -// maxLeaf int, -//) bool { -// leafCount := 0 -// keyCount := 0 -// certCount := 0 -// for _, inv := range *invs { -// log.Printf("[DEBUG] inv: %v", inv) -// certCount += len(inv.Certificates) -// -// for _, cert := range inv.Certificates { -// if cert.IssuedDN != cert.IssuerDN { -// leafCount++ -// } -// if inv.Parameters["PrivateKeyEntry"] == "Yes" { -// keyCount++ -// } -// } -// } -// if certCount < minCerts && minCerts >= 0 { -// log.Printf("[DEBUG] Store %s has %d certs, less than the required count of %d", st.Id, certCount, minCerts) -// return false -// } -// if leafCount > maxLeaf && maxLeaf >= 0 { -// log.Printf("[DEBUG] Store %s has too many leaf certs", st.Id) -// return false -// } -// -// if keyCount > maxKeys && maxKeys >= 0 { -// log.Printf("[DEBUG] Store %s has too many keys", st.Id) -// return false -// } -// -// return true -//} -// -//var ( -// rotCmd = &cobra.Command{ -// Use: "rot", -// Short: "Root of trust utility", -// Long: `Root of trust allows you to manage your trusted roots using Keyfactor certificate stores. -//For example if you wish to add a list of "root" certs to a list of certificate stores you would simply generate and fill -//out the template CSV file. These template files can be generated with the following commands: -//kfutil stores rot generate-template --type certs -//kfutil stores rot generate-template --type stores -//Once those files are filled out you can use the following command to add the certs to the stores: -//kfutil stores rot audit --certs-file --stores-file -//Will generate a CSV report file 'rot_audit.csv' of what actions will be taken. If those actions are correct you can run -//the following command to actually perform the actions: -//kfutil stores rot reconcile --certs-file --stores-file -//OR if you want to use the audit report file generated you can run this command: -//kfutil stores rot reconcile --import-csv -//`, -// } -// rotAuditCmd = &cobra.Command{ -// Use: "audit", -// Aliases: nil, -// SuggestFor: nil, -// Short: "Audit generates a CSV report of what actions will be taken based on input CSV files.", -// Long: `Root of Trust Audit: Will read and parse inputs to generate a report of certs that need to be added or removed from the "root of trust" stores.`, -// Example: "", -// ValidArgs: nil, -// ValidArgsFunction: nil, -// Args: nil, -// ArgAliases: nil, -// BashCompletionFunction: "", -// Deprecated: "", -// Annotations: nil, -// Version: "", -// PersistentPreRun: nil, -// PersistentPreRunE: nil, -// PreRun: nil, -// PreRunE: nil, -// Run: func(cmd *cobra.Command, args []string) { -// // Global flags -// debugFlag, _ := cmd.Flags().GetBool("debugFlag") -// configFile, _ := cmd.Flags().GetString("config") -// noPrompt, _ := cmd.Flags().GetBool("no-prompt") -// profile, _ := cmd.Flags().GetString("profile") -// -// kfcUsername, _ := cmd.Flags().GetString("kfcUsername") -// kfcPassword, _ := cmd.Flags().GetString("kfcPassword") -// kfcDomain, _ := cmd.Flags().GetString("kfcDomain") -// -// -// -// debugModeEnabled := checkDebug(debugFlag) -// log.Println("Debug mode enabled: ", debugModeEnabled) -// var lookupFailures []string -// kfClient, _ := initClient(false) -// storesFile, _ := cmd.Flags().GetString("stores") -// addRootsFile, _ := cmd.Flags().GetString("add-certs") -// removeRootsFile, _ := cmd.Flags().GetString("remove-certs") -// minCerts, _ := cmd.Flags().GetInt("min-certs") -// maxLeaves, _ := cmd.Flags().GetInt("max-leaf-certs") -// maxKeys, _ := cmd.Flags().GetInt("max-keys") -// dryRun, _ := cmd.Flags().GetBool("dry-run") -// outpath, _ := cmd.Flags().GetString("outpath") -// // Read in the stores CSV -// log.Printf("[DEBUG] storesFile: %s", storesFile) -// log.Printf("[DEBUG] addRootsFile: %s", addRootsFile) -// log.Printf("[DEBUG] removeRootsFile: %s", removeRootsFile) -// log.Printf("[DEBUG] dryRun: %t", dryRun) -// // Read in the stores CSV -// csvFile, _ := os.Open(storesFile) -// reader := csv.NewReader(bufio.NewReader(csvFile)) -// storeEntries, _ := reader.ReadAll() -// var stores = make(map[string]StoreCSVEntry) -// validHeader := false -// for _, entry := range storeEntries { -// if strings.EqualFold(strings.Join(entry, ","), strings.Join(StoreHeader, ",")) { -// validHeader = true -// continue // Skip header -// } -// if !validHeader { -// fmt.Printf("[ERROR] Invalid header in stores file. Expected: %s", strings.Join(StoreHeader, ",")) -// log.Fatalf("[ERROR] Stores CSV file is missing a valid header") -// } -// apiResp, err := kfClient.GetCertificateStoreByID(entry[0]) -// if err != nil { -// log.Printf("[ERROR] getting cert store: %s", err) -// _ = append(lookupFailures, strings.Join(entry, ",")) -// continue -// } -// -// //inventory, invErr := kfClient.GetCertStoreInventoryV1(entry[0]) -// //if invErr != nil { -// // log.Printf("[ERROR] getting cert store inventory for: %s\n%s", entry[0], invErr) -// //} -// var inventory []api.CertStoreInventoryV1 //TODO: Update this to use SDK inventory -// -// if !isRootStore(apiResp, &inventory, minCerts, maxLeaves, maxKeys) { -// fmt.Printf("Store %s is not a root store, skipping.\n", entry[0]) -// log.Printf("[WARN] Store %s is not a root store", apiResp.Id) -// continue -// } else { -// log.Printf("[INFO] Store %s is a root store", apiResp.Id) -// } -// -// stores[entry[0]] = StoreCSVEntry{ -// ID: entry[0], -// Type: entry[1], -// Machine: entry[2], -// Path: entry[3], -// Thumbprints: make(map[string]bool), -// Serials: make(map[string]bool), -// Ids: make(map[int]bool), -// } -// for _, cert := range inventory { -// thumb := cert.Thumbprints -// for t, v := range thumb { -// stores[entry[0]].Thumbprints[t] = v -// } -// for t, v := range cert.Serials { -// stores[entry[0]].Serials[t] = v -// } -// for t, v := range cert.Ids { -// stores[entry[0]].Ids[t] = v -// } -// } -// -// } -// -// // Read in the add addCerts CSV -// var certsToAdd = make(map[string]string) -// if addRootsFile != "" { -// var rcfErr error -// certsToAdd, rcfErr = readCertsFile(addRootsFile, kfClient) -// if rcfErr != nil { -// fmt.Printf("[ERROR] reading certs file %s: %s", addRootsFile, rcfErr) -// log.Fatalf("[ERROR] reading addCerts file: %s", rcfErr) -// } -// addCertsJSON, _ := json.Marshal(certsToAdd) -// log.Printf("[DEBUG] add certs JSON: %s", string(addCertsJSON)) -// log.Println("[DEBUG] AddCert ROT called") -// } else { -// log.Printf("[DEBUG] No addCerts file specified") -// log.Printf("[DEBUG] No addCerts = %s", certsToAdd) -// } -// -// // Read in the remove removeCerts CSV -// var certsToRemove = make(map[string]string) -// if removeRootsFile != "" { -// var rcfErr error -// certsToRemove, rcfErr = readCertsFile(removeRootsFile, kfClient) -// if rcfErr != nil { -// fmt.Printf("[ERROR] reading removeCerts file %s: %s", removeRootsFile, rcfErr) -// log.Fatalf("[ERROR] reading removeCerts file: %s", rcfErr) -// } -// removeCertsJSON, _ := json.Marshal(certsToRemove) -// log.Printf("[DEBUG] remove certs JSON: %s", string(removeCertsJSON)) -// } else { -// log.Printf("[DEBUG] No removeCerts file specified") -// log.Printf("[DEBUG] No removeCerts = %s", certsToRemove) -// } -// _, _, gErr := generateAuditReport(certsToAdd, certsToRemove, stores, outpath, kfClient) -// if gErr != nil { -// log.Fatalf("[ERROR] generating audit report: %s", gErr) -// } -// }, -// RunE: nil, -// PostRun: nil, -// PostRunE: nil, -// PersistentPostRun: nil, -// PersistentPostRunE: nil, -// FParseErrWhitelist: cobra.FParseErrWhitelist{}, -// CompletionOptions: cobra.CompletionOptions{}, -// TraverseChildren: false, -// Hidden: false, -// SilenceErrors: false, -// SilenceUsage: false, -// DisableFlagParsing: false, -// DisableAutoGenTag: false, -// DisableFlagsInUseLine: false, -// DisableSuggestions: false, -// SuggestionsMinimumDistance: 0, -// } -// rotReconcileCmd = &cobra.Command{ -// Use: "reconcile", -// Aliases: nil, -// SuggestFor: nil, -// Short: "Reconcile either takes in or will generate an audit report and then add/remove certs as needed.", -// Long: `Root of Trust (rot): Will parse either a combination of CSV files that define certs to -//add and/or certs to remove with a CSV of certificate stores or an audit CSV file. If an audit CSV file is provided, the -//add and remove actions defined in the audit file will be immediately executed. If a combination of CSV files are provided, -//the utility will first generate an audit report and then execute the add/remove actions defined in the audit report.`, -// Example: "", -// ValidArgs: nil, -// ValidArgsFunction: nil, -// Args: nil, -// ArgAliases: nil, -// BashCompletionFunction: "", -// Deprecated: "", -// Annotations: nil, -// Version: "", -// PersistentPreRun: nil, -// PersistentPreRunE: nil, -// PreRun: nil, -// PreRunE: nil, -// Run: func(cmd *cobra.Command, args []string) { -// // Global flags -// debugFlag, _ := cmd.Flags().GetBool("debugFlag") -// configFile, _ := cmd.Flags().GetString("config") -// noPrompt, _ := cmd.Flags().GetBool("no-prompt") -// profile, _ := cmd.Flags().GetString("profile") -// -// kfcUsername, _ := cmd.Flags().GetString("kfcUsername") -// kfcPassword, _ := cmd.Flags().GetString("kfcPassword") -// kfcDomain, _ := cmd.Flags().GetString("kfcDomain") -// -// -// -// debugModeEnabled := checkDebug(debugFlag) -// -// log.Println("Debug mode enabled: ", debugModeEnabled) -// -// var lookupFailures []string -// kfClient, _ := initClient(false) -// storesFile, _ := cmd.Flags().GetString("stores") -// addRootsFile, _ := cmd.Flags().GetString("add-certs") -// isCSV, _ := cmd.Flags().GetBool("import-csv") -// reportFile, _ := cmd.Flags().GetString("input-file") -// removeRootsFile, _ := cmd.Flags().GetString("remove-certs") -// minCerts, _ := cmd.Flags().GetInt("min-certs") -// maxLeaves, _ := cmd.Flags().GetInt("max-leaf-certs") -// maxKeys, _ := cmd.Flags().GetInt("max-keys") -// dryRun, _ := cmd.Flags().GetBool("dry-run") -// outpath, _ := cmd.Flags().GetString("outpath") -// -// log.Printf("[DEBUG] configFile: %s", configFile) -// log.Printf("[DEBUG] storesFile: %s", storesFile) -// log.Printf("[DEBUG] addRootsFile: %s", addRootsFile) -// log.Printf("[DEBUG] removeRootsFile: %s", removeRootsFile) -// log.Printf("[DEBUG] dryRun: %t", dryRun) -// -// // Parse existing audit report -// if isCSV && reportFile != "" { -// log.Printf("[DEBUG] isCSV: %t", isCSV) -// log.Printf("[DEBUG] reportFile: %s", reportFile) -// // Read in the CSV -// csvFile, err := os.Open(reportFile) -// if err != nil { -// fmt.Printf("[ERROR] opening file: %s", err) -// log.Fatalf("[ERROR] opening CSV file: %s", err) -// } -// validHeader := false -// -// aCSV := csv.NewReader(csvFile) -// aCSV.FieldsPerRecord = -1 -// inFile, cErr := aCSV.ReadAll() -// if cErr != nil { -// fmt.Printf("[ERROR] reading CSV file: %s", cErr) -// log.Fatalf("[ERROR] reading CSV file: %s", cErr) -// } -// actions := make(map[string][]ROTAction) -// fieldMap := make(map[int]string) -// for i, field := range AuditHeader { -// fieldMap[i] = field -// } -// for ri, row := range inFile { -// if strings.EqualFold(strings.Join(row, ","), strings.Join(AuditHeader, ",")) { -// validHeader = true -// continue // Skip header -// } -// if !validHeader { -// fmt.Printf( -// "[ERROR] Invalid header in stores file. Expected: %s", -// strings.Join(AuditHeader, ","), -// ) -// log.Fatalf("[ERROR] Stores CSV file is missing a valid header") -// } -// action := make(map[string]interface{}) -// -// for i, field := range row { -// fieldInt, iErr := strconv.Atoi(field) -// if iErr != nil { -// log.Printf("[DEBUG] Field %s is not an int", field) -// action[fieldMap[i]] = field -// } else { -// action[fieldMap[i]] = fieldInt -// } -// -// } -// -// addCertStr, aOk := action["AddCert"].(string) -// if !aOk { -// addCertStr = "" -// } -// addCert, acErr := strconv.ParseBool(addCertStr) -// if acErr != nil { -// addCert = false -// } -// -// removeCertStr, rOk := action["RemoveCert"].(string) -// if !rOk { -// removeCertStr = "" -// } -// removeCert, rcErr := strconv.ParseBool(removeCertStr) -// if rcErr != nil { -// removeCert = false -// } -// -// sType, sOk := action["StoreType"].(string) -// if !sOk { -// sType = "" -// } -// -// sPath, pOk := action["Path"].(string) -// if !pOk { -// sPath = "" -// } -// -// tp, tpOk := action["Thumbprint"].(string) -// if !tpOk { -// tp = "" -// } -// cid, cidOk := action["CertID"].(int) -// if !cidOk { -// cid = -1 -// } -// -// if !tpOk && !cidOk { -// fmt.Printf("[ERROR] Missing Thumbprint or CertID for row %d in report file %s", ri, reportFile) -// log.Printf("[ERROR] Invalid action: %v", action) -// continue -// } -// -// sId, sIdOk := action["StoreID"].(string) -// if !sIdOk { -// fmt.Printf("[ERROR] Missing StoreID for row %d in report file %s", ri, reportFile) -// log.Printf("[ERROR] Invalid action: %v", action) -// continue -// } -// if cid == -1 && tp != "" { -// certLookupReq := api.GetCertificateContextArgs{ -// IncludeMetadata: boolToPointer(true), -// IncludeLocations: boolToPointer(true), -// CollectionId: nil, -// Thumbprint: tp, -// Id: 0, -// } -// certLookup, err := kfClient.GetCertificateContext(&certLookupReq) -// if err != nil { -// fmt.Printf("[ERROR] looking up certificate %s: %s\n", tp, err) -// log.Printf("[ERROR] looking up cert: %s\n%v", tp, err) -// continue -// } -// cid = certLookup.Id -// } -// -// a := ROTAction{ -// StoreID: sId, -// StoreType: sType, -// StorePath: sPath, -// Thumbprint: tp, -// CertID: cid, -// AddCert: addCert, -// RemoveCert: removeCert, -// } -// -// actions[a.Thumbprint] = append(actions[a.Thumbprint], a) -// } -// if len(actions) == 0 { -// fmt.Println("No reconciliation actions to take, root stores are up-to-date. Exiting.") -// return -// } -// rErr := reconcileRoots(actions, kfClient, reportFile, dryRun) -// if rErr != nil { -// fmt.Printf("[ERROR] reconciling roots: %s", rErr) -// log.Fatalf("[ERROR] reconciling roots: %s", rErr) -// } -// defer csvFile.Close() -// -// orchsURL := fmt.Sprintf("https://%s/Keyfactor/Portal/AgentJobStatus/Index", kfClient.Hostname) -// -// fmt.Println(fmt.Sprintf("Reconciliation completed. Check orchestrator jobs for details. %s", orchsURL)) -// } else { -// // Read in the stores CSV -// csvFile, _ := os.Open(storesFile) -// reader := csv.NewReader(bufio.NewReader(csvFile)) -// storeEntries, _ := reader.ReadAll() -// var stores = make(map[string]StoreCSVEntry) -// for i, entry := range storeEntries { -// if entry[0] == "StoreID" || entry[0] == "StoreId" || i == 0 { -// continue // Skip header -// } -// apiResp, err := kfClient.GetCertificateStoreByID(entry[0]) -// if err != nil { -// log.Printf("[ERROR] getting cert store: %s", err) -// lookupFailures = append(lookupFailures, entry[0]) -// continue -// } -// inventory, invErr := kfClient.GetCertStoreInventoryV1(entry[0]) -// if invErr != nil { -// log.Fatalf("[ERROR] getting cert store inventory: %s", invErr) -// } -// -// if !isRootStore(apiResp, inventory, minCerts, maxLeaves, maxKeys) { -// log.Printf("[WARN] Store %s is not a root store", apiResp.Id) -// continue -// } else { -// log.Printf("[INFO] Store %s is a root store", apiResp.Id) -// } -// -// stores[entry[0]] = StoreCSVEntry{ -// ID: entry[0], -// Type: entry[1], -// Machine: entry[2], -// Path: entry[3], -// Thumbprints: make(map[string]bool), -// Serials: make(map[string]bool), -// Ids: make(map[int]bool), -// } -// for _, cert := range *inventory { -// thumb := cert.Thumbprints -// for t, v := range thumb { -// stores[entry[0]].Thumbprints[t] = v -// } -// for t, v := range cert.Serials { -// stores[entry[0]].Serials[t] = v -// } -// for t, v := range cert.Ids { -// stores[entry[0]].Ids[t] = v -// } -// } -// -// } -// if len(lookupFailures) > 0 { -// fmt.Printf("[ERROR] the following stores were not found: %s", strings.Join(lookupFailures, ",")) -// log.Fatalf("[ERROR] the following stores were not found: %s", strings.Join(lookupFailures, ",")) -// } -// if len(stores) == 0 { -// fmt.Println("[ERROR] no root stores found. Exiting.") -// log.Fatalf("[ERROR] No root stores found. Exiting.") -// } -// // Read in the add addCerts CSV -// var certsToAdd = make(map[string]string) -// if addRootsFile != "" { -// certsToAdd, _ = readCertsFile(addRootsFile, kfClient) -// log.Printf("[DEBUG] ROT add certs called") -// } else { -// log.Printf("[INFO] No addCerts file specified") -// } -// -// // Read in the remove removeCerts CSV -// var certsToRemove = make(map[string]string) -// if removeRootsFile != "" { -// certsToRemove, _ = readCertsFile(removeRootsFile, kfClient) -// log.Printf("[DEBUG] ROT remove certs called") -// } else { -// log.Printf("[DEBUG] No removeCerts file specified") -// } -// _, actions, err := generateAuditReport(certsToAdd, certsToRemove, stores, outpath, kfClient) -// if err != nil { -// log.Fatalf("[ERROR] generating audit report: %s", err) -// } -// if len(actions) == 0 { -// fmt.Println("No reconciliation actions to take, root stores are up-to-date. Exiting.") -// return -// } -// rErr := reconcileRoots(actions, kfClient, reportFile, dryRun) -// if rErr != nil { -// fmt.Printf("[ERROR] reconciling roots: %s", rErr) -// log.Fatalf("[ERROR] reconciling roots: %s", rErr) -// } -// if lookupFailures != nil { -// fmt.Printf("The following stores could not be found: %s", strings.Join(lookupFailures, ",")) -// } -// orchsURL := fmt.Sprintf("https://%s/Keyfactor/Portal/AgentJobStatus/Index", kfClient.Hostname) -// -// fmt.Println(fmt.Sprintf("Reconciliation completed. Check orchestrator jobs for details. %s", orchsURL)) -// } -// -// }, -// RunE: nil, -// PostRun: nil, -// PostRunE: nil, -// PersistentPostRun: nil, -// PersistentPostRunE: nil, -// FParseErrWhitelist: cobra.FParseErrWhitelist{}, -// CompletionOptions: cobra.CompletionOptions{}, -// TraverseChildren: false, -// Hidden: false, -// SilenceErrors: false, -// SilenceUsage: false, -// DisableFlagParsing: false, -// DisableAutoGenTag: false, -// DisableFlagsInUseLine: false, -// DisableSuggestions: false, -// SuggestionsMinimumDistance: 0, -// } -// rotGenStoreTemplateCmd = &cobra.Command{ -// Use: "generate-template", -// Aliases: nil, -// SuggestFor: nil, -// Short: "For generating Root Of Trust template(s)", -// Long: `Root Of Trust: Will parse a CSV and attempt to deploy a cert or set of certs into a list of cert stores.`, -// Example: "", -// ValidArgs: nil, -// ValidArgsFunction: nil, -// Args: nil, -// ArgAliases: nil, -// BashCompletionFunction: "", -// Deprecated: "", -// Annotations: nil, -// Version: "", -// PersistentPreRun: nil, -// PersistentPreRunE: nil, -// PreRun: nil, -// PreRunE: nil, -// Run: func(cmd *cobra.Command, args []string) { -// // Global flags -// debugFlag, _ := cmd.Flags().GetBool("debugFlag") -// configFile, _ := cmd.Flags().GetString("config") -// noPrompt, _ := cmd.Flags().GetBool("no-prompt") -// profile, _ := cmd.Flags().GetString("profile") -// -// kfcUsername, _ := cmd.Flags().GetString("kfcUsername") -// kfcPassword, _ := cmd.Flags().GetString("kfcPassword") -// kfcDomain, _ := cmd.Flags().GetString("kfcDomain") -// -// -// -// debugModeEnabled := checkDebug(debugFlag) -// log.Println("Debug mode enabled: ", debugModeEnabled) -// -// templateType, _ := cmd.Flags().GetString("type") -// format, _ := cmd.Flags().GetString("format") -// outPath, _ := cmd.Flags().GetString("outpath") -// storeType, _ := cmd.Flags().GetStringSlice("store-type") -// containerName, _ := cmd.Flags().GetStringSlice("container-name") -// collection, _ := cmd.Flags().GetStringSlice("collection") -// subjectName, _ := cmd.Flags().GetStringSlice("cn") -// stID := -1 -// var storeData []api.GetCertificateStoreResponse -// var csvStoreData [][]string -// var csvCertData [][]string -// var rowLookup = make(map[string]bool) -// kfClient, cErr := initClient(false) -// if len(storeType) != 0 { -// for _, s := range storeType { -// if cErr != nil { -// log.Fatalf("[ERROR] creating client: %s", cErr) -// } -// var sType *api.CertificateStoreType -// var stErr error -// if s == "all" { -// sType = &api.CertificateStoreType{ -// Name: "", -// ShortName: "", -// Capability: "", -// StoreType: 0, -// ImportType: 0, -// LocalStore: false, -// SupportedOperations: nil, -// Properties: nil, -// EntryParameters: nil, -// PasswordOptions: nil, -// StorePathType: "", -// StorePathValue: "", -// PrivateKeyAllowed: "", -// JobProperties: nil, -// ServerRequired: false, -// PowerShell: false, -// BlueprintAllowed: false, -// CustomAliasAllowed: "", -// ServerRegistration: 0, -// InventoryEndpoint: "", -// InventoryJobType: "", -// ManagementJobType: "", -// DiscoveryJobType: "", -// EnrollmentJobType: "", -// } -// } else { -// // check if s is an int -// sInt, err := strconv.Atoi(s) -// if err == nil { -// sType, stErr = kfClient.GetCertificateStoreTypeById(sInt) -// } else { -// sType, stErr = kfClient.GetCertificateStoreTypeByName(s) -// } -// if stErr != nil { -// fmt.Printf("[ERROR] getting store type '%s'. %s\n", s, stErr) -// continue -// } -// stID = sType.StoreType // This is the template type ID -// } -// -// if stID >= 0 || s == "all" { -// log.Printf("[DEBUG] Store type ID: %d\n", stID) -// params := make(map[string]interface{}) -// stores, sErr := kfClient.ListCertificateStores(¶ms) -// if sErr != nil { -// fmt.Printf("[ERROR] getting certificate stores of type '%s': %s\n", s, sErr) -// log.Fatalf("[ERROR] getting certificate stores of type '%s': %s", s, sErr) -// } -// for _, store := range *stores { -// if store.CertStoreType == stID || s == "all" { -// storeData = append(storeData, store) -// if !rowLookup[store.Id] { -// lineData := []string{ -// //"StoreID", "StoreType", "StoreMachine", "StorePath", "ContainerId" -// store.Id, -// fmt.Sprintf("%s", sType.ShortName), -// store.ClientMachine, -// store.StorePath, -// fmt.Sprintf("%d", store.ContainerId), -// store.ContainerName, -// getCurrentTime(""), -// } -// csvStoreData = append(csvStoreData, lineData) -// rowLookup[store.Id] = true -// } -// } -// } -// } -// } -// fmt.Println("Done") -// } -// if len(containerName) != 0 { -// for _, c := range containerName { -// -// if cErr != nil { -// log.Fatalf("[ERROR] creating client: %s", cErr) -// } -// cStoresResp, scErr := kfClient.GetCertificateStoreByContainerID(c) -// if scErr != nil { -// fmt.Printf("[ERROR] getting store container: %s\n", scErr) -// } -// if cStoresResp != nil { -// for _, store := range *cStoresResp { -// sType, stErr := kfClient.GetCertificateStoreType(store.CertStoreType) -// if stErr != nil { -// fmt.Printf("[ERROR] getting store type: %s\n", stErr) -// continue -// } -// storeData = append(storeData, store) -// if !rowLookup[store.Id] { -// lineData := []string{ -// // "StoreID", "StoreType", "StoreMachine", "StorePath", "ContainerId" -// store.Id, -// sType.ShortName, -// store.ClientMachine, -// store.StorePath, -// fmt.Sprintf("%d", store.ContainerId), -// store.ContainerName, -// getCurrentTime(""), -// } -// csvStoreData = append(csvStoreData, lineData) -// rowLookup[store.Id] = true -// } -// } -// -// } -// } -// } -// if len(collection) != 0 { -// for _, c := range collection { -// if cErr != nil { -// fmt.Println("[ERROR] connecting to Keyfactor. Please check your configuration and try again.") -// log.Fatalf("[ERROR] creating client: %s", cErr) -// } -// q := make(map[string]string) -// q["collection"] = c -// certsResp, scErr := kfClient.ListCertificates(q) -// if scErr != nil { -// fmt.Printf("No certificates found in collection: %s\n", scErr) -// } -// if certsResp != nil { -// for _, cert := range certsResp { -// if !rowLookup[cert.Thumbprint] { -// lineData := []string{ -// // "Thumbprint", "SubjectName", "Issuer", "CertID", "Locations", "LastQueriedDate" -// cert.Thumbprint, -// cert.IssuedCN, -// cert.IssuerDN, -// fmt.Sprintf("%d", cert.Id), -// fmt.Sprintf("%v", cert.Locations), -// getCurrentTime(""), -// } -// csvCertData = append(csvCertData, lineData) -// rowLookup[cert.Thumbprint] = true -// } -// } -// -// } -// } -// } -// if len(subjectName) != 0 { -// for _, s := range subjectName { -// if cErr != nil { -// fmt.Println("[ERROR] connecting to Keyfactor. Please check your configuration and try again.") -// log.Fatalf("[ERROR] creating client: %s", cErr) -// } -// q := make(map[string]string) -// q["subject"] = s -// certsResp, scErr := kfClient.ListCertificates(q) -// if scErr != nil { -// fmt.Printf("No certificates found with CN: %s\n", scErr) -// } -// if certsResp != nil { -// for _, cert := range certsResp { -// if !rowLookup[cert.Thumbprint] { -// locationsFormatted := "" -// for _, loc := range cert.Locations { -// locationsFormatted += fmt.Sprintf("%s:%s\n", loc.StoreMachine, loc.StorePath) -// } -// lineData := []string{ -// // "Thumbprint", "SubjectName", "Issuer", "CertID", "Locations", "LastQueriedDate" -// cert.Thumbprint, -// cert.IssuedCN, -// cert.IssuerDN, -// fmt.Sprintf("%d", cert.Id), -// locationsFormatted, -// getCurrentTime(""), -// } -// csvCertData = append(csvCertData, lineData) -// rowLookup[cert.Thumbprint] = true -// } -// } -// -// } -// } -// } -// // Create CSV template file -// -// var filePath string -// if outPath != "" { -// filePath = outPath -// } else { -// filePath = fmt.Sprintf("%s_template.%s", templateType, format) -// } -// file, err := os.Create(filePath) -// if err != nil { -// fmt.Printf("[ERROR] creating file: %s", err) -// log.Fatal("Cannot create file", err) -// } -// -// switch format { -// case "csv": -// writer := csv.NewWriter(file) -// var data [][]string -// switch templateType { -// case "stores": -// data = append(data, StoreHeader) -// if len(csvStoreData) != 0 { -// data = append(data, csvStoreData...) -// } -// case "certs": -// data = append(data, CertHeader) -// if len(csvCertData) != 0 { -// data = append(data, csvCertData...) -// } -// case "actions": -// data = append(data, AuditHeader) -// } -// csvErr := writer.WriteAll(data) -// if csvErr != nil { -// fmt.Println(csvErr) -// } -// defer file.Close() -// -// case "json": -// writer := bufio.NewWriter(file) -// _, err := writer.WriteString("StoreID,StoreType,StoreMachine,StorePath") -// if err != nil { -// log.Fatal("Cannot write to file", err) -// } -// } -// fmt.Printf("Template file created at %s.\n", filePath) -// }, -// RunE: nil, -// PostRun: nil, -// PostRunE: nil, -// PersistentPostRun: nil, -// PersistentPostRunE: nil, -// FParseErrWhitelist: cobra.FParseErrWhitelist{}, -// CompletionOptions: cobra.CompletionOptions{}, -// TraverseChildren: false, -// Hidden: false, -// SilenceErrors: false, -// SilenceUsage: false, -// DisableFlagParsing: false, -// DisableAutoGenTag: false, -// DisableFlagsInUseLine: false, -// DisableSuggestions: false, -// SuggestionsMinimumDistance: 0, -// } -//) -// -//func init() { -// log.SetFlags(log.LstdFlags | log.Lshortfile) -// log.SetOutput(os.Stdout) -// var ( -// stores string -// addCerts string -// removeCerts string -// minCertsInStore int -// maxPrivateKeys int -// maxLeaves int -// tType = tTypeCerts -// outPath string -// outputFormat string -// inputFile string -// storeTypes []string -// containerNames []string -// collections []string -// subjectNames []string -// ) -// -// storesCmd.AddCommand(rotCmd) -// -// // Root of trust `audit` command -// rotCmd.AddCommand(rotAuditCmd) -// rotAuditCmd.Flags().StringVarP(&stores, "stores", "s", "", "CSV file containing cert stores to enroll into") -// rotAuditCmd.Flags().StringVarP( -// &addCerts, "add-certs", "a", "", -// "CSV file containing cert(s) to enroll into the defined cert stores", -// ) -// rotAuditCmd.Flags().StringVarP( -// &removeCerts, "remove-certs", "r", "", -// "CSV file containing cert(s) to remove from the defined cert stores", -// ) -// rotAuditCmd.Flags().IntVarP( -// &minCertsInStore, -// "min-certs", -// "m", -// -1, -// "The minimum number of certs that should be in a store to be considered a 'root' store. If set to `-1` then all stores will be considered.", -// ) -// rotAuditCmd.Flags().IntVarP( -// &maxPrivateKeys, -// "max-keys", -// "k", -// -1, -// "The max number of private keys that should be in a store to be considered a 'root' store. If set to `-1` then all stores will be considered.", -// ) -// rotAuditCmd.Flags().IntVarP( -// &maxLeaves, -// "max-leaf-certs", -// "l", -// -1, -// "The max number of non-root-certs that should be in a store to be considered a 'root' store. If set to `-1` then all stores will be considered.", -// ) -// rotAuditCmd.Flags().BoolP("dry-run", "d", false, "Dry run mode") -// rotAuditCmd.Flags().StringVarP( -// &outPath, "outpath", "o", "", -// "Path to write the audit report file to. If not specified, the file will be written to the current directory.", -// ) -// -// // Root of trust `reconcile` command -// rotCmd.AddCommand(rotReconcileCmd) -// rotReconcileCmd.Flags().StringVarP(&stores, "stores", "s", "", "CSV file containing cert stores to enroll into") -// rotReconcileCmd.Flags().StringVarP( -// &addCerts, "add-certs", "a", "", -// "CSV file containing cert(s) to enroll into the defined cert stores", -// ) -// rotReconcileCmd.Flags().StringVarP( -// &removeCerts, "remove-certs", "r", "", -// "CSV file containing cert(s) to remove from the defined cert stores", -// ) -// rotReconcileCmd.Flags().IntVarP( -// &minCertsInStore, -// "min-certs", -// "m", -// -1, -// "The minimum number of certs that should be in a store to be considered a 'root' store. If set to `-1` then all stores will be considered.", -// ) -// rotReconcileCmd.Flags().IntVarP( -// &maxPrivateKeys, -// "max-keys", -// "k", -// -1, -// "The max number of private keys that should be in a store to be considered a 'root' store. If set to `-1` then all stores will be considered.", -// ) -// rotReconcileCmd.Flags().IntVarP( -// &maxLeaves, -// "max-leaf-certs", -// "l", -// -1, -// "The max number of non-root-certs that should be in a store to be considered a 'root' store. If set to `-1` then all stores will be considered.", -// ) -// rotReconcileCmd.Flags().BoolP("dry-run", "d", false, "Dry run mode") -// rotReconcileCmd.Flags().BoolP("import-csv", "v", false, "Import an audit report file in CSV format.") -// rotReconcileCmd.Flags().StringVarP( -// &inputFile, "input-file", "i", reconcileDefaultFileName, -// "Path to a file generated by 'stores rot audit' command.", -// ) -// rotReconcileCmd.Flags().StringVarP( -// &outPath, "outpath", "o", "", -// "Path to write the audit report file to. If not specified, the file will be written to the current directory.", -// ) -// //rotReconcileCmd.MarkFlagsRequiredTogether("add-certs", "stores") -// //rotReconcileCmd.MarkFlagsRequiredTogether("remove-certs", "stores") -// rotReconcileCmd.MarkFlagsMutuallyExclusive("add-certs", "import-csv") -// rotReconcileCmd.MarkFlagsMutuallyExclusive("remove-certs", "import-csv") -// rotReconcileCmd.MarkFlagsMutuallyExclusive("stores", "import-csv") -// -// // Root of trust `generate` command -// rotCmd.AddCommand(rotGenStoreTemplateCmd) -// rotGenStoreTemplateCmd.Flags().StringVarP( -// &outPath, "outpath", "o", "", -// "Path to write the template file to. If not specified, the file will be written to the current directory.", -// ) -// rotGenStoreTemplateCmd.Flags().StringVarP( -// &outputFormat, "format", "f", "csv", -// "The type of template to generate. Only `csv` is supported at this time.", -// ) -// rotGenStoreTemplateCmd.Flags().Var( -// &tType, "type", -// `The type of template to generate. Only "certs|stores|actions" are supported at this time.`, -// ) -// rotGenStoreTemplateCmd.Flags().StringSliceVar( -// &storeTypes, -// "store-type", -// []string{}, -// "Multi value flag. Attempt to pre-populate the stores template with the certificate stores matching specified store types. If not specified, the template will be empty.", -// ) -// rotGenStoreTemplateCmd.Flags().StringSliceVar( -// &containerNames, -// "container-name", -// []string{}, -// "Multi value flag. Attempt to pre-populate the stores template with the certificate stores matching specified container types. If not specified, the template will be empty.", -// ) -// rotGenStoreTemplateCmd.Flags().StringSliceVar( -// &subjectNames, -// "cn", -// []string{}, -// "Subject name(s) to pre-populate the 'certs' template with. If not specified, the template will be empty. Does not work with SANs.", -// ) -// rotGenStoreTemplateCmd.Flags().StringSliceVar( -// &collections, -// "collection", -// []string{}, -// "Certificate collection name(s) to pre-populate the stores template with. If not specified, the template will be empty.", -// ) -// -// rotGenStoreTemplateCmd.RegisterFlagCompletionFunc("type", templateTypeCompletion) -// rotGenStoreTemplateCmd.MarkFlagRequired("type") -//} +import ( + "bufio" + "encoding/csv" + "encoding/json" + "errors" + "fmt" + "os" + "strconv" + "strings" + + "github.com/Keyfactor/keyfactor-go-client/v3/api" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +type templateType string +type StoreCSVEntry struct { + ID string `json:"id"` + Type string `json:"type"` + Machine string `json:"address"` + Path string `json:"path"` + Thumbprints map[string]bool `json:"thumbprints,omitempty"` + Serials map[string]bool `json:"serials,omitempty"` + Ids map[int]bool `json:"ids,omitempty"` +} +type ROTCert struct { + ID int `json:"id,omitempty"` + ThumbPrint string `json:"thumbprint,omitempty"` + CN string `json:"cn,omitempty"` + Locations []api.CertificateLocations `json:"locations,omitempty"` +} +type ROTAction struct { + StoreID string `json:"store_id,omitempty" mapstructure:"StoreID,omitempty"` + StoreType string `json:"store_type,omitempty" mapstructure:"StoreType,omitempty"` + StorePath string `json:"store_path,omitempty" mapstructure:"StorePath,omitempty"` + Thumbprint string `json:"thumbprint,omitempty" mapstructure:"Thumbprint,omitempty"` + Alias string `json:"alias,omitempty" mapstructure:"Alias,omitempty"` + CertID int `json:"cert_id,omitempty" mapstructure:"CertID,omitempty"` + AddCert bool `json:"add,omitempty" mapstructure:"AddCert,omitempty"` + RemoveCert bool `json:"remove,omitempty" mapstructure:"RemoveCert,omitempty"` +} + +const ( + tTypeCerts templateType = "certs" + reconcileDefaultFileName string = "rot_audit.csv" +) + +var ( + AuditHeader = []string{ + "Thumbprint", + "CertID", + "SubjectName", + "Issuer", + "StoreID", + "StoreType", + "Machine", + "Path", + "AddCert", + "RemoveCert", + "Deployed", + "AuditDate", + } + ReconciledAuditHeader = []string{ + "Thumbprint", + "CertID", + "SubjectName", + "Issuer", + "StoreID", + "StoreType", + "Machine", + "Path", + "AddCert", + "RemoveCert", + "Deployed", + "ReconciledDate", + } + StoreHeader = []string{ + "StoreID", + "StoreType", + "StoreMachine", + "StorePath", + "ContainerId", + "ContainerName", + "LastQueriedDate", + } + CertHeader = []string{"Thumbprint", "SubjectName", "Issuer", "CertID", "Locations", "LastQueriedDate"} +) + +// String is used both by fmt.Print and by Cobra in help text +func (e *templateType) String() string { + return string(*e) +} + +// Set must have pointer receiver, so it doesn't change the value of a copy +func (e *templateType) Set(v string) error { + switch v { + case "certs", "stores", "actions": + *e = templateType(v) + return nil + default: + return errors.New(`must be one of "certs", "stores", or "actions"`) + } +} + +// Type is only used in help text +func (e *templateType) Type() string { + return "string" +} + +func templateTypeCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{ + "certs\tGenerates template CSV for certificate input to be used w/ `--add-certs` or `--remove-certs`", + "stores\tGenerates template CSV for certificate input to be used w/ `--stores`", + "actions\tGenerates template CSV for certificate input to be used w/ `--actions`", + }, cobra.ShellCompDirectiveDefault +} + +func generateAuditReport( + addCerts map[string]string, + removeCerts map[string]string, + stores map[string]StoreCSVEntry, + outpath string, + kfClient *api.Client, +) ([][]string, map[string][]ROTAction, error) { + log.Debug().Msg("Entering generateAuditReport") + var ( + data [][]string + ) + + data = append(data, AuditHeader) + var csvFile *os.File + var fErr error + if outpath == "" { + csvFile, fErr = os.Create(reconcileDefaultFileName) + outpath = reconcileDefaultFileName + } else { + csvFile, fErr = os.Create(outpath) + } + + if fErr != nil { + log.Error(). + Str("file", csvFile.Name()). + Msg("Error creating audit file") + outputError(fErr, true, outputFormat) + + } + csvWriter := csv.NewWriter(csvFile) + cErr := csvWriter.Write(AuditHeader) + if cErr != nil { + log.Error(). + Str("file", csvFile.Name()). + Err(cErr). + Msg("Error writing audit header") + outputError(cErr, true, outputFormat) + } + actions := make(map[string][]ROTAction) + + for _, cert := range addCerts { + certLookupReq := api.GetCertificateContextArgs{ + IncludeMetadata: boolToPointer(true), + IncludeLocations: boolToPointer(true), + CollectionId: nil, + Thumbprint: cert, + Id: 0, + } + certLookup, err := kfClient.GetCertificateContext(&certLookupReq) + if err != nil { + fmt.Printf("[ERROR] looking up certificate %s: %s\n", cert, err) + log.Printf("[ERROR] looking up cert: %s\n%v", cert, err) + continue + } + certID := certLookup.Id + certIDStr := strconv.Itoa(certID) + for _, store := range stores { + if _, ok := store.Thumbprints[cert]; ok { + // Cert is already in the store do nothing + row := []string{ + cert, + certIDStr, + certLookup.IssuedDN, + certLookup.IssuerDN, + store.ID, + store.Type, + store.Machine, + store.Path, + "false", + "false", + "true", + getCurrentTime(""), + } + data = append(data, row) + wErr := csvWriter.Write(row) + if wErr != nil { + log.Error(). + Str("file", csvFile.Name()). + Err(wErr). + Msg("Error writing audit row") + outputError(wErr, false, outputFormat) + + } + } else { + // Cert is not deployed to this store and will need to be added + row := []string{ + cert, + certIDStr, + certLookup.IssuedDN, + certLookup.IssuerDN, + store.ID, + store.Type, + store.Machine, + store.Path, + "true", + "false", + "false", + getCurrentTime(""), + } + data = append(data, row) + wErr := csvWriter.Write(row) + if wErr != nil { + log.Error(). + Err(wErr). + Str("file", csvFile.Name()). + Msg("Error writing audit row") + outputError(wErr, false, outputFormat) + + } + actions[cert] = append( + actions[cert], ROTAction{ + Thumbprint: cert, + //TODO: add Alias + CertID: certID, + StoreID: store.ID, + StoreType: store.Type, + StorePath: store.Path, + AddCert: true, + RemoveCert: false, + }, + ) + } + } + } + for _, cert := range removeCerts { + certLookupReq := api.GetCertificateContextArgs{ + IncludeMetadata: boolToPointer(true), + IncludeLocations: boolToPointer(true), + CollectionId: nil, + Thumbprint: cert, + Id: 0, + } + certLookup, err := kfClient.GetCertificateContext(&certLookupReq) + if err != nil { + log.Printf("[ERROR] looking up cert: %s", err) + continue + } + certID := certLookup.Id + certIDStr := strconv.Itoa(certID) + for _, store := range stores { + if _, ok := store.Thumbprints[cert]; ok { + // Cert is deployed to this store and will need to be removed + row := []string{ + cert, + certIDStr, + certLookup.IssuedDN, + certLookup.IssuerDN, + store.ID, + store.Type, + store.Machine, + store.Path, + "false", + "true", + "true", + getCurrentTime(""), + } + data = append(data, row) + wErr := csvWriter.Write(row) + if wErr != nil { + fmt.Printf("%s", wErr) + log.Printf("[ERROR] writing row to CSV: %s", wErr) + } + actions[cert] = append( + actions[cert], ROTAction{ + Thumbprint: cert, + CertID: certID, + StoreID: store.ID, + StoreType: store.Type, + StorePath: store.Path, + AddCert: false, + RemoveCert: true, + }, + ) + } else { + // Cert is not deployed to this store do nothing + row := []string{ + cert, + certIDStr, + certLookup.IssuedDN, + certLookup.IssuerDN, + store.ID, + store.Type, + store.Machine, + store.Path, + "false", + "false", + "false", + getCurrentTime(""), + } + data = append(data, row) + wErr := csvWriter.Write(row) + if wErr != nil { + fmt.Printf("%s", wErr) + log.Printf("[ERROR] writing row to CSV: %s", wErr) + } + } + } + } + csvWriter.Flush() + ioErr := csvFile.Close() + if ioErr != nil { + fmt.Println(ioErr) + log.Printf("[ERROR] closing audit file: %s", ioErr) + } + fmt.Printf("Audit report written to %s\n", outpath) + return data, actions, nil +} + +func reconcileRoots(actions map[string][]ROTAction, kfClient *api.Client, reportFile string, dryRun bool) error { + log.Debug().Msg("entered reconcileRoots") + if len(actions) == 0 { + log.Info().Msg("No actions to take, roots are up-to-date.") + return nil + } + rFileName := fmt.Sprintf("%s_reconciled.csv", strings.Split(reportFile, ".csv")[0]) + csvFile, fErr := os.Create(rFileName) + if fErr != nil { + log.Error(). + Err(fErr). + Str("file", rFileName). + Msg("Error creating audit file") + outputError(fErr, true, outputFormat) + return fErr + } + defer csvFile.Close() + + csvWriter := csv.NewWriter(csvFile) + cErr := csvWriter.Write(ReconciledAuditHeader) + if cErr != nil { + log.Debug(). + Str("file", csvFile.Name()). + Err(cErr). + Msg("Error writing audit header") + outputError(cErr, true, outputFormat) + return cErr + } + for thumbprint, action := range actions { + log.Debug(). + Str("thumbprint", thumbprint). + Interface("action", action). + Msg("Processing thumbprint") + for _, a := range action { + if a.AddCert { + log.Info(). + Str("thumbprint", thumbprint). + Str("store", a.StoreID). + Str("storePath", a.StorePath). + Msg("Adding cert to store") + if !dryRun { + log.Debug(). + Msg("Not a dry run") + + cStore := api.CertificateStore{ + CertificateStoreId: a.StoreID, + Overwrite: true, + Alias: a.Thumbprint, //TODO: Support non-thumbprint alias + //Alias: "", + } + + var stores []api.CertificateStore + stores = append(stores, cStore) + schedule := &api.InventorySchedule{ + Immediate: boolToPointer(true), + } + addReq := api.AddCertificateToStore{ + CertificateId: a.CertID, + CertificateStores: &stores, + InventorySchedule: schedule, + } + log.Debug(). + Str("thumbprint", thumbprint). + Str("store", a.StoreID). + Msg("Adding cert to store") + + addReqJSON, _ := json.Marshal(addReq) + + log.Debug(). + Str("addReqJSON", string(addReqJSON)). + Msg("Request payload") + _, err := kfClient.AddCertificateToStores(&addReq) + if err != nil { + log.Error(). + Err(err). + Str("thumbprint", thumbprint). + Str("store", a.StoreID). + Str("storePath", a.StorePath). + Msg("Error adding cert to store") + outputError(err, true, outputFormat) + continue + } + } else { + log.Info(). + Str("thumbprint", thumbprint). + Str("store", a.StoreID). + Str("storePath", a.StorePath). + Msg("This is a dry run, would have added cert to store") + } + } else if a.RemoveCert { + if !dryRun { + log.Info(). + Str("thumbprint", thumbprint). + Str("store", a.StoreID). + Str("storePath", a.StorePath). + Msg("Removing cert from store") + + cStore := api.CertificateStore{ + CertificateStoreId: a.StoreID, + Alias: a.Thumbprint, + } + var stores []api.CertificateStore + stores = append(stores, cStore) + schedule := &api.InventorySchedule{ + Immediate: boolToPointer(true), + } + removeReq := api.RemoveCertificateFromStore{ + CertificateId: a.CertID, + CertificateStores: &stores, + InventorySchedule: schedule, + } + _, err := kfClient.RemoveCertificateFromStores(&removeReq) + if err != nil { + fmt.Printf( + "[ERROR] removing cert %s (ID: %d) from store %s (%s): %s\n", + a.Thumbprint, + a.CertID, + a.StoreID, + a.StorePath, + err, + ) + log.Error(). + Err(err). + Str("thumbprint", thumbprint). + Str("store", a.StoreID). + Str("storePath", a.StorePath). + Msg("Error removing cert from store") + } + } else { + log.Info(). + Str("thumbprint", thumbprint). + Str("store", a.StoreID). + Str("storePath", a.StorePath). + Msg("This is a dry run, would have removed cert from store") + } + } + } + } + log.Debug().Msg("exiting reconcileRoots") + return nil +} + +func readCertsFile(certsFilePath string, kfclient *api.Client) (map[string]string, error) { + // Read in the cert CSV + csvFile, _ := os.Open(certsFilePath) + reader := csv.NewReader(bufio.NewReader(csvFile)) + certEntries, _ := reader.ReadAll() + var certs = make(map[string]string) + for _, entry := range certEntries { + switch entry[0] { + case "CertID", "thumbprint", "id", "CertId", "Thumbprint": + continue // Skip header + } + certs[entry[0]] = entry[0] + } + return certs, nil +} + +func isRootStore( + st *api.GetCertificateStoreResponse, + invs *[]api.CertStoreInventory, + minCerts int, + maxKeys int, + maxLeaf int, +) bool { + leafCount := 0 + keyCount := 0 + certCount := 0 + + log.Debug(). + Int("minCerts", minCerts). + Int("maxKeys", maxKeys). + Int("maxLeaf", maxLeaf). + Msg(fmt.Sprintf(DebugFuncExit, "isRootStore")) + + if invs == nil || len(*invs) == 0 { + nullInvErr := fmt.Errorf("nil inventory response from Keyfactor Command for store '%s'", st.Id) + log.Error().Err(nullInvErr).Str("store", st.Id).Msg("nil or empty inventory returned by Keyfactor Command") + return false + } else if st == nil { + nullStoreErr := fmt.Errorf("nil store response from Keyfactor Command for store '%s'", st.Id) + log.Error().Err(nullStoreErr).Str("store", st.Id).Msg("nil or empty store returned by Keyfactor Command") + return false + } + + for _, inv := range *invs { + certCount += len(inv.Certificates) + log.Debug(). + Int("certCount", certCount). + Str("name", inv.Name). + Msg("processing inventory") + + for _, cert := range inv.Certificates { + if cert.IssuedDN != cert.IssuerDN { + log.Debug().Str("dn", cert.IssuedDN).Msg("is a leaf cert") + leafCount++ + } else { + log.Debug().Str("dn", cert.IssuedDN).Msg("is a root cert") + } + + //TODO: Do we need to look up if a cert has a private key? If so how does one know the private key isdeployed to the store? + //if inv.Parameters["PrivateKeyEntry"] == "Yes" { + // keyCount++ + //} + } + } + if certCount < minCerts && minCerts >= 0 { + log.Debug(). + Str("store", st.Id). + Int("certCount", certCount). + Int("minCerts", minCerts). + Msg("store has too few certs") + + return false + } + if leafCount > maxLeaf && maxLeaf >= 0 { + log.Debug(). + Str("store", st.Id). + Int("certCount", certCount). + Int("minCerts", minCerts). + Msg("store has too many leaf certs") + + return false + } + + if keyCount > maxKeys && maxKeys >= 0 { + log.Debug(). + Str("store", st.Id). + Int("certCount", certCount). + Int("minCerts", minCerts). + Msg("store has too many keys") + return false + } + + log.Debug(). + Str("store", st.Id). + Int("certCount", certCount). + Int("minCerts", minCerts). + Msg("store is a root store") + + log.Debug().Msg(fmt.Sprintf(DebugFuncExit, "isRootStore")) + return true +} + +var ( + rotCmd = &cobra.Command{ + Use: "rot", + Short: "Root of trust utility", + Long: `Root of trust allows you to manage your trusted roots using Keyfactor certificate stores. +For example if you wish to add a list of "root" certs to a list of certificate stores you would simply generate and fill +out the template CSV file. These template files can be generated with the following commands: +kfutil stores rot generate-template --type certs +kfutil stores rot generate-template --type stores +Once those files are filled out you can use the following command to add the certs to the stores: +kfutil stores rot audit --certs-file --stores-file +Will generate a CSV report file 'rot_audit.csv' of what actions will be taken. If those actions are correct you can run +the following command to actually perform the actions: +kfutil stores rot reconcile --certs-file --stores-file +OR if you want to use the audit report file generated you can run this command: +kfutil stores rot reconcile --import-csv +`, + } + rotAuditCmd = &cobra.Command{ + Use: "audit", + Aliases: nil, + SuggestFor: nil, + Short: "Audit generates a CSV report of what actions will be taken based on input CSV files.", + Long: `Root of Trust Audit: Will read and parse inputs to generate a report of certs that need to be added or removed from the "root of trust" stores.`, + Example: "", + ValidArgs: nil, + ValidArgsFunction: nil, + Args: nil, + ArgAliases: nil, + BashCompletionFunction: "", + Deprecated: "", + Annotations: nil, + Version: "", + PersistentPreRun: nil, + PersistentPreRunE: nil, + PreRun: nil, + PreRunE: nil, + RunE: func(cmd *cobra.Command, args []string) error { + // Global flags + cmd.SilenceUsage = true + // expEnabled checks + isExperimental := false + debugErr := warnExperimentalFeature(expEnabled, isExperimental) + if debugErr != nil { + return debugErr + } + //stdlog.SetOutput(io.Discard) + informDebug(debugFlag) + + var lookupFailures []string + // Authenticate + kfClient, cErr := initClient(false) + if cErr != nil { + log.Error().Err(cErr).Msg("unable to authenticate") + return cErr + } + + // Local flags + storesFile, _ := cmd.Flags().GetString("stores") + addRootsFile, _ := cmd.Flags().GetString("add-certs") + removeRootsFile, _ := cmd.Flags().GetString("remove-certs") + minCerts, _ := cmd.Flags().GetInt("min-certs") + maxLeaves, _ := cmd.Flags().GetInt("max-leaf-certs") + maxKeys, _ := cmd.Flags().GetInt("max-keys") + dryRun, _ := cmd.Flags().GetBool("dry-run") + outpath, _ := cmd.Flags().GetString("outpath") + // Read in the stores CSV + log.Debug(). + Str("storesFile", storesFile). + Str("addRootsFile", addRootsFile). + Str("removeRootsFile", removeRootsFile). + Bool("dryRun", dryRun). + Int("minCerts", minCerts). + Int("maxLeaves", maxLeaves). + Int("maxKeys", maxKeys). + Str("outpath", outpath). + Msg("flags") + + // Read in the stores CSV + csvFile, _ := os.Open(storesFile) + reader := csv.NewReader(bufio.NewReader(csvFile)) + storeEntries, _ := reader.ReadAll() + var stores = make(map[string]StoreCSVEntry) + validHeader := false + for _, entry := range storeEntries { + if strings.EqualFold(strings.Join(entry, ","), strings.Join(StoreHeader, ",")) { + validHeader = true + continue // Skip header + } + if !validHeader { + fmt.Printf("[ERROR] Invalid header in stores file. Expected: %s", strings.Join(StoreHeader, ",")) + log.Error(). + Str("expectedHeader", strings.Join(StoreHeader, ",")). + Msg("Invalid header in stores file") + } + apiResp, err := kfClient.GetCertificateStoreByID(entry[0]) + if err != nil { + log.Error(). + Err(err). + Str("store", entry[0]). + Msg("Error getting store from Keyfactor Command") + _ = append(lookupFailures, strings.Join(entry, ",")) + continue + } + + inventory, invErr := kfClient.GetCertStoreInventory(entry[0]) + if invErr != nil { + log.Error().Err(invErr).Str("store", entry[0]).Msg("Error getting store inventory") + outputError(invErr, true, outputFormat) + return invErr + } + + if inventory == nil { + invalidRespErr := fmt.Errorf( + "invalid inventory response from Keyfactor Command for store '%s'", + entry[0], + ) + log.Error().Err(invalidRespErr).Str("store", entry[0]).Msg("invalid response") + outputError(invalidRespErr, true, outputFormat) + return invalidRespErr + } + //var inventory []api.CertStoreInventory //TODO: Update this to use SDK inventory + + if !isRootStore(apiResp, inventory, minCerts, maxLeaves, maxKeys) { + outputResult(fmt.Sprintf("Store %s is not a root store, skipping.\n", entry[0]), outputFormat) + log.Error(). + Str("store", entry[0]). + Str("id", apiResp.Id). + Int("type", apiResp.CertStoreType). + Str("machine", apiResp.ClientMachine). + Str("path", apiResp.StorePath). + Msg("Store is not a root store") + continue + } else { + outputResult(fmt.Sprintf("Store %s is a root store.\n", entry[0]), outputFormat) + log.Info(). + Str("store", entry[0]). + Str("id", apiResp.Id). + Int("type", apiResp.CertStoreType). + Str("machine", apiResp.ClientMachine). + Str("path", apiResp.StorePath). + Msg("Is a root store") + } + + stores[entry[0]] = StoreCSVEntry{ + ID: entry[0], + Type: entry[1], + Machine: entry[2], + Path: entry[3], + Thumbprints: make(map[string]bool), + Serials: make(map[string]bool), + Ids: make(map[int]bool), + } + + log.Debug().Str("store", entry[0]). + Str("id", apiResp.Id). + Int("type", apiResp.CertStoreType). + Str("machine", apiResp.ClientMachine). + Str("path", apiResp.StorePath). + Msg("Iterating store inventory") + for _, cert := range *inventory { + thumb := cert.Thumbprints + + log.Debug().Str("store", entry[0]). + Str("id", apiResp.Id). + Int("type", apiResp.CertStoreType). + Str("machine", apiResp.ClientMachine). + Str("path", apiResp.StorePath). + Msg("Iterating inventory thumbprints") + for _, v := range thumb { + stores[entry[0]].Thumbprints[v] = true + } + log.Debug().Str("store", entry[0]). + Str("id", apiResp.Id). + Int("type", apiResp.CertStoreType). + Str("machine", apiResp.ClientMachine). + Str("path", apiResp.StorePath). + Msg("Iterating inventory serial numbers") + for _, v := range cert.Serials { + stores[entry[0]].Serials[v] = true + } + + log.Debug().Str("store", entry[0]). + Str("id", apiResp.Id). + Int("type", apiResp.CertStoreType). + Str("machine", apiResp.ClientMachine). + Str("path", apiResp.StorePath). + Msg("Iterating certificate IDs") + for _, v := range cert.Ids { + stores[entry[0]].Ids[v] = true + } + } + + } + + // Read in the add addCerts CSV + var certsToAdd = make(map[string]string) + if addRootsFile != "" { + var rcfErr error + + log.Debug(). + Str("addRootsFile", addRootsFile). + Msg("Reading addCerts file") + certsToAdd, rcfErr = readCertsFile(addRootsFile, kfClient) + if rcfErr != nil { + outputError(rcfErr, true, outputFormat) + log.Error(). + Err(rcfErr). + Msg("reading addCerts file") + return rcfErr + } + addCertsJSON, _ := json.Marshal(certsToAdd) + log.Debug().Str("addCerts", string(addCertsJSON)).Msg("addCerts") + } else { + log.Debug().Msg("No addCerts file specified") + } + + // Read in the remove removeCerts CSV + var certsToRemove = make(map[string]string) + if removeRootsFile != "" { + var rcfErr error + certsToRemove, rcfErr = readCertsFile(removeRootsFile, kfClient) + if rcfErr != nil { + outputError(rcfErr, true, outputFormat) + log.Error().Err(rcfErr).Msg("failed reading removeCerts file") + return rcfErr + } + removeCertsJSON, _ := json.Marshal(certsToRemove) + log.Debug().Str("removeCerts", string(removeCertsJSON)).Msg("removeCerts") + } else { + log.Debug().Msg("No removeCerts file specified") + } + + log.Debug(). + Str("outpath", outpath). + Interface("certsToAdd", certsToAdd). + Interface("certsToRemove", certsToRemove). + Msg("Generating audit report") + _, _, gErr := generateAuditReport(certsToAdd, certsToRemove, stores, outpath, kfClient) + if gErr != nil { + outputError(gErr, true, outputFormat) + log.Error().Err(gErr).Msg("failed generating audit report") + return gErr + } + + return nil + }, + Run: nil, + PostRun: nil, + PostRunE: nil, + PersistentPostRun: nil, + PersistentPostRunE: nil, + FParseErrWhitelist: cobra.FParseErrWhitelist{}, + CompletionOptions: cobra.CompletionOptions{}, + TraverseChildren: false, + Hidden: false, + SilenceErrors: false, + SilenceUsage: false, + DisableFlagParsing: false, + DisableAutoGenTag: false, + DisableFlagsInUseLine: false, + DisableSuggestions: false, + SuggestionsMinimumDistance: 0, + } + rotReconcileCmd = &cobra.Command{ + Use: "reconcile", + Aliases: nil, + SuggestFor: nil, + Short: "Reconcile either takes in or will generate an audit report and then add/remove certs as needed.", + Long: `Root of Trust (rot): Will parse either a combination of CSV files that define certs to +add and/or certs to remove with a CSV of certificate stores or an audit CSV file. If an audit CSV file is provided, the +add and remove actions defined in the audit file will be immediately executed. If a combination of CSV files are provided, +the utility will first generate an audit report and then execute the add/remove actions defined in the audit report.`, + Example: "", + ValidArgs: nil, + ValidArgsFunction: nil, + Args: nil, + ArgAliases: nil, + BashCompletionFunction: "", + Deprecated: "", + Annotations: nil, + Version: "", + PersistentPreRun: nil, + PersistentPreRunE: nil, + PreRun: nil, + PreRunE: nil, + RunE: func(cmd *cobra.Command, args []string) error { + // Global flags + cmd.SilenceUsage = true + // expEnabled checks + isExperimental := false + debugErr := warnExperimentalFeature(expEnabled, isExperimental) + if debugErr != nil { + return debugErr + } + //stdlog.SetOutput(io.Discard) + informDebug(debugFlag) + + var lookupFailures []string + // Authenticate + kfClient, cErr := initClient(false) + if cErr != nil { + log.Error().Err(cErr).Msg("unable to authenticate") + return cErr + } + + // Local flags + storesFile, _ := cmd.Flags().GetString("stores") + addRootsFile, _ := cmd.Flags().GetString("add-certs") + removeRootsFile, _ := cmd.Flags().GetString("remove-certs") + minCerts, _ := cmd.Flags().GetInt("min-certs") + maxLeaves, _ := cmd.Flags().GetInt("max-leaf-certs") + maxKeys, _ := cmd.Flags().GetInt("max-keys") + dryRun, _ := cmd.Flags().GetBool("dry-run") + outpath, _ := cmd.Flags().GetString("outpath") + isCSV, _ := cmd.Flags().GetBool("import-csv") + reportFile, _ := cmd.Flags().GetString("input-file") + // Read in the stores CSV + log.Debug(). + Str("storesFile", storesFile). + Str("addRootsFile", addRootsFile). + Str("removeRootsFile", removeRootsFile). + Bool("dryRun", dryRun). + Int("minCerts", minCerts). + Int("maxLeaves", maxLeaves). + Int("maxKeys", maxKeys). + Str("outpath", outpath). + Bool("isCSV", isCSV). + Str("reportFile", reportFile). + Msg("flags") + + // Parse existing audit report + if isCSV && reportFile != "" { + log.Debug(). + Str("reportFile", reportFile). + Msg("reading CSV audit report") + // Read in the CSV + csvFile, err := os.Open(reportFile) + if err != nil { + outputError(err, true, outputFormat) + log.Error(). + Err(err). + Str("reportFile", reportFile). + Msg("failed opening CSV file") + return err + } + validHeader := false + + log.Debug(). + Str("reportFile", reportFile). + Msg("parsing CSV audit report") + aCSV := csv.NewReader(csvFile) + aCSV.FieldsPerRecord = -1 + inFile, cErr := aCSV.ReadAll() + if cErr != nil { + log.Error().Err(cErr).Str("reportFile", reportFile). + Msg("failed parsing CSV file") + } + actions := make(map[string][]ROTAction) + fieldMap := make(map[int]string) + + log.Debug(). + Str("reportFile", reportFile). + Msg("mapping CSV header") + for i, field := range AuditHeader { + fieldMap[i] = field + } + for ri, row := range inFile { + if strings.EqualFold(strings.Join(row, ","), strings.Join(AuditHeader, ",")) { + validHeader = true + log.Debug(). + Str("reportFile", reportFile). + Msg("skipping header") + continue + } + if !validHeader { + log.Error(). + Str("reportFile", reportFile). + Str("expectedHeader", strings.Join(AuditHeader, ",")). + Str("inputFileHeader", strings.Join(row, ",")). + Msg("invalid header") + log.Debug(). + Int("row", ri). + Str("reportFile", reportFile). + Msg("searching for valid header") + } + + action := make(map[string]interface{}) + + log.Debug().Int("row", ri).Msg("processing row data") + for i, field := range row { + fieldInt, iErr := strconv.Atoi(field) + if iErr != nil { + log.Debug().Int("row", ri).Str("field", field).Msg("field is not an integer") + action[fieldMap[i]] = field + } else { + log.Debug().Int("row", ri).Str("field", field).Msg("field is an integer") + action[fieldMap[i]] = fieldInt + } + } + + addCertStr, aOk := action["AddCert"].(string) + if !aOk { + addCertStr = "" + } + addCert, acErr := strconv.ParseBool(addCertStr) + if acErr != nil { + addCert = false + } + + log.Debug(). + Str("reportFile", reportFile). + Int("row", ri). + Msg("parsing \"RemoveCert\" col") + removeCertStr, rOk := action["RemoveCert"].(string) + if !rOk { + removeCertStr = "" + } + removeCert, rcErr := strconv.ParseBool(removeCertStr) + if rcErr != nil { + removeCert = false + } + + log.Debug(). + Str("reportFile", reportFile). + Int("row", ri). + Msg("parsing \"StoreType\" col") + sType, sOk := action["StoreType"].(string) + if !sOk { + sType = "" + } + + log.Debug(). + Str("reportFile", reportFile). + Int("row", ri). + Msg("parsing \"Path\" col") + sPath, pOk := action["Path"].(string) + if !pOk { + sPath = "" + } + + log.Debug(). + Str("reportFile", reportFile). + Int("row", ri). + Msg("parsing \"Thumbprint\" col") + tp, tpOk := action["Thumbprint"].(string) + if !tpOk { + tp = "" + } + + log.Debug(). + Str("reportFile", reportFile). + Int("row", ri). + Msg("parsing \"Alias\" col") + alias, aliasOk := action["Alias"].(string) + if !aliasOk { + alias = "" + } + log.Debug().Str("alias", alias).Send() + + log.Debug(). + Str("reportFile", reportFile). + Int("row", ri). + Msg("parsing \"CertID\" col") + cid, cidOk := action["CertID"].(int) + if !cidOk { + cid = -1 + } + + if !tpOk && !cidOk { + outputError( + fmt.Errorf( + fmt.Sprintf( + "Missing Thumbprint or CertID for row '%d' in report file '%s'", + ri, + reportFile, + ), + ), false, outputFormat, + ) + log.Error(). + Str("reportFile", reportFile). + Int("row", ri).Msg("missing thumbprint or certID for row") + continue + } + + log.Debug(). + Str("reportFile", reportFile). + Int("row", ri). + Msg("parsing \"StoreID\" col") + sId, sIdOk := action["StoreID"].(string) + if !sIdOk { + sIdErr := fmt.Errorf("missing 'StoreID' for row '%d' in report file '%s'", ri, reportFile) + outputError(sIdErr, true, outputFormat) + log.Error().Err(sIdErr).Str("reportFile", reportFile). + Int("row", ri).Msg("invalid action") + continue + } + + if cid == -1 && tp != "" { + log.Debug().Msg("creating lookup by thumbprint request") + certLookupReq := api.GetCertificateContextArgs{ + IncludeMetadata: boolToPointer(true), + IncludeLocations: boolToPointer(true), + CollectionId: nil, + Thumbprint: tp, + Id: 0, + } + certLookup, certLookupErr := kfClient.GetCertificateContext(&certLookupReq) + if certLookupErr != nil { + outputError(certLookupErr, true, outputFormat) + log.Error().Err(certLookupErr).Str("thumbprint", tp).Msg("failed looking up cert") + continue + } + cid = certLookup.Id + } + + log.Debug(). + Int("row", ri). + Str("reportFile", reportFile). + Str("StoreID", sId). + Str("StoreType", sType). + Str("Path", sPath). + Str("Thumbprint", tp). + Str("CertID", fmt.Sprintf("%d", cid)). + Bool("AddCert", addCert). + Bool("RemoveCert", removeCert). + Msg("creating ROTAction") + a := ROTAction{ + StoreID: sId, + StoreType: sType, + StorePath: sPath, + Thumbprint: tp, + CertID: cid, + AddCert: addCert, + RemoveCert: removeCert, + } + + actions[a.Thumbprint] = append(actions[a.Thumbprint], a) + } + if len(actions) == 0 { + outputResult( + "No reconciliation actions to take, root stores are up-to-date. Exiting.", + outputFormat, + ) + log.Info(). + Str("reportFile", reportFile). + Msg("No reconciliation actions to take, root stores are up-to-date. Exiting.") + return nil + } + + log.Debug().Msg("reconciling roots") + rErr := reconcileRoots(actions, kfClient, reportFile, dryRun) + if rErr != nil { + log.Error(). + Err(rErr). + Str("reportFile", reportFile). + Msg("failed reconciling roots") + return rErr + } + defer csvFile.Close() + + jobStatusURL := fmt.Sprintf( + "https://%s/KeyfactorPortal/AgentJobStatus/Index", + kfClient.AuthClient.GetServerConfig().Host, + ) + + log.Info().Str("reportFile", reportFile). + Str("jobStatusURL", jobStatusURL). + Msg("reconciliation complete") + outputResult("Reconciliation complete. Job status URL: "+jobStatusURL, outputFormat) + return nil + } else { + // Read in the stores CSV + log.Debug(). + Str("storesFile", storesFile). + Msg("opening stores CSV file") + csvFile, csvErr := os.Open(storesFile) + if csvErr != nil { + outputError(csvErr, true, outputFormat) + log.Error(). + Err(csvErr). + Str("storesFile", storesFile). + Msg("failed opening CSV file") + return csvErr + } + + defer csvFile.Close() + + log.Debug(). + Str("storesFile", storesFile). + Msg("reading stores CSV file data") + reader := csv.NewReader(bufio.NewReader(csvFile)) + storeEntries, stErr := reader.ReadAll() + if stErr != nil { + log.Error().Err(stErr).Str("storesFile", storesFile). + Msg("failed reading CSV file") + return stErr + } + if len(storeEntries) == 0 { + noStoresErr := fmt.Errorf("no stores found in CSV file") + outputError(noStoresErr, true, outputFormat) + log.Error(). + Str("storesFile", storesFile). + Err(noStoresErr). + Send() + return fmt.Errorf("no stores found in CSV file") + } + + log.Debug().Str("storesFile", storesFile). + Int("storeEntries", len(storeEntries)). + Msg("processing stores CSV file data") + var stores = make(map[string]StoreCSVEntry) + for i, entry := range storeEntries { + if entry[0] == "StoreID" || entry[0] == "StoreId" || i == 0 { + log.Debug().Str("storesFile", storesFile).Msg("skipping file header") + continue // Skip header + } + + log.Debug().Str("storesFile", storesFile). + Str("StoreID", entry[0]). + Msg("calling GetCertificateStoreByID for store") + apiResp, err := kfClient.GetCertificateStoreByID(entry[0]) + if err != nil { + log.Error(). + Err(err).Str("StoreID", entry[0]). + Msg("unable to get certificate by ID") + lookupFailures = append(lookupFailures, entry[0]) + continue + } + //inventory, invErr := kfClient.GetCertStoreInventoryV1(entry[0]) + inventory, invErr := kfClient.GetCertStoreInventory(entry[0]) + if invErr != nil { + outputError(invErr, true, outputFormat) + log.Error(). + Err(invErr). + Str("storesFile", storesFile). + Str("StoreID", entry[0]). + Msg("unable to get inventory") + continue + } + + if !isRootStore(apiResp, inventory, minCerts, maxLeaves, maxKeys) { + log.Info().Str("storesFile", storesFile). + Str("StoreID", entry[0]). + Int("minCerts", minCerts). + Int("maxLeaves", maxLeaves). + Int("maxKeys", maxKeys). + Msg("is not a root store") + //lookupFailures = append(lookupFailures, entry[0]) + continue + } else { + log.Info().Str("storesFile", storesFile). + Str("StoreID", entry[0]). + Msg("is a root store") + } + + stores[entry[0]] = StoreCSVEntry{ + ID: entry[0], + Type: entry[1], + Machine: entry[2], + Path: entry[3], + Thumbprints: make(map[string]bool), + Serials: make(map[string]bool), + Ids: make(map[int]bool), + } + for _, cert := range *inventory { + thumb := cert.Thumbprints + for _, v := range thumb { + stores[entry[0]].Thumbprints[v] = true + } + for _, v := range cert.Serials { + stores[entry[0]].Serials[v] = true + } + for _, v := range cert.Ids { + stores[entry[0]].Ids[v] = true + } + } + + } + if len(lookupFailures) > 0 { + failedErr := fmt.Errorf( + "the following stores were not found: %s", + strings.Join(lookupFailures, ","), + ) + outputError(failedErr, true, outputFormat) + log.Error(). + Err(failedErr). + Strs("lookupFailures", lookupFailures). + Msg("failed to lookup stores") + return failedErr + } + if len(stores) == 0 { + noStoresErr := fmt.Errorf("no stores found in CSV file %s", storesFile) + outputError(noStoresErr, true, outputFormat) + return noStoresErr + } + // Read in the add addCerts CSV + var certsToAdd = make(map[string]string) + if addRootsFile != "" { + log.Debug().Str("addRootsFile", addRootsFile).Msg("calling readCerts") + certsToAdd, _ = readCertsFile(addRootsFile, kfClient) + //TODO: Handle error here? + } else { + log.Info().Str("addRootsFile", addRootsFile).Msg("no certs to add to trust stores") + } + + // Read in the remove removeCerts CSV + var certsToRemove = make(map[string]string) + if removeRootsFile != "" { + log.Debug().Str("removeRootsFile", removeRootsFile).Msg("calling readCerts") + certsToRemove, _ = readCertsFile(removeRootsFile, kfClient) + //TODO: Handle error here? + } else { + log.Info().Str("removeRootsFile", removeRootsFile).Msg("no certs to remove from trust stores") + } + + log.Debug(). + Str("storesFile", storesFile). + Str("addRootsFile", addRootsFile). + Interface("certsToAdd", certsToAdd). + Str("removeRootsFile", removeRootsFile). + Interface("certsToRemove", certsToRemove). + Str("outpath", outpath). + Msg("calling generateAuditReport") + _, actions, err := generateAuditReport(certsToAdd, certsToRemove, stores, outpath, kfClient) + if err != nil { + outputError(err, true, outputFormat) + log.Error().Err(err). + Str("storesFile", storesFile). + Str("addRootsFile", addRootsFile). + Str("removeRootsFile", removeRootsFile). + Str("outpath", outpath). + Msg("failed to generate audit report") + return err + } + if len(actions) == 0 { + log.Info().Str("storesFile", storesFile). + Str("addRootsFile", addRootsFile). + Str("removeRootsFile", removeRootsFile). + Str("outpath", outpath). + Msg("no reconciliation actions to take, root stores are up-to-date") + outputResult( + "No reconciliation actions to take, root stores are up-to-date. Exiting.", + outputFormat, + ) + return nil + } + + log.Debug().Str("reportFile", reportFile).Msg("calling reconcileRoots") + rErr := reconcileRoots(actions, kfClient, reportFile, dryRun) + if rErr != nil { + outputError(rErr, true, outputFormat) + log.Error(). + Err(rErr). + Str("reportFile", reportFile).Msg("failed reconciling roots") + return rErr + } + if lookupFailures != nil { + lookupErr := fmt.Errorf( + "the following stores could not be found: %s", + strings.Join(lookupFailures, ","), + ) + outputError(lookupErr, true, outputFormat) + log.Error(). + Err(lookupErr). + Strs("lookupFailures", lookupFailures). + Msg("failed to lookup stores") + return lookupErr + } + orchsURL := fmt.Sprintf( + "https://%s/KeyfactorPortal/AgentJobStatus/Index", + kfClient.AuthClient.GetServerConfig().Host, + ) + log.Info(). + Str("reportFile", reportFile). + Str("orchsURL", orchsURL). + Msg("reconciliation complete") + outputResult( + fmt.Sprintf("Reconciliation completed. Check orchestrator jobs for details. %s", orchsURL), + outputFormat, + ) + return nil + } + + }, + Run: nil, + PostRun: nil, + PostRunE: nil, + PersistentPostRun: nil, + PersistentPostRunE: nil, + FParseErrWhitelist: cobra.FParseErrWhitelist{}, + CompletionOptions: cobra.CompletionOptions{}, + TraverseChildren: false, + Hidden: false, + SilenceErrors: false, + SilenceUsage: false, + DisableFlagParsing: false, + DisableAutoGenTag: false, + DisableFlagsInUseLine: false, + DisableSuggestions: false, + SuggestionsMinimumDistance: 0, + } + rotGenStoreTemplateCmd = &cobra.Command{ + Use: "generate-template", + Aliases: nil, + SuggestFor: nil, + Short: "For generating Root Of Trust template(s)", + Long: `Root Of Trust: Will parse a CSV and attempt to deploy a cert or set of certs into a list of cert stores.`, + Example: "", + ValidArgs: nil, + ValidArgsFunction: nil, + Args: nil, + ArgAliases: nil, + BashCompletionFunction: "", + Deprecated: "", + Annotations: nil, + Version: "", + PersistentPreRun: nil, + PersistentPreRunE: nil, + PreRun: nil, + PreRunE: nil, + RunE: func(cmd *cobra.Command, args []string) error { + // Global flags + cmd.SilenceUsage = true + // expEnabled checks + isExperimental := false + debugErr := warnExperimentalFeature(expEnabled, isExperimental) + if debugErr != nil { + return debugErr + } + //stdlog.SetOutput(io.Discard) + informDebug(debugFlag) + + // Authenticate + kfClient, cErr := initClient(false) + if cErr != nil { + log.Error().Err(cErr).Msg("unable to authenticate") + return cErr + } + + templateType, _ := cmd.Flags().GetString("type") + format, _ := cmd.Flags().GetString("format") + outPath, _ := cmd.Flags().GetString("outpath") + storeType, _ := cmd.Flags().GetStringSlice("store-type") + containerName, _ := cmd.Flags().GetStringSlice("container-name") + collection, _ := cmd.Flags().GetStringSlice("collection") + subjectName, _ := cmd.Flags().GetStringSlice("cn") + log.Debug().Str("templateType", templateType). + Str("format", format). + Str("outPath", outPath). + Strs("storeType", storeType). + Strs("containerName", containerName). + Strs("collection", collection). + Strs("subjectName", subjectName). + Msg("flags") + //if templateType == "" { + // return fmt.Errorf("template type must be specified") + //} + + stID := -1 + var storeData []api.GetCertificateStoreResponse + var csvStoreData [][]string + var csvCertData [][]string + var rowLookup = make(map[string]bool) + if len(storeType) != 0 { + for _, s := range storeType { + log.Debug().Str("storeType", s).Msg("processing store-type") + var sType *api.CertificateStoreType + var stErr error + if s == "all" { + log.Info().Str("storeType", s).Msg("getting all stores") + sType = &api.CertificateStoreType{ + Name: "", + ShortName: "", + Capability: "", + StoreType: 0, + ImportType: 0, + LocalStore: false, + SupportedOperations: nil, + Properties: nil, + EntryParameters: nil, + PasswordOptions: nil, + StorePathType: "", + StorePathValue: "", + PrivateKeyAllowed: "", + JobProperties: nil, + ServerRequired: false, + PowerShell: false, + BlueprintAllowed: false, + CustomAliasAllowed: "", + ServerRegistration: 0, + InventoryEndpoint: "", + InventoryJobType: "", + ManagementJobType: "", + DiscoveryJobType: "", + EnrollmentJobType: "", + } + } else { + // check if s is an int + log.Debug().Str("storeType", s).Msg("checking if store-type is an int") + sInt, err := strconv.Atoi(s) + if err == nil { + log.Debug().Str("storeType", s).Msg("calling GetCertificateStoreByID") + sType, stErr = kfClient.GetCertificateStoreTypeById(sInt) + } else { + log.Debug().Str("storeType", s).Msg("calling GetCertificateStoreByName") + sType, stErr = kfClient.GetCertificateStoreTypeByName(s) + } + if stErr != nil { + outputError(stErr, true, format) + log.Error().Err(stErr).Str("storeType", s).Msg("failed to get stores") + continue + } + stID = sType.StoreType // This is the template type ID + } + + if stID >= 0 || s == "all" { + log.Debug(). + Int("stID", stID). + Str("storeType", s). + Msg("valid store type") + params := make(map[string]interface{}) + + log.Debug().Str("storeType", s).Msg("calling ListCertificateStores") + stores, sErr := kfClient.ListCertificateStores(¶ms) + if sErr != nil { + log.Error().Err(sErr).Str("storeType", s).Msg("failed to get stores") + outputError(sErr, true, format) + return sErr + } + + if stores == nil { + invalidRespErr := fmt.Errorf("invalid response from Keyfactor Command when listing certificate stores") + log.Error().Err(invalidRespErr).Str("storeType", s).Msg("stores is nil") + outputError(invalidRespErr, true, format) + return invalidRespErr + } + log.Debug().Str("storeType", s).Msg("processing stores") + + for _, store := range *stores { + if store.CertStoreType == stID || s == "all" { + storeData = append(storeData, store) + if !rowLookup[store.Id] { + lineData := []string{ + //"StoreID", "StoreType", "StoreMachine", "StorePath", "ContainerId" + store.Id, + fmt.Sprintf("%s", sType.ShortName), + store.ClientMachine, + store.StorePath, + fmt.Sprintf("%d", store.ContainerId), + store.ContainerName, + getCurrentTime(""), + } + log.Debug().Str("storeType", s). + Strs("lineData", lineData). + Msg("adding line data") + csvStoreData = append(csvStoreData, lineData) + rowLookup[store.Id] = true + } + } + } + } + } + log.Info().Msg("lookups by store-type completed") + } + containers := len(containerName) + if containers > 0 { + log.Info(). + Int("containers", containers). + Msg("processing container-names") + for _, c := range containerName { + cStoresResp, scErr := kfClient.GetCertificateStoreByContainerID(c) + if scErr != nil { + log.Error().Err(scErr).Str("containerName", c).Msg("failed to get stores by container name") + return cErr + } + if cStoresResp == nil { + invalidRespErr := fmt.Errorf( + "invalid response from Keyfactor Command when listing stores by container name '%s'", + c, + ) + outputError(invalidRespErr, true, format) + log.Error().Err(invalidRespErr).Str("containerName", c).Msg("invalid response") + return invalidRespErr + } + for _, store := range *cStoresResp { + log.Debug(). + Str("containerName", c). + Int("storeType", store.CertStoreType). + Msg("calling GetCertificateStoreType") + sType, stErr := kfClient.GetCertificateStoreType(store.CertStoreType) + if stErr != nil { + outputError(stErr, false, format) + log.Error().Err(stErr).Str( + "containerName", + c, + ).Msg("failed to get store-type by container name") + continue + } + storeData = append(storeData, store) + if !rowLookup[store.Id] { + lineData := []string{ + // "StoreID", "StoreType", "StoreMachine", "StorePath", "ContainerId" + store.Id, + sType.ShortName, + store.ClientMachine, + store.StorePath, + fmt.Sprintf("%d", store.ContainerId), + store.ContainerName, + getCurrentTime(""), + } + log.Debug().Str("containerName", c). + Strs("lineData", lineData). + Msg("adding line data") + csvStoreData = append(csvStoreData, lineData) + rowLookup[store.Id] = true + } + } + } + log.Info().Msg("lookups by container-name completed") + } + + collections := len(collection) + if collections > 0 { + log.Info(). + Int("collections", collections). + Msg("processing collections") + for _, c := range collection { + q := make(map[string]string) + q["collection"] = c + log.Debug().Str("collection", c).Msg("calling ListCertificateStores") + certsResp, scErr := kfClient.ListCertificates(q) + if scErr != nil { + log.Error().Err(scErr).Str("collection", c).Msg("failed to list certificates by collection") + outputError(scErr, true, format) + return scErr + } + if certsResp == nil { + invalidRespErr := fmt.Errorf( + "invalid response from Keyfactor Command when listing certificates by collection '%s'", + c, + ) + outputError(invalidRespErr, true, format) + log.Error().Err(invalidRespErr).Str("collection", c).Msg("invalid response") + return invalidRespErr + } + for _, cert := range certsResp { + if !rowLookup[cert.Thumbprint] { + lineData := []string{ + // "Thumbprint", "SubjectName", "Issuer", "CertID", "Locations", "LastQueriedDate" + cert.Thumbprint, + cert.IssuedCN, + cert.IssuerDN, + fmt.Sprintf("%d", cert.Id), + fmt.Sprintf("%v", cert.Locations), + getCurrentTime(""), + } + log.Debug(). + Str("collection", c). + Strs("lineData", lineData). + Msg("adding line data") + csvCertData = append(csvCertData, lineData) + rowLookup[cert.Thumbprint] = true + } + } + } + log.Info(). + Int("collections", collections). + Msg("lookups by collection completed") + } + + cns := len(subjectName) + if cns > 0 { + log.Info(). + Int("subjectNames", cns). + Msg("processing subject-names") + for _, s := range subjectName { + q := make(map[string]string) + q["subject"] = s + + log.Debug().Str("subjectName", s).Msg("calling ListCertificates") + certsResp, scErr := kfClient.ListCertificates(q) + if scErr != nil { + log.Error().Err(scErr).Str("subjectName", s).Msg("failed to list certificates by subject name") + outputError(scErr, true, format) + return scErr + } + if certsResp == nil { + invalidRespErr := fmt.Errorf( + "invalid response returned from Keyfactor Command when calling ListCertificates by subject name '%s'", + s, + ) + log.Error(). + Err(invalidRespErr). + Str("subjectName", s). + Send() + outputError(invalidRespErr, true, format) + return invalidRespErr + } + + log.Debug(). + Str("subjectName", s). + Msg("processing certs to build 'thumbprint' to 'locations map'") + for _, cert := range certsResp { + if rowLookup[cert.Thumbprint] { + log.Debug().Str( + "thumbprint", + cert.Thumbprint, + ).Msg("thumbprint already exists in lookup, skipping") + continue + } + locationsFormatted := "" + for _, loc := range cert.Locations { + locationsFormatted += fmt.Sprintf("%s:%s\n", loc.StoreMachine, loc.StorePath) + log.Debug(). + Str("thumbprint", cert.Thumbprint). + Str("storePath", loc.StorePath). + Str("formattedLocations", locationsFormatted). + Msg("processing location") + } + lineData := []string{ + // "Thumbprint", "SubjectName", "Issuer", "CertID", "Locations", "LastQueriedDate" + cert.Thumbprint, + cert.IssuedCN, + cert.IssuerDN, + fmt.Sprintf("%d", cert.Id), + locationsFormatted, + getCurrentTime(""), + } + log.Debug(). + Str("subjectName", s). + Strs("lineData", lineData). + Msg("adding line data") + csvCertData = append(csvCertData, lineData) + rowLookup[cert.Thumbprint] = true + } + } + log.Info().Int("subjectNames", cns).Msg("lookups by subject-name completed") + } + + var filePath string + if outPath != "" { + filePath = outPath + } else { + filePath = fmt.Sprintf("%s_template.%s", templateType, format) + } + + log.Debug().Str("filePath", filePath).Msg("writing file") + file, err := os.Create(filePath) + defer file.Close() + if err != nil { + log.Error().Err(err).Str("filePath", filePath).Msg("failed to create file") + outputError(err, true, format) + return err + } + + switch format { + case "csv": + log.Debug(). + Str("filePath", filePath). + Str("templateType", templateType). + Msg("writing csv") + writer := csv.NewWriter(file) + var data [][]string + + switch templateType { + case "stores": + log.Debug(). + Str("filePath", filePath). + Str("templateType", templateType). + Msg("writing stores csv") + data = append(data, StoreHeader) + if len(csvStoreData) != 0 { + data = append(data, csvStoreData...) + } + case "certs": + log.Debug(). + Str("filePath", filePath). + Str("templateType", templateType). + Msg("writing certs csv") + data = append(data, CertHeader) + if len(csvCertData) != 0 { + data = append(data, csvCertData...) + } + case "actions": + log.Debug(). + Str("filePath", filePath). + Str("templateType", templateType). + Msg("writing audit csv") + data = append(data, AuditHeader) + } + csvErr := writer.WriteAll(data) + if csvErr != nil { + log.Error().Err(csvErr).Str("filePath", filePath).Msg("failed to write csv") + outputError(csvErr, true, format) + return csvErr + } + case "json": + log.Debug(). + Str("filePath", filePath). + Str("templateType", templateType). + Msg("writing json") + writer := bufio.NewWriter(file) + _, err := writer.WriteString("StoreID,StoreType,StoreMachine,StorePath") + if err != nil { + log.Error().Err(err).Str("filePath", filePath).Msg("failed to write json") + outputError(err, true, format) + return err + } + } + log.Info().Str("filePath", filePath).Msg("template file written") + outputResult(fmt.Sprintf("Template generated at %s", filePath), outputFormat) + return nil + }, + Run: nil, + PostRun: nil, + PostRunE: nil, + PersistentPostRun: nil, + PersistentPostRunE: nil, + FParseErrWhitelist: cobra.FParseErrWhitelist{}, + CompletionOptions: cobra.CompletionOptions{}, + TraverseChildren: false, + Hidden: false, + SilenceErrors: false, + SilenceUsage: false, + DisableFlagParsing: false, + DisableAutoGenTag: false, + DisableFlagsInUseLine: false, + DisableSuggestions: false, + SuggestionsMinimumDistance: 0, + } +) + +func init() { + var ( + stores string + addCerts string + removeCerts string + minCertsInStore int + maxPrivateKeys int + maxLeaves int + tType = tTypeCerts + outPath string + outputFormat string + inputFile string + storeTypes []string + containerNames []string + collections []string + subjectNames []string + ) + + storesCmd.AddCommand(rotCmd) + + // Root of trust `audit` command + rotCmd.AddCommand(rotAuditCmd) + rotAuditCmd.Flags().StringVarP(&stores, "stores", "s", "", "CSV file containing cert stores to enroll into") + rotAuditCmd.Flags().StringVarP( + &addCerts, "add-certs", "a", "", + "CSV file containing cert(s) to enroll into the defined cert stores", + ) + rotAuditCmd.Flags().StringVarP( + &removeCerts, "remove-certs", "r", "", + "CSV file containing cert(s) to remove from the defined cert stores", + ) + rotAuditCmd.Flags().IntVarP( + &minCertsInStore, + "min-certs", + "m", + -1, + "The minimum number of certs that should be in a store to be considered a 'root' store. If set to `-1` then all stores will be considered.", + ) + rotAuditCmd.Flags().IntVarP( + &maxPrivateKeys, + "max-keys", + "k", + -1, + "The max number of private keys that should be in a store to be considered a 'root' store. If set to `-1` then all stores will be considered.", + ) + rotAuditCmd.Flags().IntVarP( + &maxLeaves, + "max-leaf-certs", + "l", + -1, + "The max number of non-root-certs that should be in a store to be considered a 'root' store. If set to `-1` then all stores will be considered.", + ) + rotAuditCmd.Flags().BoolP("dry-run", "d", false, "Dry run mode") + rotAuditCmd.Flags().StringVarP( + &outPath, "outpath", "o", "", + "Path to write the audit report file to. If not specified, the file will be written to the current directory.", + ) + + // Root of trust `reconcile` command + rotCmd.AddCommand(rotReconcileCmd) + rotReconcileCmd.Flags().StringVarP(&stores, "stores", "s", "", "CSV file containing cert stores to enroll into") + rotReconcileCmd.Flags().StringVarP( + &addCerts, "add-certs", "a", "", + "CSV file containing cert(s) to enroll into the defined cert stores", + ) + rotReconcileCmd.Flags().StringVarP( + &removeCerts, "remove-certs", "r", "", + "CSV file containing cert(s) to remove from the defined cert stores", + ) + rotReconcileCmd.Flags().IntVarP( + &minCertsInStore, + "min-certs", + "m", + -1, + "The minimum number of certs that should be in a store to be considered a 'root' store. If set to `-1` then all stores will be considered.", + ) + rotReconcileCmd.Flags().IntVarP( + &maxPrivateKeys, + "max-keys", + "k", + -1, + "The max number of private keys that should be in a store to be considered a 'root' store. If set to `-1` then all stores will be considered.", + ) + rotReconcileCmd.Flags().IntVarP( + &maxLeaves, + "max-leaf-certs", + "l", + -1, + "The max number of non-root-certs that should be in a store to be considered a 'root' store. If set to `-1` then all stores will be considered.", + ) + rotReconcileCmd.Flags().BoolP("dry-run", "d", false, "Dry run mode") + rotReconcileCmd.Flags().BoolP("import-csv", "v", false, "Import an audit report file in CSV format.") + rotReconcileCmd.Flags().StringVarP( + &inputFile, "input-file", "i", reconcileDefaultFileName, + "Path to a file generated by 'stores rot audit' command.", + ) + rotReconcileCmd.Flags().StringVarP( + &outPath, "outpath", "o", "", + "Path to write the audit report file to. If not specified, the file will be written to the current directory.", + ) + //rotReconcileCmd.MarkFlagsRequiredTogether("add-certs", "stores") + //rotReconcileCmd.MarkFlagsRequiredTogether("remove-certs", "stores") + rotReconcileCmd.MarkFlagsMutuallyExclusive("add-certs", "import-csv") + rotReconcileCmd.MarkFlagsMutuallyExclusive("remove-certs", "import-csv") + rotReconcileCmd.MarkFlagsMutuallyExclusive("stores", "import-csv") + + // Root of trust `generate` command + rotCmd.AddCommand(rotGenStoreTemplateCmd) + rotGenStoreTemplateCmd.Flags().StringVarP( + &outPath, "outpath", "o", "", + "Path to write the template file to. If not specified, the file will be written to the current directory.", + ) + rotGenStoreTemplateCmd.Flags().StringVarP( + &outputFormat, "format", "f", "csv", + "The type of template to generate. Only `csv` is supported at this time.", + ) + rotGenStoreTemplateCmd.Flags().Var( + &tType, "type", + `The type of template to generate. Only "certs|stores|actions" are supported at this time.`, + ) + rotGenStoreTemplateCmd.Flags().StringSliceVar( + &storeTypes, + "store-type", + []string{}, + "Multi value flag. Attempt to pre-populate the stores template with the certificate stores matching specified store types. If not specified, the template will be empty.", + ) + rotGenStoreTemplateCmd.Flags().StringSliceVar( + &containerNames, + "container-name", + []string{}, + "Multi value flag. Attempt to pre-populate the stores template with the certificate stores matching specified container types. If not specified, the template will be empty.", + ) + rotGenStoreTemplateCmd.Flags().StringSliceVar( + &subjectNames, + "cn", + []string{}, + "Subject name(s) to pre-populate the 'certs' template with. If not specified, the template will be empty. Does not work with SANs.", + ) + rotGenStoreTemplateCmd.Flags().StringSliceVar( + &collections, + "collection", + []string{}, + "Certificate collection name(s) to pre-populate the stores template with. If not specified, the template will be empty.", + ) + + rotGenStoreTemplateCmd.RegisterFlagCompletionFunc("type", templateTypeCompletion) + rotGenStoreTemplateCmd.MarkFlagRequired("type") +} diff --git a/cmd/storeTypes.go b/cmd/storeTypes.go index 20e43f0..ca2be2b 100644 --- a/cmd/storeTypes.go +++ b/cmd/storeTypes.go @@ -20,13 +20,12 @@ import ( "fmt" "io" "net/http" + "net/url" "os" "sort" "strings" "time" - stdlog "log" - "github.com/AlecAivazis/survey/v2" "github.com/Keyfactor/keyfactor-go-client/v3/api" "github.com/rs/zerolog/log" @@ -54,7 +53,7 @@ var storesTypesListCmd = &cobra.Command{ if debugErr != nil { return debugErr } - stdlog.SetOutput(io.Discard) + //stdlog.SetOutput(io.Discard) informDebug(debugFlag) // Authenticate @@ -93,7 +92,6 @@ var storesTypeCreateCmd = &cobra.Command{ gitRef, _ := cmd.Flags().GetString(FlagGitRef) gitRepo, _ := cmd.Flags().GetString(FlagGitRepo) creatAll, _ := cmd.Flags().GetBool("all") - validStoreTypes := getValidStoreTypes("", gitRef, gitRepo) storeType, _ := cmd.Flags().GetString("name") listTypes, _ := cmd.Flags().GetBool("list") storeTypeConfigFile, _ := cmd.Flags().GetString("from-file") @@ -106,6 +104,8 @@ var storesTypeCreateCmd = &cobra.Command{ } informDebug(debugFlag) + validStoreTypes := getValidStoreTypes("", gitRef, gitRepo) + // Authenticate kfClient, _ := initClient(false) @@ -505,7 +505,8 @@ func getStoreTypesInternet(gitRef string, repo string) (map[string]interface{}, fileName = "integration-manifest.json" } - url := fmt.Sprintf(baseUrl, repo, gitRef, fileName) + escapedGitRef := url.PathEscape(gitRef) + url := fmt.Sprintf(baseUrl, repo, escapedGitRef, fileName) log.Debug(). Str("url", url). Msg("Getting store types from internet") diff --git a/cmd/storesBulkOperations.go b/cmd/storesBulkOperations.go index 45d61fa..4bbd4a4 100644 --- a/cmd/storesBulkOperations.go +++ b/cmd/storesBulkOperations.go @@ -47,6 +47,12 @@ var ( } ) +const bom = "\uFEFF" + +func stripAllBOMs(s string) string { + return strings.ReplaceAll(s, bom, "") +} + // formatProperties will iterate through the properties of a json object and convert any "int" values to strings // this is required because the Keyfactor API expects all properties to be strings func formatProperties(json *gabs.Container, reqPropertiesForStoreType []string) *gabs.Container { @@ -99,17 +105,54 @@ func serializeStoreFromTypeDef(storeTypeName string, input string) (string, erro var importStoresCmd = &cobra.Command{ Use: "import", - Short: "Import a file with certificate store parameters and create them in keyfactor.", + Short: "Import a file with certificate store definitions and create them in Keyfactor Command.", Long: `Tools for generating import templates and importing certificate stores`, } var storesCreateFromCSVCmd = &cobra.Command{ Use: "csv --file --store-type-id --store-type-name --results-path --dry-run ", Short: "Create certificate stores from CSV file.", - Long: `Certificate stores: Will parse a CSV and attempt to create a certificate store for each row with the provided parameters. -'store-type-name' OR 'store-type-id' are required. -'file' is the path to the file to be imported. -'resultspath' is where the import results will be written to.`, + Long: `Will parse a CSV file and attempt to create a certificate store for each row with the provided parameters. +Any errors encountered will be logged to the _results.csv file, under the 'Errors' column. + +Required Flags: +- '--store-type-name' OR '--store-type-id' +- '--file' is the path to the file to be imported. + +#### Credentials + +##### In the CSV file: + +###### Credential Fields + +| Header | Description | +| --- | --- | +| Properties.ServerUsername | This is equivalent to the 'ServerUsername' field in the Command Certificate Store UI. | +| Properties.ServerPassword | This is equivalent to the 'ServerPassword' field in the Command Certificate Store UI. | +| Password | This is equivalent to the 'StorePassword' field in the Command Certificate Store UI. | + +###### Inventory Schedule Fields +For full information on certificate store schedules visit: https://software.keyfactor.com/Core-OnPrem/v25.1.1/Content/WebAPI/KeyfactorAPI/CertificateStoresPostSchedule.htm#API-Table-Schedule + +> [!NOTE] +> Only one type of schedule can be specified in the CSV file. If multiple are specified, +> the last one will be used. For example you can't schedule both "InventorySchedule.Immediate" and "InventorySchedule. +> Interval.Minutes", in which case the value of "InventorySchedule.Interval.Minutes" would be used. + +| Header | Description | +| --- | --- | +| InventorySchedule.Immediate | A Boolean that indicates a job scheduled to run immediately (TRUE) or not (FALSE). | +| InventorySchedule.Interval.Minutes | An integer indicating the number of minutes between each interval. | +| InventorySchedule.Daily.Time | The date and time to next run the job. The date and time should be given using the ISO 8601 UTC time format "YYYY-MM-DDTHH:mm:ss.000Z"" (e.g. 2023-11-19T16:23:01Z). | +| InventorySchedule.Weekly.Days | An array of values representing the days of the week on which to run the job. These can either be entered as integers (0 for Sunday, 1 for Monday, etc.) or as days of the week (e.g. "Sunday"). | +| InventorySchedule.Weekly.Time | The time of day to inventory daily, RFC3339 format. Ex. "2023-10-01T12:00:00Z" for noon UTC. | + +##### Outside CSV file: +If you do not wish to include credentials in your CSV file they can be provided one of three ways: +- via the --server-username --server-password and --store-password flags +- via environment variables: KFUTIL_CSV_SERVER_USERNAME, KFUTIL_CSV_SERVER_PASSWORD, KFUTIL_CSV_STORE_PASSWORD +- via interactive prompts +`, RunE: func(cmd *cobra.Command, args []string) error { // Specific flags storeTypeName, _ := cmd.Flags().GetString("store-type-name") @@ -117,6 +160,19 @@ var storesCreateFromCSVCmd = &cobra.Command{ filePath, _ := cmd.Flags().GetString("file") outPath, _ := cmd.Flags().GetString("results-path") dryRun, _ := cmd.Flags().GetBool("dry-run") + serverUsername, _ := cmd.Flags().GetString("server-username") + serverPassword, _ := cmd.Flags().GetString("server-password") + storePassword, _ := cmd.Flags().GetString("store-password") + + if serverUsername == "" { + serverUsername = os.Getenv(EnvStoresImportCSVServerUsername) + } + if serverPassword == "" { + serverPassword = os.Getenv(EnvStoresImportCSVServerPassword) + } + if storePassword == "" { + storePassword = os.Getenv(EnvStoresImportCSVStorePassword) + } //// Flag Checks //inputErr := storeTypeIdentifierFlagCheck(cmd) @@ -259,18 +315,32 @@ var storesCreateFromCSVCmd = &cobra.Command{ errorCount := 0 + if !noPrompt { + promptCreds := promptForInteractiveYesNo("Input default credentials to use for certificate stores?") + if promptCreds { + outputResult("NOTE: Credentials provided in file will take precedence over prompts.", outputFormat) + serverUsername = promptForInteractiveParameter("ServerUsername", serverUsername) + log.Debug().Str("serverUsername", serverUsername).Msg("ServerUsername") + serverPassword = promptForInteractivePassword("ServerPassword", serverPassword) + log.Debug().Str("serverPassword", hashSecretValue(serverPassword)).Msg("ServerPassword") + storePassword = promptForInteractivePassword("StorePassword", storePassword) + log.Debug().Str("storePassword", hashSecretValue(storePassword)).Msg("StorePassword") + } + } + log.Info().Msgf("Processing CSV rows from file '%s'", filePath) + var inputHeader []string for idx, row := range inFile { log.Debug().Msgf("Processing row '%d'", idx) originalMap = append(originalMap, row) if idx == 0 { // skip header row + inputHeader = row log.Debug().Msgf("Skipping header row") continue } reqJson := getJsonForRequest(headerRow, row) - reqJson = formatProperties(reqJson, reqPropertiesForStoreType) reqJson.Set(intID, "CertStoreType") @@ -281,13 +351,37 @@ var storesCreateFromCSVCmd = &cobra.Command{ log.Debug().Msgf("ContainerId is 0, omitting from request") reqJson.Set(nil, "ContainerId") } - log.Debug().Msgf("Request JSON: %s", reqJson.String()) + //log.Debug().Msgf("Request JSON: %s", reqJson.String()) // parse properties var createStoreReqParameters api.CreateStoreFctArgs props := unmarshalPropertiesString(reqJson.S("Properties").String()) + + //check if ServerUsername is present in the properties + _, uOk := props["ServerUsername"] + if !uOk && serverUsername != "" { + props["ServerUsername"] = serverUsername + } + + _, pOk := props["ServerPassword"] + if !pOk && serverPassword != "" { + props["ServerPassword"] = serverPassword + } + + rowStorePassword := reqJson.S("Password").String() reqJson.Delete("Properties") // todo: why is this deleting the properties from the request json? - mJSON := reqJson.String() + var passwdParams *api.StorePasswordConfig + if rowStorePassword != "" { + reqJson.Delete("Password") + passwdParams = &api.StorePasswordConfig{ + Value: &rowStorePassword, + } + } else { + passwdParams = &api.StorePasswordConfig{ + Value: &storePassword, + } + } + mJSON := stripAllBOMs(reqJson.String()) conversionError := json.Unmarshal([]byte(mJSON), &createStoreReqParameters) if conversionError != nil { @@ -299,10 +393,10 @@ var storesCreateFromCSVCmd = &cobra.Command{ return conversionError } + createStoreReqParameters.Password = passwdParams createStoreReqParameters.Properties = props - log.Debug().Msgf("Request parameters: %v", createStoreReqParameters) + //log.Debug().Msgf("Request parameters: %v", createStoreReqParameters) - // make request. log.Info().Msgf("Calling Command to create store from row '%d'", idx) res, err := kfClient.CreateStore(&createStoreReqParameters) @@ -335,8 +429,9 @@ var storesCreateFromCSVCmd = &cobra.Command{ Int("totalSuccess", totalSuccess).Send() log.Info().Msgf("Writing results to file '%s'", outPath) + //writeCsvFile(outPath, originalMap) - mapToCSV(inputMap, outPath) + mapToCSV(inputMap, outPath, inputHeader) log.Info().Int("totalRows", totalRows). Int("totalSuccesses", totalSuccess). Int("errorCount", errorCount). @@ -993,8 +1088,6 @@ func getJsonForRequest(headerRow []string, row []string) *gabs.Container { } } } - //fmt.Printf("[DEBUG] get JSON for create store request: %s", reqJson.String()) - log.Debug().Msgf("JSON for create store request: %s", reqJson.String()) return reqJson } @@ -1111,6 +1204,39 @@ func init() { -1, "The ID of the cert store type for the stores.", ) + storesCreateFromCSVCmd.Flags().StringVarP( + &storeTypeName, + "server-username", + "u", + "", + "The username Keyfactor Command will use to use connect to the certificate store host. "+ + "This field can be specified in the CSV file in the column `Properties.ServerUsername`. "+ + "This value can also be sourced from the environmental variable `KFUTIL_CSV_SERVER_USERNAME`. "+ + "*NOTE* a value provided in the CSV file will override any other input value", + ) + storesCreateFromCSVCmd.Flags().StringVarP( + &storeTypeName, + "server-password", + "p", + "", + "The password Keyfactor Command will use to use connect to the certificate store host. "+ + "This field can be specified in the CSV file in the column `Properties.ServerPassword`. "+ + "This value can also be sourced from the environmental variable `KFUTIL_CSV_SERVER_PASSWORD`. "+ + "*NOTE* a value provided in the CSV file will override any other input value", + ) + storesCreateFromCSVCmd.Flags().StringVarP( + &storeTypeName, + "store-password", + "s", + "", + "The credential information Keyfactor Command will use to access the certificates in a specific certificate"+ + " store (the store password). This is different from credential information Keyfactor Command uses to"+ + " access a certificate store host."+ + " This field can be specified in the CSV file in the column `Password`. This value can also be sourced from"+ + " the environmental variable `KFUTIL_CSV_STORE_PASSWORD`. *NOTE* a value provided in the CSV file will"+ + " override any other input value", + ) + storesCreateFromCSVCmd.Flags().StringVarP(&file, "file", "f", "", "CSV file containing cert stores to create.") storesCreateFromCSVCmd.MarkFlagRequired("file") storesCreateFromCSVCmd.Flags().BoolP("dry-run", "d", false, "Do not import, just check for necessary fields.") diff --git a/docs/kfutil.md b/docs/kfutil.md index a7c1b0f..d861138 100644 --- a/docs/kfutil.md +++ b/docs/kfutil.md @@ -46,4 +46,4 @@ A CLI wrapper around the Keyfactor Platform API. * [kfutil stores](kfutil_stores.md) - Keyfactor certificate stores APIs and utilities. * [kfutil version](kfutil_version.md) - Shows version of kfutil -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_completion.md b/docs/kfutil_completion.md index 1fae919..e22e82b 100644 --- a/docs/kfutil_completion.md +++ b/docs/kfutil_completion.md @@ -45,4 +45,4 @@ See each sub-command's help for details on how to use the generated script. * [kfutil completion powershell](kfutil_completion_powershell.md) - Generate the autocompletion script for powershell * [kfutil completion zsh](kfutil_completion_zsh.md) - Generate the autocompletion script for zsh -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_completion_bash.md b/docs/kfutil_completion_bash.md index 527ddf2..e002d6d 100644 --- a/docs/kfutil_completion_bash.md +++ b/docs/kfutil_completion_bash.md @@ -64,4 +64,4 @@ kfutil completion bash * [kfutil completion](kfutil_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_completion_fish.md b/docs/kfutil_completion_fish.md index 3a32698..fd50972 100644 --- a/docs/kfutil_completion_fish.md +++ b/docs/kfutil_completion_fish.md @@ -55,4 +55,4 @@ kfutil completion fish [flags] * [kfutil completion](kfutil_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_completion_powershell.md b/docs/kfutil_completion_powershell.md index 50e0b78..6ae54ea 100644 --- a/docs/kfutil_completion_powershell.md +++ b/docs/kfutil_completion_powershell.md @@ -52,4 +52,4 @@ kfutil completion powershell [flags] * [kfutil completion](kfutil_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_completion_zsh.md b/docs/kfutil_completion_zsh.md index 97c7c54..b3a0672 100644 --- a/docs/kfutil_completion_zsh.md +++ b/docs/kfutil_completion_zsh.md @@ -66,4 +66,4 @@ kfutil completion zsh [flags] * [kfutil completion](kfutil_completion.md) - Generate the autocompletion script for the specified shell -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_containers.md b/docs/kfutil_containers.md index f0624a1..667a635 100644 --- a/docs/kfutil_containers.md +++ b/docs/kfutil_containers.md @@ -41,4 +41,4 @@ A collections of APIs and utilities for interacting with Keyfactor certificate s * [kfutil containers get](kfutil_containers_get.md) - Get certificate store container by ID or name. * [kfutil containers list](kfutil_containers_list.md) - List certificate store containers. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_containers_get.md b/docs/kfutil_containers_get.md index 8b01da1..54bcb25 100644 --- a/docs/kfutil_containers_get.md +++ b/docs/kfutil_containers_get.md @@ -44,4 +44,4 @@ kfutil containers get [flags] * [kfutil containers](kfutil_containers.md) - Keyfactor certificate store container API and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_containers_list.md b/docs/kfutil_containers_list.md index 7a4d56d..496661b 100644 --- a/docs/kfutil_containers_list.md +++ b/docs/kfutil_containers_list.md @@ -43,4 +43,4 @@ kfutil containers list [flags] * [kfutil containers](kfutil_containers.md) - Keyfactor certificate store container API and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_export.md b/docs/kfutil_export.md index 439e3bd..7b255a5 100644 --- a/docs/kfutil_export.md +++ b/docs/kfutil_export.md @@ -55,4 +55,4 @@ kfutil export [flags] * [kfutil](kfutil.md) - Keyfactor CLI utilities -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_helm.md b/docs/kfutil_helm.md index 297fa81..c0facc8 100644 --- a/docs/kfutil_helm.md +++ b/docs/kfutil_helm.md @@ -46,4 +46,4 @@ kubectl helm uo | helm install -f - keyfactor-universal-orchestrator keyfactor/k * [kfutil](kfutil.md) - Keyfactor CLI utilities * [kfutil helm uo](kfutil_helm_uo.md) - Configure the Keyfactor Universal Orchestrator Helm Chart -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_helm_uo.md b/docs/kfutil_helm_uo.md index 2471540..a5e2833 100644 --- a/docs/kfutil_helm_uo.md +++ b/docs/kfutil_helm_uo.md @@ -50,4 +50,4 @@ kfutil helm uo [-t ] [-o ] [-f ] [-e -e @,@ -o ./app/extension * [kfutil orchs](kfutil_orchs.md) - Keyfactor agents/orchestrators APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_orchs_get.md b/docs/kfutil_orchs_get.md index a1fdd35..245e10a 100644 --- a/docs/kfutil_orchs_get.md +++ b/docs/kfutil_orchs_get.md @@ -44,4 +44,4 @@ kfutil orchs get [flags] * [kfutil orchs](kfutil_orchs.md) - Keyfactor agents/orchestrators APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_orchs_list.md b/docs/kfutil_orchs_list.md index 7d28007..bf55a9a 100644 --- a/docs/kfutil_orchs_list.md +++ b/docs/kfutil_orchs_list.md @@ -43,4 +43,4 @@ kfutil orchs list [flags] * [kfutil orchs](kfutil_orchs.md) - Keyfactor agents/orchestrators APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_orchs_logs.md b/docs/kfutil_orchs_logs.md index f2eb557..14d971e 100644 --- a/docs/kfutil_orchs_logs.md +++ b/docs/kfutil_orchs_logs.md @@ -44,4 +44,4 @@ kfutil orchs logs [flags] * [kfutil orchs](kfutil_orchs.md) - Keyfactor agents/orchestrators APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_orchs_reset.md b/docs/kfutil_orchs_reset.md index fd0ebdc..032349a 100644 --- a/docs/kfutil_orchs_reset.md +++ b/docs/kfutil_orchs_reset.md @@ -44,4 +44,4 @@ kfutil orchs reset [flags] * [kfutil orchs](kfutil_orchs.md) - Keyfactor agents/orchestrators APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_pam.md b/docs/kfutil_pam.md index 0ee4a58..6d22a6d 100644 --- a/docs/kfutil_pam.md +++ b/docs/kfutil_pam.md @@ -48,4 +48,4 @@ programmatically create, delete, edit, and list PAM Providers. * [kfutil pam types-list](kfutil_pam_types-list.md) - Returns a list of all available PAM provider types. * [kfutil pam update](kfutil_pam_update.md) - Updates an existing PAM Provider, currently only supported from file. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_pam_create.md b/docs/kfutil_pam_create.md index f68938c..1fd5f60 100644 --- a/docs/kfutil_pam_create.md +++ b/docs/kfutil_pam_create.md @@ -44,4 +44,4 @@ kfutil pam create [flags] * [kfutil pam](kfutil_pam.md) - Keyfactor PAM Provider APIs. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_pam_delete.md b/docs/kfutil_pam_delete.md index dbf74c0..28c16e1 100644 --- a/docs/kfutil_pam_delete.md +++ b/docs/kfutil_pam_delete.md @@ -44,4 +44,4 @@ kfutil pam delete [flags] * [kfutil pam](kfutil_pam.md) - Keyfactor PAM Provider APIs. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_pam_get.md b/docs/kfutil_pam_get.md index 8bae60e..afc07d7 100644 --- a/docs/kfutil_pam_get.md +++ b/docs/kfutil_pam_get.md @@ -44,4 +44,4 @@ kfutil pam get [flags] * [kfutil pam](kfutil_pam.md) - Keyfactor PAM Provider APIs. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_pam_list.md b/docs/kfutil_pam_list.md index e6006c7..217b568 100644 --- a/docs/kfutil_pam_list.md +++ b/docs/kfutil_pam_list.md @@ -43,4 +43,4 @@ kfutil pam list [flags] * [kfutil pam](kfutil_pam.md) - Keyfactor PAM Provider APIs. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_pam_types-create.md b/docs/kfutil_pam_types-create.md index 32713bb..9c0e244 100644 --- a/docs/kfutil_pam_types-create.md +++ b/docs/kfutil_pam_types-create.md @@ -51,4 +51,4 @@ kfutil pam types-create [flags] * [kfutil pam](kfutil_pam.md) - Keyfactor PAM Provider APIs. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_pam_types-list.md b/docs/kfutil_pam_types-list.md index d9e88af..8fe920d 100644 --- a/docs/kfutil_pam_types-list.md +++ b/docs/kfutil_pam_types-list.md @@ -43,4 +43,4 @@ kfutil pam types-list [flags] * [kfutil pam](kfutil_pam.md) - Keyfactor PAM Provider APIs. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_pam_update.md b/docs/kfutil_pam_update.md index b3f7608..35ab4bb 100644 --- a/docs/kfutil_pam_update.md +++ b/docs/kfutil_pam_update.md @@ -44,4 +44,4 @@ kfutil pam update [flags] * [kfutil pam](kfutil_pam.md) - Keyfactor PAM Provider APIs. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_status.md b/docs/kfutil_status.md index 87f0f2d..193d051 100644 --- a/docs/kfutil_status.md +++ b/docs/kfutil_status.md @@ -43,4 +43,4 @@ kfutil status [flags] * [kfutil](kfutil.md) - Keyfactor CLI utilities -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_store-types.md b/docs/kfutil_store-types.md index a7e15b9..05c3cac 100644 --- a/docs/kfutil_store-types.md +++ b/docs/kfutil_store-types.md @@ -44,4 +44,4 @@ A collections of APIs and utilities for interacting with Keyfactor certificate s * [kfutil store-types list](kfutil_store-types_list.md) - List certificate store types. * [kfutil store-types templates-fetch](kfutil_store-types_templates-fetch.md) - Fetches store type templates from Keyfactor's Github. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_store-types_create.md b/docs/kfutil_store-types_create.md index 79b7b77..ef02796 100644 --- a/docs/kfutil_store-types_create.md +++ b/docs/kfutil_store-types_create.md @@ -18,7 +18,7 @@ kfutil store-types create [flags] -b, --git-ref string The git branch or tag to reference when pulling store-types from the internet. (default "main") -h, --help help for create -l, --list List valid store types. - -n, --name string Short name of the certificate store type to get. Valid choices are: AKV, AWS-ACM, Akamai, AppGwBin, AzureApp, AzureApp2, AzureAppGw, AzureSP, AzureSP2, BIPCamera, CiscoAsa, CitrixAdc, F5-BigIQ, F5-CA-REST, F5-SL-REST, F5-WS-REST, Fortigate, GCPLoadBal, GcpCertMgr, HCVKV, HCVKVJKS, HCVKVP12, HCVKVPEM, HCVKVPFX, HCVPKI, IISU, Imperva, K8SCert, K8SCluster, K8SJKS, K8SNS, K8SPKCS12, K8SSecret, K8STLSSecr, MOST, Nmap, PaloAlto, RFDER, RFJKS, RFKDB, RFORA, RFPEM, RFPkcs12, SAMPLETYPE, Signum, VMware-NSX, WinCerMgmt, WinCert, WinSql + -n, --name string Short name of the certificate store type to get. Valid choices are: AKV, AWS-ACM, Akamai, AppGwBin, AzureApp, AzureApp2, AzureAppGw, AzureSP, AzureSP2, BIPCamera, CiscoAsa, CitrixAdc, DataPower, F5-BigIQ, F5-CA-REST, F5-SL-REST, F5-WS-REST, FortiWeb, Fortigate, GCPLoadBal, GcpCertMgr, HCVKV, HCVKVJKS, HCVKVP12, HCVKVPEM, HCVKVPFX, HCVPKI, IISU, Imperva, K8SCert, K8SCluster, K8SJKS, K8SNS, K8SPKCS12, K8SSecret, K8STLSSecr, MOST, Nmap, PaloAlto, RFDER, RFJKS, RFKDB, RFORA, RFPEM, RFPkcs12, SAMPLETYPE, Signum, VMware-NSX, WinCerMgmt, WinCert, WinSql, f5WafCa, f5WafTls, iDRAC -r, --repo string The repository to pull store-types definitions from. (default "kfutil") ``` @@ -49,4 +49,4 @@ kfutil store-types create [flags] * [kfutil store-types](kfutil_store-types.md) - Keyfactor certificate store types APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_store-types_delete.md b/docs/kfutil_store-types_delete.md index cff57c8..a8b9500 100644 --- a/docs/kfutil_store-types_delete.md +++ b/docs/kfutil_store-types_delete.md @@ -47,4 +47,4 @@ kfutil store-types delete [flags] * [kfutil store-types](kfutil_store-types.md) - Keyfactor certificate store types APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_store-types_get.md b/docs/kfutil_store-types_get.md index c1c54d8..9a249cc 100644 --- a/docs/kfutil_store-types_get.md +++ b/docs/kfutil_store-types_get.md @@ -48,4 +48,4 @@ kfutil store-types get [-i | -n ] [-b * [kfutil store-types](kfutil_store-types.md) - Keyfactor certificate store types APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_store-types_list.md b/docs/kfutil_store-types_list.md index 1345bb1..4fb07e6 100644 --- a/docs/kfutil_store-types_list.md +++ b/docs/kfutil_store-types_list.md @@ -43,4 +43,4 @@ kfutil store-types list [flags] * [kfutil store-types](kfutil_store-types.md) - Keyfactor certificate store types APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_store-types_templates-fetch.md b/docs/kfutil_store-types_templates-fetch.md index c888ca3..9df5d69 100644 --- a/docs/kfutil_store-types_templates-fetch.md +++ b/docs/kfutil_store-types_templates-fetch.md @@ -45,4 +45,4 @@ kfutil store-types templates-fetch [flags] * [kfutil store-types](kfutil_store-types.md) - Keyfactor certificate store types APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_store-types_update.md b/docs/kfutil_store-types_update.md index ab7d3ca..0b6be52 100644 --- a/docs/kfutil_store-types_update.md +++ b/docs/kfutil_store-types_update.md @@ -21,4 +21,4 @@ kfutil store-types update [flags] * [kfutil store-types](kfutil_store-types.md) - Keyfactor certificate store types APIs and utilities. -###### Auto generated by spf13/cobra on 1-Dec-2022 +###### Auto generated on 1-Dec-2022 diff --git a/docs/kfutil_stores.md b/docs/kfutil_stores.md index 570b2db..c15f5c2 100644 --- a/docs/kfutil_stores.md +++ b/docs/kfutil_stores.md @@ -41,8 +41,10 @@ A collections of APIs and utilities for interacting with Keyfactor certificate s * [kfutil stores delete](kfutil_stores_delete.md) - Delete a certificate store by ID. * [kfutil stores export](kfutil_stores_export.md) - Export existing defined certificate stores by type or store Id. * [kfutil stores get](kfutil_stores_get.md) - Get a certificate store by ID. -* [kfutil stores import](kfutil_stores_import.md) - Import a file with certificate store parameters and create them in keyfactor. +* [kfutil stores import](kfutil_stores_import.md) - Import a file with certificate store definitions and create them + in Keyfactor Command. * [kfutil stores inventory](kfutil_stores_inventory.md) - Commands related to certificate store inventory management * [kfutil stores list](kfutil_stores_list.md) - List certificate stores. +* [kfutil stores rot](kfutil_stores_rot.md) - Root of trust utility -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_delete.md b/docs/kfutil_stores_delete.md index 731109b..c8f68b9 100644 --- a/docs/kfutil_stores_delete.md +++ b/docs/kfutil_stores_delete.md @@ -46,4 +46,4 @@ kfutil stores delete [flags] * [kfutil stores](kfutil_stores.md) - Keyfactor certificate stores APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_export.md b/docs/kfutil_stores_export.md index 967bd76..85fac2a 100644 --- a/docs/kfutil_stores_export.md +++ b/docs/kfutil_stores_export.md @@ -47,4 +47,4 @@ kfutil stores export [flags] * [kfutil stores](kfutil_stores.md) - Keyfactor certificate stores APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_get.md b/docs/kfutil_stores_get.md index 19beb3d..f2966da 100644 --- a/docs/kfutil_stores_get.md +++ b/docs/kfutil_stores_get.md @@ -44,4 +44,4 @@ kfutil stores get [flags] * [kfutil stores](kfutil_stores.md) - Keyfactor certificate stores APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_import.md b/docs/kfutil_stores_import.md index 5616626..6144088 100644 --- a/docs/kfutil_stores_import.md +++ b/docs/kfutil_stores_import.md @@ -1,6 +1,6 @@ ## kfutil stores import -Import a file with certificate store parameters and create them in keyfactor. +Import a file with certificate store definitions and create them in Keyfactor Command. ### Synopsis @@ -41,4 +41,4 @@ Tools for generating import templates and importing certificate stores * [kfutil stores import csv](kfutil_stores_import_csv.md) - Create certificate stores from CSV file. * [kfutil stores import generate-template](kfutil_stores_import_generate-template.md) - For generating a CSV template with headers for bulk store creation. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_import_csv.md b/docs/kfutil_stores_import_csv.md index 5e8237f..c34731e 100644 --- a/docs/kfutil_stores_import_csv.md +++ b/docs/kfutil_stores_import_csv.md @@ -4,10 +4,49 @@ Create certificate stores from CSV file. ### Synopsis -Certificate stores: Will parse a CSV and attempt to create a certificate store for each row with the provided parameters. -'store-type-name' OR 'store-type-id' are required. -'file' is the path to the file to be imported. -'resultspath' is where the import results will be written to. +Will parse a CSV file and attempt to create a certificate store for each row with the provided parameters. +Any errors encountered will be logged to the _results.csv file, under the 'Errors' column. + +Required Flags: +- '--store-type-name' OR '--store-type-id' +- '--file' is the path to the file to be imported. + +#### Credentials + +##### In the CSV file: + +###### Credential Fields + +| Header | Description | +|---------------------------|---------------------------------------------------------------------------------------| +| Properties.ServerUsername | This is equivalent to the 'ServerUsername' field in the Command Certificate Store UI. | +| Properties.ServerPassword | This is equivalent to the 'ServerPassword' field in the Command Certificate Store UI. | +| Password | This is equivalent to the 'StorePassword' field in the Command Certificate Store UI. | + +###### Inventory Schedule Fields + +For full information on certificate store schedules +visit: https://software.keyfactor.com/Core-OnPrem/v25.1.1/Content/WebAPI/KeyfactorAPI/CertificateStoresPostSchedule.htm#API-Table-Schedule + +> [!NOTE] +> Only one type of schedule can be specified in the CSV file. If multiple are specified, +> the last one will be used. For example you can't schedule both "InventorySchedule.Immediate" and "InventorySchedule. +> Interval.Minutes", in which case the value of "InventorySchedule.Interval.Minutes" would be used. + +| Header | Description | +|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| InventorySchedule.Immediate | A Boolean that indicates a job scheduled to run immediately (TRUE) or not (FALSE). | +| InventorySchedule.Interval.Minutes | An integer indicating the number of minutes between each interval. | +| InventorySchedule.Daily.Time | The date and time to next run the job. The date and time should be given using the ISO 8601 UTC time format "YYYY-MM-DDTHH:mm:ss.000Z"" (e.g. 2023-11-19T16:23:01Z). | +| InventorySchedule.Weekly.Days | An array of values representing the days of the week on which to run the job. These can either be entered as integers (0 for Sunday, 1 for Monday, etc.) or as days of the week (e.g. "Sunday"). | +| InventorySchedule.Weekly.Time | The time of day to inventory daily, RFC3339 format. Ex. "2023-10-01T12:00:00Z" for noon UTC. | + +##### Outside CSV file: +If you do not wish to include credentials in your CSV file they can be provided one of three ways: +- via the --server-username --server-password and --store-password flags +- via environment variables: KFUTIL_CSV_SERVER_USERNAME, KFUTIL_CSV_SERVER_PASSWORD, KFUTIL_CSV_STORE_PASSWORD +- via interactive prompts + ``` kfutil stores import csv --file --store-type-id --store-type-name --results-path --dry-run [flags] @@ -16,12 +55,15 @@ kfutil stores import csv --file --store-type-id _results.csv - -i, --store-type-id int The ID of the cert store type for the stores. (default -1) - -n, --store-type-name string The name of the cert store type. Use if store-type-id is unknown. + -d, --dry-run Do not import, just check for necessary fields. + -f, --file string CSV file containing cert stores to create. + -h, --help help for csv + -o, --results-path string CSV file containing cert stores to create. defaults to _results.csv + -p, --server-password Properties.ServerPassword The password Keyfactor Command will use to use connect to the certificate store host. This field can be specified in the CSV file in the column Properties.ServerPassword. This value can also be sourced from the environmental variable `KFUTIL_CSV_SERVER_PASSWORD`. *NOTE* a value provided in the CSV file will override any other input value + -u, --server-username Properties.ServerUsername The username Keyfactor Command will use to use connect to the certificate store host. This field can be specified in the CSV file in the column Properties.ServerUsername. This value can also be sourced from the environmental variable `KFUTIL_CSV_SERVER_USERNAME`. *NOTE* a value provided in the CSV file will override any other input value + -s, --store-password Password The credential information Keyfactor Command will use to access the certificates in a specific certificate store (the store password). This is different from credential information Keyfactor Command uses to access a certificate store host. This field can be specified in the CSV file in the column Password. This value can also be sourced from the environmental variable `KFUTIL_CSV_STORE_PASSWORD`. *NOTE* a value provided in the CSV file will override any other input value + -i, --store-type-id int The ID of the cert store type for the stores. (default -1) + -n, --store-type-name string The name of the cert store type. Use if store-type-id is unknown. ``` ### Options inherited from parent commands @@ -49,6 +91,7 @@ kfutil stores import csv --file --store-type-id --store-t ### SEE ALSO -* [kfutil stores import](kfutil_stores_import.md) - Import a file with certificate store parameters and create them in keyfactor. +* [kfutil stores import](kfutil_stores_import.md) - Import a file with certificate store definitions and create them + in Keyfactor Command. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_inventory.md b/docs/kfutil_stores_inventory.md index 5445f58..bdfee73 100644 --- a/docs/kfutil_stores_inventory.md +++ b/docs/kfutil_stores_inventory.md @@ -42,4 +42,4 @@ Commands related to certificate store inventory management * [kfutil stores inventory remove](kfutil_stores_inventory_remove.md) - Removes a certificate from the certificate store inventory. * [kfutil stores inventory show](kfutil_stores_inventory_show.md) - Show the inventory of a certificate store. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_inventory_add.md b/docs/kfutil_stores_inventory_add.md index d783dd5..3f2c38c 100644 --- a/docs/kfutil_stores_inventory_add.md +++ b/docs/kfutil_stores_inventory_add.md @@ -57,4 +57,4 @@ kfutil stores inventory add [flags] * [kfutil stores inventory](kfutil_stores_inventory.md) - Commands related to certificate store inventory management -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_inventory_clear.md b/docs/kfutil_stores_inventory_clear.md index fd44857..4206c40 100644 --- a/docs/kfutil_stores_inventory_clear.md +++ b/docs/kfutil_stores_inventory_clear.md @@ -37,4 +37,4 @@ kfutil stores inventory clear [flags] * [kfutil stores inventory](kfutil_stores_inventory.md) - Commands related to certificate store inventory management -###### Auto generated by spf13/cobra on 14-Jun-2023 +###### Auto generated on 14-Jun-2023 diff --git a/docs/kfutil_stores_inventory_remove.md b/docs/kfutil_stores_inventory_remove.md index 8c00d8c..06719df 100644 --- a/docs/kfutil_stores_inventory_remove.md +++ b/docs/kfutil_stores_inventory_remove.md @@ -53,4 +53,4 @@ kfutil stores inventory remove [flags] * [kfutil stores inventory](kfutil_stores_inventory.md) - Commands related to certificate store inventory management -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_inventory_show.md b/docs/kfutil_stores_inventory_show.md index 5c337ea..e6d6bc2 100644 --- a/docs/kfutil_stores_inventory_show.md +++ b/docs/kfutil_stores_inventory_show.md @@ -47,4 +47,4 @@ kfutil stores inventory show [flags] * [kfutil stores inventory](kfutil_stores_inventory.md) - Commands related to certificate store inventory management -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_list.md b/docs/kfutil_stores_list.md index fc471da..9557d62 100644 --- a/docs/kfutil_stores_list.md +++ b/docs/kfutil_stores_list.md @@ -43,4 +43,4 @@ kfutil stores list [flags] * [kfutil stores](kfutil_stores.md) - Keyfactor certificate stores APIs and utilities. -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_rot.md b/docs/kfutil_stores_rot.md index ff49440..d47038c 100644 --- a/docs/kfutil_stores_rot.md +++ b/docs/kfutil_stores_rot.md @@ -30,17 +30,20 @@ kfutil stores rot reconcile --import-csv --api-path string API Path to use for authenticating to Keyfactor Command. (default is KeyfactorAPI) (default "KeyfactorAPI") --auth-provider-profile string The profile to use defined in the securely stored config. If not specified the config named 'default' will be used if it exists. (default "default") --auth-provider-type string Provider type choices: (azid) + --client-id string OAuth2 client-id to use for authenticating to Keyfactor Command. + --client-secret string OAuth2 client-secret to use for authenticating to Keyfactor Command. --config string Full path to config file in JSON format. (default is $HOME/.keyfactor/command_config.json) --debug Enable debugFlag logging. --domain string Domain to use for authenticating to Keyfactor Command. --exp Enable expEnabled features. (USE AT YOUR OWN RISK, these features are not supported and may change or be removed at any time.) --format text How to format the CLI output. Currently only text is supported. (default "text") --hostname string Hostname to use for authenticating to Keyfactor Command. - --log-insecure Log insecure API requests. (USE AT YOUR OWN RISK, this WILL log sensitive information to the console.) --no-prompt Do not prompt for any user input and assume defaults or environmental variables are set. --offline Will not attempt to connect to GitHub for latest release information and resources. --password string Password to use for authenticating to Keyfactor Command. WARNING: Remember to delete your console history if providing kfcPassword here in plain text. --profile string Use a specific profile from your config file. If not specified the config named 'default' will be used if it exists. + --skip-tls-verify Disable TLS verification for API requests to Keyfactor Command. + --token-url string OAuth2 token endpoint full URL to use for authenticating to Keyfactor Command. --username string Username to use for authenticating to Keyfactor Command. ``` @@ -51,4 +54,4 @@ kfutil stores rot reconcile --import-csv * [kfutil stores rot generate-template](kfutil_stores_rot_generate-template.md) - For generating Root Of Trust template(s) * [kfutil stores rot reconcile](kfutil_stores_rot_reconcile.md) - Reconcile either takes in or will generate an audit report and then add/remove certs as needed. -###### Auto generated by spf13/cobra on 8-Sep-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_rot_audit.md b/docs/kfutil_stores_rot_audit.md index 7d8a321..02af86c 100644 --- a/docs/kfutil_stores_rot_audit.md +++ b/docs/kfutil_stores_rot_audit.md @@ -30,17 +30,20 @@ kfutil stores rot audit [flags] --api-path string API Path to use for authenticating to Keyfactor Command. (default is KeyfactorAPI) (default "KeyfactorAPI") --auth-provider-profile string The profile to use defined in the securely stored config. If not specified the config named 'default' will be used if it exists. (default "default") --auth-provider-type string Provider type choices: (azid) + --client-id string OAuth2 client-id to use for authenticating to Keyfactor Command. + --client-secret string OAuth2 client-secret to use for authenticating to Keyfactor Command. --config string Full path to config file in JSON format. (default is $HOME/.keyfactor/command_config.json) --debug Enable debugFlag logging. --domain string Domain to use for authenticating to Keyfactor Command. --exp Enable expEnabled features. (USE AT YOUR OWN RISK, these features are not supported and may change or be removed at any time.) --format text How to format the CLI output. Currently only text is supported. (default "text") --hostname string Hostname to use for authenticating to Keyfactor Command. - --log-insecure Log insecure API requests. (USE AT YOUR OWN RISK, this WILL log sensitive information to the console.) --no-prompt Do not prompt for any user input and assume defaults or environmental variables are set. --offline Will not attempt to connect to GitHub for latest release information and resources. --password string Password to use for authenticating to Keyfactor Command. WARNING: Remember to delete your console history if providing kfcPassword here in plain text. --profile string Use a specific profile from your config file. If not specified the config named 'default' will be used if it exists. + --skip-tls-verify Disable TLS verification for API requests to Keyfactor Command. + --token-url string OAuth2 token endpoint full URL to use for authenticating to Keyfactor Command. --username string Username to use for authenticating to Keyfactor Command. ``` @@ -48,4 +51,4 @@ kfutil stores rot audit [flags] * [kfutil stores rot](kfutil_stores_rot.md) - Root of trust utility -###### Auto generated by spf13/cobra on 8-Sep-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_rot_generate-template.md b/docs/kfutil_stores_rot_generate-template.md index 054ec02..92f6180 100644 --- a/docs/kfutil_stores_rot_generate-template.md +++ b/docs/kfutil_stores_rot_generate-template.md @@ -29,16 +29,19 @@ kfutil stores rot generate-template [flags] --api-path string API Path to use for authenticating to Keyfactor Command. (default is KeyfactorAPI) (default "KeyfactorAPI") --auth-provider-profile string The profile to use defined in the securely stored config. If not specified the config named 'default' will be used if it exists. (default "default") --auth-provider-type string Provider type choices: (azid) + --client-id string OAuth2 client-id to use for authenticating to Keyfactor Command. + --client-secret string OAuth2 client-secret to use for authenticating to Keyfactor Command. --config string Full path to config file in JSON format. (default is $HOME/.keyfactor/command_config.json) --debug Enable debugFlag logging. --domain string Domain to use for authenticating to Keyfactor Command. --exp Enable expEnabled features. (USE AT YOUR OWN RISK, these features are not supported and may change or be removed at any time.) --hostname string Hostname to use for authenticating to Keyfactor Command. - --log-insecure Log insecure API requests. (USE AT YOUR OWN RISK, this WILL log sensitive information to the console.) --no-prompt Do not prompt for any user input and assume defaults or environmental variables are set. --offline Will not attempt to connect to GitHub for latest release information and resources. --password string Password to use for authenticating to Keyfactor Command. WARNING: Remember to delete your console history if providing kfcPassword here in plain text. --profile string Use a specific profile from your config file. If not specified the config named 'default' will be used if it exists. + --skip-tls-verify Disable TLS verification for API requests to Keyfactor Command. + --token-url string OAuth2 token endpoint full URL to use for authenticating to Keyfactor Command. --username string Username to use for authenticating to Keyfactor Command. ``` @@ -46,4 +49,4 @@ kfutil stores rot generate-template [flags] * [kfutil stores rot](kfutil_stores_rot.md) - Root of trust utility -###### Auto generated by spf13/cobra on 8-Sep-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_stores_rot_reconcile.md b/docs/kfutil_stores_rot_reconcile.md index 6f45ed9..b50170d 100644 --- a/docs/kfutil_stores_rot_reconcile.md +++ b/docs/kfutil_stores_rot_reconcile.md @@ -35,17 +35,20 @@ kfutil stores rot reconcile [flags] --api-path string API Path to use for authenticating to Keyfactor Command. (default is KeyfactorAPI) (default "KeyfactorAPI") --auth-provider-profile string The profile to use defined in the securely stored config. If not specified the config named 'default' will be used if it exists. (default "default") --auth-provider-type string Provider type choices: (azid) + --client-id string OAuth2 client-id to use for authenticating to Keyfactor Command. + --client-secret string OAuth2 client-secret to use for authenticating to Keyfactor Command. --config string Full path to config file in JSON format. (default is $HOME/.keyfactor/command_config.json) --debug Enable debugFlag logging. --domain string Domain to use for authenticating to Keyfactor Command. --exp Enable expEnabled features. (USE AT YOUR OWN RISK, these features are not supported and may change or be removed at any time.) --format text How to format the CLI output. Currently only text is supported. (default "text") --hostname string Hostname to use for authenticating to Keyfactor Command. - --log-insecure Log insecure API requests. (USE AT YOUR OWN RISK, this WILL log sensitive information to the console.) --no-prompt Do not prompt for any user input and assume defaults or environmental variables are set. --offline Will not attempt to connect to GitHub for latest release information and resources. --password string Password to use for authenticating to Keyfactor Command. WARNING: Remember to delete your console history if providing kfcPassword here in plain text. --profile string Use a specific profile from your config file. If not specified the config named 'default' will be used if it exists. + --skip-tls-verify Disable TLS verification for API requests to Keyfactor Command. + --token-url string OAuth2 token endpoint full URL to use for authenticating to Keyfactor Command. --username string Username to use for authenticating to Keyfactor Command. ``` @@ -53,4 +56,4 @@ kfutil stores rot reconcile [flags] * [kfutil stores rot](kfutil_stores_rot.md) - Root of trust utility -###### Auto generated by spf13/cobra on 8-Sep-2024 +###### Auto generated on 17-Jun-2025 diff --git a/docs/kfutil_version.md b/docs/kfutil_version.md index 6df9ce0..43447ad 100644 --- a/docs/kfutil_version.md +++ b/docs/kfutil_version.md @@ -43,4 +43,4 @@ kfutil version [flags] * [kfutil](kfutil.md) - Keyfactor CLI utilities -###### Auto generated by spf13/cobra on 12-Dec-2024 +###### Auto generated on 17-Jun-2025 diff --git a/go.mod b/go.mod index 38cdf70..a424d59 100644 --- a/go.mod +++ b/go.mod @@ -1,42 +1,42 @@ module kfutil -go 1.23 +go 1.24.0 -toolchain go1.23.2 +toolchain go1.24.3 require ( github.com/AlecAivazis/survey/v2 v2.3.7 - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 github.com/Jeffail/gabs v1.4.0 - github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.9 + github.com/Keyfactor/keyfactor-auth-client-go v1.3.0 github.com/Keyfactor/keyfactor-go-client-sdk/v2 v2.0.0 github.com/Keyfactor/keyfactor-go-client/v3 v3.1.0 github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 github.com/creack/pty v1.1.24 - github.com/google/go-cmp v0.6.0 + github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 github.com/joho/godotenv v1.5.1 - github.com/rs/zerolog v1.33.0 - github.com/spf13/cobra v1.8.1 - github.com/spf13/pflag v1.0.5 + github.com/rs/zerolog v1.34.0 + github.com/spf13/cobra v1.9.1 + github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 - golang.org/x/crypto v0.32.0 - golang.org/x/term v0.28.0 + golang.org/x/crypto v0.39.0 + golang.org/x/term v0.32.0 gopkg.in/yaml.v3 v3.0.1 //github.com/google/go-cmp/cmp v0.5.9 ) require ( - github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.18.0 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -44,15 +44,15 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spbsoluble/go-pkcs12 v0.3.3 // indirect go.mozilla.org/pkcs7 v0.9.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/oauth2 v0.25.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.26.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 440c026..ea69bb4 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,25 @@ github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 h1:WLUIpeyv04H0RCcQHaA4TNoyrQ39Ox7V+re+iaqzTe0= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0/go.mod h1:hd8hTTIY3VmUVPRHNH7GVCHO3SHgXkJKZHReby/bnUQ= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 h1:eXnN9kaS8TiDwXjoie3hMRLuwdUBUMW9KRgOqB3mCaw= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0/go.mod h1:XIpam8wumeZ5rVMuhdDQLMfIPDf1WO3IzrCRO3e3e3o= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 h1:mrkDCdkMsD4l9wjFGhofFHFrV43Y3c53RSLKOCJ5+Ow= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1/go.mod h1:hPv41DbqMmnxcGralanA/kVlfdH5jv3T4LxGku2E1BY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo= github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= -github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.9 h1:CaPTRbFwssCFPvFQlKE4h+0LG6mKMIjAtnniuiZp+38= -github.com/Keyfactor/keyfactor-auth-client-go v1.2.0-rc.9/go.mod h1:7htRcBIWn+X4fI5jaYBALSYwP84H/djN7d8y3n0ZDQ0= +github.com/Keyfactor/keyfactor-auth-client-go v1.3.0 h1:otC213b6CYzqeN9b3CRlH1Qj1hTFIN5nqPA8gTlHdLg= +github.com/Keyfactor/keyfactor-auth-client-go v1.3.0/go.mod h1:97vCisBNkdCK0l2TuvOSdjlpvQa4+GHsMut1UTyv1jo= github.com/Keyfactor/keyfactor-go-client-sdk/v2 v2.0.0 h1:ehk5crxEGVBwkC8yXsoQXcyITTDlgbxMEkANrl1dA2Q= github.com/Keyfactor/keyfactor-go-client-sdk/v2 v2.0.0/go.mod h1:11WXGG9VVKSV0EPku1IswjHbGGpzHDKqD4pe2vD7vas= github.com/Keyfactor/keyfactor-go-client/v3 v3.1.0 h1:DQgb93m3xHZZ0FxWGFS90XI8prwS5fmIGrXNxP2IfHM= @@ -29,8 +29,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= @@ -43,10 +43,10 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= @@ -62,8 +62,8 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -83,8 +83,9 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -92,21 +93,21 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjL github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= -github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= +github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= +github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spbsoluble/go-pkcs12 v0.3.3 h1:3nh7IKn16RDpmrSMtOu1JvbB0XHYq1j+IsICdU1c7J4= github.com/spbsoluble/go-pkcs12 v0.3.3/go.mod h1:MAxKIUEIl/QVcua/I1L4Otyxl9UvLCCIktce2Tjz6Nw= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= @@ -117,16 +118,16 @@ go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI= go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -144,18 +145,18 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/main.go b/main.go index 15e0228..68cf1d6 100644 --- a/main.go +++ b/main.go @@ -16,19 +16,21 @@ package main import ( _ "embed" + "flag" + "os" "github.com/spf13/cobra/doc" "kfutil/cmd" ) func main() { - //var docsFlag bool - //flag.BoolVar(&docsFlag, "makedocs", false, "Create markdown docs.") - //flag.Parse() - //if docsFlag { - // docs() - // os.Exit(0) - //} + var docsFlag bool + flag.BoolVar(&docsFlag, "makedocs", false, "Create markdown docs.") + flag.Parse() + if docsFlag { + docs() + os.Exit(0) + } cmd.Execute() } diff --git a/pkg/version/version.go b/pkg/version/version.go index 31f7456..433d935 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -15,7 +15,7 @@ package version var ( - VERSION = "1.6.0" - BUILD_DATE = "2024-03-25" + VERSION = "1.7.0" + BUILD_DATE = "2025-05-20" COMMIT = "HEAD" ) diff --git a/readme_source.md b/readme_source.md index 50a45c0..1b81598 100644 --- a/readme_source.md +++ b/readme_source.md @@ -276,10 +276,10 @@ set of defined certificates are present in each store that meets a certain set o ```bash echo "Generating cert template file certs_template.csv" -kfutil stores rot generate-template-rot --type certs +kfutil stores rot generate-template --type certs # edit the certs_template.csv file echo "Generating stores template file stores_template.csv" -kfutil stores rot generate-template-rot --type stores +kfutil stores rot generate-template --type stores # edit the stores_template.csv file kfutil stores rot audit --add-certs certs_template.csv --stores stores_template.csv #This will audit the stores and generate a report file # review/edit the report file generated `rot_audit.csv` @@ -295,7 +295,7 @@ For full documentation, see [stores rot generate template](docs/kfutil_stores_ro This will write the file `certs_template.csv` to the current directory. ```bash -kfutil stores generate-template-rot --type certs +kfutil stores rot generate-template --type certs ``` #### Generate Certificate Store List Template @@ -305,7 +305,7 @@ For full documentation, see [stores rot generate template](docs/kfutil_stores_ro This will write the file `stores_template.csv` to the current directory. For full documentation ```bash -kfutil stores generate-template-rot --type stores +kfutil stores rot generate-template --type stores ``` #### Run Root of Trust Audit