From cac82d1eddcd8311b2e4d65ba8670cae8bb7c72b Mon Sep 17 00:00:00 2001 From: Luis Presuel Date: Tue, 21 May 2024 17:46:39 -0600 Subject: [PATCH 01/11] first commit for provisioning with vcert cli --- cmd/vcert/args.go | 7 ++ cmd/vcert/cmdCloudProviders.go | 108 ++++++++++++++++++ cmd/vcert/flags.go | 46 ++++++++ cmd/vcert/main.go | 2 + cmd/vcert/result_writer.go | 70 ++++++++++-- cmd/vcert/usage.go | 15 --- cmd/vcert/utils.go | 63 ++++++---- cmd/vcert/validators.go | 18 +++ go.sum | 5 + pkg/endpoint/provisioning.go | 6 +- pkg/util/utils.go | 18 +++ pkg/venafi/cloud/cloudproviders.go | 15 +++ pkg/venafi/cloud/connector.go | 72 +++++++----- .../cloudproviders/cloudproviders.go | 21 +--- 14 files changed, 371 insertions(+), 95 deletions(-) create mode 100644 cmd/vcert/cmdCloudProviders.go diff --git a/cmd/vcert/args.go b/cmd/vcert/args.go index 6f413f7a..6cb9e065 100644 --- a/cmd/vcert/args.go +++ b/cmd/vcert/args.go @@ -36,6 +36,8 @@ const ( commandSshPickupName = "sshpickup" commandSshEnrollName = "sshenroll" commandSshGetConfigName = "sshgetconfig" + commandProvisionName = "provision" + subCommandCloudKeystore = "cloudkeystore" ) var ( @@ -141,4 +143,9 @@ type commandFlags struct { sshCertWindows bool sshFileCertEnroll string sshFileGetConfig string + certificateID string + keystoreID string + providerName string + keystoreName string + keystoreCertName string } diff --git a/cmd/vcert/cmdCloudProviders.go b/cmd/vcert/cmdCloudProviders.go new file mode 100644 index 00000000..dd4c3026 --- /dev/null +++ b/cmd/vcert/cmdCloudProviders.go @@ -0,0 +1,108 @@ +package main + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/urfave/cli/v2" + + "github.com/Venafi/vcert/v5" + "github.com/Venafi/vcert/v5/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/venafi/cloud" +) + +var ( + commandProvision = &cli.Command{ + Before: runBeforeCommand, + Name: commandProvisionName, + Usage: "To provision a certificate", + UsageText: ` vcert provision + + vcert provision cloudkeystore -k + vcert provision cloudkeystore -k + vcert provision cloudkeystore -p vcp -t `, + Subcommands: []*cli.Command{ + { + Name: subCommandCloudKeystore, + Flags: provisionFlags, + Usage: "set Cloud Keystore for provision", + UsageText: `vcert provision cloudkeystore`, + Action: doCommandProvision, + }, + }, + } +) + +func doCommandProvision(c *cli.Context) error { + err := validateProvisionFlags(c.Command.Name) + if err != nil { + return err + } + err = setTLSConfig() + if err != nil { + return err + } + + cfg, err := buildConfig(c, &flags) + if err != nil { + return fmt.Errorf("Failed to build vcert config: %s", err) + } + + connector, err := vcert.NewClient(&cfg) + if err != nil { + logf("Unable to connect to %s: %s", cfg.ConnectorType, err) + } else { + logf("Successfully connected to %s", cfg.ConnectorType) + } + + var req = &endpoint.ProvisioningRequest{} + var options *endpoint.ProvisioningOptions + + log.Printf("fetching keystore information for provided keystore information from flags. KeystoreID: %s, KeystoreName: %s, ProviderName: %s", flags.keystoreID, flags.keystoreName, flags.providerName) + getKeystoreReq := buildGetCloudKeystoreRequest(&flags) + cloudKeystore, err := connector.(*cloud.Connector).GetCloudKeystore(getKeystoreReq) + if err != nil { + return err + } + log.Printf("successfully fetched keystore") + + if flags.pickupIDFile != "" { + bytes, err := os.ReadFile(flags.pickupIDFile) + if err != nil { + return fmt.Errorf("failed to read Pickup ID value: %s", err) + } + flags.pickupID = strings.TrimSpace(string(bytes)) + } + + req, options = fillProvisioningRequest(req, *cloudKeystore, &flags) + + metadata, err := connector.ProvisionCertificate(req, options) + if err != nil { + return err + } + + arn := metadata.GetAWSCertificateMetadata().GetARN() + azureID := metadata.GetAzureCertificateMetadata().GetID() + azureName := metadata.GetAzureCertificateMetadata().GetName() + azureVersion := metadata.GetAzureCertificateMetadata().GetVersion() + gcpID := metadata.GetGCPCertificateMetadata().GetID() + gcpName := metadata.GetGCPCertificateMetadata().GetName() + + result := &ProvisioningResult{ + ARN: &arn, + AzureID: &azureID, + AzureName: &azureName, + AzureVersion: &azureVersion, + GcpID: &gcpID, + GcpName: &gcpName, + } + + err = result.Flush(flags.format) + + if err != nil { + return fmt.Errorf("failed to output the results: %s", err) + } + return nil +} diff --git a/cmd/vcert/flags.go b/cmd/vcert/flags.go index 11731a98..d4c7e2f9 100644 --- a/cmd/vcert/flags.go +++ b/cmd/vcert/flags.go @@ -708,6 +708,36 @@ var ( TakesFile: true, } + flagCertificateID = &cli.StringFlag{ + Name: "certificate-id", + Usage: "The id of the certificate to be provisioned to a cloud keystore.", + Destination: &flags.certificateID, + } + + flagKeystoreID = &cli.StringFlag{ + Name: "keystore-id", + Usage: "The id of the cloud keystore where the certificate will be provisioned.", + Destination: &flags.keystoreID, + } + + flagKeystoreName = &cli.StringFlag{ + Name: "keystore-name", + Usage: "The name of the cloud keystore where the certificate will be provisioned. Must be set along with provider-name flag.", + Destination: &flags.keystoreName, + } + + flagProviderName = &cli.StringFlag{ + Name: "provider-name", + Usage: "Name of the cloud provider which owns the cloud keystore where the certificate will be provisioned. Must be set along with keystore-name flag.", + Destination: &flags.providerName, + } + + flagKeystoreCertName = &cli.StringFlag{ + Name: "cloudkeystore-certname", + Usage: "Use to specify Cloud Keystore Certificate Name if it supports it", + Destination: &flags.keystoreCertName, + } + commonFlags = []cli.Flag{flagInsecure, flagVerbose, flagNoPrompt} keyFlags = []cli.Flag{flagKeyType, flagKeySize, flagKeyCurve, flagKeyFile, flagKeyPassword} sansFlags = []cli.Flag{flagDNSSans, flagEmailSans, flagIPSans, flagURISans, flagUPNSans} @@ -846,6 +876,22 @@ var ( )), ) + provisionFlags = flagsApppend( + credentialsFlags, + flagCertificateID, + flagPickupID, + flagPickupIDFile, + flagKeystoreID, + flagKeystoreName, + flagProviderName, + flagKeystoreCertName, + flagFormat, + sortedFlags(flagsApppend( + commonFlags, + sortableCredentialsFlags, + )), + ) + commonCredFlags = []cli.Flag{flagConfig, flagProfile, flagUrl, flagToken, flagTrustBundle} getCredFlags = sortedFlags(flagsApppend( diff --git a/cmd/vcert/main.go b/cmd/vcert/main.go index fecc6fe0..814c231a 100644 --- a/cmd/vcert/main.go +++ b/cmd/vcert/main.go @@ -86,6 +86,7 @@ func main() { commandSshEnroll, commandSshGetConfig, commandRunPlaybook, + commandProvision, }, EnableBashCompletion: true, //todo: write BashComplete function for options Authors: authors, @@ -115,6 +116,7 @@ ACTIONS: retire tpp | vcp To retire a certificate revoke tpp To revoke a certificate run tpp | vcp | firefly To retrieve and install certificates using a vcert playbook file + provision vcp To provision a certificate to cloud keystore getpolicy tpp | vcp To retrieve the certificate policy of a zone setpolicy tpp | vcp To apply a certificate policy specification to a zone diff --git a/cmd/vcert/result_writer.go b/cmd/vcert/result_writer.go index a0e30e83..e5097455 100644 --- a/cmd/vcert/result_writer.go +++ b/cmd/vcert/result_writer.go @@ -33,6 +33,10 @@ import ( "github.com/Venafi/vcert/v5/pkg/util" ) +const ( + formatJson = "json" +) + type Config struct { Command string Format string @@ -56,6 +60,15 @@ type Result struct { Config *Config } +type ProvisioningResult struct { + ARN *string `json:"arn"` + AzureID *string `json:"azureId"` + AzureName *string `json:"azureName"` + AzureVersion *string `json:"azureVersion"` + GcpID *string `json:"gcpId"` + GcpName *string `json:"gcpName"` +} + type Output struct { Certificate string `json:",omitempty"` CSR string `json:",omitempty"` @@ -315,24 +328,24 @@ func (r *Result) Flush() error { allFileOutput.Chain = r.Pcc.Chain allFileOutput.CSR = r.Pcc.CSR - var bytes []byte + var fileBytes []byte if r.Config.Format == P12Format || r.Config.Format == LegacyP12Format { - bytes, err = allFileOutput.AsPKCS12(r.Config) + fileBytes, err = allFileOutput.AsPKCS12(r.Config) if err != nil { return fmt.Errorf("failed to encode pkcs12: %s", err) } } else if r.Config.Format == JKSFormat { - bytes, err = allFileOutput.AsJKS(r.Config) + fileBytes, err = allFileOutput.AsJKS(r.Config) if err != nil { return err } } else { - bytes, err = allFileOutput.Format(r.Config) + fileBytes, err = allFileOutput.Format(r.Config) if err != nil { return err } } - err = os.WriteFile(r.Config.AllFile, bytes, 0600) + err = os.WriteFile(r.Config.AllFile, fileBytes, 0600) errors = append(errors, err) } else { @@ -388,11 +401,12 @@ func (r *Result) Flush() error { } // and flush the rest to STDOUT - bytes, err := stdOut.Format(r.Config) + configBytes, err := stdOut.Format(r.Config) if err != nil { return err // something worse than file permission problem } - fmt.Fprint(os.Stdout, string(bytes)) + _, err = fmt.Fprint(os.Stdout, string(configBytes)) + errors = append(errors, err) var finalError error for _, e := range errors { @@ -430,3 +444,45 @@ func outputJSON(resp interface{}) error { } return err } + +func (r *ProvisioningResult) Flush(format string) error { + + result, err := r.Format(format) + if err != nil { + return err + } + + _, err = fmt.Fprint(os.Stdout, result) + if err != nil { + return fmt.Errorf("failed to print provisioning result to STDOUT: %w", err) + } + + return nil +} + +func (r *ProvisioningResult) Format(format string) (string, error) { + result := "" + switch strings.ToLower(format) { + case formatJson: + b, err := json.Marshal(r) + if err != nil { + return "", fmt.Errorf("failed to construct JSON: %s", err) + } + result = string(b) + default: + if r.ARN != nil { + result += fmt.Sprintf("arn: %s\n", util.StringPointerToString(r.ARN)) + } + if r.AzureID != nil { + result += fmt.Sprintf("azureId: %s\n", util.StringPointerToString(r.AzureID)) + result += fmt.Sprintf("azureName: %s\n", util.StringPointerToString(r.AzureName)) + result += fmt.Sprintf("azureVersion: %s\n", util.StringPointerToString(r.AzureVersion)) + + } + if r.GcpID != nil { + result += fmt.Sprintf("gcpId %s:", util.StringPointerToString(r.GcpID)) + result += fmt.Sprintf("gcpName %s:", util.StringPointerToString(r.GcpName)) + } + } + return result, nil +} diff --git a/cmd/vcert/usage.go b/cmd/vcert/usage.go index e5c6ed06..6208d5fb 100644 --- a/cmd/vcert/usage.go +++ b/cmd/vcert/usage.go @@ -18,7 +18,6 @@ package main import ( "bytes" - "fmt" "unicode" ) @@ -72,17 +71,3 @@ func wrapArgumentDescriptionText(text string) string { return buf.String() } - -func showvcertUsage() { - fmt.Printf("\tTo obtain a new token for authentication, use the 'getcred' action.\n") - fmt.Printf("\tTo check whether an authentication token is valid, use the 'checkcred' action.\n") - fmt.Printf("\tTo invalidate an authentication token, use the 'voidcred' action.\n") - fmt.Printf("\tTo generate a certificate signing request (CSR), use the 'gencsr' action.\n") - fmt.Printf("\tTo enroll a certificate, use the 'enroll' action.\n") - fmt.Printf("\tTo retrieve a certificate, use the 'pickup' action.\n") - fmt.Printf("\tTo renew a certificate, use the 'renew' action.\n") - fmt.Printf("\tTo revoke a certificate, use the 'revoke' action.\n") - fmt.Printf("\tTo retire a certificate, use the 'retire' action.\n") - fmt.Printf("\tTo retrieve certificate policy, use the 'getpolicy' action.\n") - fmt.Printf("\tTo apply certificate policy, use the 'setpolicy' action.\n") -} diff --git a/cmd/vcert/utils.go b/cmd/vcert/utils.go index 61325e25..9bc62c43 100644 --- a/cmd/vcert/utils.go +++ b/cmd/vcert/utils.go @@ -23,6 +23,9 @@ import ( "encoding/hex" "encoding/pem" "fmt" + "github.com/Venafi/vcert/v5/pkg/domain" + "github.com/Venafi/vcert/v5/pkg/venafi/cloud" + "github.com/Venafi/vcert/v5/pkg/webclient/cloudproviders" "io" "log" "math/rand" @@ -616,33 +619,47 @@ func randRunes(n int) string { return string(b) } -func getUserParameterProvidedForGetCred() (string, error) { - - tokenS := flags.token - if tokenS == "" { - tokenS = getPropertyFromEnvironment(vCertToken) - } - - identityParameters := map[string]bool{ - flagUser.Name: flags.userName != "", - flagToken.Name: tokenS != "", - flagClientP12.Name: flags.clientP12 != "", - flagEmail.Name: flags.email != "", - } - - var uniqueIdentity string - for identityName, identityValue := range identityParameters { - if identityValue { - if uniqueIdentity != "" { - return "", fmt.Errorf("only one of either --username, --p12-file, -t or --email can be specified") +// fillProvisioningRequest populates the provisioning request payload with values from command flags +func fillProvisioningRequest(req *endpoint.ProvisioningRequest, keystore domain.CloudKeystore, cf *commandFlags) (*endpoint.ProvisioningRequest, *endpoint.ProvisioningOptions) { + req.CertificateID = cleanEmptyStringPointer(cf.certificateID) + req.Keystore = &keystore + req.PickupID = &(cf.pickupID) + + var options endpoint.ProvisioningOptions + if cf.keystoreCertName != "" { + switch keystore.Type { + case string(cloudproviders.CloudKeystoreTypeAkv): + optionsGcp := &cloud.CloudProvisioningGCPOptions{ + ID: &cf.keystoreCertName, + } + options = endpoint.ProvisioningOptions(optionsGcp) + case string(cloudproviders.CloudKeystoreTypeGcm): + optionsGcp := &cloud.CloudProvisioningAzureOptions{ + Name: &cf.keystoreCertName, } - uniqueIdentity = identityName + options = endpoint.ProvisioningOptions(optionsGcp) } + return req, &options } - if uniqueIdentity == "" { - return "", fmt.Errorf("either --username, --p12-file, -t or --email must be specified") + return req, nil +} + +func buildGetCloudKeystoreRequest(flags *commandFlags) domain.GetCloudKeystoreRequest { + + getKeystoreReq := domain.GetCloudKeystoreRequest{ + CloudProviderID: nil, + CloudProviderName: cleanEmptyStringPointer(flags.providerName), + CloudKeystoreID: cleanEmptyStringPointer(flags.keystoreID), + CloudKeystoreName: cleanEmptyStringPointer(flags.keystoreName), } - return uniqueIdentity, nil + return getKeystoreReq +} + +func cleanEmptyStringPointer(s string) *string { + if s == "" { + return nil + } + return &s } diff --git a/cmd/vcert/validators.go b/cmd/vcert/validators.go index d4db7e0e..b64df94d 100644 --- a/cmd/vcert/validators.go +++ b/cmd/vcert/validators.go @@ -657,6 +657,24 @@ func validateSshRetrieveFlags(commandName string) error { return nil } +func validateProvisionFlags(commandName string) error { + err := validateConnectionFlags(commandName) + if err != nil { + return err + } + err = validateCommonFlags(commandName) + if err != nil { + return err + } + + err = readData(commandName) + if err != nil { + return err + } + + return nil +} + func validateExistingFile(f string) error { fileNames, err := getExistingSshFiles(f) diff --git a/go.sum b/go.sum index 98516dde..99f919dd 100644 --- a/go.sum +++ b/go.sum @@ -244,6 +244,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -281,6 +282,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -308,15 +310,18 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/pkg/endpoint/provisioning.go b/pkg/endpoint/provisioning.go index 23f3915f..fa809893 100644 --- a/pkg/endpoint/provisioning.go +++ b/pkg/endpoint/provisioning.go @@ -1,6 +1,9 @@ package endpoint -import "time" +import ( + "github.com/Venafi/vcert/v5/pkg/domain" + "time" +) type ProvisioningRequest struct { CertificateID *string @@ -9,6 +12,7 @@ type ProvisioningRequest struct { KeystoreName *string ProviderName *string Timeout time.Duration + Keystore *domain.CloudKeystore } type ProvisioningMetadata interface { diff --git a/pkg/util/utils.go b/pkg/util/utils.go index ff346f8f..823b2da2 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -144,3 +144,21 @@ func StringPointerToString(input *string) string { } return "" } + +func GetKeystoreOptionsString(cloudProviderID *string, cloudKeystoreID *string, cloudProviderName *string, cloudKeystoreName *string) string { + msg := "" + if cloudProviderID != nil { + msg += fmt.Sprintf("Cloud Provider ID: %s, ", *cloudProviderID) + } + if cloudKeystoreID != nil { + msg += fmt.Sprintf("Cloud Keystore ID: %s, ", *cloudKeystoreID) + } + if cloudProviderName != nil { + msg += fmt.Sprintf("Cloud Provider Name: %s, ", *cloudProviderName) + } + if cloudKeystoreName != nil { + msg += fmt.Sprintf("Cloud Keystore Name: %s", *cloudKeystoreName) + } + + return msg +} diff --git a/pkg/venafi/cloud/cloudproviders.go b/pkg/venafi/cloud/cloudproviders.go index 0df3dcb2..58c92829 100644 --- a/pkg/venafi/cloud/cloudproviders.go +++ b/pkg/venafi/cloud/cloudproviders.go @@ -121,6 +121,9 @@ func (cpgo CloudProvisioningGCPOptions) GetType() string { func setProvisioningOptions(options *endpoint.ProvisioningOptions) (*cloudproviders.CertificateProvisioningOptionsInput, error) { var cloudOptions *cloudproviders.CertificateProvisioningOptionsInput + if options == nil { + return nil, fmt.Errorf("options for provisioning cannot be null when trying to set them") + } dataOptions, err := json.Marshal(options) if err != nil { return nil, err @@ -240,6 +243,18 @@ func (c *Connector) GetCloudKeystoreByName(cloudProviderID string, cloudKeystore return cloudKeystore, nil } +func (c *Connector) GetCloudKeystore(request domain.GetCloudKeystoreRequest) (*domain.CloudKeystore, error) { + cloudKeystore, err := c.cloudProvidersClient.GetCloudKeystore(context.Background(), request) + if err != nil { + return nil, fmt.Errorf("failed to retrieve Cloud Keystore: %w", err) + } + if cloudKeystore == nil { + msg := util.GetKeystoreOptionsString(request.CloudProviderID, request.CloudKeystoreID, request.CloudProviderName, request.CloudKeystoreName) + return nil, fmt.Errorf("could not find Cloud Keystore with %s: %w", msg, err) + } + return cloudKeystore, nil +} + func getCloudMetadataFromWebsocketResponse(respMap interface{}, keystoreType string, keystoreId string) (*CloudProvisioningMetadata, error) { val := CloudKeystoreProvisioningResult{} diff --git a/pkg/venafi/cloud/connector.go b/pkg/venafi/cloud/connector.go index 9bc4de79..fbcca1bb 100644 --- a/pkg/venafi/cloud/connector.go +++ b/pkg/venafi/cloud/connector.go @@ -25,6 +25,7 @@ import ( "encoding/json" "encoding/pem" "fmt" + "github.com/Venafi/vcert/v5/pkg/domain" "io" "log" "net/http" @@ -38,7 +39,6 @@ import ( "golang.org/x/net/context" "github.com/Venafi/vcert/v5/pkg/certificate" - "github.com/Venafi/vcert/v5/pkg/domain" "github.com/Venafi/vcert/v5/pkg/endpoint" "github.com/Venafi/vcert/v5/pkg/policy" "github.com/Venafi/vcert/v5/pkg/util" @@ -775,42 +775,54 @@ func (c *Connector) ProvisionCertificate(req *endpoint.ProvisioningRequest, opti log.Println("Certificate is valid for provisioning (VCP generated)") // setting options for provisioning - log.Println("setting provisioning options") - provisioningOptions, err := setProvisioningOptions(options) - if err != nil { - return nil, err + var provisioningOptions *cloudproviders.CertificateProvisioningOptionsInput + if options != nil { + log.Println("setting provisioning options") + provisioningOptions, err = setProvisioningOptions(options) + if err != nil { + return nil, err + } + log.Println("provisioning options successfully set") } - log.Println("provisioning options successfully set") - if reqData.KeystoreID == nil { - if reqData.ProviderName == nil || reqData.KeystoreName == nil { - return nil, fmt.Errorf("any of keystore ID or both Provider Name and Keystore Name must be provided for provisioning") + ctx := context.Background() + + var keystoreIDString string + var cloudKeystoreType string + + if reqData.Keystore == nil { + if reqData.KeystoreID == nil { + if reqData.ProviderName == nil || reqData.KeystoreName == nil { + return nil, fmt.Errorf("any of keystore ID or both Provider Name and Keystore Name must be provided for provisioning") + } } - } - // Getting Keystore to find type - keystoreIDInput := util.StringPointerToString(reqData.KeystoreID) - keystoreNameInput := util.StringPointerToString(reqData.KeystoreName) - providerNameInput := util.StringPointerToString(reqData.ProviderName) + // Getting Keystore to find type + keystoreIDInput := util.StringPointerToString(reqData.KeystoreID) + keystoreNameInput := util.StringPointerToString(reqData.KeystoreName) + providerNameInput := util.StringPointerToString(reqData.ProviderName) - log.Printf("fetching keystore information for provided keystore information. KeystoreID: %s, KeystoreName: %s, ProviderName: %s", keystoreIDInput, keystoreNameInput, providerNameInput) - ctx := context.Background() - request := domain.GetCloudKeystoreRequest{ - CloudProviderID: nil, - CloudProviderName: req.ProviderName, - CloudKeystoreID: req.KeystoreID, - CloudKeystoreName: req.KeystoreName, - } - cloudKeystore, err := c.cloudProvidersClient.GetCloudKeystore(ctx, request) - if err != nil { - return nil, err - } + log.Printf("fetching keystore information for provided keystore information. KeystoreID: %s, KeystoreName: %s, ProviderName: %s", keystoreIDInput, keystoreNameInput, providerNameInput) + cloudKeystore, err := c.GetCloudKeystore(domain.GetCloudKeystoreRequest{ + CloudProviderID: nil, + CloudProviderName: req.ProviderName, + CloudKeystoreID: req.KeystoreID, + CloudKeystoreName: req.KeystoreName, + }) + if err != nil { + return nil, err + } - keystoreIDString := cloudKeystore.ID + keystoreIDString = cloudKeystore.ID + cloudKeystoreType = cloudKeystore.Type - log.Printf("successfully fetched keystore information for KeystoreID: %s", keystoreIDString) + log.Printf("successfully fetched keystore information for KeystoreID: %s", keystoreIDString) + } else { + log.Printf("Keystore was provided") + keystoreIDString = reqData.Keystore.ID + cloudKeystoreType = reqData.Keystore.Type + } log.Printf("Keystore ID for provisioning: %s", keystoreIDString) - wsClientID := uuid.New().String() wsConn, err := websocket.Subscribe(c.apiKey, c.accessToken, c.baseURL, wsClientID) @@ -831,7 +843,7 @@ func (c *Connector) ProvisionCertificate(req *endpoint.ProvisioningRequest, opti // parsing metadata from websocket response log.Printf("Getting Cloud Metadata of Certificate ID %s and Keystore ID: %s", certificateIDString, keystoreIDString) - cloudMetadata, err := getCloudMetadataFromWebsocketResponse(ar.Data.Result, cloudKeystore.Type, keystoreIDString) + cloudMetadata, err := getCloudMetadataFromWebsocketResponse(ar.Data.Result, cloudKeystoreType, keystoreIDString) if err != nil { return nil, err } diff --git a/pkg/webclient/cloudproviders/cloudproviders.go b/pkg/webclient/cloudproviders/cloudproviders.go index 07a499e4..a8b8b68e 100644 --- a/pkg/webclient/cloudproviders/cloudproviders.go +++ b/pkg/webclient/cloudproviders/cloudproviders.go @@ -3,6 +3,7 @@ package cloudproviders import ( "context" "fmt" + "github.com/Venafi/vcert/v5/pkg/util" "net/http" "github.com/Khan/genqlient/graphql" @@ -60,7 +61,7 @@ func (c *CloudProvidersClient) GetCloudKeystore(ctx context.Context, request dom } resp, err := GetCloudKeystores(ctx, c.graphqlClient, request.CloudKeystoreID, request.CloudKeystoreName, request.CloudProviderID, request.CloudProviderName) - msg := getKeystoreOptionsString(request.CloudProviderID, request.CloudKeystoreID, request.CloudProviderName, request.CloudKeystoreName) + msg := util.GetKeystoreOptionsString(request.CloudProviderID, request.CloudKeystoreID, request.CloudProviderName, request.CloudKeystoreName) if err != nil { return nil, fmt.Errorf("failed to retrieve Cloud Keystore with %s: %w", msg, err) } @@ -83,24 +84,6 @@ func (c *CloudProvidersClient) GetCloudKeystore(ctx context.Context, request dom }, nil } -func getKeystoreOptionsString(cloudProviderID *string, cloudKeystoreID *string, cloudProviderName *string, cloudKeystoreName *string) string { - msg := "" - if cloudProviderID != nil { - msg += fmt.Sprintf("Cloud Provider ID: %s, ", *cloudProviderID) - } - if cloudKeystoreID != nil { - msg += fmt.Sprintf("Cloud Keystore ID: %s, ", *cloudKeystoreID) - } - if cloudProviderName != nil { - msg += fmt.Sprintf("Cloud Provider Name: %s, ", *cloudProviderName) - } - if cloudKeystoreName != nil { - msg += fmt.Sprintf("Cloud Keystore Name: %s", *cloudKeystoreName) - } - - return msg -} - func (c *CloudProvidersClient) ProvisionCertificate(ctx context.Context, certificateID string, cloudKeystoreID string, wsClientID string, options *CertificateProvisioningOptionsInput) (*domain.ProvisioningResponse, error) { if certificateID == "" { return nil, fmt.Errorf("certificateID cannot be empty") From 96f1a86faecf962efc48f21cce4f801e751e8efa Mon Sep 17 00:00:00 2001 From: Luis Presuel Date: Wed, 22 May 2024 00:38:04 -0600 Subject: [PATCH 02/11] fixes flags, flush result logic, and documentation in cli --- cmd/vcert/args.go | 7 +++- cmd/vcert/cmdCloudProviders.go | 70 +++++++++++++++++++++------------- cmd/vcert/flags.go | 31 ++++++++++----- cmd/vcert/result_writer.go | 21 +++++----- 4 files changed, 83 insertions(+), 46 deletions(-) diff --git a/cmd/vcert/args.go b/cmd/vcert/args.go index 6cb9e065..0cb24d01 100644 --- a/cmd/vcert/args.go +++ b/cmd/vcert/args.go @@ -41,7 +41,10 @@ const ( ) var ( - flags commandFlags + flags commandFlags + provisionCommands = stringSlice{ + subCommandCloudKeystore, + } ) type commandFlags struct { @@ -148,4 +151,6 @@ type commandFlags struct { providerName string keystoreName string keystoreCertName string + provisionOutputFile string + provisionPickupID string } diff --git a/cmd/vcert/cmdCloudProviders.go b/cmd/vcert/cmdCloudProviders.go index dd4c3026..80d29b45 100644 --- a/cmd/vcert/cmdCloudProviders.go +++ b/cmd/vcert/cmdCloudProviders.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/Venafi/vcert/v5/pkg/webclient/cloudproviders" "log" "os" "strings" @@ -16,26 +17,40 @@ import ( var ( commandProvision = &cli.Command{ Before: runBeforeCommand, + Action: doCommandProvision, Name: commandProvisionName, - Usage: "To provision a certificate", - UsageText: ` vcert provision - - vcert provision cloudkeystore -k - vcert provision cloudkeystore -k - vcert provision cloudkeystore -p vcp -t `, + Usage: "To provision a certificate from Venafi Platform to a Cloud Keystore", Subcommands: []*cli.Command{ { - Name: subCommandCloudKeystore, - Flags: provisionFlags, - Usage: "set Cloud Keystore for provision", - UsageText: `vcert provision cloudkeystore`, - Action: doCommandProvision, + Name: subCommandCloudKeystore, + Flags: provisionFlags, + Usage: "provision certificate from Venafi Platform to Cloud Keystore", + UsageText: `vcert provision cloudkeystore + + vcert provision cloudkeystore -k --certificate-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --keystore-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --format json + vcert provision cloudkeystore -k --pickup-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --provider-name "My GCP Provider"--keystore-name "My GCP provider" --certificate-name "example-venafi-com" + vcert provision cloudkeystore -p vcp -t --certificate-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --provider-name "My GCP Provider"--keystore-name "My GCP provider" --file "/path/to/file.txt"`, + Action: doCommandProvisionCloudKeystore, }, }, } ) func doCommandProvision(c *cli.Context) error { + return fmt.Errorf("the following subcommand(s) are required: \n%s", createBulletList(provisionCommands)) +} + +func createBulletList(items []string) string { + var builder strings.Builder + for _, item := range items { + builder.WriteString("• ") + builder.WriteString(item) + builder.WriteString("\n") + } + return builder.String() +} + +func doCommandProvisionCloudKeystore(c *cli.Context) error { err := validateProvisionFlags(c.Command.Name) if err != nil { return err @@ -47,7 +62,7 @@ func doCommandProvision(c *cli.Context) error { cfg, err := buildConfig(c, &flags) if err != nil { - return fmt.Errorf("Failed to build vcert config: %s", err) + return fmt.Errorf("failed to build vcert config: %s", err) } connector, err := vcert.NewClient(&cfg) @@ -83,20 +98,23 @@ func doCommandProvision(c *cli.Context) error { return err } - arn := metadata.GetAWSCertificateMetadata().GetARN() - azureID := metadata.GetAzureCertificateMetadata().GetID() - azureName := metadata.GetAzureCertificateMetadata().GetName() - azureVersion := metadata.GetAzureCertificateMetadata().GetVersion() - gcpID := metadata.GetGCPCertificateMetadata().GetID() - gcpName := metadata.GetGCPCertificateMetadata().GetName() - - result := &ProvisioningResult{ - ARN: &arn, - AzureID: &azureID, - AzureName: &azureName, - AzureVersion: &azureVersion, - GcpID: &gcpID, - GcpName: &gcpName, + result := ProvisioningResult{} + switch cloudKeystore.Type { + case string(cloudproviders.CloudKeystoreTypeAcm): + arn := metadata.GetAWSCertificateMetadata().GetARN() + result.ARN = &arn + case string(cloudproviders.CloudKeystoreTypeAkv): + azureID := metadata.GetAzureCertificateMetadata().GetID() + azureName := metadata.GetAzureCertificateMetadata().GetName() + azureVersion := metadata.GetAzureCertificateMetadata().GetVersion() + result.AzureID = &azureID + result.AzureName = &azureName + result.AzureVersion = &azureVersion + case string(cloudproviders.CloudKeystoreTypeGcm): + gcpID := metadata.GetGCPCertificateMetadata().GetID() + gcpName := metadata.GetGCPCertificateMetadata().GetName() + result.GcpID = &gcpID + result.GcpName = &gcpName } err = result.Flush(flags.format) diff --git a/cmd/vcert/flags.go b/cmd/vcert/flags.go index d4c7e2f9..8a4850e4 100644 --- a/cmd/vcert/flags.go +++ b/cmd/vcert/flags.go @@ -364,7 +364,7 @@ var ( } flagPickupIDFile = &cli.StringFlag{ - Name: "pickup-id-file", + Name: "pickup-file", Usage: "Use to specify the file name from where to read or write the Pickup ID. " + "Either --pickup-id or --pickup-id-file is required.", Destination: &flags.pickupIDFile, @@ -733,11 +733,25 @@ var ( } flagKeystoreCertName = &cli.StringFlag{ - Name: "cloudkeystore-certname", + Name: "certificate-name", Usage: "Use to specify Cloud Keystore Certificate Name if it supports it", Destination: &flags.keystoreCertName, } + flagProvisionOutputFile = &cli.StringFlag{ + Name: "file", + Usage: "Use to specify a file name and a location where the output should be written. " + + "Example: --file /path-to/provision-output", + Destination: &flags.provisionOutputFile, + TakesFile: true, + } + + flagProvisionPickupID = &cli.StringFlag{ + Name: "pickup-id", + Usage: "Use to specify the Pickup ID (for VCP is the Request ID) of the certificate to be provisioned.", + Destination: &flags.provisionPickupID, + } + commonFlags = []cli.Flag{flagInsecure, flagVerbose, flagNoPrompt} keyFlags = []cli.Flag{flagKeyType, flagKeySize, flagKeyCurve, flagKeyFile, flagKeyPassword} sansFlags = []cli.Flag{flagDNSSans, flagEmailSans, flagIPSans, flagURISans, flagUPNSans} @@ -879,17 +893,14 @@ var ( provisionFlags = flagsApppend( credentialsFlags, flagCertificateID, - flagPickupID, + flagProvisionPickupID, flagPickupIDFile, - flagKeystoreID, - flagKeystoreName, - flagProviderName, flagKeystoreCertName, + flagProviderName, + flagKeystoreName, + flagKeystoreID, flagFormat, - sortedFlags(flagsApppend( - commonFlags, - sortableCredentialsFlags, - )), + flagProvisionOutputFile, ) commonCredFlags = []cli.Flag{flagConfig, flagProfile, flagUrl, flagToken, flagTrustBundle} diff --git a/cmd/vcert/result_writer.go b/cmd/vcert/result_writer.go index e5097455..7112fd09 100644 --- a/cmd/vcert/result_writer.go +++ b/cmd/vcert/result_writer.go @@ -61,12 +61,12 @@ type Result struct { } type ProvisioningResult struct { - ARN *string `json:"arn"` - AzureID *string `json:"azureId"` - AzureName *string `json:"azureName"` - AzureVersion *string `json:"azureVersion"` - GcpID *string `json:"gcpId"` - GcpName *string `json:"gcpName"` + ARN *string `json:"arn,omitempty"` + AzureID *string `json:"azureId,omitempty"` + AzureName *string `json:"azureName,omitempty"` + AzureVersion *string `json:"azureVersion,omitempty"` + GcpID *string `json:"gcpId,omitempty"` + GcpName *string `json:"gcpName,omitempty"` } type Output struct { @@ -464,10 +464,13 @@ func (r *ProvisioningResult) Format(format string) (string, error) { result := "" switch strings.ToLower(format) { case formatJson: - b, err := json.Marshal(r) + b, err := json.MarshalIndent(r, "", " ") if err != nil { return "", fmt.Errorf("failed to construct JSON: %s", err) } + if err != nil { + return "", err + } result = string(b) default: if r.ARN != nil { @@ -480,8 +483,8 @@ func (r *ProvisioningResult) Format(format string) (string, error) { } if r.GcpID != nil { - result += fmt.Sprintf("gcpId %s:", util.StringPointerToString(r.GcpID)) - result += fmt.Sprintf("gcpName %s:", util.StringPointerToString(r.GcpName)) + result += fmt.Sprintf("gcpId %s\n", util.StringPointerToString(r.GcpID)) + result += fmt.Sprintf("gcpName %s\n", util.StringPointerToString(r.GcpName)) } } return result, nil From e9a9d5a9aa9391f32f9ec8463dfc91abe036dbb5 Mon Sep 17 00:00:00 2001 From: Luis Presuel Date: Wed, 22 May 2024 16:50:02 -0600 Subject: [PATCH 03/11] fixes code. Adds constants for cloud types. Adds machineIdenty interface and logic to be retrieved for both SDK and CLI. removes GetKeystoreByName in favor of GetKeystore --- cmd/vcert/cmdCloudProviders.go | 30 ++++++------- cmd/vcert/flags.go | 4 +- cmd/vcert/result_writer.go | 35 ++++++++------- cmd/vcert/utils.go | 10 ++--- examples/provisionWithRequest/main.go | 2 + pkg/endpoint/provisioning.go | 6 +++ pkg/venafi/cloud/cloudproviders.go | 63 ++++++++++++++------------- 7 files changed, 78 insertions(+), 72 deletions(-) diff --git a/cmd/vcert/cmdCloudProviders.go b/cmd/vcert/cmdCloudProviders.go index 80d29b45..55011434 100644 --- a/cmd/vcert/cmdCloudProviders.go +++ b/cmd/vcert/cmdCloudProviders.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "github.com/Venafi/vcert/v5/pkg/webclient/cloudproviders" "log" "os" "strings" @@ -29,7 +28,7 @@ var ( vcert provision cloudkeystore -k --certificate-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --keystore-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --format json vcert provision cloudkeystore -k --pickup-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --provider-name "My GCP Provider"--keystore-name "My GCP provider" --certificate-name "example-venafi-com" - vcert provision cloudkeystore -p vcp -t --certificate-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --provider-name "My GCP Provider"--keystore-name "My GCP provider" --file "/path/to/file.txt"`, + vcert provision cloudkeystore -p vcp -t --certificate-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --provider-name "My GCP Provider" --keystore-name "My GCP provider" --file "/path/to/file.txt"`, Action: doCommandProvisionCloudKeystore, }, }, @@ -100,23 +99,20 @@ func doCommandProvisionCloudKeystore(c *cli.Context) error { result := ProvisioningResult{} switch cloudKeystore.Type { - case string(cloudproviders.CloudKeystoreTypeAcm): - arn := metadata.GetAWSCertificateMetadata().GetARN() - result.ARN = &arn - case string(cloudproviders.CloudKeystoreTypeAkv): - azureID := metadata.GetAzureCertificateMetadata().GetID() - azureName := metadata.GetAzureCertificateMetadata().GetName() - azureVersion := metadata.GetAzureCertificateMetadata().GetVersion() - result.AzureID = &azureID - result.AzureName = &azureName - result.AzureVersion = &azureVersion - case string(cloudproviders.CloudKeystoreTypeGcm): - gcpID := metadata.GetGCPCertificateMetadata().GetID() - gcpName := metadata.GetGCPCertificateMetadata().GetName() - result.GcpID = &gcpID - result.GcpName = &gcpName + case cloud.KeystoreTypeACM: + result.ARN = metadata.GetAWSCertificateMetadata().GetARN() + case cloud.KeystoreTypeAKV: + result.AzureID = metadata.GetAzureCertificateMetadata().GetID() + result.AzureName = metadata.GetAzureCertificateMetadata().GetName() + result.AzureVersion = metadata.GetAzureCertificateMetadata().GetVersion() + case cloud.KeystoreTypeGCM: + result.GcpID = metadata.GetGCPCertificateMetadata().GetID() + result.GcpName = metadata.GetGCPCertificateMetadata().GetName() } + result.MachineIdentityId = metadata.GetMachineIdentityMetadata().GetID() + result.MachineIdentityActionType = metadata.GetMachineIdentityMetadata().GetActionType() + err = result.Flush(flags.format) if err != nil { diff --git a/cmd/vcert/flags.go b/cmd/vcert/flags.go index 8a4850e4..2556f788 100644 --- a/cmd/vcert/flags.go +++ b/cmd/vcert/flags.go @@ -364,7 +364,7 @@ var ( } flagPickupIDFile = &cli.StringFlag{ - Name: "pickup-file", + Name: "pickup-id-file", Usage: "Use to specify the file name from where to read or write the Pickup ID. " + "Either --pickup-id or --pickup-id-file is required.", Destination: &flags.pickupIDFile, @@ -900,7 +900,7 @@ var ( flagKeystoreName, flagKeystoreID, flagFormat, - flagProvisionOutputFile, + flagProvisionOutputFile, // TODO: implement this flag ) commonCredFlags = []cli.Flag{flagConfig, flagProfile, flagUrl, flagToken, flagTrustBundle} diff --git a/cmd/vcert/result_writer.go b/cmd/vcert/result_writer.go index 7112fd09..1e341d60 100644 --- a/cmd/vcert/result_writer.go +++ b/cmd/vcert/result_writer.go @@ -61,12 +61,14 @@ type Result struct { } type ProvisioningResult struct { - ARN *string `json:"arn,omitempty"` - AzureID *string `json:"azureId,omitempty"` - AzureName *string `json:"azureName,omitempty"` - AzureVersion *string `json:"azureVersion,omitempty"` - GcpID *string `json:"gcpId,omitempty"` - GcpName *string `json:"gcpName,omitempty"` + ARN string `json:"arn,omitempty"` + AzureID string `json:"azureId,omitempty"` + AzureName string `json:"azureName,omitempty"` + AzureVersion string `json:"azureVersion,omitempty"` + GcpID string `json:"gcpId,omitempty"` + GcpName string `json:"gcpName,omitempty"` + MachineIdentityId string `json:"machineIdentityId,omitempty"` + MachineIdentityActionType string `json:"machineIdentityActionType,omitempty"` } type Output struct { @@ -468,23 +470,20 @@ func (r *ProvisioningResult) Format(format string) (string, error) { if err != nil { return "", fmt.Errorf("failed to construct JSON: %s", err) } - if err != nil { - return "", err - } result = string(b) default: - if r.ARN != nil { - result += fmt.Sprintf("arn: %s\n", util.StringPointerToString(r.ARN)) + if r.ARN != "" { + result += fmt.Sprintf("arn: %s\n", r.ARN) } - if r.AzureID != nil { - result += fmt.Sprintf("azureId: %s\n", util.StringPointerToString(r.AzureID)) - result += fmt.Sprintf("azureName: %s\n", util.StringPointerToString(r.AzureName)) - result += fmt.Sprintf("azureVersion: %s\n", util.StringPointerToString(r.AzureVersion)) + if r.AzureID != "" { + result += fmt.Sprintf("azureId: %s\n", r.AzureID) + result += fmt.Sprintf("azureName: %s\n", r.AzureName) + result += fmt.Sprintf("azureVersion: %s\n", r.AzureVersion) } - if r.GcpID != nil { - result += fmt.Sprintf("gcpId %s\n", util.StringPointerToString(r.GcpID)) - result += fmt.Sprintf("gcpName %s\n", util.StringPointerToString(r.GcpName)) + if r.GcpID != "" { + result += fmt.Sprintf("gcpId %s\n", r.GcpID) + result += fmt.Sprintf("gcpName %s\n", r.GcpName) } } return result, nil diff --git a/cmd/vcert/utils.go b/cmd/vcert/utils.go index 9bc62c43..2531524a 100644 --- a/cmd/vcert/utils.go +++ b/cmd/vcert/utils.go @@ -629,13 +629,13 @@ func fillProvisioningRequest(req *endpoint.ProvisioningRequest, keystore domain. if cf.keystoreCertName != "" { switch keystore.Type { case string(cloudproviders.CloudKeystoreTypeAkv): - optionsGcp := &cloud.CloudProvisioningGCPOptions{ - ID: &cf.keystoreCertName, + optionsAkv := &cloud.CloudProvisioningAzureOptions{ + Name: &cf.keystoreCertName, } - options = endpoint.ProvisioningOptions(optionsGcp) + options = endpoint.ProvisioningOptions(optionsAkv) case string(cloudproviders.CloudKeystoreTypeGcm): - optionsGcp := &cloud.CloudProvisioningAzureOptions{ - Name: &cf.keystoreCertName, + optionsGcp := &cloud.CloudProvisioningGCPOptions{ + ID: &cf.keystoreCertName, } options = endpoint.ProvisioningOptions(optionsGcp) } diff --git a/examples/provisionWithRequest/main.go b/examples/provisionWithRequest/main.go index 59cc89f8..06a80540 100644 --- a/examples/provisionWithRequest/main.go +++ b/examples/provisionWithRequest/main.go @@ -104,4 +104,6 @@ func main() { //log.Printf("Certificate Azure Metadata Version:\n%v", certMetaData.GetAzureCertificateMetadata().GetVersion()) log.Printf("Certificate GCP Metadata ID:\n%v", certMetaData.GetGCPCertificateMetadata().GetID()) log.Printf("Certificate GCP Metadata Name:\n%v", certMetaData.GetGCPCertificateMetadata().GetName()) + log.Printf("Certificate Machine Identity Metadata ID:\n%v", certMetaData.GetMachineIdentityMetadata().GetID()) + log.Printf("Certificate Machine Identity Action Type:\n%v", certMetaData.GetMachineIdentityMetadata().GetActionType()) } diff --git a/pkg/endpoint/provisioning.go b/pkg/endpoint/provisioning.go index fa809893..c5e11601 100644 --- a/pkg/endpoint/provisioning.go +++ b/pkg/endpoint/provisioning.go @@ -19,6 +19,7 @@ type ProvisioningMetadata interface { GetAWSCertificateMetadata() AWSCertificateMetadata GetAzureCertificateMetadata() AzureCertificateMetadata GetGCPCertificateMetadata() GCPCertificateMetadata + GetMachineIdentityMetadata() MachineIdentityMetadata } type AWSCertificateMetadata interface { @@ -36,6 +37,11 @@ type GCPCertificateMetadata interface { GetName() string } +type MachineIdentityMetadata interface { + GetID() string + GetActionType() string +} + type ProvisioningOptions interface { GetType() string } diff --git a/pkg/venafi/cloud/cloudproviders.go b/pkg/venafi/cloud/cloudproviders.go index 58c92829..9dbf2a7a 100644 --- a/pkg/venafi/cloud/cloudproviders.go +++ b/pkg/venafi/cloud/cloudproviders.go @@ -21,13 +21,22 @@ type CloudKeystoreProvisioningResult struct { CloudProviderCertificateID string `json:"cloudProviderCertificateId"` CloudCertificateName string `json:"cloudProviderCertificateName"` CloudCertificateVersion string `json:"cloudProviderCertificateVersion"` + MachineIdentityActionType string `json:"machineIdentityActionType"` + MachineIdentityId string `json:"machineIdentityId"` Error error `json:"error"` } +const ( + KeystoreTypeACM = "ACM" + KeystoreTypeAKV = "AKV" + KeystoreTypeGCM = "GCM" +) + type CloudProvisioningMetadata struct { - awsMetadata CloudAwsMetadata - azureMetadata CloudAzureMetadata - gcpMetadata CloudGcpMetadata + awsMetadata CloudAwsMetadata + azureMetadata CloudAzureMetadata + gcpMetadata CloudGcpMetadata + machineMetadata MachineIdentityMetadata } func (cpm *CloudProvisioningMetadata) GetAWSCertificateMetadata() endpoint.AWSCertificateMetadata { @@ -42,6 +51,10 @@ func (cpm *CloudProvisioningMetadata) GetGCPCertificateMetadata() endpoint.GCPCe return &cpm.gcpMetadata } +func (cpm *CloudProvisioningMetadata) GetMachineIdentityMetadata() endpoint.MachineIdentityMetadata { + return &cpm.machineMetadata +} + type CloudAwsMetadata struct { result CloudKeystoreProvisioningResult } @@ -78,6 +91,18 @@ func (cam *CloudAzureMetadata) GetID() string { return cam.result.CloudProviderCertificateID } +type MachineIdentityMetadata struct { + result CloudKeystoreProvisioningResult +} + +func (mim *MachineIdentityMetadata) GetID() string { + return mim.result.MachineIdentityId +} + +func (mim *MachineIdentityMetadata) GetActionType() string { + return mim.result.MachineIdentityActionType +} + // GCMCertificateScope Indicates the Scope for a certificate provisioned to GCP Certificate Manager type GCMCertificateScope string @@ -105,7 +130,7 @@ type CloudProvisioningAzureOptions struct { } func (cpao CloudProvisioningAzureOptions) GetType() string { - return "AKV" + return KeystoreTypeAKV } type CloudProvisioningGCPOptions struct { @@ -116,7 +141,7 @@ type CloudProvisioningGCPOptions struct { } func (cpgo CloudProvisioningGCPOptions) GetType() string { - return "GCM" + return KeystoreTypeGCM } func setProvisioningOptions(options *endpoint.ProvisioningOptions) (*cloudproviders.CertificateProvisioningOptionsInput, error) { @@ -218,31 +243,6 @@ func (c *Connector) GetCloudProviderByName(name string) (*domain.CloudProvider, return cloudProvider, nil } -func (c *Connector) GetCloudKeystoreByName(cloudProviderID string, cloudKeystoreName string) (*domain.CloudKeystore, error) { - if cloudProviderID == "" { - return nil, fmt.Errorf("cloud provider ID cannot be empty") - } - if cloudKeystoreName == "" { - return nil, fmt.Errorf("cloud keystore name cannot be empty") - } - - request := domain.GetCloudKeystoreRequest{ - CloudProviderID: &cloudProviderID, - CloudProviderName: nil, - CloudKeystoreID: nil, - CloudKeystoreName: &cloudKeystoreName, - } - - cloudKeystore, err := c.cloudProvidersClient.GetCloudKeystore(context.Background(), request) - if err != nil { - return nil, fmt.Errorf("failed to retrieve Cloud Keystore with name %s from Cloud Provider with ID %s: %w", cloudKeystoreName, cloudProviderID, err) - } - if cloudKeystore == nil { - return nil, fmt.Errorf("could not find Cloud Keystore with name %s in Cloud Provider with ID %s", cloudKeystoreName, cloudProviderID) - } - return cloudKeystore, nil -} - func (c *Connector) GetCloudKeystore(request domain.GetCloudKeystoreRequest) (*domain.CloudKeystore, error) { cloudKeystore, err := c.cloudProvidersClient.GetCloudKeystore(context.Background(), request) if err != nil { @@ -286,5 +286,8 @@ func getCloudMetadataFromWebsocketResponse(respMap interface{}, keystoreType str err = fmt.Errorf("unknown type %v for keystore with ID: %s", keystoreType, keystoreId) return nil, err } + + cloudMetadata.machineMetadata.result = val + return cloudMetadata, err } From f3d9dbdf25ad5a75099d8b0c4fe9827f61e151bb Mon Sep 17 00:00:00 2001 From: Luis Presuel Date: Wed, 22 May 2024 17:40:57 -0600 Subject: [PATCH 04/11] adds validations for flags. fixes goimports --- cmd/vcert/utils.go | 6 +++--- cmd/vcert/validators.go | 16 +++++++++++++--- pkg/venafi/cloud/connector.go | 4 ++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/cmd/vcert/utils.go b/cmd/vcert/utils.go index 2531524a..51dff434 100644 --- a/cmd/vcert/utils.go +++ b/cmd/vcert/utils.go @@ -23,9 +23,6 @@ import ( "encoding/hex" "encoding/pem" "fmt" - "github.com/Venafi/vcert/v5/pkg/domain" - "github.com/Venafi/vcert/v5/pkg/venafi/cloud" - "github.com/Venafi/vcert/v5/pkg/webclient/cloudproviders" "io" "log" "math/rand" @@ -40,8 +37,11 @@ import ( "github.com/Venafi/vcert/v5" "github.com/Venafi/vcert/v5/pkg/certificate" + "github.com/Venafi/vcert/v5/pkg/domain" "github.com/Venafi/vcert/v5/pkg/endpoint" "github.com/Venafi/vcert/v5/pkg/util" + "github.com/Venafi/vcert/v5/pkg/venafi/cloud" + "github.com/Venafi/vcert/v5/pkg/webclient/cloudproviders" ) const ( diff --git a/cmd/vcert/validators.go b/cmd/vcert/validators.go index b64df94d..a36ef40f 100644 --- a/cmd/vcert/validators.go +++ b/cmd/vcert/validators.go @@ -662,9 +662,19 @@ func validateProvisionFlags(commandName string) error { if err != nil { return err } - err = validateCommonFlags(commandName) - if err != nil { - return err + + if flags.format != "" && flags.format != "json" { + return fmt.Errorf("unexpected output format: %s", flags.format) + } + + if flags.certificateID == "" && flags.provisionPickupID == "" { + return fmt.Errorf("please, provide any of certificate-id or pickup-id") + } + + if flags.keystoreID == "" { + if flags.keystoreName == "" || flags.providerName == "" { + return fmt.Errorf("any of keystore object, keystore ID or both Provider Name and Keystore Name must be provided for provisioning") + } } err = readData(commandName) diff --git a/pkg/venafi/cloud/connector.go b/pkg/venafi/cloud/connector.go index fbcca1bb..1beb9fdb 100644 --- a/pkg/venafi/cloud/connector.go +++ b/pkg/venafi/cloud/connector.go @@ -25,7 +25,6 @@ import ( "encoding/json" "encoding/pem" "fmt" - "github.com/Venafi/vcert/v5/pkg/domain" "io" "log" "net/http" @@ -39,6 +38,7 @@ import ( "golang.org/x/net/context" "github.com/Venafi/vcert/v5/pkg/certificate" + "github.com/Venafi/vcert/v5/pkg/domain" "github.com/Venafi/vcert/v5/pkg/endpoint" "github.com/Venafi/vcert/v5/pkg/policy" "github.com/Venafi/vcert/v5/pkg/util" @@ -793,7 +793,7 @@ func (c *Connector) ProvisionCertificate(req *endpoint.ProvisioningRequest, opti if reqData.Keystore == nil { if reqData.KeystoreID == nil { if reqData.ProviderName == nil || reqData.KeystoreName == nil { - return nil, fmt.Errorf("any of keystore ID or both Provider Name and Keystore Name must be provided for provisioning") + return nil, fmt.Errorf("any of keystore object, keystore ID or both Provider Name and Keystore Name must be provided for provisioning") } } From 797f37fd84e91f2b2b38132c45ef2203b40eeec3 Mon Sep 17 00:00:00 2001 From: Luis Presuel Date: Wed, 22 May 2024 17:43:48 -0600 Subject: [PATCH 05/11] fixes more goimports --- pkg/webclient/cloudproviders/cloudproviders.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/webclient/cloudproviders/cloudproviders.go b/pkg/webclient/cloudproviders/cloudproviders.go index a8b8b68e..a6aa3c26 100644 --- a/pkg/webclient/cloudproviders/cloudproviders.go +++ b/pkg/webclient/cloudproviders/cloudproviders.go @@ -3,12 +3,12 @@ package cloudproviders import ( "context" "fmt" - "github.com/Venafi/vcert/v5/pkg/util" "net/http" "github.com/Khan/genqlient/graphql" "github.com/Venafi/vcert/v5/pkg/domain" + "github.com/Venafi/vcert/v5/pkg/util" ) //go:generate go run -mod=mod github.com/Khan/genqlient genqlient.yaml From ca91c415c7a1665db5226dcc0d83ef83e654f270 Mon Sep 17 00:00:00 2001 From: Luis Presuel Date: Thu, 23 May 2024 10:10:49 -0600 Subject: [PATCH 06/11] keeps tabulation for vcp in main cli menu. moves cloudKeystore code to single file. refactors flags connection validations to be more specific for provision use case and in so, avoiding make any API call before reaching provision function --- cmd/vcert/args.go | 34 +++++------ cmd/vcert/cmdCloudKeystores.go | 100 +++++++++++++++++++++++++++++++++ cmd/vcert/cmdCloudProviders.go | 100 ++------------------------------- cmd/vcert/main.go | 2 +- cmd/vcert/validators.go | 93 +++++++++++++++++++++--------- 5 files changed, 191 insertions(+), 138 deletions(-) create mode 100644 cmd/vcert/cmdCloudKeystores.go diff --git a/cmd/vcert/args.go b/cmd/vcert/args.go index 0cb24d01..5e31fcc4 100644 --- a/cmd/vcert/args.go +++ b/cmd/vcert/args.go @@ -22,28 +22,28 @@ import ( ) const ( - commandGenCSRName = "gencsr" - commandEnrollName = "enroll" - commandPickupName = "pickup" - commandRevokeName = "revoke" - commandRenewName = "renew" - commandRetireName = "retire" - commandGetCredName = "getcred" - commandCheckCredName = "checkcred" - commandVoidCredName = "voidcred" - commandCreatePolicyName = "setpolicy" - commandGetePolicyName = "getpolicy" - commandSshPickupName = "sshpickup" - commandSshEnrollName = "sshenroll" - commandSshGetConfigName = "sshgetconfig" - commandProvisionName = "provision" - subCommandCloudKeystore = "cloudkeystore" + commandGenCSRName = "gencsr" + commandEnrollName = "enroll" + commandPickupName = "pickup" + commandRevokeName = "revoke" + commandRenewName = "renew" + commandRetireName = "retire" + commandGetCredName = "getcred" + commandCheckCredName = "checkcred" + commandVoidCredName = "voidcred" + commandCreatePolicyName = "setpolicy" + commandGetePolicyName = "getpolicy" + commandSshPickupName = "sshpickup" + commandSshEnrollName = "sshenroll" + commandSshGetConfigName = "sshgetconfig" + commandProvisionName = "provision" + subCommandCloudKeystoreName = "cloudkeystore" ) var ( flags commandFlags provisionCommands = stringSlice{ - subCommandCloudKeystore, + subCommandCloudKeystoreName, } ) diff --git a/cmd/vcert/cmdCloudKeystores.go b/cmd/vcert/cmdCloudKeystores.go new file mode 100644 index 00000000..d8e970e8 --- /dev/null +++ b/cmd/vcert/cmdCloudKeystores.go @@ -0,0 +1,100 @@ +package main + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/urfave/cli/v2" + + "github.com/Venafi/vcert/v5" + "github.com/Venafi/vcert/v5/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/venafi/cloud" +) + +var ( + subCommandCloudKeystore = &cli.Command{ + Name: subCommandCloudKeystoreName, + Flags: provisionFlags, + Usage: "provision certificate from Venafi Platform to Cloud Keystore", + UsageText: `vcert provision cloudkeystore + + vcert provision cloudkeystore -k --certificate-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --keystore-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --format json + vcert provision cloudkeystore -k --pickup-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --provider-name "My GCP Provider"--keystore-name "My GCP provider" --certificate-name "example-venafi-com" + vcert provision cloudkeystore -p vcp -t --certificate-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --provider-name "My GCP Provider" --keystore-name "My GCP provider" --file "/path/to/file.txt"`, + Action: doCommandProvisionCloudKeystore, + } +) + +func doCommandProvisionCloudKeystore(c *cli.Context) error { + err := validateProvisionFlags(c.Command.Name) + if err != nil { + return err + } + err = setTLSConfig() + if err != nil { + return err + } + + cfg, err := buildConfig(c, &flags) + if err != nil { + return fmt.Errorf("failed to build vcert config: %s", err) + } + + connector, err := vcert.NewClient(&cfg) + if err != nil { + logf("Unable to connect to %s: %s", cfg.ConnectorType, err) + } else { + logf("Successfully connected to %s", cfg.ConnectorType) + } + + var req = &endpoint.ProvisioningRequest{} + var options *endpoint.ProvisioningOptions + + log.Printf("fetching keystore information for provided keystore information from flags. KeystoreID: %s, KeystoreName: %s, ProviderName: %s", flags.keystoreID, flags.keystoreName, flags.providerName) + getKeystoreReq := buildGetCloudKeystoreRequest(&flags) + cloudKeystore, err := connector.(*cloud.Connector).GetCloudKeystore(getKeystoreReq) + if err != nil { + return err + } + log.Printf("successfully fetched keystore") + + if flags.pickupIDFile != "" { + bytes, err := os.ReadFile(flags.pickupIDFile) + if err != nil { + return fmt.Errorf("failed to read Pickup ID value: %s", err) + } + flags.pickupID = strings.TrimSpace(string(bytes)) + } + + req, options = fillProvisioningRequest(req, *cloudKeystore, &flags) + + metadata, err := connector.ProvisionCertificate(req, options) + if err != nil { + return err + } + + result := ProvisioningResult{} + switch cloudKeystore.Type { + case cloud.KeystoreTypeACM: + result.ARN = metadata.GetAWSCertificateMetadata().GetARN() + case cloud.KeystoreTypeAKV: + result.AzureID = metadata.GetAzureCertificateMetadata().GetID() + result.AzureName = metadata.GetAzureCertificateMetadata().GetName() + result.AzureVersion = metadata.GetAzureCertificateMetadata().GetVersion() + case cloud.KeystoreTypeGCM: + result.GcpID = metadata.GetGCPCertificateMetadata().GetID() + result.GcpName = metadata.GetGCPCertificateMetadata().GetName() + } + + result.MachineIdentityId = metadata.GetMachineIdentityMetadata().GetID() + result.MachineIdentityActionType = metadata.GetMachineIdentityMetadata().GetActionType() + + err = result.Flush(flags.format) + + if err != nil { + return fmt.Errorf("failed to output the results: %s", err) + } + return nil +} diff --git a/cmd/vcert/cmdCloudProviders.go b/cmd/vcert/cmdCloudProviders.go index 55011434..688dfdf8 100644 --- a/cmd/vcert/cmdCloudProviders.go +++ b/cmd/vcert/cmdCloudProviders.go @@ -2,36 +2,18 @@ package main import ( "fmt" - "log" - "os" "strings" "github.com/urfave/cli/v2" - - "github.com/Venafi/vcert/v5" - "github.com/Venafi/vcert/v5/pkg/endpoint" - "github.com/Venafi/vcert/v5/pkg/venafi/cloud" ) var ( commandProvision = &cli.Command{ - Before: runBeforeCommand, - Action: doCommandProvision, - Name: commandProvisionName, - Usage: "To provision a certificate from Venafi Platform to a Cloud Keystore", - Subcommands: []*cli.Command{ - { - Name: subCommandCloudKeystore, - Flags: provisionFlags, - Usage: "provision certificate from Venafi Platform to Cloud Keystore", - UsageText: `vcert provision cloudkeystore - - vcert provision cloudkeystore -k --certificate-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --keystore-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --format json - vcert provision cloudkeystore -k --pickup-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --provider-name "My GCP Provider"--keystore-name "My GCP provider" --certificate-name "example-venafi-com" - vcert provision cloudkeystore -p vcp -t --certificate-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --provider-name "My GCP Provider" --keystore-name "My GCP provider" --file "/path/to/file.txt"`, - Action: doCommandProvisionCloudKeystore, - }, - }, + Before: runBeforeCommand, + Action: doCommandProvision, + Name: commandProvisionName, + Usage: "To provision a certificate from Venafi Platform to a Cloud Keystore", + Subcommands: []*cli.Command{subCommandCloudKeystore}, } ) @@ -48,75 +30,3 @@ func createBulletList(items []string) string { } return builder.String() } - -func doCommandProvisionCloudKeystore(c *cli.Context) error { - err := validateProvisionFlags(c.Command.Name) - if err != nil { - return err - } - err = setTLSConfig() - if err != nil { - return err - } - - cfg, err := buildConfig(c, &flags) - if err != nil { - return fmt.Errorf("failed to build vcert config: %s", err) - } - - connector, err := vcert.NewClient(&cfg) - if err != nil { - logf("Unable to connect to %s: %s", cfg.ConnectorType, err) - } else { - logf("Successfully connected to %s", cfg.ConnectorType) - } - - var req = &endpoint.ProvisioningRequest{} - var options *endpoint.ProvisioningOptions - - log.Printf("fetching keystore information for provided keystore information from flags. KeystoreID: %s, KeystoreName: %s, ProviderName: %s", flags.keystoreID, flags.keystoreName, flags.providerName) - getKeystoreReq := buildGetCloudKeystoreRequest(&flags) - cloudKeystore, err := connector.(*cloud.Connector).GetCloudKeystore(getKeystoreReq) - if err != nil { - return err - } - log.Printf("successfully fetched keystore") - - if flags.pickupIDFile != "" { - bytes, err := os.ReadFile(flags.pickupIDFile) - if err != nil { - return fmt.Errorf("failed to read Pickup ID value: %s", err) - } - flags.pickupID = strings.TrimSpace(string(bytes)) - } - - req, options = fillProvisioningRequest(req, *cloudKeystore, &flags) - - metadata, err := connector.ProvisionCertificate(req, options) - if err != nil { - return err - } - - result := ProvisioningResult{} - switch cloudKeystore.Type { - case cloud.KeystoreTypeACM: - result.ARN = metadata.GetAWSCertificateMetadata().GetARN() - case cloud.KeystoreTypeAKV: - result.AzureID = metadata.GetAzureCertificateMetadata().GetID() - result.AzureName = metadata.GetAzureCertificateMetadata().GetName() - result.AzureVersion = metadata.GetAzureCertificateMetadata().GetVersion() - case cloud.KeystoreTypeGCM: - result.GcpID = metadata.GetGCPCertificateMetadata().GetID() - result.GcpName = metadata.GetGCPCertificateMetadata().GetName() - } - - result.MachineIdentityId = metadata.GetMachineIdentityMetadata().GetID() - result.MachineIdentityActionType = metadata.GetMachineIdentityMetadata().GetActionType() - - err = result.Flush(flags.format) - - if err != nil { - return fmt.Errorf("failed to output the results: %s", err) - } - return nil -} diff --git a/cmd/vcert/main.go b/cmd/vcert/main.go index 814c231a..86333ba3 100644 --- a/cmd/vcert/main.go +++ b/cmd/vcert/main.go @@ -116,7 +116,7 @@ ACTIONS: retire tpp | vcp To retire a certificate revoke tpp To revoke a certificate run tpp | vcp | firefly To retrieve and install certificates using a vcert playbook file - provision vcp To provision a certificate to cloud keystore + provision vcp To provision a certificate to cloud keystore getpolicy tpp | vcp To retrieve the certificate policy of a zone setpolicy tpp | vcp To apply a certificate policy specification to a zone diff --git a/cmd/vcert/validators.go b/cmd/vcert/validators.go index a36ef40f..f664cd40 100644 --- a/cmd/vcert/validators.go +++ b/cmd/vcert/validators.go @@ -97,31 +97,10 @@ func validateCommonFlags(commandName string) error { } func validateConnectionFlags(commandName string) error { - if flags.config != "" { - if flags.apiKey != "" || - flags.userName != "" || - flags.password != "" || - flags.token != "" || - flags.url != "" || - flags.tokenURL != "" || - flags.externalJWT != "" || - flags.testMode { - return fmt.Errorf("connection details cannot be specified with flags when --config is used") - } - return nil - } - - if flags.profile != "" { - return fmt.Errorf("--profile option cannot be used without --config option") - } - // Nothing to do in test mode - if flags.testMode { - if commandName == commandGetCredName { - // unless it is get credentials which cannot be emulated - return fmt.Errorf("there is no test mode for %s command", commandName) - } - return nil + err := commonConnectionFlagsValidations(commandName) + if err != nil { + return err } switch flags.platform { @@ -152,6 +131,70 @@ func validateConnectionFlags(commandName string) error { } } +func validateProvisionConnectionFlags(commandName string) error { + err := commonConnectionFlagsValidations(commandName) + if err != nil { + return err + } + + switch flags.platform { + case venafi.TPP: + return fmt.Errorf("command %s not supported for %s", commandName, venafi.TPP.String()) + case venafi.TLSPCloud: + return validateConnectionFlagsCloud(commandName) + case venafi.Firefly: + return fmt.Errorf("command %s not supported for %s", commandName, venafi.TPP.String()) + } + + tppToken := flags.token + if tppToken == "" { + tppToken = getPropertyFromEnvironment(vCertToken) + } + + //Guessing the platform by checking flags + // - Firefly not present here as it is required to pass the platform flag + // - Token empty is considered to mean Cloud connector to keep previous behavior where token was exclusive to TPP + // - To use token with VaaS, the platform flag is required. + // - If the platform flag is set we would not be guessing here + if flags.userName == "" && tppToken == "" && flags.clientP12 == "" { + // should be SaaS endpoint + return validateConnectionFlagsCloud(commandName) + } else { + // should be TPP service + return fmt.Errorf("command %s not supported for %s", commandName, venafi.TPP.String()) + } +} + +func commonConnectionFlagsValidations(commandName string) error { + if flags.config != "" { + if flags.apiKey != "" || + flags.userName != "" || + flags.password != "" || + flags.token != "" || + flags.url != "" || + flags.tokenURL != "" || + flags.externalJWT != "" || + flags.testMode { + return fmt.Errorf("connection details cannot be specified with flags when --config is used") + } + return nil + } + + if flags.profile != "" { + return fmt.Errorf("--profile option cannot be used without --config option") + } + + // Nothing to do in test mode + if flags.testMode { + if commandName == commandGetCredName { + // unless it is get credentials which cannot be emulated + return fmt.Errorf("there is no test mode for %s command", commandName) + } + return nil + } + return nil +} + func validatePKCS12Flags(commandName string) error { if flags.format == P12Format || flags.format == LegacyP12Format { if commandName == commandEnrollName { @@ -658,7 +701,7 @@ func validateSshRetrieveFlags(commandName string) error { } func validateProvisionFlags(commandName string) error { - err := validateConnectionFlags(commandName) + err := validateProvisionConnectionFlags(commandName) if err != nil { return err } From a0e730dda8aa95ef84988baa4d507bd9ee891cc9 Mon Sep 17 00:00:00 2001 From: Luis Presuel Date: Thu, 23 May 2024 11:31:11 -0600 Subject: [PATCH 07/11] adds platform flag and updates examples accordingly --- cmd/vcert/cmdCloudKeystores.go | 4 ++-- cmd/vcert/flags.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/vcert/cmdCloudKeystores.go b/cmd/vcert/cmdCloudKeystores.go index d8e970e8..83b129ac 100644 --- a/cmd/vcert/cmdCloudKeystores.go +++ b/cmd/vcert/cmdCloudKeystores.go @@ -20,8 +20,8 @@ var ( Usage: "provision certificate from Venafi Platform to Cloud Keystore", UsageText: `vcert provision cloudkeystore - vcert provision cloudkeystore -k --certificate-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --keystore-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --format json - vcert provision cloudkeystore -k --pickup-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --provider-name "My GCP Provider"--keystore-name "My GCP provider" --certificate-name "example-venafi-com" + vcert provision cloudkeystore --platform vcp -k --certificate-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --keystore-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --format json + vcert provision cloudkeystore --platform vcp -k --pickup-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --provider-name "My GCP Provider"--keystore-name "My GCP provider" --certificate-name "example-venafi-com" vcert provision cloudkeystore -p vcp -t --certificate-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx --provider-name "My GCP Provider" --keystore-name "My GCP provider" --file "/path/to/file.txt"`, Action: doCommandProvisionCloudKeystore, } diff --git a/cmd/vcert/flags.go b/cmd/vcert/flags.go index 2556f788..cd728d0c 100644 --- a/cmd/vcert/flags.go +++ b/cmd/vcert/flags.go @@ -892,6 +892,7 @@ var ( provisionFlags = flagsApppend( credentialsFlags, + flagPlatform, flagCertificateID, flagProvisionPickupID, flagPickupIDFile, From b3f79968bd264bc20c88abb7723441e0bfa624d7 Mon Sep 17 00:00:00 2001 From: Luis Presuel Date: Thu, 23 May 2024 12:33:51 -0600 Subject: [PATCH 08/11] renames file from cmdCloudProviders to cmdProvisioning --- cmd/vcert/{cmdCloudProviders.go => cmdProvisioning.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmd/vcert/{cmdCloudProviders.go => cmdProvisioning.go} (100%) diff --git a/cmd/vcert/cmdCloudProviders.go b/cmd/vcert/cmdProvisioning.go similarity index 100% rename from cmd/vcert/cmdCloudProviders.go rename to cmd/vcert/cmdProvisioning.go From 7489f00d18dd2e993a5e6acee68e9e62887412e7 Mon Sep 17 00:00:00 2001 From: Luis Presuel Date: Thu, 23 May 2024 12:44:59 -0600 Subject: [PATCH 09/11] fixes error message, fixes goimports --- cmd/vcert/validators.go | 2 +- pkg/endpoint/provisioning.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/vcert/validators.go b/cmd/vcert/validators.go index f664cd40..5803aaa1 100644 --- a/cmd/vcert/validators.go +++ b/cmd/vcert/validators.go @@ -716,7 +716,7 @@ func validateProvisionFlags(commandName string) error { if flags.keystoreID == "" { if flags.keystoreName == "" || flags.providerName == "" { - return fmt.Errorf("any of keystore object, keystore ID or both Provider Name and Keystore Name must be provided for provisioning") + return fmt.Errorf("any of keystore ID or both Provider Name and Keystore Name must be provided for provisioning") } } diff --git a/pkg/endpoint/provisioning.go b/pkg/endpoint/provisioning.go index c5e11601..fbad7f4b 100644 --- a/pkg/endpoint/provisioning.go +++ b/pkg/endpoint/provisioning.go @@ -1,8 +1,9 @@ package endpoint import ( - "github.com/Venafi/vcert/v5/pkg/domain" "time" + + "github.com/Venafi/vcert/v5/pkg/domain" ) type ProvisioningRequest struct { From af43037382bc3e460831408f390f107c07a39185 Mon Sep 17 00:00:00 2001 From: Luis Presuel Date: Thu, 23 May 2024 15:23:52 -0600 Subject: [PATCH 10/11] adds custom provision format to avoid issue STDOUT flow of provisioning. Adds machine Identity values to STDOUT version --- cmd/vcert/args.go | 1 + cmd/vcert/cmdCloudKeystores.go | 2 +- cmd/vcert/flags.go | 8 +++++++- cmd/vcert/result_writer.go | 4 ++++ cmd/vcert/validators.go | 2 +- 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/cmd/vcert/args.go b/cmd/vcert/args.go index 5e31fcc4..60a3f886 100644 --- a/cmd/vcert/args.go +++ b/cmd/vcert/args.go @@ -153,4 +153,5 @@ type commandFlags struct { keystoreCertName string provisionOutputFile string provisionPickupID string + provisionFormat string } diff --git a/cmd/vcert/cmdCloudKeystores.go b/cmd/vcert/cmdCloudKeystores.go index 83b129ac..f33bc7a4 100644 --- a/cmd/vcert/cmdCloudKeystores.go +++ b/cmd/vcert/cmdCloudKeystores.go @@ -91,7 +91,7 @@ func doCommandProvisionCloudKeystore(c *cli.Context) error { result.MachineIdentityId = metadata.GetMachineIdentityMetadata().GetID() result.MachineIdentityActionType = metadata.GetMachineIdentityMetadata().GetActionType() - err = result.Flush(flags.format) + err = result.Flush(flags.provisionFormat) if err != nil { return fmt.Errorf("failed to output the results: %s", err) diff --git a/cmd/vcert/flags.go b/cmd/vcert/flags.go index cd728d0c..56f9f107 100644 --- a/cmd/vcert/flags.go +++ b/cmd/vcert/flags.go @@ -752,6 +752,12 @@ var ( Destination: &flags.provisionPickupID, } + flagProvisionFormat = &cli.StringFlag{ + Name: "format", + Usage: "The format of the operation output: text or JSON. Defaults to text.", + Destination: &flags.provisionFormat, + } + commonFlags = []cli.Flag{flagInsecure, flagVerbose, flagNoPrompt} keyFlags = []cli.Flag{flagKeyType, flagKeySize, flagKeyCurve, flagKeyFile, flagKeyPassword} sansFlags = []cli.Flag{flagDNSSans, flagEmailSans, flagIPSans, flagURISans, flagUPNSans} @@ -900,7 +906,7 @@ var ( flagProviderName, flagKeystoreName, flagKeystoreID, - flagFormat, + flagProvisionFormat, flagProvisionOutputFile, // TODO: implement this flag ) diff --git a/cmd/vcert/result_writer.go b/cmd/vcert/result_writer.go index 1e341d60..a2aaaf5f 100644 --- a/cmd/vcert/result_writer.go +++ b/cmd/vcert/result_writer.go @@ -485,6 +485,10 @@ func (r *ProvisioningResult) Format(format string) (string, error) { result += fmt.Sprintf("gcpId %s\n", r.GcpID) result += fmt.Sprintf("gcpName %s\n", r.GcpName) } + if r.MachineIdentityId != "" { + result += fmt.Sprintf("machineIdentityId %s\n", r.MachineIdentityId) + result += fmt.Sprintf("machineIdentityActionType %s\n", r.MachineIdentityActionType) + } } return result, nil } diff --git a/cmd/vcert/validators.go b/cmd/vcert/validators.go index 5803aaa1..36787f61 100644 --- a/cmd/vcert/validators.go +++ b/cmd/vcert/validators.go @@ -706,7 +706,7 @@ func validateProvisionFlags(commandName string) error { return err } - if flags.format != "" && flags.format != "json" { + if flags.provisionFormat != "" && flags.provisionFormat != "json" { return fmt.Errorf("unexpected output format: %s", flags.format) } From 990f5fc1d1732ac617ff57279a463d0819d03662 Mon Sep 17 00:00:00 2001 From: Luis Presuel Date: Thu, 23 May 2024 17:41:19 -0600 Subject: [PATCH 11/11] fixes bug when reading from config file --- cmd/vcert/validators.go | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/cmd/vcert/validators.go b/cmd/vcert/validators.go index 36787f61..914e20b8 100644 --- a/cmd/vcert/validators.go +++ b/cmd/vcert/validators.go @@ -98,9 +98,31 @@ func validateCommonFlags(commandName string) error { func validateConnectionFlags(commandName string) error { - err := commonConnectionFlagsValidations(commandName) - if err != nil { - return err + if flags.config != "" { + if flags.apiKey != "" || + flags.userName != "" || + flags.password != "" || + flags.token != "" || + flags.url != "" || + flags.tokenURL != "" || + flags.externalJWT != "" || + flags.testMode { + return fmt.Errorf("connection details cannot be specified with flags when --config is used") + } + return nil + } + + if flags.profile != "" { + return fmt.Errorf("--profile option cannot be used without --config option") + } + + // Nothing to do in test mode + if flags.testMode { + if commandName == commandGetCredName { + // unless it is get credentials which cannot be emulated + return fmt.Errorf("there is no test mode for %s command", commandName) + } + return nil } switch flags.platform {