diff --git a/cmd/vcert/args.go b/cmd/vcert/args.go index 6f413f7a..60a3f886 100644 --- a/cmd/vcert/args.go +++ b/cmd/vcert/args.go @@ -22,24 +22,29 @@ 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" + 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 + flags commandFlags + provisionCommands = stringSlice{ + subCommandCloudKeystoreName, + } ) type commandFlags struct { @@ -141,4 +146,12 @@ type commandFlags struct { sshCertWindows bool sshFileCertEnroll string sshFileGetConfig string + certificateID string + keystoreID string + providerName string + keystoreName string + keystoreCertName string + provisionOutputFile string + provisionPickupID string + provisionFormat string } diff --git a/cmd/vcert/cmdCloudKeystores.go b/cmd/vcert/cmdCloudKeystores.go new file mode 100644 index 00000000..f33bc7a4 --- /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 --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, + } +) + +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.provisionFormat) + + if err != nil { + return fmt.Errorf("failed to output the results: %s", err) + } + return nil +} diff --git a/cmd/vcert/cmdProvisioning.go b/cmd/vcert/cmdProvisioning.go new file mode 100644 index 00000000..688dfdf8 --- /dev/null +++ b/cmd/vcert/cmdProvisioning.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/urfave/cli/v2" +) + +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{subCommandCloudKeystore}, + } +) + +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() +} diff --git a/cmd/vcert/flags.go b/cmd/vcert/flags.go index 11731a98..56f9f107 100644 --- a/cmd/vcert/flags.go +++ b/cmd/vcert/flags.go @@ -708,6 +708,56 @@ 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: "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, + } + + 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} @@ -846,6 +896,20 @@ var ( )), ) + provisionFlags = flagsApppend( + credentialsFlags, + flagPlatform, + flagCertificateID, + flagProvisionPickupID, + flagPickupIDFile, + flagKeystoreCertName, + flagProviderName, + flagKeystoreName, + flagKeystoreID, + flagProvisionFormat, + flagProvisionOutputFile, // TODO: implement this flag + ) + 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..86333ba3 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..a2aaaf5f 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,17 @@ type Result struct { Config *Config } +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"` + MachineIdentityId string `json:"machineIdentityId,omitempty"` + MachineIdentityActionType string `json:"machineIdentityActionType,omitempty"` +} + type Output struct { Certificate string `json:",omitempty"` CSR string `json:",omitempty"` @@ -315,24 +330,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 +403,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 +446,49 @@ 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.MarshalIndent(r, "", " ") + if err != nil { + return "", fmt.Errorf("failed to construct JSON: %s", err) + } + result = string(b) + default: + if r.ARN != "" { + result += fmt.Sprintf("arn: %s\n", r.ARN) + } + 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 != "" { + 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/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..51dff434 100644 --- a/cmd/vcert/utils.go +++ b/cmd/vcert/utils.go @@ -37,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 ( @@ -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): + optionsAkv := &cloud.CloudProvisioningAzureOptions{ + Name: &cf.keystoreCertName, + } + options = endpoint.ProvisioningOptions(optionsAkv) + case string(cloudproviders.CloudKeystoreTypeGcm): + optionsGcp := &cloud.CloudProvisioningGCPOptions{ + ID: &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..914e20b8 100644 --- a/cmd/vcert/validators.go +++ b/cmd/vcert/validators.go @@ -97,6 +97,7 @@ func validateCommonFlags(commandName string) error { } func validateConnectionFlags(commandName string) error { + if flags.config != "" { if flags.apiKey != "" || flags.userName != "" || @@ -152,6 +153,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 { @@ -657,6 +722,34 @@ func validateSshRetrieveFlags(commandName string) error { return nil } +func validateProvisionFlags(commandName string) error { + err := validateProvisionConnectionFlags(commandName) + if err != nil { + return err + } + + if flags.provisionFormat != "" && flags.provisionFormat != "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 ID or both Provider Name and Keystore Name must be provided for provisioning") + } + } + + err = readData(commandName) + if err != nil { + return err + } + + return nil +} + func validateExistingFile(f string) error { fileNames, err := getExistingSshFiles(f) 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 23f3915f..fbad7f4b 100644 --- a/pkg/endpoint/provisioning.go +++ b/pkg/endpoint/provisioning.go @@ -1,6 +1,10 @@ package endpoint -import "time" +import ( + "time" + + "github.com/Venafi/vcert/v5/pkg/domain" +) type ProvisioningRequest struct { CertificateID *string @@ -9,12 +13,14 @@ type ProvisioningRequest struct { KeystoreName *string ProviderName *string Timeout time.Duration + Keystore *domain.CloudKeystore } type ProvisioningMetadata interface { GetAWSCertificateMetadata() AWSCertificateMetadata GetAzureCertificateMetadata() AzureCertificateMetadata GetGCPCertificateMetadata() GCPCertificateMetadata + GetMachineIdentityMetadata() MachineIdentityMetadata } type AWSCertificateMetadata interface { @@ -32,6 +38,11 @@ type GCPCertificateMetadata interface { GetName() string } +type MachineIdentityMetadata interface { + GetID() string + GetActionType() string +} + type ProvisioningOptions interface { GetType() string } 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..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,11 +141,14 @@ type CloudProvisioningGCPOptions struct { } func (cpgo CloudProvisioningGCPOptions) GetType() string { - return "GCM" + return KeystoreTypeGCM } 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 @@ -215,27 +243,14 @@ 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, - } - +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 with name %s from Cloud Provider with ID %s: %w", cloudKeystoreName, cloudProviderID, err) + return nil, fmt.Errorf("failed to retrieve Cloud Keystore: %w", err) } if cloudKeystore == nil { - return nil, fmt.Errorf("could not find Cloud Keystore with name %s in Cloud Provider with ID %s", cloudKeystoreName, cloudProviderID) + 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 } @@ -271,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 } diff --git a/pkg/venafi/cloud/connector.go b/pkg/venafi/cloud/connector.go index 9bc4de79..1beb9fdb 100644 --- a/pkg/venafi/cloud/connector.go +++ b/pkg/venafi/cloud/connector.go @@ -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 object, 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..a6aa3c26 100644 --- a/pkg/webclient/cloudproviders/cloudproviders.go +++ b/pkg/webclient/cloudproviders/cloudproviders.go @@ -8,6 +8,7 @@ import ( "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 @@ -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")