From 673e8784a0a14ebd6bb8f470149f8fbf4d5f06bf Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Thu, 3 Aug 2023 16:13:25 -0400 Subject: [PATCH 1/2] LG-10342 Add the ability to specify a key_id for the KMS Client We are migrating to a new KMS instance for encryption that supports multi-region keys. We will need to encrypt and decrypt ciphertexts with both the current single region key and the new multi-region key. To support that the KMS client will need to be able to be configured for one of the given keys. This commit adds the ability to specify a key ID to use for the KMS client. To preserve the original behavior as the new key is being phased in the KMS client uses the old single region key if no key is specified. [skip changelog] --- app/services/encryption/kms_client.rb | 8 +++++- config/application.yml.default | 2 ++ lib/identity_config.rb | 2 ++ spec/services/encryption/kms_client_spec.rb | 30 ++++++++++++++++++++- 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/app/services/encryption/kms_client.rb b/app/services/encryption/kms_client.rb index c687f473840..3e36aee1597 100644 --- a/app/services/encryption/kms_client.rb +++ b/app/services/encryption/kms_client.rb @@ -25,6 +25,12 @@ class KmsClient end # rubocop:enable Layout/LineLength + attr_reader :kms_key_id + + def initialize(kms_key_id: IdentityConfig.store.aws_kms_key_id) + @kms_key_id = kms_key_id + end + def encrypt(plaintext, encryption_context) KmsLogger.log(:encrypt, encryption_context) return encrypt_kms(plaintext, encryption_context) if FeatureManagement.use_kms? @@ -69,7 +75,7 @@ def encrypt_raw_kms(plaintext, encryption_context) KMS_CLIENT_POOL.with do |client| client.encrypt( - key_id: IdentityConfig.store.aws_kms_key_id, + key_id: kms_key_id, plaintext: plaintext, encryption_context: encryption_context, ).ciphertext_blob diff --git a/config/application.yml.default b/config/application.yml.default index 9796844e003..a46b67ea264 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -58,6 +58,8 @@ aws_http_retry_max_delay: 1 aws_kms_key_id: alias/login-dot-gov-test-keymaker aws_kms_client_contextless_pool_size: 5 aws_kms_client_multi_pool_size: 5 +aws_kms_multi_region_key_id: alias/login-dot-gov-keymaker-multi-region +aws_kms_multi_region_write_enabled: false aws_logo_bucket: '' aws_region: 'us-west-2' backup_code_cost: '2000$8$1$' diff --git a/lib/identity_config.rb b/lib/identity_config.rb index 210b871c338..635d16acdc2 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -145,6 +145,8 @@ def self.build_store(config_map) config.add(:aws_kms_client_contextless_pool_size, type: :integer) config.add(:aws_kms_client_multi_pool_size, type: :integer) config.add(:aws_kms_key_id, type: :string) + config.add(:aws_kms_multi_region_key_id, type: :string) + config.add(:aws_kms_multi_region_write_enabled, type: :boolean) config.add(:aws_logo_bucket, type: :string) config.add(:aws_region, type: :string) config.add(:backup_code_cost, type: :string) diff --git a/spec/services/encryption/kms_client_spec.rb b/spec/services/encryption/kms_client_spec.rb index 907b7d8d4f4..b2a515b451b 100644 --- a/spec/services/encryption/kms_client_spec.rb +++ b/spec/services/encryption/kms_client_spec.rb @@ -4,7 +4,7 @@ before do stub_const( 'Encryption::KmsClient::KMS_CLIENT_POOL', - FakeConnectionPool.new { Aws::KMS::Client.new(region: aws_region) }, + FakeConnectionPool.new { aws_kms_client }, ) # rubocop:disable Layout/LineLength @@ -36,6 +36,7 @@ allow(IdentityConfig.store).to receive(:aws_kms_key_id).and_return(key_id) end + let(:aws_kms_client) { Aws::KMS::Client.new(region: aws_region) } let(:key_id) { 'key1' } let(:plaintext) { 'a' * 3000 + 'b' * 3000 + 'c' * 3000 } let(:encryption_context) { { 'context' => 'attribute-bundle', 'user_id' => '123-abc-456-def' } } @@ -72,6 +73,33 @@ end end + context 'with a KMS key ID specified' do + subject { described_class.new(kms_key_id: 'custom-key-id') } + + before do + stub_mapped_aws_kms_client( + [ + # rubocop:disable Layout/LineLength + { plaintext: 'a' * 3000, ciphertext: 'us-north-1:kms1', key_id: 'custom-key-id', region: 'us-north-1' }, + { plaintext: 'b' * 3000, ciphertext: 'us-north-1:kms2', key_id: 'custom-key-id', region: 'us-north-1' }, + { plaintext: 'c' * 3000, ciphertext: 'us-north-1:kms3', key_id: 'custom-key-id', region: 'us-north-1' }, + # rubocop:enable Layout/LineLength + ], + ) + end + + it 'encrypts with the specified key ID' do + result = subject.encrypt(plaintext, encryption_context) + + expect(result).to eq(kms_ciphertext) + expect(aws_kms_client.api_requests.count).to eq(3) + + aws_kms_client.api_requests.each do |api_request| + expect(api_request[:params][:key_id]).to eq('custom-key-id') + end + end + end + context 'with KMS disabled' do let(:kms_enabled) { false } From 1b71e303ad99ebeec604df35cee95f0d57297568 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Tue, 8 Aug 2023 11:09:56 -0400 Subject: [PATCH 2/2] add a key id to the kms client helper --- spec/support/aws_kms_client.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/support/aws_kms_client.rb b/spec/support/aws_kms_client.rb index a355349210e..98ba87e2538 100644 --- a/spec/support/aws_kms_client.rb +++ b/spec/support/aws_kms_client.rb @@ -1,6 +1,9 @@ module AwsKmsClientHelper - def stub_aws_kms_client(random_key = random_str, ciphered_key = random_str) + def stub_aws_kms_client( + random_key = random_str, + ciphered_key = random_str, aws_key_id = IdentityConfig.store.aws_kms_key_id + ) Aws.config[:kms] = { stub_responses: { encrypt: { ciphertext_blob: ciphered_key, key_id: aws_key_id },