From 4af64e12e671eb3c13f7e42e7cb457de30e79cff Mon Sep 17 00:00:00 2001 From: Alena Kastsiukavets Date: Tue, 6 Oct 2020 20:28:58 -0700 Subject: [PATCH 01/10] Improve logging during DownloadBundle step Improve logging during DownloadBundle step Add log as `:info`, so it is visible by default --- lib/instance_agent/plugins/codedeploy/command_executor.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/instance_agent/plugins/codedeploy/command_executor.rb b/lib/instance_agent/plugins/codedeploy/command_executor.rb index 99463247..9a1ab9f1 100644 --- a/lib/instance_agent/plugins/codedeploy/command_executor.rb +++ b/lib/instance_agent/plugins/codedeploy/command_executor.rb @@ -239,7 +239,7 @@ def most_recent_install_file_path(deployment_group) private def download_from_s3(deployment_spec, bucket, key, version, etag) - log(:debug, "Downloading artifact bundle from bucket '#{bucket}' and key '#{key}', version '#{version}', etag '#{etag}'") + log(:info, "Downloading artifact bundle from bucket '#{bucket}' and key '#{key}', version '#{version}', etag '#{etag}'") s3 = Aws::S3::Client.new(s3_options) @@ -257,7 +257,7 @@ def download_from_s3(deployment_spec, bucket, key, version, etag) raise RuntimeError, msg end end - log(:debug, "Download complete from bucket #{bucket} and key #{key}") + log(:info, "Download complete from bucket #{bucket} and key #{key}") end public @@ -345,7 +345,7 @@ def download_from_github(deployment_spec, account, repo, commit, anonymous, toke if retries < 3 time_to_sleep = (10 * (3 ** retries)) # 10 sec, 30 sec, 90 sec - log(:debug, "Retrying download in #{time_to_sleep} seconds.") + log(:info, "Retrying download in #{time_to_sleep} seconds.") sleep(time_to_sleep) retries += 1 retry @@ -359,6 +359,7 @@ def download_from_github(deployment_spec, account, repo, commit, anonymous, toke def handle_local_file(deployment_spec, local_location) # Symlink local file to the location where download is expected to go bundle_file = artifact_bundle(deployment_spec) + log(:info, "Handle local file #{bundle_file}") begin File.symlink local_location, bundle_file rescue @@ -371,6 +372,7 @@ def handle_local_file(deployment_spec, local_location) def handle_local_directory(deployment_spec, local_location) # Copy local directory to the location where a file would have been extracted # We copy instead of symlinking in order to preserve revision history + log(:info, "Handle local directory #{local_location}") FileUtils.cp_r local_location, archive_root_dir(deployment_spec) end From 4c764a35dc1aa5f4d6d5315afcafffa3985edc82 Mon Sep 17 00:00:00 2001 From: Tanuj Pankaj Date: Fri, 31 Jul 2020 21:05:39 +0000 Subject: [PATCH 02/10] [ISO Release] Add endpoint support for ADCS, S3, and S3 in the install/update scripts. Prior to this change, CodeDeploy endpoints did not have support for ISO regions. S3 endpoints did not rely on the AWS SDK when it could have for getting those endpoints. Install/Update scripts did not have ISO region support. This change uses the instance metadata to get the CodeDeploy endpoint. Uses the SDK to retrieve the S3 endpoint (for both fips and non-fips). Install and update scripts now use instance metadata to get the endpoints. * Unit Tests : Y * Integration Tests : Tested sample deployment in us-west-2. The S3 and CodeDepoly endpoints were tested in a Government region. Still waiting on ISO region testing to make sure that everything works. --- bin/install | 21 ++++--- bin/update | 22 +++++--- .../plugins/codedeploy/command_executor.rb | 27 +++++++-- lib/instance_metadata.rb | 4 ++ .../codedeploy/codedeploy_control_test.rb | 2 + .../codedeploy/command_executor_test.rb | 56 +++++++++++-------- test/instance_metadata_test.rb | 4 ++ .../aws/plugins/deploy_control_endpoint.rb | 14 ++--- 8 files changed, 100 insertions(+), 50 deletions(-) diff --git a/bin/install b/bin/install index 76217349..07b28fc2 100755 --- a/bin/install +++ b/bin/install @@ -86,6 +86,16 @@ class IMDSV2 JSON.parse(get_request(DOCUMENT_PATH).strip) end end + + private + def self.get_metadata_wrapper(path) + token = put_request('/latest/api/token') + get_request(path, token) + end + + def self.domain + get_metadata_wrapper('/latest/meta-data/services/domain').strip + end end begin @@ -275,13 +285,10 @@ EOF end def get_s3_uri(region, bucket, key) - if (region == 'us-east-1') - URI.parse("https://#{bucket}.s3.amazonaws.com/#{key}") - elsif (region.split("-")[0] == 'cn') - URI.parse("https://#{bucket}.s3.#{region}.amazonaws.com.cn/#{key}") - else - URI.parse("https://#{bucket}.s3.#{region}.amazonaws.com/#{key}") - end + domain = IMDSV2.domain + endpoint = "https://#{bucket}.s3.#{region}.#{domain}/#{key}" + @log.info("Endpoint: #{endpoint}") + URI.parse("https://#{bucket}.s3.#{region}.#{domain}/#{key}") end def get_package_from_s3(region, bucket, key, package_file) diff --git a/bin/update b/bin/update index b4919fed..cf39377e 100755 --- a/bin/update +++ b/bin/update @@ -87,6 +87,16 @@ class IMDSV2 JSON.parse(get_request(DOCUMENT_PATH).strip) end end + + private + def self.get_metadata_wrapper(path) + token = put_request('/latest/api/token') + get_request(path, token) + end + + def self.domain + get_metadata_wrapper('/latest/meta-data/services/domain').strip + end end require 'set' @@ -342,18 +352,14 @@ EOF end def get_s3_uri(key) - if (REGION == 'us-east-1') - URI.parse("https://#{BUCKET}.s3.amazonaws.com/#{key}") - elsif (REGION.split("-")[0] == 'cn') - URI.parse("https://#{BUCKET}.s3.#{REGION}.amazonaws.com.cn/#{key}") - else - URI.parse("https://#{BUCKET}.s3.#{REGION}.amazonaws.com/#{key}") - end + domain = IMDSV2.domain + endpoint = "https://#{BUCKET}.s3.#{REGION}.#{domain}/#{key}" + @log.info("Endpoint: #{endpoint}") + URI.parse(endpoint) end def get_package_from_s3(key, package_file) @log.info("Downloading package from BUCKET #{BUCKET} and key #{key}...") - uri = get_s3_uri(key) # stream package file to disk diff --git a/lib/instance_agent/plugins/codedeploy/command_executor.rb b/lib/instance_agent/plugins/codedeploy/command_executor.rb index 9a1ab9f1..72db066e 100644 --- a/lib/instance_agent/plugins/codedeploy/command_executor.rb +++ b/lib/instance_agent/plugins/codedeploy/command_executor.rb @@ -240,16 +240,31 @@ def most_recent_install_file_path(deployment_group) private def download_from_s3(deployment_spec, bucket, key, version, etag) log(:info, "Downloading artifact bundle from bucket '#{bucket}' and key '#{key}', version '#{version}', etag '#{etag}'") - - s3 = Aws::S3::Client.new(s3_options) + + options = s3_options + s3 = Aws::S3::Client.new(options) + ProcessManager::Log.info("s3 client configuration below:") + ProcessManager::Log.info(s3.config) File.open(artifact_bundle(deployment_spec), 'wb') do |file| + begin if !version.nil? object = s3.get_object({:bucket => bucket, :key => key, :version_id => version}, :target => file) else object = s3.get_object({:bucket => bucket, :key => key}, :target => file) end + rescue Seahorse::Client::NetworkingError => e + if e.message.include? "unable to connect to" + if InstanceAgent::Config.config[:use_fips_mode] + raise $!, "#{$!}. Check that Fips exists in #{options[:region]}. Or, try using s3 endpoint override.", $!.backtrace + else + raise $!, "#{$!}. Try using s3 endpoint override.", $!.backtrace + end + else + raise + end + end if(!etag.nil? && !(etag.gsub(/"/,'').eql? object.etag.gsub(/"/,''))) msg = "Expected deployment artifact bundle etag #{etag} but was actually #{object.etag}" @@ -269,11 +284,11 @@ def s3_options region = ENV['AWS_REGION'] || InstanceMetadata.region options[:region] = region if !InstanceAgent::Config.config[:s3_endpoint_override].to_s.empty? + ProcessManager::Log.info("using s3 override endpoint #{InstanceAgent::Config.config[:s3_endpoint_override]}") options[:endpoint] = URI(InstanceAgent::Config.config[:s3_endpoint_override]) - elsif InstanceAgent::Config.config[:use_fips_mode] - #S3 Fips pseudo-regions are not supported by the SDK yet - #source for the URL: https://aws.amazon.com/compliance/fips/ - options[:endpoint] = "https://s3-fips.#{region}.amazonaws.com" + elsif InstanceAgent::Config.config[:use_fips_mode] # need to test fips mode and non fips mode + ProcessManager::Log.info("using fips endpoint") + options[:region] = "fips-#{region}" end proxy_uri = nil if InstanceAgent::Config.config[:proxy_uri] diff --git a/lib/instance_metadata.rb b/lib/instance_metadata.rb index 6c9238cc..8ab4078d 100644 --- a/lib/instance_metadata.rb +++ b/lib/instance_metadata.rb @@ -22,6 +22,10 @@ def self.partition get_metadata_wrapper(PARTITION_PATH).strip end + def self.domain + get_metadata_wrapper('/latest/meta-data/services/domain').strip + end + def self.region doc['region'].strip end diff --git a/test/instance_agent/plugins/codedeploy/codedeploy_control_test.rb b/test/instance_agent/plugins/codedeploy/codedeploy_control_test.rb index 2b0fc59f..0d3a1846 100644 --- a/test/instance_agent/plugins/codedeploy/codedeploy_control_test.rb +++ b/test/instance_agent/plugins/codedeploy/codedeploy_control_test.rb @@ -13,6 +13,8 @@ class CodeDeployControlTest < InstanceAgentTestCase ENV['AWS_REGION'] = nil ENV['DEPLOYMENT_CREATOR'] = "User" ENV['DEPLOYMENT_TYPE'] = "IN_PLACE" + InstanceMetadata.stubs(:region).returns('us-west-2') + InstanceMetadata.stubs(:domain).returns('amazonaws.com') end context "with region, endpoint and credentials" do diff --git a/test/instance_agent/plugins/codedeploy/command_executor_test.rb b/test/instance_agent/plugins/codedeploy/command_executor_test.rb index 46635e03..bdd7b509 100644 --- a/test/instance_agent/plugins/codedeploy/command_executor_test.rb +++ b/test/instance_agent/plugins/codedeploy/command_executor_test.rb @@ -32,7 +32,11 @@ def generate_signed_message_for(map) :deploy_control_client => @deploy_control_client, :hook_mapping => @test_hook_mapping}) @aws_region = 'us-east-1' + @partition = 'aws' + @domain = 'amazonaws.com' InstanceMetadata.stubs(:region).returns(@aws_region) + InstanceMetadata.stubs(:partition).returns(@partition) + InstanceMetadata.stubs(:domain).returns(@domain) end context "deployment_system method" do @@ -249,6 +253,7 @@ def generate_signed_message_for(map) @mock_file.stubs(:close) @http.stubs(:request_get) @s3 = mock + @s3.stubs(:config).returns("hello") Aws::S3::Client.stubs(:new).returns(@s3) end @@ -318,33 +323,40 @@ def generate_signed_message_for(map) context "when creating S3 options" do - should "use right region" do - assert_equal 'us-east-1', @command_executor.s3_options[:region] - end - should "use right signature version" do assert_equal 'v4', @command_executor.s3_options[:signature_version] end - - should "use right endpoint when using Fips" do - InstanceAgent::Config.config[:use_fips_mode] = true - assert_equal 'https://s3-fips.us-east-1.amazonaws.com', @command_executor.s3_options[:endpoint] - InstanceAgent::Config.config[:use_fips_mode] = false - end - - should "use right endpoint when using endpoint override" do - s3_endpoint_override_url = 'htpp://testendpointoverride' - InstanceAgent::Config.config[:s3_endpoint_override] = s3_endpoint_override_url - assert_equal s3_endpoint_override_url, @command_executor.s3_options[:endpoint].to_s - InstanceAgent::Config.config[:s3_endpoint_override] = nil + + context "when override endpoint provided" do + setup do + InstanceAgent::Config.config[:s3_endpoint_override] = "https://example.override.endpoint.com" + end + should "use the override endpoint" do + assert_equal "https://example.override.endpoint.com", @command_executor.s3_options[:endpoint].to_s + end end - - should "use no endpoint when neither using Fips nor Endpoint override" do - InstanceAgent::Config.config[:s3_endpoint_override] = nil - InstanceAgent::Config.config[:use_fips_mode] = false - assert_false @command_executor.s3_options.include? :endpoint + + context "when no override endpoint provided and not using fips" do + setup do + InstanceAgent::Config.config[:s3_endpoint_override] = nil + InstanceAgent::Config.config[:use_fips_mode] = false + end + should "use correct region and custom endpoint" do + assert_equal 'us-east-1', @command_executor.s3_options[:region] + assert_false @command_executor.s3_options.include? :endpoint + end end - + + context "when no override endpoint provided and using fips" do + setup do + InstanceAgent::Config.config[:s3_endpoint_override] = nil + InstanceAgent::Config.config[:use_fips_mode] = true + end + should "use correct region and custom endpoint" do + assert_equal 'fips-us-east-1', @command_executor.s3_options[:region] + assert_false @command_executor.s3_options.include? :endpoint + end + end end context "downloading bundle from S3" do diff --git a/test/instance_metadata_test.rb b/test/instance_metadata_test.rb index 09e58ce4..beba6bcb 100644 --- a/test/instance_metadata_test.rb +++ b/test/instance_metadata_test.rb @@ -21,6 +21,7 @@ def self.should_check_status_code(&blk) account_id = '123456789012' instance_id = 'i-deadbeef' @partition = 'aws' + @domain = 'amazonaws.com' @host_identifier = "arn:#{@partition}:ec2:#{region}:#{account_id}:instance/#{instance_id}" @instance_document = JSON.dump({"accountId" => account_id, "region" => region, "instanceId" => instance_id}) @instance_document_region_whitespace = JSON.dump({"accountId" => account_id, "region" => " us-east-1 \t", "instanceId" => instance_id}) @@ -32,6 +33,9 @@ def self.should_check_status_code(&blk) stub_request(:get, 'http://169.254.169.254/latest/meta-data/services/partition'). with(headers: {'X-aws-ec2-metadata-token' => @token}). to_return(status: 200, body: @partition, headers: {}) + stub_request(:get, 'http://169.254.169.254/latest/meta-data/services/domain'). + with(headers: {'X-aws-ec2-metadata-token' => @token}). + to_return(status: 200, body: @domain, headers: {}) stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). with(headers: {'X-aws-ec2-metadata-token' => @token}). to_return(status: 200, body: @instance_document, headers: {}) diff --git a/vendor/gems/codedeploy-commands-1.0.0/lib/aws/plugins/deploy_control_endpoint.rb b/vendor/gems/codedeploy-commands-1.0.0/lib/aws/plugins/deploy_control_endpoint.rb index c1e17996..a3ba4739 100644 --- a/vendor/gems/codedeploy-commands-1.0.0/lib/aws/plugins/deploy_control_endpoint.rb +++ b/vendor/gems/codedeploy-commands-1.0.0/lib/aws/plugins/deploy_control_endpoint.rb @@ -4,19 +4,19 @@ module Aws module Plugins class DeployControlEndpoint < Seahorse::Client::Plugin option(:endpoint) do |cfg| + service = 'codedeploy-commands' + region = InstanceMetadata.region + domain = InstanceMetadata.domain url = InstanceAgent::Config.config[:deploy_control_endpoint] if url.nil? - url = "https://codedeploy-commands" if InstanceAgent::Config.config[:enable_auth_policy] - url.concat "-secure" + service += '-secure' end if InstanceAgent::Config.config[:use_fips_mode] - url.concat "-fips" - end - url.concat ".#{cfg.region}.amazonaws.com" - if "cn" == cfg.region.split("-")[0] - url.concat(".cn") + service += '-fips' end + url = "https://#{service}.#{region}.#{domain}" + ProcessManager::Log.info("ADCS endpoint: #{url}") end url end From 23cb8525a680ab0682d682f9f70e2a954e418107 Mon Sep 17 00:00:00 2001 From: Kaiwen Sun Date: Mon, 12 Oct 2020 22:18:08 -0700 Subject: [PATCH 03/10] fix integration tests --- features/codedeploy-agent/agent.feature | 1 + .../codedeploy-local/codedeploy_local.feature | 1 + features/step_definitions/agent_steps.rb | 15 ++++++- features/step_definitions/common_steps.rb | 42 +++++++------------ 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/features/codedeploy-agent/agent.feature b/features/codedeploy-agent/agent.feature index 75a1cd12..f46b5996 100644 --- a/features/codedeploy-agent/agent.feature +++ b/features/codedeploy-agent/agent.feature @@ -1,5 +1,6 @@ # language: en @codedeploy-agent +@override-aws-config Feature: Deploy using AWS CodeDeploy Agent Scenario: Doing a sample deployment diff --git a/features/codedeploy-local/codedeploy_local.feature b/features/codedeploy-local/codedeploy_local.feature index 6b0f871d..eae4dd1e 100644 --- a/features/codedeploy-local/codedeploy_local.feature +++ b/features/codedeploy-local/codedeploy_local.feature @@ -52,6 +52,7 @@ Feature: Local Deploy using AWS CodeDeploy Local CLI And the expected files should have have been locally deployed to my host And the scripts should have been executed during local deployment + @override-aws-config Scenario: Doing a sample local deployment using an s3 bundle Given I have a sample bundle uploaded to s3 When I create a local deployment with my bundle diff --git a/features/step_definitions/agent_steps.rb b/features/step_definitions/agent_steps.rb index ad59b820..508ea370 100644 --- a/features/step_definitions/agent_steps.rb +++ b/features/step_definitions/agent_steps.rb @@ -3,6 +3,9 @@ require 'tempfile' require 'zip' require 'fileutils' +require 'aws-sdk-core' +require 'aws-sdk-codedeploy' +require 'aws-sdk-iam' $:.unshift File.join(File.dirname(File.expand_path('../..', __FILE__)), 'lib') $:.unshift File.join(File.dirname(File.expand_path('../..', __FILE__)), 'features') @@ -86,6 +89,16 @@ def configure_local_agent(working_directory) FileUtils.rm_rf(@working_directory) unless @working_directory.nil? end +Before("@override-aws-config") do + # agent may override config like Aws.config[:credentials]. We don't want to leak agent's config to test runner. + @aws_config = Aws.config + Aws.config = Aws.config.clone +end + +After("@override-aws-config") do + Aws.config = @aws_config +end + Given(/^I have a CodeDeploy application$/) do @application_name = "codedeploy-integ-testapp-#{SecureRandom.hex(10)}" @codedeploy_client.create_application(:application_name => @application_name) @@ -140,7 +153,7 @@ def create_aws_credentials_file create_deployment_group end -When(/^I create a deployment for the application and deployment group with the test S(\d+) revision$/) do |arg1| +When(/^I create a deployment for the application and deployment group with the test S3 revision$/) do @deployment_id = @codedeploy_client.create_deployment({:application_name => @application_name, :deployment_group_name => @deployment_group_name, :revision => { :revision_type => "S3", diff --git a/features/step_definitions/common_steps.rb b/features/step_definitions/common_steps.rb index 97508a78..2392309a 100644 --- a/features/step_definitions/common_steps.rb +++ b/features/step_definitions/common_steps.rb @@ -4,36 +4,24 @@ $:.unshift File.join(File.dirname(File.expand_path('../..', __FILE__)), 'features') require 'step_definitions/step_constants' -@bucket_creation_count = 0; Given(/^I have a sample bundle uploaded to s3$/) do -=begin -This fails if the s3 upload is attempted after assume_role is called in the first integration test. -This is because once you call assume role the next time it instantiates a client it is using different permissions. In my opinion thats a bug because it doesn't match the documentation for the AWS SDK. -https://docs.aws.amazon.com/sdkforruby/api/index.html - -Their documentation says an assumed role is the LAST permission it will try to rely on but it looks like its always the first. But the s3 upload is the only place that this mattered so I simply forced this code so it doesn't do it again since the bundle is identical for both tests. -=end - if @bucket_creation_count == 0 - s3 = Aws::S3::Client.new - - begin - s3.create_bucket({ - bucket: StepConstants::APP_BUNDLE_BUCKET, # required - create_bucket_configuration: { - location_constraint: Aws.config[:region], - } - }) - rescue Aws::S3::Errors::BucketAlreadyOwnedByYou - #Already created the bucket - end + s3 = Aws::S3::Client.new + + begin + s3.create_bucket({ + bucket: StepConstants::APP_BUNDLE_BUCKET, # required + create_bucket_configuration: { + location_constraint: Aws.config[:region], + } + }) + rescue Aws::S3::Errors::BucketAlreadyOwnedByYou + #Already created the bucket + end - Dir.mktmpdir do |temp_directory_to_create_zip_file| - File.open(zip_app_bundle(temp_directory_to_create_zip_file), 'rb') do |file| - s3.put_object(bucket: StepConstants::APP_BUNDLE_BUCKET, key: StepConstants::APP_BUNDLE_KEY, body: file) - end + Dir.mktmpdir do |temp_directory_to_create_zip_file| + File.open(zip_app_bundle(temp_directory_to_create_zip_file), 'rb') do |file| + s3.put_object(bucket: StepConstants::APP_BUNDLE_BUCKET, key: StepConstants::APP_BUNDLE_KEY, body: file) end - - @bucket_creation_count += 1 end @bundle_type = 'zip' From 156b3f7128f4d0ed58f0239de3e39389a8d0ea7a Mon Sep 17 00:00:00 2001 From: Kaiwen Sun Date: Wed, 14 Oct 2020 12:03:31 -0700 Subject: [PATCH 04/10] add on-prem integ test using iam user creds --- README.md | 1 + features/codedeploy-agent/agent.feature | 12 +- .../codedeploy-local/codedeploy_local.feature | 2 +- features/step_definitions/agent_steps.rb | 119 +++++++++++++++--- 4 files changed, 114 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 56a3ec1c..2d4aa616 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Please do the build steps mentioned above before running the integration test. The integration test creates the following * An IAM role "codedeploy-agent-integ-test-deployment-role" if it doesn't exist * An IAM role "codedeploy-agent-integ-test-instance-role" if it doesn't exist +* An IAM user "codedeploy-agent-integ-test-instance-user" if it doesn't exist. (Access key will be recreated.) * A CodeDeploy application * Startup the codedeploy agent on your host * A CodeDeploy deployment group with your host in it diff --git a/features/codedeploy-agent/agent.feature b/features/codedeploy-agent/agent.feature index f46b5996..91d70d3a 100644 --- a/features/codedeploy-agent/agent.feature +++ b/features/codedeploy-agent/agent.feature @@ -1,12 +1,12 @@ # language: en @codedeploy-agent -@override-aws-config +@isolate-agent-config Feature: Deploy using AWS CodeDeploy Agent - Scenario: Doing a sample deployment + Scenario Outline: Doing a sample deployment Given I have a sample bundle uploaded to s3 And I have a CodeDeploy application - And I register my host in CodeDeploy + And I register my host in CodeDeploy using And I startup the CodeDeploy agent locally And I have a deployment group containing my host When I create a deployment for the application and deployment group with the test S3 revision @@ -15,3 +15,9 @@ Feature: Deploy using AWS CodeDeploy Agent And the overall deployment should eventually succeed And the expected files should have have been deployed to my host And the scripts should have been executed + + Examples: + | credential method | + | IAM user | + | IAM session | +# | environment variables | \ No newline at end of file diff --git a/features/codedeploy-local/codedeploy_local.feature b/features/codedeploy-local/codedeploy_local.feature index eae4dd1e..067f349e 100644 --- a/features/codedeploy-local/codedeploy_local.feature +++ b/features/codedeploy-local/codedeploy_local.feature @@ -52,7 +52,7 @@ Feature: Local Deploy using AWS CodeDeploy Local CLI And the expected files should have have been locally deployed to my host And the scripts should have been executed during local deployment - @override-aws-config + @isolate-agent-config Scenario: Doing a sample local deployment using an s3 bundle Given I have a sample bundle uploaded to s3 When I create a local deployment with my bundle diff --git a/features/step_definitions/agent_steps.rb b/features/step_definitions/agent_steps.rb index 508ea370..c7b8fb35 100644 --- a/features/step_definitions/agent_steps.rb +++ b/features/step_definitions/agent_steps.rb @@ -21,7 +21,12 @@ DEPLOYMENT_ROLE_NAME = "#{StepConstants::CODEDEPLOY_TEST_PREFIX}deployment-role" INSTANCE_ROLE_NAME = "#{StepConstants::CODEDEPLOY_TEST_PREFIX}instance-role" +INSTANCE_USER_NAME = "#{StepConstants::CODEDEPLOY_TEST_PREFIX}instance-user" DEPLOYMENT_ROLE_POLICY = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"codedeploy.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}" +S3_READONLY_ACCESS_ARN = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" +CODEDEPLOY_ROLE_ARN = "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole" +ISOLATED_ENV_KEYS = %w(AWS_REGION AWS_ACCESS_KEY AWS_SECRET_KEY AWS_HOST_IDENTIFIER AWS_CREDENTIALS_FILE + AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_CREDENTIAL_FILE) def instance_name @instance_name ||= SecureRandom.uuid @@ -89,14 +94,28 @@ def configure_local_agent(working_directory) FileUtils.rm_rf(@working_directory) unless @working_directory.nil? end -Before("@override-aws-config") do - # agent may override config like Aws.config[:credentials]. We don't want to leak agent's config to test runner. +Before("@isolate-agent-config") do + # If not reset DeploymentLog, log may leak to the log file of the last scenario + class InstanceAgent::DeploymentLog + def self.reset + Singleton.send :__init__, self + end + end + InstanceAgent::DeploymentLog.reset + # agent may override config like Aws.config[:credentials] and ENV['AWS_ACCESS_KEY']. We don't want to leak agent's config to test runner. @aws_config = Aws.config Aws.config = Aws.config.clone + @env_vars = {} + ISOLATED_ENV_KEYS.each do |key| + @env_vars[key] = ENV[key] + end end -After("@override-aws-config") do +After("@isolate-agent-config") do Aws.config = @aws_config + ISOLATED_ENV_KEYS.each do |key| + ENV[key] = @env_vars[key] + end end Given(/^I have a CodeDeploy application$/) do @@ -104,27 +123,77 @@ def configure_local_agent(working_directory) @codedeploy_client.create_application(:application_name => @application_name) end -Given(/^I register my host in CodeDeploy$/) do +Given(/^I register my host in CodeDeploy using (IAM user|IAM session|environment variables)$/) do |method| + case method + when "IAM user" + register_on_premises_with_iam_user + when "IAM session" + register_on_premises_with_iam_session + when "environment variables" + raise "Not implemented yet" + end +end + +def register_on_premises_with_iam_user + user = create_instance_user + access_key = recreate_instance_user_access_key + begin + @codedeploy_client.register_on_premises_instance(:instance_name => instance_name, :iam_user_arn => user.arn) + rescue Aws::CodeDeploy::Errors::IamUserArnAlreadyRegisteredException + # One IAM user cannot be used used to register more than one instance. Clean up old instances. + next_token = nil + loop do + resp = @codedeploy_client.list_on_premises_instances({:registration_status => "Registered", :next_token => next_token}) + instance_names = resp.instance_names + next_token = resp.next_token + @codedeploy_client.batch_get_on_premises_instances({:instance_names => instance_names}).instance_infos.select do | instance | + instance.iam_user_arn == user.arn + end.each do | instance | + @codedeploy_client.deregister_on_premises_instance({:instance_name => instance.instance_name}) + next_token = nil + end + break if next_token.nil? + end + @codedeploy_client.register_on_premises_instance(:instance_name => instance_name, :iam_user_arn => user.arn) + end + @codedeploy_client.add_tags_to_on_premises_instances(:instance_names => [instance_name], :tags => [{:key => instance_name}]) + configure_agent_for_on_premise({:iam_user_arn => user.arn, + :access_key_id => access_key.access_key_id, + :secret_access_key => access_key.secret_access_key}) +end + +def register_on_premises_with_iam_session iam_session_arn = create_iam_assume_role_session @codedeploy_client.register_on_premises_instance(:instance_name => instance_name, :iam_session_arn => iam_session_arn) @codedeploy_client.add_tags_to_on_premises_instances(:instance_names => [instance_name], :tags => [{:key => instance_name}]) - - configure_agent_for_on_premise(iam_session_arn) + configure_agent_for_on_premise({:iam_session_arn => iam_session_arn}) end -def configure_agent_for_on_premise(iam_session_arn) - on_premise_configuration_content = <<-CONFIG +def configure_agent_for_on_premise(options={}) + if !options[:iam_session_arn] == !options[:iam_user_arn] + raise "Exactly one of :iam_session_arn and :iam_user_arn is required" + end + if options[:iam_session_arn] + on_premise_configuration_content = <<-CONFIG --- region: #{Aws.config[:region]} -aws_credentials_file: #{create_aws_credentials_file} -iam_session_arn: #{iam_session_arn} +aws_credentials_file: #{create_aws_credentials_session_file} +iam_session_arn: #{options[:iam_session_arn]} CONFIG - + else + on_premise_configuration_content = <<-CONFIG +--- +region: #{Aws.config[:region]} +aws_access_key_id: #{options[:access_key_id]} +aws_secret_access_key: #{options[:secret_access_key]} +iam_user_arn: #{options[:iam_user_arn]} + CONFIG + end File.open(InstanceAgent::Config.config[:on_premises_config_file], 'w') { |file| file.write(on_premise_configuration_content) } InstanceAgent::Plugins::CodeDeployPlugin::OnPremisesConfig.configure end -def create_aws_credentials_file +def create_aws_credentials_session_file aws_credentials_content = <<-CREDENTIALS --- [default] @@ -213,7 +282,7 @@ def create_deployment_role @iam_client.create_role({:role_name => DEPLOYMENT_ROLE_NAME, :assume_role_policy_document => DEPLOYMENT_ROLE_POLICY}).role.arn @iam_client.attach_role_policy({:role_name => DEPLOYMENT_ROLE_NAME, - :policy_arn => "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole"}) + :policy_arn => CODEDEPLOY_ROLE_ARN}) rescue Aws::IAM::Errors::EntityAlreadyExists #Using the existing role end @@ -224,14 +293,32 @@ def create_deployment_role end end +def create_instance_user + begin + user = @iam_client.create_user({:user_name => INSTANCE_USER_NAME}).user + @iam_client.wait_until(:user_exists, user_name: INSTANCE_USER_NAME) + @iam_client.attach_user_policy({:user_name => INSTANCE_USER_NAME, + :policy_arn => S3_READONLY_ACCESS_ARN}) + rescue Aws::IAM::Errors::EntityAlreadyExists + user = @iam_client.get_user({:user_name => INSTANCE_USER_NAME}).user + end + user +end + +def recreate_instance_user_access_key + @iam_client.list_access_keys({:user_name => INSTANCE_USER_NAME}).access_key_metadata.each do |key| + @iam_client.delete_access_key({:user_name => key.user_name, + :access_key_id => key.access_key_id}) + end + @iam_client.create_access_key({:user_name => INSTANCE_USER_NAME}).access_key +end + def create_instance_role begin @iam_client.create_role({:role_name => INSTANCE_ROLE_NAME, :assume_role_policy_document => instance_role_policy}).role.arn @iam_client.attach_role_policy({:role_name => INSTANCE_ROLE_NAME, - :policy_arn => "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"}) - @iam_client.attach_role_policy({:role_name => INSTANCE_ROLE_NAME, - :policy_arn => "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole"}) + :policy_arn => S3_READONLY_ACCESS_ARN}) rescue Aws::IAM::Errors::EntityAlreadyExists #Using the existing role end From 86bd40e0a4bcab8a2c4d16646ffc1503a3f064f5 Mon Sep 17 00:00:00 2001 From: Kaiwen Sun Date: Fri, 16 Oct 2020 20:45:22 -0700 Subject: [PATCH 05/10] remove unsupported on-prem test option cr https://code.amazon.com/reviews/CR-36518575 --- features/codedeploy-agent/agent.feature | 1 - features/step_definitions/agent_steps.rb | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/features/codedeploy-agent/agent.feature b/features/codedeploy-agent/agent.feature index 91d70d3a..6ece6e1b 100644 --- a/features/codedeploy-agent/agent.feature +++ b/features/codedeploy-agent/agent.feature @@ -20,4 +20,3 @@ Feature: Deploy using AWS CodeDeploy Agent | credential method | | IAM user | | IAM session | -# | environment variables | \ No newline at end of file diff --git a/features/step_definitions/agent_steps.rb b/features/step_definitions/agent_steps.rb index c7b8fb35..03b4d258 100644 --- a/features/step_definitions/agent_steps.rb +++ b/features/step_definitions/agent_steps.rb @@ -123,14 +123,12 @@ def self.reset @codedeploy_client.create_application(:application_name => @application_name) end -Given(/^I register my host in CodeDeploy using (IAM user|IAM session|environment variables)$/) do |method| +Given(/^I register my host in CodeDeploy using (IAM user|IAM session)$/) do |method| case method when "IAM user" register_on_premises_with_iam_user when "IAM session" register_on_premises_with_iam_session - when "environment variables" - raise "Not implemented yet" end end From 143f99ecea54b8a76c395e1f0d4c0468df39c823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Johnson=20=F0=9F=9A=80?= Date: Tue, 20 Oct 2020 11:18:43 -0700 Subject: [PATCH 06/10] [Fix] Moved AWS loading till after config is resolved AWS::CodeDeployCommand requires that runtime configurations be resolved before it is able to accurately load up its API definition. This update allows the Windows version of the Agent to provide AWS::CodeDeployCommand the opportunity to resolve those config settings with minimal impact to the wider codebase. --- lib/winagent.rb | 8 +++++++- .../lib/aws/add_service_wrapper.rb | 1 - 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/winagent.rb b/lib/winagent.rb index 5fc72acb..344b73e3 100644 --- a/lib/winagent.rb +++ b/lib/winagent.rb @@ -10,7 +10,6 @@ require 'instance_agent/log' require 'instance_agent/platform' require 'instance_agent/platform/windows_util' -require 'instance_agent/plugins/codedeploy/register_plugin' require 'pathname' include Win32 @@ -102,4 +101,11 @@ def with_error_handling end end +# InstanceAgentService loads in a set of configurations that are assumed by the CodeDeploy plugin registery to be in place when the +# the classes are loaded. This happens to be true for the Linux agent as `GLI::App.pre` loads up the config before resolving additional +# gem dependencies. However, Aws.add_service requires that the service be defined when the gem is loaded so there is not an efficient way +# to resolve this at runtime; thus, the config must be resolved before the CodeDeployCommand class is loaded. +InstanceAgentService.new.read_config unless defined?(Ocra) +require 'instance_agent/plugins/codedeploy/register_plugin' + InstanceAgentService.mainloop unless defined?(Ocra) diff --git a/vendor/gems/codedeploy-commands-1.0.0/lib/aws/add_service_wrapper.rb b/vendor/gems/codedeploy-commands-1.0.0/lib/aws/add_service_wrapper.rb index f46b01fa..813a6fc9 100644 --- a/vendor/gems/codedeploy-commands-1.0.0/lib/aws/add_service_wrapper.rb +++ b/vendor/gems/codedeploy-commands-1.0.0/lib/aws/add_service_wrapper.rb @@ -43,7 +43,6 @@ def self.add_service(name, options = {}) module_name: module_name, api: api_hash, paginators: options[:paginators], - paginators: options[:paginators], waiters: options[:waiters], resources: options[:resources], gem_dependencies: { 'aws-sdk-core' => '3' }, From b70ba82bd5ff6720fb6510e4abdd18fa259d7264 Mon Sep 17 00:00:00 2001 From: Vakul Bhatia Date: Thu, 8 Oct 2020 14:41:37 -0700 Subject: [PATCH 07/10] Bump up agent version to 1.3.0 --- codedeploy_agent.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codedeploy_agent.gemspec b/codedeploy_agent.gemspec index f03ae183..64858521 100644 --- a/codedeploy_agent.gemspec +++ b/codedeploy_agent.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = 'aws_codedeploy_agent' - spec.version = '1.2.1' + spec.version = '1.3.0' spec.summary = 'Packages AWS CodeDeploy agent libraries' spec.description = 'AWS CodeDeploy agent is responsible for doing the actual work of deploying software on an individual EC2 instance' spec.author = 'Amazon Web Services' From 237f3d1bb85e16d6e9ba6e50da70bdda68d5b10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Johnson=20=F0=9F=9A=80?= Date: Thu, 10 Dec 2020 17:44:26 -0800 Subject: [PATCH 08/10] IMDS service is no-longer assumed to be available IMDS provides a wide range of host and data properties that can be used to setup and run the service. However, it is not always available due to security constraints or as the agent is running from an on-prem host. This updates allows for the situation where IMDS is not available and provides reasonable fallbacks. --- bin/install | 203 ++++++++++++------ bin/update | 153 +++++++++---- .../application_specification/range_info.rb | 2 +- .../plugins/codedeploy/command_executor.rb | 11 +- lib/instance_metadata.rb | 102 ++++++--- .../codedeploy/codedeploy_control_test.rb | 21 +- test/instance_metadata_test.rb | 99 ++++++--- .../aws/plugins/deploy_control_endpoint.rb | 20 +- 8 files changed, 438 insertions(+), 173 deletions(-) diff --git a/bin/install b/bin/install index 07b28fc2..83f9913a 100755 --- a/bin/install +++ b/bin/install @@ -5,8 +5,6 @@ # than 2.0. Testing on multiple Ruby versions is required for # changes to this part of the code. ################################################################## -require 'json' - class Proxy instance_methods.each do |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ @@ -43,58 +41,119 @@ end @log.level = Logger::INFO require 'net/http' -require 'json' -TOKEN_PATH = '/latest/api/token' -DOCUMENT_PATH = '/latest/dynamic/instance-identity/document' +# This class is copied (almost directly) from lib/instance_metadata.rb +# It is not loaded as the InstanceMetadata makes additional assumptions +# about the runtime that cannot be satisfied at install time, hence the +# trimmed copy. +class IMDS + IP_ADDRESS = '169.254.169.254' + TOKEN_PATH = '/latest/api/token' + BASE_PATH = '/latest/meta-data' + IDENTITY_DOCUMENT_PATH = '/latest/dynamic/instance-identity/document' + DOMAIN_PATH = '/latest/meta-data/services/domain' + + def self.imds_supported? + imds_v2? || imds_v1? + end + + def self.imds_v1? + begin + get_request(BASE_PATH) { |response| + return response.kind_of? Net::HTTPSuccess + } + rescue + false + end + end + + def self.imds_v2? + begin + put_request(TOKEN_PATH) { |token_response| + (token_response.kind_of? Net::HTTPSuccess) && get_request(BASE_PATH, token_response.body) { |response| + return response.kind_of? Net::HTTPSuccess + } + } + rescue + false + end + end -class IMDSV2 def self.region - doc['region'].strip + begin + identity_document()['region'].strip + rescue + nil + end + end + + def self.domain + begin + get_instance_metadata(DOMAIN_PATH).strip + rescue + nil + end + end + + def self.identity_document + # JSON is lazy loaded to ensure we dont break older ruby runtimes + require 'json' + JSON.parse(get_instance_metadata(IDENTITY_DOCUMENT_PATH).strip) + end + + private + def self.get_instance_metadata(path) + begin + token = put_request(TOKEN_PATH) + get_request(path, token) + rescue + get_request(path) + end end + private def self.http_request(request) - Net::HTTP.start('169.254.169.254', 80, :read_timeout => 120, :open_timeout => 120) do |http| + Net::HTTP.start(IP_ADDRESS, 80, :read_timeout => 10, :open_timeout => 10) do |http| response = http.request(request) - if response.code.to_i != 200 + if block_given? + yield(response) + elsif response.kind_of? Net::HTTPSuccess + response.body + else raise "HTTP error from metadata service: #{response.message}, code #{response.code}" end - return response.body end end - def self.put_request(path) + def self.put_request(path, &block) request = Net::HTTP::Put.new(path) request['X-aws-ec2-metadata-token-ttl-seconds'] = '21600' - http_request(request) + http_request(request, &block) end - def self.get_request(path, token = nil) + def self.get_request(path, token = nil, &block) request = Net::HTTP::Get.new(path) unless token.nil? request['X-aws-ec2-metadata-token'] = token end - http_request(request) - end - - def self.doc - begin - token = put_request(TOKEN_PATH) - JSON.parse(get_request(DOCUMENT_PATH, token).strip) - rescue - JSON.parse(get_request(DOCUMENT_PATH).strip) - end + http_request(request, &block) end +end - private - def self.get_metadata_wrapper(path) - token = put_request('/latest/api/token') - get_request(path, token) +class S3Bucket + # Split out as older versions of ruby dont like multi entry attr + attr :domain + attr :region + attr :bucket + def initialize(domain, region, bucket) + @domain = domain + @region = region + @bucket = bucket end - def self.domain - get_metadata_wrapper('/latest/meta-data/services/domain').strip + def object_uri(object_key) + URI.parse("https://#{@bucket}.s3.#{@region}.#{@domain}/#{object_key}") end end @@ -207,6 +266,7 @@ EOF @sanity_check = true when '--help' usage + exit(0) when '--re-execed' @reexeced = true when '--proxy' @@ -243,13 +303,19 @@ EOF end end + parse_args() + + # Be helpful when 'help' was used but not '--help' + if @type == 'help' + usage + exit(0) + end + if (Process.uid != 0) @log.error('Must run as root to install packages') exit(1) end - parse_args() - ########## Force running as Ruby 2.x or fail here ########## ruby_interpreter_path = check_ruby_version_and_symlink force_ruby2x(ruby_interpreter_path) @@ -262,13 +328,17 @@ EOF return exit_ok end - def get_ec2_metadata_region - begin - return IMDSV2.region - rescue => error - @log.warn("Could not get region from EC2 metadata service at '#{error.message}'") - return nil + def get_ec2_metadata_property(property) + if IMDS.imds_supported? + begin + return IMDS.send(property) + rescue => error + @log.warn("Could not get #{property} from EC2 metadata service at '#{error.message}'") + end + else + @log.warn("EC2 metadata service unavailable...") end + return nil end def get_region @@ -277,24 +347,36 @@ EOF return region if region @log.info('Checking EC2 metadata service for region information...') - region = get_ec2_metadata_region + region = get_ec2_metadata_property(:region) return region if region @log.info('Using fail-safe default region: us-east-1') return 'us-east-1' end - def get_s3_uri(region, bucket, key) - domain = IMDSV2.domain - endpoint = "https://#{bucket}.s3.#{region}.#{domain}/#{key}" - @log.info("Endpoint: #{endpoint}") - URI.parse("https://#{bucket}.s3.#{region}.#{domain}/#{key}") + def get_domain(fallback_region = nil) + @log.info('Checking AWS_DOMAIN environment variable for domain information...') + domain = ENV['AWS_DOMAIN'] + return domain if domain + + @log.info('Checking EC2 metadata service for domain information...') + domain = get_ec2_metadata_property(:domain) + return domain if domain + + domain = 'amazonaws.com' + if !fallback_region.nil? && fallback_region.split("-")[0] == 'cn' + domain = 'amazonaws.com.cn' + end + + @log.info("Using fail-safe default domain: #{domain}") + return domain end - def get_package_from_s3(region, bucket, key, package_file) - @log.info("Downloading package from bucket #{bucket} and key #{key}...") + def get_package_from_s3(s3_bucket, key, package_file) + @log.info("Downloading package from bucket #{s3_bucket.bucket} and key #{key}...") - uri = get_s3_uri(region, bucket, key) + uri = s3_bucket.object_uri(key) + @log.info("Endpoint: #{uri}") # stream package file to disk retries ||= 0 @@ -316,10 +398,11 @@ EOF end end - def get_version_file_from_s3(region, bucket, key) - @log.info("Downloading version file from bucket #{bucket} and key #{key}...") + def get_version_file_from_s3(s3_bucket, key) + @log.info("Downloading version file from bucket #{s3_bucket.bucket} and key #{key}...") - uri = get_s3_uri(region, bucket, key) + uri = s3_bucket.object_uri(key) + @log.info("Endpoint: #{uri}") begin require 'json' @@ -332,13 +415,13 @@ end end end - def install_from_s3(region, bucket, package_key, install_cmd) + def install_from_s3(s3_bucket, package_key, install_cmd) package_base_name = File.basename(package_key) package_extension = File.extname(package_base_name) package_name = File.basename(package_base_name, package_extension) package_file = Tempfile.new(["#{package_name}.tmp-", package_extension]) # unique file with 0600 permissions - get_package_from_s3(region, bucket, package_key, package_file) + get_package_from_s3(s3_bucket, package_key, package_file) package_file.close install_cmd << package_file.path @@ -365,10 +448,10 @@ end end end - def get_target_version(target_version, type, region, bucket) + def get_target_version(target_version, type, s3_bucket) if target_version.nil? version_file_key = 'latest/LATEST_VERSION' - version_data = get_version_file_from_s3(region, bucket, version_file_key) + version_data = get_version_file_from_s3(s3_bucket, version_file_key) if version_data.include? type return version_data[type] else @@ -415,14 +498,14 @@ end end end - region = get_region + region = get_region() + domain = get_domain(region) bucket = "aws-codedeploy-#{region}" + s3_bucket = S3Bucket.new(domain, region, bucket) - target_version = get_target_version(@target_version_arg, @type, region, bucket) + target_version = get_target_version(@target_version_arg, @type, s3_bucket) case @type - when 'help' - usage when 'rpm' running_version = `rpm -q codedeploy-agent` running_version.strip! @@ -431,7 +514,7 @@ end else #use -y to answer yes to confirmation prompts install_cmd = ['/usr/bin/yum', '-y', 'localinstall'] - install_from_s3(region, bucket, target_version, install_cmd) + install_from_s3(s3_bucket, target_version, install_cmd) do_sanity_check('/sbin/service') end when 'deb' @@ -450,13 +533,13 @@ end #use -n for non-interactive mode #use -o to not overwrite config files unless they have not been changed install_cmd = ['/usr/bin/gdebi', '-n', '-o', 'Dpkg::Options::=--force-confdef', '-o', 'Dkpg::Options::=--force-confold'] - install_from_s3(region, bucket, target_version, install_cmd) + install_from_s3(s3_bucket, target_version, install_cmd) do_sanity_check('/usr/sbin/service') end when 'zypper' #use -n for non-interactive mode install_cmd = ['/usr/bin/zypper', 'install', '-n'] - install_from_s3(region, bucket, target_version, install_cmd) + install_from_s3(s3_bucket, target_version, install_cmd) else @log.error("Unsupported package type '#{@type}'") exit(1) diff --git a/bin/update b/bin/update index cf39377e..7bc0ef13 100755 --- a/bin/update +++ b/bin/update @@ -5,8 +5,6 @@ # than 2.0. Testing on multiple Ruby versions is required for # changes to this part of the code. ################################################################## -require 'json' - class Proxy instance_methods.each do |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ @@ -44,58 +42,102 @@ end @log.level = Logger::INFO require 'net/http' -require 'json' -TOKEN_PATH = '/latest/api/token' -DOCUMENT_PATH = '/latest/dynamic/instance-identity/document' +# This class is copied (almost directly) from lib/instance_metadata.rb +# It is not loaded as the InstanceMetadata makes additional assumptions +# about the runtime that cannot be satisfied at install time, hence the +# trimmed copy. +class IMDS + IP_ADDRESS = '169.254.169.254' + TOKEN_PATH = '/latest/api/token' + BASE_PATH = '/latest/meta-data' + IDENTITY_DOCUMENT_PATH = '/latest/dynamic/instance-identity/document' + DOMAIN_PATH = '/latest/meta-data/services/domain' -class IMDSV2 - def self.region - doc['region'].strip + def self.imds_supported? + imds_v2? || imds_v1? end - private - def self.http_request(request) - Net::HTTP.start('169.254.169.254', 80, :read_timeout => 120, :open_timeout => 120) do |http| - response = http.request(request) - if response.code.to_i != 200 - raise "HTTP error from metadata service: #{response.message}, code #{response.code}" - end - return response.body + def self.imds_v1? + begin + get_request(BASE_PATH) { |response| + return response.kind_of? Net::HTTPSuccess + } + rescue + false end end - def self.put_request(path) - request = Net::HTTP::Put.new(path) - request['X-aws-ec2-metadata-token-ttl-seconds'] = '21600' - http_request(request) + def self.imds_v2? + begin + put_request(TOKEN_PATH) { |token_response| + (token_response.kind_of? Net::HTTPSuccess) && get_request(BASE_PATH, token_response.body) { |response| + return response.kind_of? Net::HTTPSuccess + } + } + rescue + false + end end - def self.get_request(path, token = nil) - request = Net::HTTP::Get.new(path) - unless token.nil? - request['X-aws-ec2-metadata-token'] = token + def self.region + begin + identity_document()['region'].strip + rescue + nil end - http_request(request) end - def self.doc + def self.domain + begin + get_instance_metadata(DOMAIN_PATH).strip + rescue + nil + end + end + + def self.identity_document + # JSON is lazy loaded to ensure we dont break older ruby runtimes + require 'json' + JSON.parse(get_instance_metadata(IDENTITY_DOCUMENT_PATH).strip) + end + + private + def self.get_instance_metadata(path) begin token = put_request(TOKEN_PATH) - JSON.parse(get_request(DOCUMENT_PATH, token).strip) + get_request(path, token) rescue - JSON.parse(get_request(DOCUMENT_PATH).strip) + get_request(path) end end private - def self.get_metadata_wrapper(path) - token = put_request('/latest/api/token') - get_request(path, token) + def self.http_request(request) + Net::HTTP.start(IP_ADDRESS, 80, :read_timeout => 10, :open_timeout => 10) do |http| + response = http.request(request) + if block_given? + yield(response) + elsif response.kind_of? Net::HTTPSuccess + response.body + else + raise "HTTP error from metadata service: #{response.message}, code #{response.code}" + end + end end - def self.domain - get_metadata_wrapper('/latest/meta-data/services/domain').strip + def self.put_request(path, &block) + request = Net::HTTP::Put.new(path) + request['X-aws-ec2-metadata-token-ttl-seconds'] = '21600' + http_request(request, &block) + end + + def self.get_request(path, token = nil, &block) + request = Net::HTTP::Get.new(path) + unless token.nil? + request['X-aws-ec2-metadata-token'] = token + end + http_request(request, &block) end end @@ -217,6 +259,7 @@ EOF @sanity_check = true when '--help' usage + exit(0) when '--re-execed' @reexeced = true when '--downgrade' @@ -312,6 +355,7 @@ EOF parse_args() + # Be helpful when 'help' was used but not '--help' if @type == 'help' usage exit(0) @@ -329,13 +373,17 @@ EOF return exit_ok end - def get_ec2_metadata_region - begin - return IMDSV2.region - rescue => error - @log.warn("Could not get region from EC2 metadata service at '#{error.message}'") - return nil + def get_ec2_metadata_property(property) + if IMDS.imds_supported? + begin + return IMDS.send(property) + rescue => error + @log.warn("Could not get #{property} from EC2 metadata service at '#{error.message}'") + end + else + @log.warn("EC2 metadata service unavailable...") end + return nil end def get_region @@ -344,16 +392,33 @@ EOF return region if region @log.info('Checking EC2 metadata service for region information...') - region = get_ec2_metadata_region + region = get_ec2_metadata_property(:region) return region if region @log.info('Using fail-safe default region: us-east-1') return 'us-east-1' end + def get_domain(fallback_region = nil) + @log.info('Checking AWS_DOMAIN environment variable for domain information...') + domain = ENV['AWS_DOMAIN'] + return domain if domain + + @log.info('Checking EC2 metadata service for domain information...') + domain = get_ec2_metadata_property(:domain) + return domain if domain + + domain = 'amazonaws.com' + if !fallback_region.nil? && fallback_region.split("-")[0] == 'cn' + domain = 'amazonaws.com.cn' + end + + @log.info("Using fail-safe default domain: #{domain}") + return domain + end + def get_s3_uri(key) - domain = IMDSV2.domain - endpoint = "https://#{BUCKET}.s3.#{REGION}.#{domain}/#{key}" + endpoint = "https://#{BUCKET}.s3.#{REGION}.#{DOMAIN}/#{key}" @log.info("Endpoint: #{endpoint}") URI.parse(endpoint) end @@ -486,8 +551,10 @@ EOF exit(1) end - REGION = get_region + REGION = get_region() + DOMAIN = get_domain(REGION) BUCKET = "aws-codedeploy-#{REGION}" + VERSION_FILE_KEY = 'latest/LATEST_VERSION' NO_AGENT_INSTALLED_REPORTED_WINDOWS_VERSION = 'No Agent Installed' diff --git a/lib/instance_agent/plugins/codedeploy/application_specification/range_info.rb b/lib/instance_agent/plugins/codedeploy/application_specification/range_info.rb index 0b879b89..5530dae3 100644 --- a/lib/instance_agent/plugins/codedeploy/application_specification/range_info.rb +++ b/lib/instance_agent/plugins/codedeploy/application_specification/range_info.rb @@ -100,7 +100,7 @@ def get_range if (@low_sensitivity != @high_sensitivity) range = range + "-s" + @high_sensitivity.to_s end - if @categories + if !@categories.nil? range = range + ":" index = 0 while index < @categories.length diff --git a/lib/instance_agent/plugins/codedeploy/command_executor.rb b/lib/instance_agent/plugins/codedeploy/command_executor.rb index 72db066e..ff285df1 100644 --- a/lib/instance_agent/plugins/codedeploy/command_executor.rb +++ b/lib/instance_agent/plugins/codedeploy/command_executor.rb @@ -240,8 +240,7 @@ def most_recent_install_file_path(deployment_group) private def download_from_s3(deployment_spec, bucket, key, version, etag) log(:info, "Downloading artifact bundle from bucket '#{bucket}' and key '#{key}', version '#{version}', etag '#{etag}'") - - options = s3_options + options = s3_options() s3 = Aws::S3::Client.new(options) ProcessManager::Log.info("s3 client configuration below:") ProcessManager::Log.info(s3.config) @@ -283,13 +282,15 @@ def s3_options region = ENV['AWS_REGION'] || InstanceMetadata.region options[:region] = region + if !InstanceAgent::Config.config[:s3_endpoint_override].to_s.empty? ProcessManager::Log.info("using s3 override endpoint #{InstanceAgent::Config.config[:s3_endpoint_override]}") options[:endpoint] = URI(InstanceAgent::Config.config[:s3_endpoint_override]) - elsif InstanceAgent::Config.config[:use_fips_mode] # need to test fips mode and non fips mode + elsif InstanceAgent::Config.config[:use_fips_mode] ProcessManager::Log.info("using fips endpoint") - options[:region] = "fips-#{region}" - end + # This is not a true region but a way to signal to the S3 client that a FIPS enpoint should be used; added in SDK3. + options[:region] = "fips-#{region}" + end proxy_uri = nil if InstanceAgent::Config.config[:proxy_uri] proxy_uri = URI(InstanceAgent::Config.config[:proxy_uri]) diff --git a/lib/instance_metadata.rb b/lib/instance_metadata.rb index 8ab4078d..951dcf1e 100644 --- a/lib/instance_metadata.rb +++ b/lib/instance_metadata.rb @@ -2,47 +2,94 @@ require 'json' require 'instance_agent' +# InstanceMetadata provides access to IMDS V1 and V2. When able, InstanceMetadata will use +# IMDS v2 but fall back to v1 when not. Should both fail, any property inquary will be nil. +# More info about IMDS can be found at: # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html class InstanceMetadata IP_ADDRESS = '169.254.169.254' PORT = 80 - HTTP_TIMEOUT = 30 + # "The IP address 169.254.169.254 is a link-local address and is valid only from the instance." Hence why + # the low timeout + HTTP_TIMEOUT = 10 + BASE_PATH = '/latest/meta-data' PARTITION_PATH = '/latest/meta-data/services/partition' + DOMAIN_PATH = '/latest/meta-data/services/domain' INSTANCE_ID_PATH = '/latest/meta-data/instance-id' TOKEN_PATH = '/latest/api/token' - DOCUMENT_PATH = '/latest/dynamic/instance-identity/document' + IDENTITY_DOCUMENT_PATH = '/latest/dynamic/instance-identity/document' + + def self.imds_supported? + imds_v2? || imds_v1? + end + + def self.imds_v1? + begin + get_request(BASE_PATH) { |response| + return response.kind_of? Net::HTTPSuccess + } + rescue + false + end + end def self.host_identifier - "arn:#{partition}:ec2:#{doc['region']}:#{doc['accountId']}:instance/#{doc['instanceId']}" + doc = identity_document() + doc.nil? ? nil : "arn:#{partition()}:ec2:#{doc['region']}:#{doc['accountId']}:instance/#{doc['instanceId']}" + end + + def self.imds_v2? + begin + put_request(TOKEN_PATH) { |token_response| + (token_response.kind_of? Net::HTTPSuccess) && get_request(BASE_PATH, token_response.body) { |response| + return response.kind_of? Net::HTTPSuccess + } + } + rescue + false + end end def self.partition - get_metadata_wrapper(PARTITION_PATH).strip + begin + get_instance_metadata(PARTITION_PATH).strip + rescue + nil + end end def self.domain - get_metadata_wrapper('/latest/meta-data/services/domain').strip + begin + get_instance_metadata(DOMAIN_PATH).strip + rescue + nil + end end - def self.region - doc['region'].strip + def self.instance_id + begin + get_instance_metadata(INSTANCE_ID_PATH).strip + rescue + nil + end end - def self.instance_id + def self.region begin - get_metadata_wrapper(INSTANCE_ID_PATH) + identity_document()['region'].strip rescue - return nil + nil end end - class InstanceMetadataError < StandardError + def self.identity_document + JSON.parse(get_instance_metadata(IDENTITY_DOCUMENT_PATH).strip) end private - def self.get_metadata_wrapper(path) + def self.get_instance_metadata(path) begin token = put_request(TOKEN_PATH) get_request(path, token) @@ -50,40 +97,35 @@ def self.get_metadata_wrapper(path) InstanceAgent::Log.send(:info, "IMDSv2 http request failed, falling back to IMDSv1.") get_request(path) end - end + private def self.http_request(request) - Net::HTTP.start(IP_ADDRESS, PORT, :read_timeout => 120, :open_timeout => 120) do |http| + Net::HTTP.start(IP_ADDRESS, PORT, :read_timeout => HTTP_TIMEOUT, :open_timeout => HTTP_TIMEOUT) do |http| response = http.request(request) - if response.code.to_i != 200 + if block_given? + yield(response) + elsif response.kind_of? Net::HTTPSuccess + response.body + else raise "HTTP error from metadata service: #{response.message}, code #{response.code}" end - return response.body end end - def self.put_request(path) + private + def self.put_request(path, &block) request = Net::HTTP::Put.new(path) request['X-aws-ec2-metadata-token-ttl-seconds'] = '21600' - http_request(request) + http_request(request, &block) end - def self.get_request(path, token = nil) + private + def self.get_request(path, token = nil, &block) request = Net::HTTP::Get.new(path) unless token.nil? request['X-aws-ec2-metadata-token'] = token end - http_request(request) - end - - def self.doc - begin - token = put_request(TOKEN_PATH) - JSON.parse(get_request(DOCUMENT_PATH, token).strip) - rescue - InstanceAgent::Log.send(:info, "IMDSv2 http request failed, falling back to IMDSv1.") - JSON.parse(get_request(DOCUMENT_PATH).strip) - end + http_request(request, &block) end end diff --git a/test/instance_agent/plugins/codedeploy/codedeploy_control_test.rb b/test/instance_agent/plugins/codedeploy/codedeploy_control_test.rb index 0d3a1846..992f103d 100644 --- a/test/instance_agent/plugins/codedeploy/codedeploy_control_test.rb +++ b/test/instance_agent/plugins/codedeploy/codedeploy_control_test.rb @@ -13,6 +13,7 @@ class CodeDeployControlTest < InstanceAgentTestCase ENV['AWS_REGION'] = nil ENV['DEPLOYMENT_CREATOR'] = "User" ENV['DEPLOYMENT_TYPE'] = "IN_PLACE" + InstanceMetadata.stubs(:imds_supported?).returns(true) InstanceMetadata.stubs(:region).returns('us-west-2') InstanceMetadata.stubs(:domain).returns('amazonaws.com') end @@ -53,7 +54,7 @@ class CodeDeployControlTest < InstanceAgentTestCase end end - context "with ADCS endpoint set in an environment variable" do + context "with CodeDeploy endpoint set in an environment variable" do setup do InstanceAgent::Config.config[:deploy_control_endpoint] = "https://tempuri" end @@ -108,6 +109,24 @@ class CodeDeployControlTest < InstanceAgentTestCase assert_equal "codedeploy-commands-secure-fips.us-west-2.amazonaws.com", codedeploy_control_client.get_client.config.endpoint.host end end + + context "without IMDS" do + setup do + InstanceMetadata.stubs(:imds_supported?).returns(false) + InstanceMetadata.stubs(:region).returns(nil) + InstanceMetadata.stubs(:domain).returns(nil) + end + + should "use the config defined settings" do + codedeploy_control_client = CodeDeployControl.new :region => "us-east-1" + assert_equal "codedeploy-commands.us-east-1.amazonaws.com", codedeploy_control_client.get_client.config.endpoint.host + end + + should "resolve non .com domains" do + codedeploy_control_client = CodeDeployControl.new :region => "cn-north-1" + assert_equal "codedeploy-commands.cn-north-1.amazonaws.com.cn", codedeploy_control_client.get_client.config.endpoint.host + end + end end end diff --git a/test/instance_metadata_test.rb b/test/instance_metadata_test.rb index beba6bcb..fec912b4 100644 --- a/test/instance_metadata_test.rb +++ b/test/instance_metadata_test.rb @@ -6,30 +6,29 @@ class InstanceMetadataTest < InstanceAgentTestCase include WebMock::API - def self.should_check_status_code(&blk) - should 'raise unless status code is 200' do - stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). - to_return(status: 503, body: @instance_document, headers: {}) - assert_raise(&blk) - end + setup do + region = 'us-east-1' + account_id = '123456789012' + instance_id = 'i-deadbeef' + @partition = 'aws' + @domain = 'amazonaws.com' + @top_level_metadata = JSON.dump(['services','instance-id']) + @host_identifier = "arn:#{@partition}:ec2:#{region}:#{account_id}:instance/#{instance_id}" + @instance_document = JSON.dump({"accountId" => account_id, "region" => region, "instanceId" => instance_id}) + @instance_document_region_whitespace = JSON.dump({"accountId" => account_id, "region" => " us-east-1 \t", "instanceId" => instance_id}) + @token = "mock_token" end context 'The instance metadata service' do setup do WebMock.disable_net_connect!(allow_localhost: true) - region = 'us-east-1' - account_id = '123456789012' - instance_id = 'i-deadbeef' - @partition = 'aws' - @domain = 'amazonaws.com' - @host_identifier = "arn:#{@partition}:ec2:#{region}:#{account_id}:instance/#{instance_id}" - @instance_document = JSON.dump({"accountId" => account_id, "region" => region, "instanceId" => instance_id}) - @instance_document_region_whitespace = JSON.dump({"accountId" => account_id, "region" => " us-east-1 \t", "instanceId" => instance_id}) - @token = "mock_token" stub_request(:put, 'http://169.254.169.254/latest/api/token'). with(headers: {'X-aws-ec2-metadata-token-ttl-seconds' => '21600'}). to_return(status: 200, body: @token, headers: {}) + stub_request(:get, 'http://169.254.169.254/latest/meta-data'). + with(headers: {'X-aws-ec2-metadata-token' => @token}). + to_return(status: 200, body: @top_level_metadata , headers: {}) stub_request(:get, 'http://169.254.169.254/latest/meta-data/services/partition'). with(headers: {'X-aws-ec2-metadata-token' => @token}). to_return(status: 200, body: @partition, headers: {}) @@ -41,48 +40,69 @@ def self.should_check_status_code(&blk) to_return(status: 200, body: @instance_document, headers: {}) end + context 'availability report' do + should 'show IMDSv1 as available' do + stub_request(:get, 'http://169.254.169.254/latest/meta-data'). + with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). + to_return(status: 200, body: @top_level_metadata , headers: {}) + assert_true(InstanceMetadata.imds_v1?) + end + + should 'show IMDSv2 as available' do + assert_true(InstanceMetadata.imds_v2?) + end + + should 'show any version as available' do + assert_true(InstanceMetadata.imds_supported?) + end + end + context 'getting the host identifier' do should 'call the correct URL' do InstanceMetadata.host_identifier - assert_requested(:put, 'http://169.254.169.254/latest/api/token', times: 4) + assert_requested(:put, 'http://169.254.169.254/latest/api/token', times: 2) assert_requested(:get, 'http://169.254.169.254/latest/meta-data/services/partition', times: 1) - assert_requested(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document', times: 3) + assert_requested(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document', times: 1) end - should 'return the body' do + should 'return sucessfully with a host identifier' do assert_equal(@host_identifier, InstanceMetadata.host_identifier) end - should 'return the body if IMDSv2 http request status code is not 200' do + should 'fallback to IMDSv1 when available' do + # Fail the token get stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). with(headers: {'X-aws-ec2-metadata-token' => @token}). to_return(status: 503, body: @instance_document, headers: {}) + + # And expect that the V1 endpoint is available stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). to_return(status: 200, body: @instance_document, headers: {}) + assert_equal(@host_identifier, InstanceMetadata.host_identifier) end - should 'return the body if IMDSv2 http request errors out' do + should 'fallback to IMDSv1 when v2 errors out' do + # Fail the token get stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). with(headers: {'X-aws-ec2-metadata-token' => @token}). to_raise(StandardError) + # And expect that the V1 endpoint is available stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). to_return(status: 200, body: @instance_document, headers: {}) + assert_equal(@host_identifier, InstanceMetadata.host_identifier) end - should 'strip whitesace in the body' do + should 'strip whitesace in from the response body' do stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). with(headers: {'X-aws-ec2-metadata-token' => @token}). to_return(status: 200, body: " \t#{@instance_document} ", headers: {}) assert_equal(@host_identifier, InstanceMetadata.host_identifier) end - - should_check_status_code { InstanceMetadata.host_identifier } - end context 'getting the region' do @@ -97,7 +117,7 @@ def self.should_check_status_code(&blk) assert_equal("us-east-1", InstanceMetadata.region) end - should 'return the region if IMDSv2 http request status code is not 200' do + should 'fallback to IMDSv1 when available' do stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). with(headers: {'X-aws-ec2-metadata-token' => @token}). to_return(status: 503, body: @instance_document, headers: {}) @@ -107,7 +127,7 @@ def self.should_check_status_code(&blk) assert_equal("us-east-1", InstanceMetadata.region) end - should 'return the region if IMDSv2 http request errors out' do + should 'fallback to IMDSv1 when v2 errors out' do stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). with(headers: {'X-aws-ec2-metadata-token' => @token}). to_raise(StandardError) @@ -117,17 +137,38 @@ def self.should_check_status_code(&blk) assert_equal("us-east-1", InstanceMetadata.region) end - should 'strip whitesace in the body' do + should 'strip whitesace in from the response body' do stub_request(:get, 'http://169.254.169.254/latest/dynamic/instance-identity/document'). with(headers: {'X-aws-ec2-metadata-token' => @token}). to_return(status: 200, body: @instance_document_region_whitespace , headers: {}) assert_equal("us-east-1", InstanceMetadata.region) end + end + + end + + context 'The instance metadata service without access' do + setup do + WebMock.disable_net_connect!(allow_localhost: true) - should_check_status_code { InstanceMetadata.region } + stub_request(:put, 'http://169.254.169.254/latest/api/token'). + with(headers: {'X-aws-ec2-metadata-token-ttl-seconds' => '21600'}). + to_raise(StandardError) + stub_request(:get, 'http://169.254.169.254/latest/meta-data'). + with(headers: {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}). + to_raise(StandardError) + end + should 'show unavailable for IMDSv1' do + assert_false(InstanceMetadata.imds_v1?) end - end + should 'show unavailable for IMDSv2' do + assert_false(InstanceMetadata.imds_v2?) + end + should 'show unavailable for any IMDS version' do + assert_false(InstanceMetadata.imds_supported?) + end + end end diff --git a/vendor/gems/codedeploy-commands-1.0.0/lib/aws/plugins/deploy_control_endpoint.rb b/vendor/gems/codedeploy-commands-1.0.0/lib/aws/plugins/deploy_control_endpoint.rb index a3ba4739..eff60a52 100644 --- a/vendor/gems/codedeploy-commands-1.0.0/lib/aws/plugins/deploy_control_endpoint.rb +++ b/vendor/gems/codedeploy-commands-1.0.0/lib/aws/plugins/deploy_control_endpoint.rb @@ -4,20 +4,32 @@ module Aws module Plugins class DeployControlEndpoint < Seahorse::Client::Plugin option(:endpoint) do |cfg| - service = 'codedeploy-commands' - region = InstanceMetadata.region - domain = InstanceMetadata.domain + + # Allow the customer to manually configure the endpoint url = InstanceAgent::Config.config[:deploy_control_endpoint] + if url.nil? + service = 'codedeploy-commands' if InstanceAgent::Config.config[:enable_auth_policy] service += '-secure' end if InstanceAgent::Config.config[:use_fips_mode] service += '-fips' end + + if InstanceMetadata.imds_supported? + region = InstanceMetadata.region + domain = InstanceMetadata.domain + else + region = cfg.region + domain = 'amazonaws.com' + domain += '.cn' if region.split("-")[0] == 'cn' + end + url = "https://#{service}.#{region}.#{domain}" - ProcessManager::Log.info("ADCS endpoint: #{url}") end + + ProcessManager::Log.info("CodeDeploy endpoint: #{url}") url end end From da2935556a9a4a73d712dfe29ef25da68463cb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Johnson=20=F0=9F=9A=80?= Date: Tue, 15 Dec 2020 14:17:34 -0800 Subject: [PATCH 09/10] Reverting part of the FIPS change Only the very latest version of the S3 tool has the region tips computation. As such any older loads of the library will fail to operate. --- lib/instance_agent/plugins/codedeploy/command_executor.rb | 7 +++++-- .../plugins/codedeploy/command_executor_test.rb | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/instance_agent/plugins/codedeploy/command_executor.rb b/lib/instance_agent/plugins/codedeploy/command_executor.rb index ff285df1..4c3fae2f 100644 --- a/lib/instance_agent/plugins/codedeploy/command_executor.rb +++ b/lib/instance_agent/plugins/codedeploy/command_executor.rb @@ -288,8 +288,11 @@ def s3_options options[:endpoint] = URI(InstanceAgent::Config.config[:s3_endpoint_override]) elsif InstanceAgent::Config.config[:use_fips_mode] ProcessManager::Log.info("using fips endpoint") - # This is not a true region but a way to signal to the S3 client that a FIPS enpoint should be used; added in SDK3. - options[:region] = "fips-#{region}" + # There was a recent change to S3 client to decompose the region and use a FIPS endpoint is "fips-" is appended + # to the region. However, this is such a recent change that we cannot rely on the latest version of the SDK to be loaded. + # For now, the endpoint will be set directly if FIPS is active but can switch to the S3 method once we have broader support. + # options[:region] = "fips-#{region}" + options[:endpoint] = "https://s3-fips.#{region}.amazonaws.com" end proxy_uri = nil if InstanceAgent::Config.config[:proxy_uri] diff --git a/test/instance_agent/plugins/codedeploy/command_executor_test.rb b/test/instance_agent/plugins/codedeploy/command_executor_test.rb index bdd7b509..e1a290f9 100644 --- a/test/instance_agent/plugins/codedeploy/command_executor_test.rb +++ b/test/instance_agent/plugins/codedeploy/command_executor_test.rb @@ -353,8 +353,8 @@ def generate_signed_message_for(map) InstanceAgent::Config.config[:use_fips_mode] = true end should "use correct region and custom endpoint" do - assert_equal 'fips-us-east-1', @command_executor.s3_options[:region] - assert_false @command_executor.s3_options.include? :endpoint + assert_equal 'us-east-1', @command_executor.s3_options[:region] + assert_true @command_executor.s3_options.include? :endpoint end end end From 1f52e69ced0e001a7875aa5e53cbdfcab901d08d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Johnson=20=F0=9F=9A=80?= Date: Mon, 30 Nov 2020 16:53:38 -0800 Subject: [PATCH 10/10] Revision update to 1.3.1 --- codedeploy_agent.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codedeploy_agent.gemspec b/codedeploy_agent.gemspec index 64858521..7d178dad 100644 --- a/codedeploy_agent.gemspec +++ b/codedeploy_agent.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = 'aws_codedeploy_agent' - spec.version = '1.3.0' + spec.version = '1.3.1' spec.summary = 'Packages AWS CodeDeploy agent libraries' spec.description = 'AWS CodeDeploy agent is responsible for doing the actual work of deploying software on an individual EC2 instance' spec.author = 'Amazon Web Services'