diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 15e1dbf..96ceb10 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,4 +38,5 @@ jobs: env: # GitHub sets the GITHUB_TOKEN secret automatically. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} \ No newline at end of file + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} + GPG_TTY: $(tty) \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index e9073ea..d184672 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -33,7 +33,9 @@ archives: - format: zip name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' checksum: - extra_files: [] + extra_files: + - glob: 'integration-manifest.json' + name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' algorithm: sha256 signs: @@ -49,8 +51,35 @@ signs: - "--detach-sign" - "${artifact}" release: - extra_files: [] + extra_files: + - glob: 'integration-manifest.json' + name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' # If you want to manually examine the release before its live, uncomment this line: - draft: true + # draft: true changelog: - skip: true \ No newline at end of file + sort: asc + use: github + filters: + exclude: + - '^test:' + - '^chore' + - 'merge conflict' + - Merge pull request + - Merge remote-tracking branch + - Merge branch + - go mod tidy + groups: + - title: Dependency updates + regexp: "^.*(feat|fix)\\(deps\\)*:+.*$" + order: 300 + - title: 'New Features' + regexp: "^.*feat[(\\w)]*:+.*$" + order: 100 + - title: 'Bug fixes' + regexp: "^.*fix[(\\w)]*:+.*$" + order: 200 + - title: 'Documentation updates' + regexp: "^.*docs[(\\w)]*:+.*$" + order: 400 + - title: Other work + order: 9999 diff --git a/GNUmakefile b/GNUmakefile index 42e8a9b..3a24104 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -6,7 +6,7 @@ NAMESPACE=keyfactor WEBSITE_REPO=https://github.com/Keyfactor/kfutil NAME=kfutil BINARY=${NAME} -VERSION=0.0.2 +VERSION=v0.0.4 OS_ARCH := $(shell go env GOOS)_$(shell go env GOARCH) BASEDIR := ${HOME}/go/bin INSTALLDIR := ${BASEDIR} @@ -35,7 +35,8 @@ install: rm -rf ${INSTALLDIR}/${BINARY} mkdir -p ${INSTALLDIR} chmod oug+x ${BINARY} - mv ${BINARY} ${INSTALLDIR} + cp ${BINARY} ${INSTALLDIR} + mv ${BINARY} /usr/local/bin/${BINARY} test: @@ -46,8 +47,8 @@ fmt: gofmt -w $(GOFMT_FILES) prerelease: - git tag -d $(VERSION) - git push origin :$(VERSION) + git tag -d $(VERSION) || true + git push origin :$(VERSION) || true git tag $(VERSION) git push origin $(VERSION) diff --git a/cmd/containers.go b/cmd/containers.go new file mode 100644 index 0000000..49b53ef --- /dev/null +++ b/cmd/containers.go @@ -0,0 +1,80 @@ +/* +Copyright © 2022 NAME HERE +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// containersCmd represents the containers command +var containersCmd = &cobra.Command{ + Use: "containers", + Short: "Keyfactor CertificateStoreContainer API and utilities.", + Long: `A collections of APIs and utilities for interacting with Keyfactor certificate store containers.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("containers called") + }, +} + +var containersCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create certificate store container.", + Long: `Create certificate store container.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("containers create called") + }, +} + +var containersGetCmd = &cobra.Command{ + Use: "get", + Short: "Get certificate store container by ID or name.", + Long: `Get certificate store container by ID or name.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("containers get called") + }, +} + +var containersUpdateCmd = &cobra.Command{ + Use: "update", + Short: "Update certificate store container by ID or name.", + Long: `Update certificate store container by ID or name.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("containers update called") + }, +} + +var containersDeleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete certificate store container by ID or name.", + Long: `Delete certificate store container by ID or name.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("containers delete called") + }, +} + +var containersListCmd = &cobra.Command{ + Use: "list", + Short: "List certificate store containers.", + Long: `List certificate store containers.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("containers list called") + }, +} + +func init() { + rootCmd.AddCommand(containersCmd) + // LIST containers command + containersCmd.AddCommand(containersListCmd) + // GET containers command + containersCmd.AddCommand(containersGetCmd) + // CREATE containers command + containersCmd.AddCommand(containersCreateCmd) + // UPDATE containers command + containersCmd.AddCommand(containersUpdateCmd) + // DELETE containers command + containersCmd.AddCommand(containersDeleteCmd) + // Utility functions +} diff --git a/cmd/orchs.go b/cmd/orchs.go new file mode 100644 index 0000000..ce2b16c --- /dev/null +++ b/cmd/orchs.go @@ -0,0 +1,112 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + + "github.com/spf13/cobra" +) + +// orchsCmd represents the orchs command +var orchsCmd = &cobra.Command{ + Use: "orchs", + Short: "Keyfactor agents APIs and utilities.", + Long: `A collections of APIs and utilities for interacting with Keyfactor orchestrators.`, +} + +var getOrchestratorCmd = &cobra.Command{ + Use: "get", + Short: "Get orchestrator by ID or machine/host name.", + Long: `Get orchestrator by ID or machine/host name.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("orchestrator get called") + }, +} + +var approveOrchestratorCmd = &cobra.Command{ + Use: "approve", + Short: "Approve orchestrator by ID or machine/host name.", + Long: `Approve orchestrator by ID or machine/host name.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("orchestrator approve called") + }, +} + +var disapproveOrchestratorCmd = &cobra.Command{ + Use: "disapprove", + Short: "Disapprove orchestrator by ID or machine/host name.", + Long: `Disapprove orchestrator by ID or machine/host name.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("orchestrator disapprove called") + }, +} + +var resetOrchestratorCmd = &cobra.Command{ + Use: "reset", + Short: "Reset orchestrator by ID or machine/host name.", + Long: `Reset orchestrator by ID or machine/host name.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("orchestrator reset called") + }, +} + +var getLogsOrchestratorCmd = &cobra.Command{ + Use: "logs", + Short: "Get orchestrator logs by ID or machine/host name.", + Long: `Get orchestrator logs by ID or machine/host name.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("orchestrator logs called") + }, +} + +var listOrchestratorsCmd = &cobra.Command{ + Use: "list", + Short: "List orchestrators.", + Long: `Returns a JSON list of Keyfactor orchestrators.`, + Run: func(cmd *cobra.Command, args []string) { + log.SetOutput(ioutil.Discard) + kfClient, _ := initClient() + agents, err := kfClient.GetAgentList() + if err != nil { + log.Printf("Error: %s", err) + } + output, jErr := json.Marshal(agents) + if jErr != nil { + log.Printf("Error: %s", jErr) + } + fmt.Printf("%s", output) + }, +} + +func init() { + rootCmd.AddCommand(orchsCmd) + + // LIST orchestrators command + orchsCmd.AddCommand(listOrchestratorsCmd) + // GET orchestrator command + orchsCmd.AddCommand(getOrchestratorCmd) + // CREATE orchestrator command TODO: API NOT SUPPORTED + //orchsCmd.AddCommand(createOrchestratorCmd) + // UPDATE orchestrator command TODO: API NOT SUPPORTED + //orchsCmd.AddCommand(updateOrchestratorCmd) + // DELETE orchestrator command TODO: API NOT SUPPORTED + //orchsCmd.AddCommand(deleteOrchestratorCmd) + // APPROVE orchestrator command + orchsCmd.AddCommand(approveOrchestratorCmd) + // DISAPPROVE orchestrator command + orchsCmd.AddCommand(disapproveOrchestratorCmd) + // RESET orchestrator command + orchsCmd.AddCommand(resetOrchestratorCmd) + // GET orchestrator logs command + orchsCmd.AddCommand(getLogsOrchestratorCmd) + // SET orchestrator auth certificate reenrollment command TODO: Not implemented + //orchsCmd.AddCommand(setOrchestratorAuthCertReenrollCmd) + // Utility commands + //orchsCmd.AddCommand(downloadOrchestrator) TODO: Not implemented +} diff --git a/cmd/root.go b/cmd/root.go index 7547ab2..8ae07d3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,19 +7,35 @@ package cmd import ( "os" + "github.com/Keyfactor/keyfactor-go-client/api" "github.com/spf13/cobra" + "log" ) +func initClient() (*api.Client, error) { + var clientAuth api.AuthConfig + clientAuth.Username = os.Getenv("KEYFACTOR_USERNAME") + log.Printf("[DEBUG] Username: %s", clientAuth.Username) + clientAuth.Password = os.Getenv("KEYFACTOR_PASSWORD") + log.Printf("[DEBUG] Password: %s", clientAuth.Password) + clientAuth.Domain = os.Getenv("KEYFACTOR_DOMAIN") + log.Printf("[DEBUG] Domain: %s", clientAuth.Domain) + clientAuth.Hostname = os.Getenv("KEYFACTOR_HOSTNAME") + log.Printf("[DEBUG] Hostname: %s", clientAuth.Hostname) + + c, err := api.NewKeyfactorClient(&clientAuth) + + if err != nil { + log.Fatalf("Error creating Keyfactor client: %s", err) + } + return c, err +} + // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "kfutil", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Short: "Keyfactor CLI utilities", + Long: `A CLI wrapper around the Keyfactor Platform API.`, // Uncomment the following line if your bare application // has an action associated with it: // Run: func(cmd *cobra.Command, args []string) { }, diff --git a/cmd/rot.go b/cmd/rot.go index 90d7288..7029957 100644 --- a/cmd/rot.go +++ b/cmd/rot.go @@ -1,238 +1,852 @@ -/* -Copyright © 2022 NAME HERE - -*/ +// Package cmd /* package cmd import ( "bufio" "encoding/csv" "encoding/json" + "errors" "fmt" + "io/ioutil" "log" "os" + "strconv" "strings" "github.com/Keyfactor/keyfactor-go-client/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"` + 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" + tTypeStores templateType = "stores" + tTypeActions templateType = "actions" + reconcileDefaultFileName = "rot_audit.csv" +) + +var ( + AuditHeader = []string{"Thumbprint", "CertID", "StoreID", "StoreType", "Machine", "Path", "AddCert", "RemoveCert", "Deployed"} + StoreHeader = []string{"StoreID", "StoreType", "StoreMachine", "StorePath"} + CertHeader = []string{"Thumbprint"} +) -type RotCert struct { - //Id string `json:"id"` - ThumbPrint string `json:"thumbprint"` +// String is used both by fmt.Print and by Cobra in help text +func (e *templateType) String() string { + return string(*e) } -// rotCmd represents the rot command -var rotCmd = &cobra.Command{ - Use: "rot", - Short: "Root Of Trust", - Long: `Root Of Trust: Will parse a CSV and attempt to enroll a cert or set of certs into a list of cert stores.`, - Run: func(cmd *cobra.Command, args []string) { - var lookupFailures []string - kfClient, _ := initClient() - storesFile, _ := cmd.Flags().GetString("stores") - addRootsFile, _ := cmd.Flags().GetString("add-certs") - removeRootsFile, _ := cmd.Flags().GetString("remove-certs") - dryRun, _ := cmd.Flags().GetBool("dry-run") - 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) - for _, entry := range storeEntries { - if entry[0] == "StoreId" { - continue // Skip header - } - apiResp, err := kfClient.GetCertificateStoreByID(entry[0]) - if err != nil { - //log.Fatalf("Error getting cert store: %s", err) - log.Printf("[ERROR] Error getting cert store: %s", err) - lookupFailures = append(lookupFailures, strings.Join(entry, ",")) - continue - } - stores[entry[0]] = StoreCSVEntry{ - Id: entry[0], - Type: entry[1], - Machine: entry[2], - Path: entry[3], +// 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, kfClient *api.Client) ([][]string, map[string][]ROTAction, error) { + log.Println("[DEBUG] generateAuditReport called") + var ( + data [][]string + ) + + data = append(data, AuditHeader) + csvFile, fErr := os.Create(reconcileDefaultFileName) + if fErr != nil { + fmt.Printf("%s", fErr) + log.Fatalf("[ERROR] Error creating audit file: %s", fErr) + } + csvWriter := csv.NewWriter(csvFile) + cErr := csvWriter.Write(AuditHeader) + if cErr != nil { + fmt.Printf("%s", cErr) + log.Fatalf("[ERROR] 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 { + log.Printf("[ERROR] 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, store.ID, store.Type, store.Machine, store.Path, "false", "false", "true"} + data = append(data, row) + wErr := csvWriter.Write(row) + if wErr != nil { + fmt.Printf("%s", wErr) + log.Printf("[ERROR] Error writing audit row: %s", wErr) + } + } else { + // Cert is not deployed to this store and will need to be added + row := []string{cert, certIDStr, store.ID, store.Type, store.Machine, store.Path, "true", "false", "false"} + data = append(data, row) + wErr := csvWriter.Write(row) + if wErr != nil { + fmt.Printf("%s", wErr) + log.Printf("[ERROR] 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, + }) } - log.Printf("[DEBUG] Store: %s", apiResp) } - storesJson, _ := json.Marshal(stores) - fmt.Println(string(storesJson)) - - // Read in the add addCerts CSV - var addCerts = make(map[string]RotCert) - if addRootsFile != "" { - addCerts, err := readCertsFile(addRootsFile) - if err != nil { - log.Fatalf("Error reading addCerts file: %s", err) + } + 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] 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, store.ID, store.Type, store.Machine, store.Path, "false", "true", "true"} + data = append(data, row) + wErr := csvWriter.Write(row) + if wErr != nil { + fmt.Printf("%s", wErr) + log.Printf("[ERROR] 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, store.ID, store.Type, store.Machine, store.Path, "false", "false", "false"} + data = append(data, row) + wErr := csvWriter.Write(row) + if wErr != nil { + fmt.Printf("%s", wErr) + log.Printf("[ERROR] Error writing row to CSV: %s", wErr) + } } - addCertsJson, _ := json.Marshal(addCerts) - fmt.Printf("[DEBUG] add certs JSON: %s", string(addCertsJson)) - fmt.Println("add rot called") - } else { - log.Printf("[DEBUG] No addCerts file specified") - log.Printf("[DEBUG] No addCerts = %s", addCerts) } + } + csvWriter.Flush() + ioErr := csvFile.Close() + if ioErr != nil { + fmt.Println(ioErr) + log.Printf("[ERROR] Error closing audit file: %s", ioErr) + } + fmt.Printf("Audit report written to %s\n", reconcileDefaultFileName) + return data, actions, nil +} - // Read in the remove removeCerts CSV - var removeCerts = make(map[string]RotCert) - if removeRootsFile != "" { - removeCerts, err := readCertsFile(removeRootsFile) - if err != nil { - log.Fatalf("Error reading removeCerts file: %s", err) +func reconcileRoots(actions map[string][]ROTAction, kfClient *api.Client, 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 + } + 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, + } + _, 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 (%d) from store %s (%s): %s", a.Thumbprint, a.CertID, a.StoreID, a.StorePath, err) + log.Fatalf("[ERROR] Error removing cert from store: %s", err) + } + } else { + log.Printf("[INFO] DRY RUN: Would have removed cert %s from store %s", thumbprint, a.StoreID) + } } - removeCertsJson, _ := json.Marshal(removeCerts) - fmt.Println(string(removeCertsJson)) - fmt.Println("remove rot called") - } else { - log.Printf("[DEBUG] No removeCerts file specified") - log.Printf("[DEBUG] No removeCerts = %s", removeCerts) } - }, + } + return nil } -func readCertsFile(certsFilePath string) (map[string]RotCert, error) { +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]RotCert) + var certs = make(map[string]string) for _, entry := range certEntries { switch entry[0] { - case "CertId", "thumbprint", "id", "certId", "Thumbprint": + case "CertID", "thumbprint", "id", "CertId", "Thumbprint": continue // Skip header } - - certs[entry[0]] = RotCert{ - ThumbPrint: entry[0], - } - // Get certificate context - //args := &api.GetCertificateContextArgs{ - // IncludeMetadata: boolToPointer(true), - // IncludeLocations: boolToPointer(true), - // CollectionId: nil, - // Id: certificateIdInt, - //} - //cResp, err := r.p.client.GetCertificateContext(args) + certs[entry[0]] = entry[0] } return certs, nil } -var rotGenStoreTemplateCmd = &cobra.Command{ - Use: "generate-template-rot", - Short: "For generating Root Of Trust template(s)", - Long: `Root Of Trust: Will parse a CSV and attempt to enroll a cert or set of certs into a list of cert stores.`, - Run: func(cmd *cobra.Command, args []string) { +func isRootStore(st *api.GetStoreByIDResp, invs *[]api.CertStoreInventory, 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) - templateType, _ := cmd.Flags().GetString("type") - format, _ := cmd.Flags().GetString("format") - outpath, _ := cmd.Flags().GetString("outpath") + 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 + } - // Create CSV template file + return true +} - var filePath string - if outpath != "" { - filePath = outpath - } else { - filePath = fmt.Sprintf("%s_template.%s", templateType, format) - } - file, err := os.Create(filePath) - if err != nil { - log.Fatal("Cannot create file", err) - } +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.`, + } + rotAuditCmd = &cobra.Command{ + Use: "audit", + Aliases: nil, + SuggestFor: nil, + Short: "Root Of Trust Audit", + 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) { + var lookupFailures []string + kfClient, _ := initClient() + 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") + // 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] Error getting cert store: %s", err) + _ = append(lookupFailures, strings.Join(entry, ",")) + continue + } + + inventory, invErr := kfClient.GetCertStoreInventory(entry[0]) + if invErr != nil { + log.Printf("[ERROR] Error getting cert store inventory for: %s\n%s", entry[0], 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) + } - switch format { - case "csv": - writer := csv.NewWriter(file) - var data = [][]string{} - switch templateType { - case "stores": - data = [][]string{ - {"StoreId", "StoreType", "StoreMachine", "StorePath"}, + 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), } - case "certs": - data = [][]string{ - {"Thumbprint"}, + 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 + } } + } - csvErr := writer.WriteAll(data) - if csvErr != nil { - fmt.Println(csvErr) + + // Read in the add addCerts CSV + var certsToAdd = make(map[string]string) + if addRootsFile != "" { + certsToAdd, _ = readCertsFile(addRootsFile, kfClient) + //if err != nil { + // log.Fatalf("Error reading addCerts file: %s", err) + //} + 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) } - 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) + // Read in the remove removeCerts CSV + var certsToRemove = make(map[string]string) + if removeRootsFile != "" { + certsToRemove, rErr := readCertsFile(removeRootsFile, kfClient) + if rErr != nil { + fmt.Printf("Error reading removeCerts file: %s", rErr) + log.Fatalf("Error reading removeCerts file: %s", rErr) + } + 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, 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: "Root Of Trust", + Long: `Root Of Trust: Will parse a CSV and attempt to enroll 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) { + var lookupFailures []string + kfClient, _ := initClient() + 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") + 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 + inFile, cErr := csv.NewReader(csvFile).ReadAll() + if cErr != nil { + 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 _, 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{}) -func isRootStore(client *api.Client) bool { - //client.GetCertInventory() - return true -} + 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 + } + + } + addCert, _ := strconv.ParseBool(action["AddCert"].(string)) + removeCert, _ := strconv.ParseBool(action["RemoveCert"].(string)) + + a := ROTAction{ + StoreID: action["StoreID"].(string), + StoreType: action["StoreType"].(string), + StorePath: action["Path"].(string), + Thumbprint: action["Thumbprint"].(string), + CertID: action["CertID"].(int), + AddCert: addCert, + RemoveCert: removeCert, + } + + actions[a.Thumbprint] = append(actions[a.Thumbprint], a) + + //actions[cert] = ROTAction{ + // Thumbprint: cert, + // CertID: certID, + // StoreID: store.ID, + // StoreType: store.Type, + // StorePath: store.Path, + // AddCert: true, + // RemoveCert: false, + //} + } + if len(actions) == 0 { + fmt.Println("No reconciliation actions to take, root stores are up-to-date. Exiting.") + return + } + rErr := reconcileRoots(actions, kfClient, dryRun) + if rErr != nil { + fmt.Printf("Error reconciling roots: %s", rErr) + log.Fatalf("[ERROR] Error reconciling roots: %s", rErr) + } + defer csvFile.Close() + fmt.Println("Reconciliation completed. Check orchestrator jobs for details.") + } 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] Error getting cert store: %s", err) + lookupFailures = append(lookupFailures, entry[0]) + continue + } + inventory, invErr := kfClient.GetCertStoreInventory(entry[0]) + if invErr != nil { + log.Fatalf("[ERROR] 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] 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") + } -func initClient() (*api.Client, error) { - var clientAuth api.AuthConfig - clientAuth.Username = os.Getenv("KEYFACTOR_USERNAME") - log.Printf("[DEBUG] Username: %s", clientAuth.Username) - clientAuth.Password = os.Getenv("KEYFACTOR_PASSWORD") - log.Printf("[DEBUG] Password: %s", clientAuth.Password) - clientAuth.Domain = os.Getenv("KEYFACTOR_DOMAIN") - log.Printf("[DEBUG] Domain: %s", clientAuth.Domain) - clientAuth.Hostname = os.Getenv("KEYFACTOR_HOSTNAME") - log.Printf("[DEBUG] Hostname: %s", clientAuth.Hostname) + // 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, kfClient) + if err != nil { + log.Fatalf("[ERROR] 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, dryRun) + if rErr != nil { + fmt.Printf("Error reconciling roots: %s", rErr) + log.Fatalf("[ERROR] Error reconciling roots: %s", rErr) + } + if lookupFailures != nil { + fmt.Printf("The following stores could not be found: %s", strings.Join(lookupFailures, ",")) + } + fmt.Println("Reconciliation completed. Check orchestrator jobs for details.") + } + + }, + 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 enroll 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) { - c, err := api.NewKeyfactorClient(&clientAuth) + templateType, _ := cmd.Flags().GetString("type") + format, _ := cmd.Flags().GetString("format") + outPath, _ := cmd.Flags().GetString("outPath") - if err != nil { - log.Fatalf("Error creating Keyfactor client: %s", err) + // 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 { + log.Fatal("Cannot create file", err) + } + + switch format { + case "csv": + writer := csv.NewWriter(file) + var data []string + switch templateType { + case "stores": + data = StoreHeader + case "certs": + data = CertHeader + case "actions": + data = AuditHeader + } + csvErr := writer.WriteAll([][]string{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) + } + } + + }, + 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, } - return c, err -} +) func init() { + log.SetFlags(log.LstdFlags | log.Lshortfile) + log.SetOutput(os.Stdout) + log.SetOutput(ioutil.Discard) //todo: remove this and set it global + var ( + stores string + addCerts string + removeCerts string + minCertsInStore int + maxPrivateKeys int + maxLeaves int + tType = tTypeCerts + outPath string + outputFormat string + inputFile string + ) + storesCmd.AddCommand(rotCmd) - var stores string - var certs string - rotCmd.Flags().StringVarP(&stores, "stores", "s", "", "CSV file containing cert stores to enroll into") - rotCmd.MarkFlagRequired("stores") - rotCmd.Flags().StringVarP(&certs, "add-certs", "a", "", "CSV file containing cert(s) to enroll into the defined cert stores") - rotCmd.Flags().StringVarP(&certs, "remove-certs", "r", "", "CSV file containing cert(s) to remove from the defined cert stores") - - rotCmd.Flags().BoolP("dry-run", "d", false, "Dry run mode") - rotCmd.MarkFlagRequired("certs") - storesCmd.AddCommand(rotGenStoreTemplateCmd) - rotGenStoreTemplateCmd.Flags().String("outpath", "template.csv", "Output file to write the template to") - rotGenStoreTemplateCmd.Flags().String("format", "csv", "The type of template to generate. Only `csv` is supported at this time.") - rotGenStoreTemplateCmd.Flags().String("type", "stores", "The type of template to generate. Only `certs|stores` are supported at this time.") - //rotGenStoreTemplateCmd.MarkFlagRequired("type") - //rotGenStoreTemplateCmd.MarkFlagRequired("format") - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // rotCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // rotCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + + // 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.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.RegisterFlagCompletionFunc("type", templateTypeCompletion) + rotGenStoreTemplateCmd.MarkFlagRequired("type") } diff --git a/cmd/status.go b/cmd/status.go new file mode 100644 index 0000000..2d85d84 --- /dev/null +++ b/cmd/status.go @@ -0,0 +1,46 @@ +/* +Copyright © 2022 NAME HERE + +*/ +package cmd + +import ( + "fmt" + "github.com/spf13/cobra" +) + +// statusCmd represents the status command +var statusCmd = &cobra.Command{ + Use: "status", + Short: "List the status of Keyfactor services.", + Long: `Returns a list of all API endpoints.`, + Run: func(cmd *cobra.Command, args []string) { + //log.SetOutput(ioutil.Discard) + //kfClient, _ := initClient() + //status, err := kfClient.GetStatus() + //if err != nil { + // log.Printf("Error: %s", err) + //} + //output, jErr := json.Marshal(status) + //if jErr != nil { + // log.Printf("Error: %s", jErr) + //} + //fmt.Printf("%s", output) + // + fmt.Println("status called") + }, +} + +func init() { + rootCmd.AddCommand(statusCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // statusCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // statusCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/storeTypes.go b/cmd/storeTypes.go new file mode 100644 index 0000000..b4ba286 --- /dev/null +++ b/cmd/storeTypes.go @@ -0,0 +1,340 @@ +/* +Copyright © 2022 NAME HERE +*/ +package cmd + +import ( + "encoding/json" + "fmt" + "github.com/Keyfactor/keyfactor-go-client/api" + "io/ioutil" + "log" + "sort" + "strings" + + "github.com/spf13/cobra" +) + +// Flag enums + +// End enums + +// Helpers +func buildStoreTypePropertiesInterface(properties []interface{}) ([]api.StoreTypePropertyDefinition, error) { + var output []api.StoreTypePropertyDefinition + + for _, prop := range properties { + log.Printf("Prop: %v", prop) + p := prop.(map[string]interface{}) + output = append(output, api.StoreTypePropertyDefinition{ + Name: p["Name"].(string), + DisplayName: p["DisplayName"].(string), + Type: p["Type"].(string), + DependsOn: p["DependsOn"].(string), + DefaultValue: p["DefaultValue"], + Required: p["Required"].(bool), + }) + } + + return output, nil +} + +// End helpers + +// storeTypesCmd represents the storeTypes command +var storeTypesCmd = &cobra.Command{ + Use: "store-types", + Short: "Keyfactor certificate store types APIs and utilities.", + Long: `A collections of APIs and utilities for interacting with Keyfactor certificate store types.`, +} + +// storesTypesListCmd represents the list command +var storesTypesListCmd = &cobra.Command{ + Use: "list", + Short: "List certificate store types.", + Long: `List certificate store types.`, + Run: func(cmd *cobra.Command, args []string) { + log.SetOutput(ioutil.Discard) + kfClient, _ := initClient() + storeTypes, err := kfClient.ListCertificateStoreTypes() + if err != nil { + log.Printf("Error: %s", err) + fmt.Printf("Error: %s\n", err) + return + } + output, jErr := json.Marshal(storeTypes) + if jErr != nil { + log.Printf("Error: %s", jErr) + } + fmt.Printf("%s", output) + }, +} + +var storesTypeGetCmd = &cobra.Command{ + Use: "get", + Short: "Get a specific store type by either name or ID.", + Long: `Get a specific store type by either name or ID.`, + Run: func(cmd *cobra.Command, args []string) { + log.SetOutput(ioutil.Discard) + id, _ := cmd.Flags().GetInt("id") + name, _ := cmd.Flags().GetString("name") + kfClient, _ := initClient() + var st interface{} + // Check inputs + if id < 0 && name == "" { + log.Printf("Error: ID must be a positive integer.") + fmt.Printf("Error: ID must be a positive integer.\n") + return + } else if id >= 0 && name != "" { + log.Printf("Error: ID and Name are mutually exclusive.") + fmt.Printf("Error: ID and Name are mutually exclusive.\n") + return + } else if id >= 0 { + st = id + } else if name != "" { + st = name + } else { + log.Printf("Error: Invalid input.") + fmt.Printf("Error: Invalid input.\n") + return + } + + storeTypes, err := kfClient.GetCertificateStoreType(st) + if err != nil { + log.Printf("Error: %s", err) + fmt.Printf("Error: %s\n", err) + return + } + output, jErr := json.Marshal(storeTypes) + if jErr != nil { + log.Printf("Error: %s", jErr) + } + fmt.Printf("%s", output) + }, +} + +var storesTypeCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a new certificate store type in Keyfactor.", + Long: `Create a new certificate store type in Keyfactor.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("create called") + //Check if store type is valid + validStoreTypes := getValidStoreTypes() + storeType, _ := cmd.Flags().GetString("name") + storeTypeIsValid := false + for _, v := range validStoreTypes { + if strings.EqualFold(v, strings.ToUpper(storeType)) { + log.Printf("[DEBUG] Valid store type: %s", storeType) + storeTypeIsValid = true + break + } + } + if !storeTypeIsValid { + fmt.Printf("Error: Invalid store type: %s\nValid types are: %s", storeType, validStoreTypes) + log.Fatalf("Error: Invalid store type: %s", storeType) + } else { + kfClient, _ := initClient() + storeTypeConfig, _ := readStoreTypesConfig() + log.Printf("[DEBUG] Store type config: %v", storeTypeConfig[storeType]) + sConfig := storeTypeConfig[storeType].(map[string]interface{}) + props, pErr := buildStoreTypePropertiesInterface(sConfig["Properties"].([]interface{})) + if pErr != nil { + fmt.Printf("Error: %s", pErr) + log.Printf("Error: %s", pErr) + } + createReq := api.CertificateStoreType{ + Name: storeTypeConfig[storeType].(map[string]interface{})["Name"].(string), + ShortName: storeTypeConfig[storeType].(map[string]interface{})["ShortName"].(string), + Capability: storeTypeConfig[storeType].(map[string]interface{})["Capability"].(string), + SupportedOperations: struct { + Add bool `json:"Add"` + Create bool `json:"Create"` + Discovery bool `json:"Discovery"` + Enrollment bool `json:"Enrollment"` + Remove bool `json:"Remove"` + }{ + Add: storeTypeConfig[storeType].(map[string]interface{})["SupportedOperations"].(map[string]interface{})["Add"].(bool), + Create: storeTypeConfig[storeType].(map[string]interface{})["SupportedOperations"].(map[string]interface{})["Create"].(bool), + Discovery: storeTypeConfig[storeType].(map[string]interface{})["SupportedOperations"].(map[string]interface{})["Discovery"].(bool), + Enrollment: storeTypeConfig[storeType].(map[string]interface{})["SupportedOperations"].(map[string]interface{})["Enrollment"].(bool), + Remove: storeTypeConfig[storeType].(map[string]interface{})["SupportedOperations"].(map[string]interface{})["Remove"].(bool), + }, + Properties: props, + EntryParameters: []api.EntryParameter{}, + PasswordOptions: struct { + EntrySupported bool `json:"EntrySupported"` + StoreRequired bool `json:"StoreRequired"` + Style string `json:"Style"` + }{ + EntrySupported: storeTypeConfig[storeType].(map[string]interface{})["PasswordOptions"].(map[string]interface{})["EntrySupported"].(bool), + StoreRequired: storeTypeConfig[storeType].(map[string]interface{})["PasswordOptions"].(map[string]interface{})["StoreRequired"].(bool), + Style: storeTypeConfig[storeType].(map[string]interface{})["PasswordOptions"].(map[string]interface{})["Style"].(string), + }, + //StorePathType: "", + //StorePathValue: "", + PrivateKeyAllowed: storeTypeConfig[storeType].(map[string]interface{})["PrivateKeyAllowed"].(string), + JobProperties: nil, + ServerRequired: storeTypeConfig[storeType].(map[string]interface{})["ServerRequired"].(bool), + PowerShell: storeTypeConfig[storeType].(map[string]interface{})["PowerShell"].(bool), + BlueprintAllowed: storeTypeConfig[storeType].(map[string]interface{})["BlueprintAllowed"].(bool), + CustomAliasAllowed: storeTypeConfig[storeType].(map[string]interface{})["CustomAliasAllowed"].(string), + //ServerRegistration: 0, + //InventoryEndpoint: "", + //InventoryJobType: "", + //ManagementJobType: "", + //DiscoveryJobType: "", + //EnrollmentJobType: "", + } + log.Printf("[DEBUG] Create request: %v", createReq) + createResp, err := kfClient.CreateStoreType(&createReq) + if err != nil { + fmt.Printf("Error creating store type: %s", err) + log.Printf("[ERROR] creating store type : %s", err) + } + log.Printf("[DEBUG] Create response: %v", createResp) + } + }, +} + +var storesTypeUpdateCmd = &cobra.Command{ + Use: "update", + Short: "Update a certificate store type in Keyfactor.", + Long: `Update a certificate store type in Keyfactor.`, + Run: func(cmd *cobra.Command, args []string) { + log.SetOutput(ioutil.Discard) + fmt.Println("update called") + }, +} + +var storesTypeDeleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete a specific store type by ID.", + Long: `Delete a specific store type by ID.`, + Run: func(cmd *cobra.Command, args []string) { + log.SetOutput(ioutil.Discard) + id, _ := cmd.Flags().GetInt("id") + dryRun, _ := cmd.Flags().GetBool("dry-run") + kfClient, _ := initClient() + _, err := kfClient.GetCertificateStoreType(id) + if err != nil { + log.Printf("Error: %s", err) + fmt.Printf("Error: %s\n", err) + return + } + if dryRun { + fmt.Printf("dry run delete called on certificate store type with ID: %d", id) + } else { + d, err := kfClient.DeleteCertificateStoreType(id) + if err != nil { + log.Printf("Error: %s", err) + fmt.Printf("Error: %s\n", err) + return + } + fmt.Printf("Certificate store type with ID: %d deleted", d.ID) + } + }, +} + +var fetchStoreTypes = &cobra.Command{ + Use: "templates-fetch", + Short: "Fetches store type templates from Keyfactor's Github.", + Long: `Fetches store type templates from Keyfactor's Github.`, + Run: func(cmd *cobra.Command, args []string) { + templates, err := readStoreTypesConfig() + if err != nil { + log.Printf("Error: %s", err) + fmt.Printf("Error: %s\n", err) + return + } + output, jErr := json.Marshal(templates) + if jErr != nil { + log.Printf("Error: %s", jErr) + } + fmt.Println(string(output)) + }, +} + +var generateStoreTypeTemplate = &cobra.Command{ + Use: "templates-generate", + Short: "Generates either a JSON or CSV template file for certificate store type bulk operations.", + Long: `Generates either a JSON or CSV template file for certificate store type bulk operations.`, + Run: func(cmd *cobra.Command, args []string) { + templates, err := readStoreTypesConfig() + if err != nil { + log.Printf("Error: %s", err) + fmt.Printf("Error: %s\n", err) + return + } + output, jErr := json.Marshal(templates) + if jErr != nil { + log.Printf("Error: %s", jErr) + } + fmt.Println(string(output)) + }, +} + +func readStoreTypesConfig() (map[string]interface{}, error) { + content, err := ioutil.ReadFile("./store_types.json") //todo: make this read from github + if err != nil { + log.Fatal("Error when opening file: ", err) + } + + // Now let's unmarshall the data into `payload` + //var payload map[string]api.CertificateStoreType + var datas map[string]interface{} + err = json.Unmarshal(content, &datas) + if err != nil { + log.Printf("Error during Unmarshal(): %s", err) + return nil, err + } + return datas, nil +} + +func getValidStoreTypes() []string { + validStoreTypes, _ := readStoreTypesConfig() + validStoreTypesList := make([]string, 0, len(validStoreTypes)) + for k := range validStoreTypes { + validStoreTypesList = append(validStoreTypesList, k) + } + sort.Strings(validStoreTypesList) + return validStoreTypesList + +} + +func init() { + + validTypesString := strings.Join(getValidStoreTypes(), ", ") + rootCmd.AddCommand(storeTypesCmd) + + // GET store type templates + storeTypesCmd.AddCommand(fetchStoreTypes) + + // LIST command + storeTypesCmd.AddCommand(storesTypesListCmd) + + // GET commands + storeTypesCmd.AddCommand(storesTypeGetCmd) + var storeTypeID int + var storeTypeName string + var dryRun bool + storesTypeGetCmd.Flags().IntVarP(&storeTypeID, "id", "i", -1, "ID of the certificate store type to get.") + storesTypeGetCmd.Flags().StringVarP(&storeTypeName, "name", "n", "", "Name of the certificate store type to get.") + storesTypeGetCmd.MarkFlagsMutuallyExclusive("id", "name") + + // CREATE command + storeTypesCmd.AddCommand(storesTypeCreateCmd) + storesTypeCreateCmd.Flags().StringVarP(&storeTypeName, "name", "n", "", "Name of the certificate store type to get. Valid choices are: "+validTypesString) + storesTypeCreateCmd.MarkFlagRequired("name") + + // UPDATE command + storeTypesCmd.AddCommand(storesTypeUpdateCmd) + storesTypeUpdateCmd.Flags().StringVarP(&storeTypeName, "name", "n", "", "Name of the certificate store type to get.") + + // DELETE command + storeTypesCmd.AddCommand(storesTypeDeleteCmd) + storesTypeDeleteCmd.Flags().IntVarP(&storeTypeID, "id", "i", -1, "ID of the certificate store type to get.") + storesTypeDeleteCmd.Flags().BoolVarP(&dryRun, "dry-run", "t", false, "Specifies whether to perform a dry run.") + storesTypeDeleteCmd.MarkFlagRequired("id") + +} diff --git a/cmd/stores.go b/cmd/stores.go index c54e735..5ed3f40 100644 --- a/cmd/stores.go +++ b/cmd/stores.go @@ -5,28 +5,68 @@ Copyright © 2022 NAME HERE package cmd import ( + "encoding/json" "fmt" - "github.com/spf13/cobra" + "io/ioutil" + "log" ) // storesCmd represents the stores command var storesCmd = &cobra.Command{ Use: "stores", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: + Short: "Keyfactor certificate stores APIs and utilities.", + Long: `A collections of APIs and utilities for interacting with Keyfactor certificate stores.`, + //Run: func(cmd *cobra.Command, args []string) { + // fmt.Println("stores called") + //}, +} + +var storesListCmd = &cobra.Command{ + Use: "list", + Short: "List certificate stores.", + Long: `List certificate stores.`, + Run: func(cmd *cobra.Command, args []string) { + log.SetOutput(ioutil.Discard) + kfClient, _ := initClient() + stores, err := kfClient.ListCertificateStores() + if err != nil { + log.Printf("Error: %s", err) + } + output, jErr := json.Marshal(stores) + if jErr != nil { + log.Printf("Error: %s", jErr) + } + fmt.Printf("%s", output) + }, +} -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, +var storesGetCmd = &cobra.Command{ + Use: "get", + Short: "Get a certificate store by ID.", + Long: `Get a certificate store by ID.`, Run: func(cmd *cobra.Command, args []string) { - fmt.Println("stores called") + log.SetOutput(ioutil.Discard) //todo: remove this and set it global + storeId, _ := cmd.Flags().GetString("id") + kfClient, _ := initClient() + stores, err := kfClient.GetCertificateStoreByID(storeId) + if err != nil { + log.Printf("Error: %s", err) + } + output, jErr := json.Marshal(stores) + if jErr != nil { + log.Printf("Error: %s", jErr) + } + fmt.Printf("%s", output) }, } func init() { + var storeId string rootCmd.AddCommand(storesCmd) + storesCmd.AddCommand(storesListCmd) + storesCmd.AddCommand(storesGetCmd) + storesGetCmd.Flags().StringVarP(&storeId, "id", "i", "", "ID of the certificate store to get.") // Here you will define your flags and configuration settings. diff --git a/createTypes.sh b/createTypes.sh new file mode 100644 index 0000000..f78ad79 --- /dev/null +++ b/createTypes.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +kfutil store-types create --name A10 +kfutil store-types create --name AZUREKEYVAULT +kfutil store-types create --name FORTANIX +kfutil store-types create --name GCPAPIGEE +kfutil store-types create --name HASHIVAULT +kfutil store-types create --name IISWBINDINGS +kfutil store-types create --name JKSSSH +kfutil store-types create --name PALOALTOFW +kfutil store-types create --name RFJKS +kfutil store-types create --name RFPEM +kfutil store-types create --name RFPKCS12 +kfutil store-types create --name SAMPLETYPE +kfutil store-types create --name WINCERMGMT + +# Precreated store types +kfutil store-types create --name AWS +kfutil store-types create --name F5CABUNDLES +kfutil store-types create --name F5PROFILES +kfutil store-types create --name F5PROFILESREST +kfutil store-types create --name F5WEB +kfutil store-types create --name F5WEBREST +kfutil store-types create --name FTP +kfutil store-types create --name IISPERSONAL +kfutil store-types create --name IISREVOKED +kfutil store-types create --name IISROOTS +kfutil store-types create --name JKS +kfutil store-types create --name NETSCALER +kfutil store-types create --name PEM diff --git a/go.mod b/go.mod index 456fb35..598fd6a 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module kfutil -go 1.18 +go 1.19 require ( - github.com/Keyfactor/keyfactor-go-client v1.0.2 - github.com/spf13/cobra v1.5.0 + github.com/Keyfactor/keyfactor-go-client v1.1.0 + github.com/spf13/cobra v1.6.0 ) require ( diff --git a/go.sum b/go.sum index 0cff442..0e4960d 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,13 @@ -github.com/Keyfactor/keyfactor-go-client v1.0.2 h1:rBZevcPWPsq7bMtc8p/IIMZRE9k7I05uhHBu79Er6vQ= -github.com/Keyfactor/keyfactor-go-client v1.0.2/go.mod h1:u1M1AjcwiO/Tbvc7EsNl9YTy757hO5wmey1/W/7Qkbs= +github.com/Keyfactor/keyfactor-go-client v1.1.0 h1:Q+Aa9yb86N/VmIJDoeYamjWipWoUqo0V56NRHN4tDG4= +github.com/Keyfactor/keyfactor-go-client v1.1.0/go.mod h1:u1M1AjcwiO/Tbvc7EsNl9YTy757hO5wmey1/W/7Qkbs= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spbsoluble/go-pkcs12 v0.3.1 h1:3DWrjdP3HOeYW6aTUSO9pqqAgRL8VKZLqvD5PGkLVMo= github.com/spbsoluble/go-pkcs12 v0.3.1/go.mod h1:MX7DY37hx8xHKEMuJ16EMaVT8sT+4KPqK4gTTLFGcH0= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= @@ -23,4 +22,4 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/integration-manifest.json b/integration-manifest.json new file mode 100644 index 0000000..4eb30a0 --- /dev/null +++ b/integration-manifest.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://keyfactor.github.io/integration-manifest-schema.json", + "integration_type": "api-client", + "name": "kfutil", + "status": "pilot", + "description": "", + "support_level": "kf-community", + "link_github": false, + "update_catalog": false +} + diff --git a/rot_demo.sh b/rot_demo.sh new file mode 100644 index 0000000..d075fa0 --- /dev/null +++ b/rot_demo.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# ROT Help +kfutil stores rot --help + +# Create a report of actions the utility will take based on inputs from addCerts.csv and removeCerts.csv. Actions added +# to the report will also fit the criteria of a max of 1 private key, a minimum of 3 certs in the inventory and a +# maximum of 1 leaf cert. +kfutil stores reconcile audit \ + --add-certs addCerts.csv \ + --remove-certs removeCerts.csv \ + --max-keys 1 \ + --min-certs 3 \ + --max-leaf-certs 1 \ + --stores stores.csv + +# Add certs listed in addCerts.csv to stores listed if they meet the criteria of a max of 1 private key, a minimum of 3 +# certs in the inventory and a maximum of 1 leaf cert. Then remove the certs listed in removeCerts.csv from the stores. +kfutil stores rot reconcile \ + --add-certs addCerts.csv \ + --remove-certs removeCerts.csv \ + --max-keys 1 \ + --min-certs 3 \ + --max-leaf-certs 1 \ + --stores stores.csv + +# Add certs listed in addCerts.csv to stores listed with no criteria. Then remove the certs listed in removeCerts.csv \ +# from the stores. +kfutil stores rot reconcile \ + --add-certs addCerts.csv \ + --remove-certs removeCerts.csv \ + --stores stores.csv + +# Remove all added certs from the demo +kfutil stores rot reconcile \ + --remove-certs removeCerts.csv \ + --stores stores.csv +kfutil stores rot reconcile \ + --remove-certs removeCerts2.csv \ + --stores stores.csv +kfutil stores rot reconcile \ + --remove-certs addCerts.csv \ + --stores stores.csv +kfutil stores rot reconcile \ + --remove-certs addCerts2.csv \ + --stores stores.csv + +# List stores and convert to CSV into stores.csv format +echo '"StoreId","StoreType","StoreMachine","StorePath"' > meow.csv +kfutil stores list | jq -r '.[] | [.Id, .cert_store_type, .ClientMachine, .Storepath] | @csv' >> meow.csv + diff --git a/store_types.json b/store_types.json new file mode 100644 index 0000000..d149fa2 --- /dev/null +++ b/store_types.json @@ -0,0 +1,1204 @@ +{ + "A10": { + "Name": "A10 vThunder", + "ShortName": "vThunderU", + "Capability": "vThunderU", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "protocol", + "DisplayName": "Protocol", + "Type": "String", + "DependsOn": "", + "DefaultValue": null, + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "allowInvalidCert", + "DisplayName": "Allow Invalid Cert", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Optional", + "JobProperties": [], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": true, + "CustomAliasAllowed": "Required" + }, + "AZUREKEYVAULT": { + "Name": "Azure KeyVault", + "ShortName": "AKV", + "Capability": "AKV", + "SupportedOperations": { + "Add": true, + "Create": true, + "Discovery": true, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "VaultName", + "DisplayName": "Vault Name", + "Type": "String", + "DependsOn": "", + "DefaultValue": null, + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "ResourceGroupName", + "DisplayName": "Resource Group Name", + "Type": "String", + "DependsOn": "", + "DefaultValue": null, + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Required", + "JobProperties": [], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Required" + }, + "AWS": { + "Name": "Amazon Web Services", + "ShortName": "AWS", + "Capability": "AWS", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "AccessKey", + "DisplayName": "Access Key", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "SecretKey", + "DisplayName": "Secret Key", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": null, + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "[\"AP Northeast 1\", \"AP Northeast 2\", \"AP South 1\", \"AP Southeast 1\", \"AP Southeast 2\", \"CA Central 1\", \"EU Central 1\", \"EU West 1\", \"EU West 2\", \"EU West 3\", \"SA East 1\", \"US East 1\", \"US East 2\", \"US West 1\", \"US West 2\"]", + "PrivateKeyAllowed": "Required", + "JobProperties": [], + "ServerRequired": false, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Forbidden" + }, + "F5PROFILES": { + "Name": "F5 SSL Profiles", + "ShortName": "F5", + "Capability": "F5", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Optional", + "JobProperties": [], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Required" + }, + "F5WEB": { + "Name": "F5 Web Server", + "ShortName": "F5", + "Capability": "F5", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": false + }, + "Properties": [], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "F5 Web Server", + "PrivateKeyAllowed": "Required", + "JobProperties": [], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Forbidden" + }, + "F5CABUNDLES": { + "Name": "F5 CA Bundles REST", + "ShortName": "F5-CA-REST", + "Capability": "F5-CA-REST", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": true, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "PrimaryNode", + "DisplayName": "Primary Node", + "Type": "String", + "DependsOn": "", + "DefaultValue": "", + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "PrimaryNodeCheckRetryMax", + "DisplayName": "Primary Node Check Retry Maximum", + "Type": "String", + "DependsOn": "", + "DefaultValue": "3", + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "PrimaryNodeCheckRetryWaitSecs", + "DisplayName": "Primary Node Check Retry Wait Seconds", + "Type": "String", + "DependsOn": "", + "DefaultValue": "120", + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "F5Version", + "DisplayName": "Version of F5", + "Type": "MultipleChoice", + "DependsOn": "", + "DefaultValue": "v12,v13,v14,v15", + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Forbidden", + "JobProperties": [], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": true, + "CustomAliasAllowed": "Required" + }, + "F5PROFILESREST": { + "Name": "F5 SSL Profiles REST", + "ShortName": "F5-SL-REST", + "Capability": "F5-SL-REST", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": true, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "PrimaryNode", + "DisplayName": "Primary Node", + "Type": "String", + "DependsOn": "", + "DefaultValue": "", + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "PrimaryNodeCheckRetryWaitSecs", + "DisplayName": "Primary Node Check Retry Wait Seconds", + "Type": "String", + "DependsOn": "", + "DefaultValue": "120", + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "PrimaryNodeCheckRetryMax", + "DisplayName": "Primary Node Check Retry Maximum", + "Type": "String", + "DependsOn": "", + "DefaultValue": "3", + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "F5Version", + "DisplayName": "Version of F5", + "Type": "MultipleChoice", + "DependsOn": "", + "DefaultValue": "v12,v13,v14,v15", + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Optional", + "JobProperties": [], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": true, + "CustomAliasAllowed": "Required" + }, + "F5WEBREST": { + "Name": "F5 Web Server REST", + "ShortName": "F5-WS-REST", + "Capability": "F5-WS-REST", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": false + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "PrimaryNode", + "DisplayName": "Primary Node", + "Type": "String", + "DependsOn": "", + "DefaultValue": "", + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "PrimaryNodeCheckRetryWaitSecs", + "DisplayName": "Primary Node Check Retry Wait Seconds", + "Type": "String", + "DependsOn": "", + "DefaultValue": "120", + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "PrimaryNodeCheckRetryMax", + "DisplayName": "Primary Node Check Retry Maximum", + "Type": "String", + "DependsOn": "", + "DefaultValue": "3", + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "F5Version", + "DisplayName": "Version of F5", + "Type": "MultipleChoice", + "DependsOn": "", + "DefaultValue": "v12,v13,v14,v15", + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "WebServer", + "PrivateKeyAllowed": "Required", + "JobProperties": [], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Forbidden" + }, + "FORTANIX": { + "Name": "Fortanix", + "ShortName": "Fortanix", + "Capability": "Fortanix", + "SupportedOperations": { + "Add": false, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": false + }, + "Properties": [], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": true, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Optional", + "JobProperties": [], + "ServerRequired": false, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Required", + "ServerRegistration": 0 + }, + "FTP": { + "Name": "File Transfer Protocol", + "ShortName": "FTP", + "Capability": "FTP", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Optional", + "JobProperties": [], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Forbidden" + }, + "GCPAPIGEE" : { + "Name": "Google Cloud Provider Apigee", + "ShortName": "GcpApigee", + "Capability": "GcpApigee", + "SupportedOperations": { + "Add": true, + "Create": true, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "Name": "isTrustStore", + "DisplayName": "Is Trust Store?", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": true + }, + { + "Name": "jsonKey", + "DisplayName": "Google Json Key File", + "Type": "Secret", + "DependsOn": "", + "DefaultValue": "", + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Optional", + "JobProperties": [], + "ServerRequired": false, + "PowerShell": false, + "BlueprintAllowed": true, + "CustomAliasAllowed": "Required", + "ServerRegistration": 0 + }, + "HASHIVAULT": { + "Name": "Hashicorp Vault", + "ShortName": "HCV", + "Capability": "HCV", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": true, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "MountPoint", + "DisplayName": "Mount Point", + "Type": "String", + "DependsOn": "", + "DefaultValue": null, + "Required": false + }, + { + "StoreTypeId;omitempty": 0, + "Name": "VaultServerUrl", + "DisplayName": "VaultServerURL", + "Type": "String", + "DependsOn": "", + "DefaultValue": null, + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "VaultToken", + "DisplayName": "Vault Token", + "Type": "String", + "DependsOn": "", + "DefaultValue": null, + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Forbidden", + "JobProperties": [], + "ServerRequired": false, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Forbidden" + }, + "IISROOTS": { + "Name": "IIS Roots", + "ShortName": "IIS", + "Capability": "IIS", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "UseSSL", + "DisplayName": "UseSSL", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "IIS Trusted Roots", + "PrivateKeyAllowed": "Forbidden", + "JobProperties": [], + "ServerRequired": false, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Forbidden", + "ServerRegistration": 0 + }, + "IISPERSONAL": { + "Name": "IIS Personal", + "ShortName": "IIS", + "Capability": "IIS", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "UseSSL", + "DisplayName": "UseSSL", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "IIS Personal", + "PrivateKeyAllowed": "Required", + "JobProperties": [], + "ServerRequired": false, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Optional", + "ServerRegistration": 0 + }, + "IISREVOKED": { + "Name": "IIS Revoked", + "ShortName": "IIS", + "Capability": "IIS", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "UseSSL", + "DisplayName": "UseSSL", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "IIS Revoked", + "PrivateKeyAllowed": "Optional", + "JobProperties": [], + "ServerRequired": false, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Forbidden" + }, + "IISWBINDINGS": { + "Name": "IISWithBindings", + "ShortName": "IISWBin", + "Capability": "IISBindings", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "spnwithport", + "DisplayName": "SPN With Port?", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": false + }, + { + "StoreTypeId;omitempty": 0, + "Name": "WinRm Protocol", + "DisplayName": "WinRm Protocol", + "Type": "MultipleChoice", + "DependsOn": "", + "DefaultValue": "http", + "Required": true + }, + { + "StoreTypeId;omitempty": 0, + "Name": "WinRm Port", + "DisplayName": "WinRm Port", + "Type": "String", + "DependsOn": "", + "DefaultValue": "5985", + "Required": true + } + ], + "EntryParameters": [ + { + "StoreTypeId": 103, + "Name": "Site Name", + "DisplayName": "Site Name", + "Type": "String", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": true, + "OnRemove": true, + "OnReenrollment": false + }, + "DependsOn": "", + "DefaultValue": "Default Web Site", + "Options": "" + }, + { + "StoreTypeId": 103, + "Name": "Port", + "DisplayName": "Port", + "Type": "String", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": true, + "OnRemove": true, + "OnReenrollment": false + }, + "DependsOn": "", + "DefaultValue": "443", + "Options": "" + }, + { + "StoreTypeId": 103, + "Name": "IP Address", + "DisplayName": "IP Address", + "Type": "String", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": true, + "OnRemove": true, + "OnReenrollment": false + }, + "DependsOn": "", + "DefaultValue": "*", + "Options": "" + }, + { + "StoreTypeId": 103, + "Name": "Host Name", + "DisplayName": "Host Name", + "Type": "String", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": false, + "OnRemove": false, + "OnReenrollment": false + }, + "DependsOn": "", + "DefaultValue": "", + "Options": "" + }, + { + "StoreTypeId": 103, + "Name": "Sni Flag", + "DisplayName": "Sni Flag", + "Type": "String", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": false, + "OnRemove": false, + "OnReenrollment": false + }, + "DependsOn": "", + "DefaultValue": "0 - No SNI", + "Options": "" + }, + { + "StoreTypeId": 103, + "Name": "Protocol", + "DisplayName": "Protocol", + "Type": "String", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": true, + "OnRemove": true, + "OnReenrollment": false + }, + "DependsOn": "", + "DefaultValue": "https", + "Options": "" + } + ], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "[\"My\",\"WebHosting\"]", + "PrivateKeyAllowed": "Required", + "JobProperties": [ + "Site Name", + "Port", + "IP Address", + "Host Name", + "Sni Flag", + "Protocol" + ], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Forbidden" + }, + "JKS": { + "Name": "Java Keystore", + "ShortName": "JKS", + "Capability": "JKS", + "SupportedOperations": { + "Add": true, + "Create": true, + "Discovery": true, + "Enrollment": true, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "ProviderType", + "DisplayName": "Type", + "Type": "MultipleChoice", + "DependsOn": "", + "DefaultValue": "JKS,PKCS12,Windows-MY", + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": true, + "StoreRequired": true, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Optional", + "JobProperties": [], + "ServerRequired": false, + "PowerShell": false, + "BlueprintAllowed": true, + "CustomAliasAllowed": "Required" + }, + "JKSSSH": { + "Name": "JKS-SSH", + "ShortName": "JKS-SSH", + "Capability": "JKS-SSH", + "SupportedOperations": { + "Add": true, + "Create": true, + "Discovery": true, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "linuxFilePermissionsOnStoreCreation", + "DisplayName": "Linux Create File Permissions", + "Type": "String", + "DependsOn": "", + "DefaultValue": null, + "Required": false + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": true, + "StoreRequired": true, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Optional", + "JobProperties": [], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Required" + }, + "NETSCALER": { + "Name": "NetScaler", + "ShortName": "NS", + "Capability": "NS", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [], + "EntryParameters": [ + { + "StoreTypeId": 5, + "Name": "NetscalerVserver", + "DisplayName": "NetscalerVserver", + "Type": "String", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": false, + "OnRemove": false, + "OnReenrollment": false + }, + "DependsOn": "", + "DefaultValue": "", + "Options": "" + } + ], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Optional", + "JobProperties": [ + "NetscalerVserver" + ], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Required" + }, + "PALOALTOFW": { + "Name": "PaloAlto", + "ShortName": "PaloAlto", + "Capability": "PaloAlto", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [], + "EntryParameters": [ + { + "StoreTypeId": 112, + "Name": "Trusted Root", + "DisplayName": "Trusted Root", + "Type": "Bool", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": true, + "OnRemove": false, + "OnReenrollment": false + }, + "DependsOn": "", + "DefaultValue": "false", + "Options": "" + } + ], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Optional", + "JobProperties": [ + "Trusted Root" + ], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Required" + }, + "PEM": { + "Name": "PEM File", + "ShortName": "PEM", + "Capability": "PEM", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": true, + "Enrollment": true, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "separatePrivateKey", + "DisplayName": "Separate Private Key", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": false + }, + { + "StoreTypeId;omitempty": 0, + "Name": "privateKeyPath", + "DisplayName": "Path To Private Key File", + "Type": "String", + "DependsOn": "separatePrivateKey", + "DefaultValue": null, + "Required": true + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Custom" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Optional", + "JobProperties": [], + "ServerRequired": false, + "PowerShell": false, + "BlueprintAllowed": true, + "CustomAliasAllowed": "Forbidden" + }, + "RFJKS" : { + "Name": "RFJKS", + "ShortName": "RFJKS", + "Capability": "RFJKS", + "ServerRequired": true, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Required", + "PowerShell": false, + "PrivateKeyAllowed": "Optional", + "SupportedOperations": { + "Add": true, + "Create": true, + "Discovery": true, + "Enrollment": false, + "Remove": true + }, + "PasswordOptions": { + "Style": "Default", + "EntrySupported": false, + "StoreRequired": true + }, + "Properties": [ + { + "Name": "LinuxFilePermissionsOnStoreCreation", + "DisplayName": "Linux File Permissions on Store Creation", + "Required": false, + "DependsOn": "", + "Type": "String", + "DefaultValue": "" + } + ], + "EntryParameters": [] + }, + "RFPEM" : { + "Name": "RFPEM", + "ShortName": "RFPEM", + "Capability": "RFPEM", + "ServerRequired": true, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Forbidden", + "PowerShell": false, + "PrivateKeyAllowed": "Optional", + "SupportedOperations": { + "Add": true, + "Create": true, + "Discovery": true, + "Enrollment": false, + "Remove": true + }, + "PasswordOptions": { + "Style": "Default", + "EntrySupported": false, + "StoreRequired": true + }, + "Properties": [ + { + "Name": "LinuxFilePermissionsOnStoreCreation", + "DisplayName": "Linux File Permissions on Store Creation", + "Required": false, + "DependsOn": "", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "IsTrustStore", + "DisplayName": "Trust Store", + "Required": false, + "DependsOn": "", + "Type": "Bool", + "DefaultValue": false + }, + { + "Name": "IncludesChain", + "DisplayName": "Store Includes Chain", + "Required": false, + "DependsOn": "", + "Type": "Bool", + "DefaultValue": false + }, + { + "Name": "SeparatePrivateKeyFilePath", + "DisplayName": "Separate Private Key File Location", + "Required": false, + "DependsOn": "", + "Type": "String", + "DefaultValue": "" + } + ], + "EntryParameters": [] + }, + "RFPKCS12" : { + "Name": "RFPkcs12", + "ShortName": "RFPkcs12", + "Capability": "RFPkcs12", + "ServerRequired": true, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Required", + "PowerShell": false, + "PrivateKeyAllowed": "Optional", + "SupportedOperations": { + "Add": true, + "Create": true, + "Discovery": true, + "Enrollment": false, + "Remove": true + }, + "PasswordOptions": { + "Style": "Default", + "EntrySupported": false, + "StoreRequired": true + }, + "Properties": [ + { + "Name": "LinuxFilePermissionsOnStoreCreation", + "DisplayName": "Linux File Permissions on Store Creation", + "Required": false, + "DependsOn": "", + "Type": "String", + "DefaultValue": "" + } + ], + "EntryParameters": [] + }, + "SAMPLETYPE": { + "Name": "Sample Store Type", + "ShortName": "SAMPLETYPE", + "Capability": "SAMPLETYPE", + "SupportedOperations": { + "Add": false, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": false + }, + "Properties": [], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Forbidden", + "JobProperties": [], + "ServerRequired": false, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Forbidden" + }, + "WINCERMGMT": { + "Name": "Windows Certificate Store Manager", + "ShortName": "WinCerMgmt", + "Capability": "WinCerMgmt", + "SupportedOperations": { + "Add": true, + "Create": false, + "Discovery": false, + "Enrollment": false, + "Remove": true + }, + "Properties": [ + { + "StoreTypeId;omitempty": 0, + "Name": "spnwithport", + "DisplayName": "spnwithport", + "Type": "Bool", + "DependsOn": "", + "DefaultValue": "false", + "Required": false + } + ], + "EntryParameters": [], + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": false, + "Style": "Default" + }, + "StorePathType": "", + "StorePathValue": "", + "PrivateKeyAllowed": "Optional", + "JobProperties": [], + "ServerRequired": true, + "PowerShell": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Forbidden" + } +} \ No newline at end of file