From f0a908c9509c6ef52459e6a0683b2210553b6075 Mon Sep 17 00:00:00 2001 From: Luis Presuel Date: Wed, 5 Jun 2024 11:35:57 -0600 Subject: [PATCH] changes output in CLI to unify returned ID's from providers in Cloud ID. Provides ability to provider ARN as well adds ARN flag in CLI. Adds AWS Cloud Provider testing for provisioning in cucumber tests and also refactors code accordingly. Adds AWS client to delete certificate after certificate is provisioned afterward --- README-CLI-CLOUD.md | 5 +- aruba/Dockerfile | 1 + aruba/cucumber.sh | 8 +- .../provision_cloudkeystore.feature | 23 ++++- .../steps_definitions/my_steps.rb | 90 +++++++++++++++---- aruba/features/support/aruba.rb | 4 + aruba/features/support/aws_provider.rb | 20 +++++ aruba/features/support/google_provider.rb | 4 +- cmd/vcert/args.go | 1 + cmd/vcert/cmdCloudKeystores.go | 7 +- cmd/vcert/flags.go | 19 ++-- cmd/vcert/result_writer.go | 34 ++++--- cmd/vcert/utils.go | 11 +-- examples/provision/main.go | 2 +- pkg/domain/provisioning.go | 3 + pkg/venafi/cloud/cloudproviders.go | 5 +- 16 files changed, 175 insertions(+), 62 deletions(-) create mode 100644 aruba/features/support/aws_provider.rb 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..f785d778 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..533f254d 100644 --- a/pkg/venafi/cloud/cloudproviders.go +++ b/pkg/venafi/cloud/cloudproviders.go @@ -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, }