diff --git a/README-CLI-CLOUD.md b/README-CLI-CLOUD.md index e7034cb7..75ea006b 100644 --- a/README-CLI-CLOUD.md +++ b/README-CLI-CLOUD.md @@ -231,15 +231,16 @@ Options: | Command | Description | |-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--arn` | Use to specify AWS Resource Name which provisioned certificate will replace (only for AWS Certificate Manager) | | `--certificate-id` | The id of the certificate to be provisioned to a cloud keystore. | | `--certificate-id-file` | Use to specify a file name that contains the unique identifier of the certificate. Required when `--certificate-id` is not specified. | -| `--certificate-name` | Use to specify Cloud Keystore Certificate Name if it supports it | +| `--certificate-name` | Use to specify Cloud Keystore Certificate Name to be set or replaced by provisioned certificate (only for Azure Key Vault and Google Certificate Manager) | | `--file` | Use to specify a file name and a location where the output should be written. Example: --file /path-to/provision-output | | `--format` | The format of the operation output: text or JSON. Defaults to text. | | `--keystore-id` | The id of the cloud keystore where the certificate will be provisioned. | | `--keystore-name` | The name of the cloud keystore where the certificate will be provisioned. Must be set along with provider-name flag. | -| `--pickup-id-file` | Use to specify a file name that contains the unique identifier of the certificate returned by the enroll or renew actions if --no-pickup was used or a timeout occurred. Required when `--pickup-id` is not specified. | | `--pickup-id` | Use to specify the unique identifier of the certificate returned by the enroll or renew actions. Required when `--pickup-id-file` is not specified. | +| `--pickup-id-file` | Use to specify a file name that contains the unique identifier of the certificate returned by the enroll or renew actions if --no-pickup was used or a timeout occurred. Required when `--pickup-id` is not specified. | | `--provider-name` | The name of the cloud provider which owns the cloud keystore where the certificate will be provisioned. Must be set along with keystore-name flag. | ## Parameters for Applying Certificate Policy diff --git a/aruba/Dockerfile b/aruba/Dockerfile index 25244795..c92d1b42 100644 --- a/aruba/Dockerfile +++ b/aruba/Dockerfile @@ -3,6 +3,7 @@ MAINTAINER Venafi DevOps Integrations RUN gem install aruba json_spec RUN gem install google-cloud-certificate_manager-v1 +RUN gem install aws-sdk-acm COPY . /vcert/ ENV BUNDLE_PATH="/vcert/tpp" ENV GCP_AUTH_PATH="/vcert/cloud_providers" diff --git a/aruba/cucumber.sh b/aruba/cucumber.sh index be94f561..14ff49a6 100755 --- a/aruba/cucumber.sh +++ b/aruba/cucumber.sh @@ -32,7 +32,13 @@ RUN_COMMAND="docker run -t --rm \ -e GCP_REGION \ -e GCP_PROVIDER_NAME \ -e GCP_KEYSTORE_NAME \ - -e GCP_KEYSTORE_ID" + -e GCP_KEYSTORE_ID \ + -e AWS_ACCESS_KEY_ID \ + -e AWS_REGION \ + -e AWS_SECRET_ACCESS_KEY \ + -e AWS_PROVIDER_NAME \ + -e AWS_KEYSTORE_NAME \ + -e AWS_KEYSTORE_ID" # Use getopts to handle command-line options while getopts "a:b:" opt; do diff --git a/aruba/features/provision/cloudkeystore/provision_cloudkeystore.feature b/aruba/features/provision/cloudkeystore/provision_cloudkeystore.feature index 41d3369b..ba1706f9 100644 --- a/aruba/features/provision/cloudkeystore/provision_cloudkeystore.feature +++ b/aruba/features/provision/cloudkeystore/provision_cloudkeystore.feature @@ -12,19 +12,38 @@ Feature: provision to cloud keystore And I remember the output And I use previous Pickup ID to provision from VCP a certificate to cloudkeystore "" setting keystore and provider names And I remember the output - And it should output cloud ID + And I grab cloud ID from output Then I clean up previous installed certificate from cloudkeystore Examples: | cloudkeystore | | GOOGLE | + | AWS | Scenario Outline: Enroll certificate and execute provisioning for cloud keystore and get output in JSON Given I enroll a random certificate with defined platform VCP with -csr service -no-prompt And I remember the output And I use previous Pickup ID to provision from VCP a certificate to cloudkeystore "" setting keystore and provider names with -format json And I remember the output - And it should output cloud ID in JSON + And I grab cloud ID from JSON output Then I clean up previous installed certificate from cloudkeystore Examples: | cloudkeystore | | GOOGLE | + | AWS | + + Scenario Outline: Enroll certificate, execute provisioning and then provisioning again for replace + Given I enroll a random certificate with defined platform VCP with -csr service -no-prompt + And I remember the output + And I use previous Pickup ID to provision from VCP a certificate to cloudkeystore "" setting keystore and provider names + And I remember the output + And the output should contain "cloudId:" + And the output should contain "machineIdentityActionType: New" + And I grab cloud ID from output + Then I use previous Pickup ID and cloud ID to provision again + And I remember the output + And the output should contain the previous cloud ID + And the output should contain "machineIdentityActionType: ReProvision" + Then I clean up previous installed certificate from cloudkeystore + Examples: + | cloudkeystore | + | AWS | diff --git a/aruba/features/provision/cloudkeystore/steps_definitions/my_steps.rb b/aruba/features/provision/cloudkeystore/steps_definitions/my_steps.rb index 79bb76b2..d6579337 100644 --- a/aruba/features/provision/cloudkeystore/steps_definitions/my_steps.rb +++ b/aruba/features/provision/cloudkeystore/steps_definitions/my_steps.rb @@ -12,8 +12,23 @@ steps %{Then I try to run `#{cmd}`} end +And(/^I use previous Pickup ID and cloud ID to provision again$/) do + keystore_provider_names = true + flags = "" + if @cloudkeystore_type == $keystore_type_aws + flags += " -arn #{@cloud_id}" + elsif @cloudkeystore_type == $keystore_type_azure or @cloudkeystore_type == $keystore_type_gcp + flags += " -certificate-name #{@cloud_id}" + end + flags += @global_set_provision_flags + cmd = build_provision_cmd($platform_vcp, @cloudkeystore_type, keystore_provider_names, flags) + steps %{Then I try to run `#{cmd}`} +end + def build_provision_cmd(platform, cloudkeystore_type, keystore_provider_names, flags = "") + @global_set_provision_flags = flags + platform_flag = " -platform " + platform cmd = "vcert provision cloudkeystore #{platform_flag} #{ENDPOINTS[$platform_vcp]} -pickup-id #{@pickup_id}" @@ -22,7 +37,14 @@ def build_provision_cmd(platform, cloudkeystore_type, keystore_provider_names, f provider_name = "" keystore_id = "" case cloudkeystore_type - when $keystore_type_azure + when $keystore_type_aws + if keystore_provider_names + keystore_name = $aws_keystore_name + provider_name = $aws_provider_name + @cloudkeystore_type = $keystore_type_aws + else + keystore_name = $aws_keystore_id + end when $keystore_type_gcp if keystore_provider_names keystore_name = $gcp_keystore_name @@ -51,45 +73,75 @@ def build_provision_cmd(platform, cloudkeystore_type, keystore_provider_names, f return cmd end -Then(/^it should output cloud ID( in JSON)?$/) do |json| +Then(/^I grab cloud ID from( JSON)? output$/) do |json| + + @cloud_id = get_cloud_id_from_output(json) + +end +def get_cloud_id_from_output(json = false) if @previous_command_output.nil? fail(ArgumentError.new('@previous_command_output is nil')) end Kernel.puts("Checking output:\n"+@previous_command_output) - cloud_id = "" - case @cloudkeystore_type - when $keystore_type_aws - when $keystore_type_azure - when $keystore_type_gcp - cloud_id = "gcpId" - else - fail(ArgumentError.new("Unexpected : #{@cloudkeystore_type}")) - end + cloud_id_attr = "cloudId" + if json json_string = extract_json_from_output(@previous_command_output) JSON.parse(json_string) - @cloud_id = unescape_text(normalize_json(json_string, "#{cloud_id}")).tr('"', '') + cloud_id = unescape_text(normalize_json(json_string, "#{cloud_id_attr}")).tr('"', '') else - m = @previous_command_output.match /#{cloud_id} (.+)$/ - @cloud_id = m[1] + m = @previous_command_output.match /#{cloud_id_attr}: (.+)$/ + cloud_id = m[1] + end + cloud_id +end + +Then(/^the output( in JSON)? should contain the previous cloud ID$/) do |json| + old_cloud_id = @cloud_id + new_cloud_id = get_cloud_id_from_output(json) + if old_cloud_id != new_cloud_id + cleanup_keystore(old_cloud_id) + cleanup_keystore(new_cloud_id) + fail(ArgumentError.new("Expected old Cloud ID: #{old_cloud_id} to be same as new Cloud ID, but got: #{new_cloud_id}")) end end And(/^I clean up previous installed certificate from cloudkeystore/) do || + cleanup_keystore +end + +def cleanup_keystore(cloud_id = "") case @cloudkeystore_type when $keystore_type_aws + cleanup_aws(cloud_id) when $keystore_type_azure when $keystore_type_gcp - cleanup_google + cleanup_google(cloud_id) else fail(ArgumentError.new("Unexpected : #{@cloudkeystore_type}")) end end -def cleanup_google - client = create_certificate_manager_client - certificate_name = "projects/#{ENV['GCP_PROJECT']}/locations/#{ENV['GCP_REGION']}/certificates/#{@cloud_id}" - delete_certificate(client, certificate_name) +def cleanup_google(cloud_id = "") + client = create_google_certificate_manager_client + if cloud_id != "" + certificate_name = "projects/#{ENV['GCP_PROJECT']}/locations/#{ENV['GCP_REGION']}/certificates/#{cloud_id}" + else + certificate_name = "projects/#{ENV['GCP_PROJECT']}/locations/#{ENV['GCP_REGION']}/certificates/#{@cloud_id}" + end + + delete_gcm_certificate(client, certificate_name) +end + +def cleanup_aws(cloud_id = "") + client = create_aws_certificate_manager_client + if cloud_id != "" + certificate_arn = cloud_id + else + certificate_arn = @cloud_id + end + + delete_acm_certificate(client, certificate_arn) end diff --git a/aruba/features/support/aruba.rb b/aruba/features/support/aruba.rb index 7c055d27..f1864da9 100644 --- a/aruba/features/support/aruba.rb +++ b/aruba/features/support/aruba.rb @@ -23,6 +23,10 @@ $gcp_keystore_name = ENV["GCP_KEYSTORE_NAME"] $gcp_provider_name = ENV["GCP_PROVIDER_NAME"] +$aws_keystore_id = ENV["AWS_KEYSTORE_ID"] +$aws_keystore_name = ENV["AWS_KEYSTORE_NAME"] +$aws_provider_name = ENV["AWS_PROVIDER_NAME"] + def last_json last_command_started.stdout.to_s end diff --git a/aruba/features/support/aws_provider.rb b/aruba/features/support/aws_provider.rb new file mode 100644 index 00000000..3477a21f --- /dev/null +++ b/aruba/features/support/aws_provider.rb @@ -0,0 +1,20 @@ +require 'aws-sdk-acm' + +# Initialize the Certificate Manager Client +def create_aws_certificate_manager_client + Aws::ACM::Client.new( + region: ENV['AWS_REGION'], + access_key_id: ENV['AWS_ACCESS_KEY_ID'], + secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'] + ) +end + +# Delete a certificate +def delete_acm_certificate(client, certificate_arn) + begin + client.delete_certificate({ certificate_arn: certificate_arn }) + puts "Certificate with ARN #{certificate_arn} deleted successfully." + rescue Aws::ACM::Errors::ServiceError => e + puts "Error deleting certificate: #{e.message}" + end +end diff --git a/aruba/features/support/google_provider.rb b/aruba/features/support/google_provider.rb index f8422d16..6efb7aa7 100644 --- a/aruba/features/support/google_provider.rb +++ b/aruba/features/support/google_provider.rb @@ -4,12 +4,12 @@ ENV['GOOGLE_APPLICATION_CREDENTIALS'] = ENV['GCP_AUTH_PATH'] # Initialize the Certificate Manager Client -def create_certificate_manager_client +def create_google_certificate_manager_client Google::Cloud::CertificateManager::V1::CertificateManager::Client.new end # Delete a certificate -def delete_certificate(client, certificate_name) +def delete_gcm_certificate(client, certificate_name) request = Google::Cloud::CertificateManager::V1::DeleteCertificateRequest.new( name: certificate_name ) diff --git a/cmd/vcert/args.go b/cmd/vcert/args.go index a2c18d85..5bffe290 100644 --- a/cmd/vcert/args.go +++ b/cmd/vcert/args.go @@ -152,6 +152,7 @@ type commandFlags struct { providerName string keystoreName string keystoreCertName string + keystoreARN string provisionOutputFile string provisionPickupID string provisionFormat string diff --git a/cmd/vcert/cmdCloudKeystores.go b/cmd/vcert/cmdCloudKeystores.go index 37eaf65e..3463fa7d 100644 --- a/cmd/vcert/cmdCloudKeystores.go +++ b/cmd/vcert/cmdCloudKeystores.go @@ -77,21 +77,20 @@ func doCommandProvisionCloudKeystore(c *cli.Context) error { MachineIdentityId: metadata.MachineIdentityID, MachineIdentityActionType: metadata.MachineIdentityActionType, } + result.CloudID = metadata.CertificateID switch metadata.CloudKeystoreType { case domain.CloudKeystoreTypeACM: - result.ARN = metadata.CertificateID + // do nothing case domain.CloudKeystoreTypeAKV: - result.AzureID = metadata.CertificateID result.AzureName = metadata.CertificateName result.AzureVersion = metadata.CertificateVersion case domain.CloudKeystoreTypeGCM: - result.GcpID = metadata.CertificateID result.GcpName = metadata.CertificateName default: return fmt.Errorf("unknown keystore metadata type: %s", metadata.CloudKeystoreType) } - err = result.Flush(flags.provisionFormat, flags.provisionOutputFile) + err = result.Flush(flags.provisionFormat, flags.provisionOutputFile, metadata.CloudKeystoreType) 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 bf10cbf9..fddce627 100644 --- a/cmd/vcert/flags.go +++ b/cmd/vcert/flags.go @@ -741,10 +741,16 @@ var ( flagKeystoreCertName = &cli.StringFlag{ Name: "certificate-name", - Usage: "Use to specify Cloud Keystore Certificate Name if it supports it", + Usage: "Use to specify Cloud Keystore Certificate Name to be set or replaced by provisioned certificate (only for Azure Key Vault and Google Certificate Manager)", Destination: &flags.keystoreCertName, } + flagKeystoreARN = &cli.StringFlag{ + Name: "arn", + Usage: "Use to specify AWS Resource Name which provisioned certificate will replace (only for AWS Certificate Manager)", + Destination: &flags.keystoreARN, + } + flagProvisionOutputFile = &cli.StringFlag{ Name: "file", Usage: "Use to specify a file name and a location where the output should be written. " + @@ -906,16 +912,17 @@ var ( provisionFlags = flagsApppend( credentialsFlags, flagPlatform, + flagKeystoreARN, flagCertificateID, flagCertificateIDFile, + flagKeystoreCertName, + flagProvisionOutputFile, + flagProvisionFormat, + flagKeystoreID, + flagKeystoreName, flagProvisionPickupID, flagPickupIDFile, - flagKeystoreCertName, flagProviderName, - flagKeystoreName, - flagKeystoreID, - flagProvisionFormat, - flagProvisionOutputFile, ) commonCredFlags = []cli.Flag{flagConfig, flagProfile, flagUrl, flagToken, flagTrustBundle} diff --git a/cmd/vcert/result_writer.go b/cmd/vcert/result_writer.go index 77b13db2..efbe2d97 100644 --- a/cmd/vcert/result_writer.go +++ b/cmd/vcert/result_writer.go @@ -22,6 +22,7 @@ import ( "encoding/json" "encoding/pem" "fmt" + "github.com/Venafi/vcert/v5/pkg/domain" "os" "strings" "time" @@ -61,11 +62,9 @@ type Result struct { } type ProvisioningResult struct { - ARN string `json:"arn,omitempty"` - AzureID string `json:"azureId,omitempty"` + CloudID string `json:"cloudId,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"` @@ -447,9 +446,9 @@ func outputJSON(resp interface{}) error { return err } -func (r *ProvisioningResult) Flush(format string, filePath string) error { +func (r *ProvisioningResult) Flush(format string, filePath string, keystoreType domain.CloudKeystoreType) error { - result, err := r.Format(format) + result, err := r.Format(format, keystoreType) if err != nil { return err } @@ -475,7 +474,7 @@ func (r *ProvisioningResult) WriteFile(result string, filePath string) error { return nil } -func (r *ProvisioningResult) Format(format string) (string, error) { +func (r *ProvisioningResult) Format(format string, keystoreType domain.CloudKeystoreType) (string, error) { result := "" switch strings.ToLower(format) { case formatJson: @@ -485,22 +484,21 @@ func (r *ProvisioningResult) Format(format string) (string, error) { } 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("cloudId: %s\n", r.CloudID) + switch keystoreType { + case domain.CloudKeystoreTypeACM: + // do nothing + case domain.CloudKeystoreTypeAKV: 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) + case domain.CloudKeystoreTypeGCM: + result += fmt.Sprintf("gcpName: %s\n", r.GcpName) + default: + return "", fmt.Errorf("during formatting response, got unknown keystore type: %v", keystoreType) } if r.MachineIdentityId != "" { - result += fmt.Sprintf("machineIdentityId %s\n", r.MachineIdentityId) - result += fmt.Sprintf("machineIdentityActionType %s\n", r.MachineIdentityActionType) + result += fmt.Sprintf("machineIdentityId: %s\n", r.MachineIdentityId) + result += fmt.Sprintf("machineIdentityActionType: %s\n", r.MachineIdentityActionType) } } return result, nil diff --git a/cmd/vcert/utils.go b/cmd/vcert/utils.go index 7ea35373..72249617 100644 --- a/cmd/vcert/utils.go +++ b/cmd/vcert/utils.go @@ -623,13 +623,14 @@ func fillProvisioningRequest(req *domain.ProvisioningRequest, keystore domain.Cl req.Keystore = &keystore req.PickupID = &(cf.provisionPickupID) - if cf.keystoreCertName == "" { - return req, nil - } + var options *domain.ProvisioningOptions - options := &domain.ProvisioningOptions{ - CloudCertificateName: cf.keystoreCertName, + if cf.keystoreCertName != "" || cf.keystoreARN != "" { + options = &domain.ProvisioningOptions{} + options.CloudCertificateName = cf.keystoreCertName + options.ARN = cf.keystoreARN } + return req, options } diff --git a/examples/provision/main.go b/examples/provision/main.go index f007d0b3..17ce1636 100644 --- a/examples/provision/main.go +++ b/examples/provision/main.go @@ -60,7 +60,7 @@ func main() { // Example to get values from other keystores machine identities metadata if certMetaData.CloudKeystoreType == domain.CloudKeystoreTypeACM { - log.Printf("Certificate AWS Metadata ARN:\n%v", certMetaData.ARN) + log.Printf("Certificate AWS Metadata ARN:\n%v", certMetaData.CertificateID) } if certMetaData.CloudKeystoreType == domain.CloudKeystoreTypeAKV { log.Printf("Certificate Azure Metadata ID:\n%v", certMetaData.CertificateID) diff --git a/pkg/domain/provisioning.go b/pkg/domain/provisioning.go index 1e344081..798ba5bc 100644 --- a/pkg/domain/provisioning.go +++ b/pkg/domain/provisioning.go @@ -25,5 +25,8 @@ type ProvisioningMetadata struct { } type ProvisioningOptions struct { + // for ACM only + ARN string + // for AKV and GCM only CloudCertificateName string } diff --git a/pkg/venafi/cloud/cloudproviders.go b/pkg/venafi/cloud/cloudproviders.go index 44185ba3..9d0bcb70 100644 --- a/pkg/venafi/cloud/cloudproviders.go +++ b/pkg/venafi/cloud/cloudproviders.go @@ -200,7 +200,7 @@ func (c *Connector) ProvisionCertificateToMachineIdentity(req domain.Provisionin return nil, err } - keystoreType := domain.CloudKeystoreTypeUnknown + var keystoreType domain.CloudKeystoreType if req.Keystore == nil { log.Printf("fetching machine identity to get type") machineIdentity, err := c.cloudProvidersClient.GetMachineIdentity(ctx, domain.GetCloudMachineIdentityRequest{ @@ -277,12 +277,13 @@ func (c *Connector) DeleteMachineIdentity(machineIdentityID string) (bool, error } func setProvisioningOptions(options domain.ProvisioningOptions, keystoreType domain.CloudKeystoreType) (*cloudproviders.CertificateProvisioningOptionsInput, error) { + awsOptions := &cloudproviders.CertificateProvisioningAWSOptionsInput{} azureOptions := &cloudproviders.CertificateProvisioningAzureOptionsInput{} gcpOptions := &cloudproviders.CertificateProvisioningGCPOptionsInput{} switch keystoreType { case domain.CloudKeystoreTypeACM: - // nothing + awsOptions.Arn = &options.ARN case domain.CloudKeystoreTypeAKV: azureOptions.Name = &options.CloudCertificateName case domain.CloudKeystoreTypeGCM: @@ -292,7 +293,7 @@ func setProvisioningOptions(options domain.ProvisioningOptions, keystoreType dom } provisioningOptions := &cloudproviders.CertificateProvisioningOptionsInput{ - AwsOptions: nil, + AwsOptions: awsOptions, AzureOptions: azureOptions, GcpOptions: gcpOptions, }