Class: SymmetricEncryption::Keystore::Aws
- Inherits:
-
Object
- Object
- SymmetricEncryption::Keystore::Aws
- Includes:
- Utils::Files
- Defined in:
- lib/symmetric_encryption/keystore/aws.rb
Overview
Support AWS Key Management Service (KMS)
Terms:
Aws
Amazon Web Services.
CMK
Customer Master Key.
Master key to encrypt and decrypt data, specifically the DEK in this case.
Stored in AWS, cannot be exported.
DEK
Data Encryption Key.
Key used to encrypt local data.
Encrypted with the CMK and stored locally.
KMS
Key Management Service.
For generating and storing the CMK.
Used to encrypt and decrypt the DEK.
Recommended reading:
Concepts:
https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html
Overview:
https://docs.aws.amazon.com/kms/latest/developerguide/overview.html
Process:
-
Create a customer master key (CMK) along with an alias for use by Symmetric Encryption.
- Note: CMK is region specific. - Stored exclusively in AWS KMS, cannot be exported.
-
Generate and encrypt a data encryption key (DEK).
- CMK is used to encrypt the DEK. - Encrypted DEK is stored locally. - Encrypted DEK is region specific. - DEK can be shared, but then must be re-encrypted in each region. - Shared DEK across regions for database replication. - List of regions to publish DEK to during generation / key-rotation. - DEK must be encrypted with CMK in each region consecutively.
Warning:
If access to the AWS KMS is ever lost, then it is not possible to decrypt any encrypted data.
Examples:
- Loss of access to AWS accounts.
- Loss of region(s) in which master keys are stored.
Instance Attribute Summary collapse
-
#key_files ⇒ Object
readonly
Returns the value of attribute key_files.
-
#master_key_alias ⇒ Object
readonly
Returns the value of attribute master_key_alias.
-
#region ⇒ Object
readonly
Returns the value of attribute region.
Class Method Summary collapse
-
.generate_data_key(cipher_name:, app_name:, environment:, key_path:, version: 0, regions: Utils::Aws::AWS_US_REGIONS, dek: nil, **_args) ⇒ Object
Returns [Hash] a new keystore configuration after generating the data key.
-
.master_key_alias(app_name, environment) ⇒ Object
Alias pointing to the active version of the master key for that region.
Instance Method Summary collapse
- #aws(region) ⇒ Object
-
#initialize(key_files:, master_key_alias:, region: nil, key_encrypting_key: nil) ⇒ Aws
constructor
Stores the Encryption key in a file.
-
#read ⇒ Object
Reads the data key environment variable, if present, otherwise a file.
-
#write(data_key) ⇒ Object
Encrypt and write the data key to the file for each region.
Constructor Details
#initialize(key_files:, master_key_alias:, region: nil, key_encrypting_key: nil) ⇒ Aws
Stores the Encryption key in a file. Secures the Encryption key by encrypting it with a key encryption key.
115 116 117 118 119 120 121 122 123 |
# File 'lib/symmetric_encryption/keystore/aws.rb', line 115 def initialize(key_files:, master_key_alias:, region: nil, key_encrypting_key: nil) @key_files = key_files @master_key_alias = master_key_alias @region = region || ENV["AWS_REGION"] || ENV["AWS_DEFAULT_REGION"] || ::Aws.config[:region] if key_encrypting_key raise(SymmetricEncryption::ConfigError, "AWS KMS keystore encrypts the key itself, so does not support supplying a key_encrypting_key") end end |
Instance Attribute Details
#key_files ⇒ Object (readonly)
Returns the value of attribute key_files.
55 56 57 |
# File 'lib/symmetric_encryption/keystore/aws.rb', line 55 def key_files @key_files end |
#master_key_alias ⇒ Object (readonly)
Returns the value of attribute master_key_alias.
55 56 57 |
# File 'lib/symmetric_encryption/keystore/aws.rb', line 55 def master_key_alias @master_key_alias end |
#region ⇒ Object (readonly)
Returns the value of attribute region.
55 56 57 |
# File 'lib/symmetric_encryption/keystore/aws.rb', line 55 def region @region end |
Class Method Details
.generate_data_key(cipher_name:, app_name:, environment:, key_path:, version: 0, regions: Utils::Aws::AWS_US_REGIONS, dek: nil, **_args) ⇒ Object
Returns [Hash] a new keystore configuration after generating the data key.
Increments the supplied version number by 1.
Sample Hash layout returned:
cipher_name: aes-256-cbc,
version: 8,
keystore: :aws,
master_key_alias: 'alias/symmetric-encryption/application/production',
key_files: [
{region: blah1, file_name: "~/symmetric-encryption/application_production_blah1_v6.encrypted_key",
blah2, file_name: "~/symmetric-encryption/application_production_blah2_v6.encrypted_key",
],
iv: 'T80pYzD0E6e/bJCdjZ6TiQ=='
}
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/symmetric_encryption/keystore/aws.rb', line 73 def self.generate_data_key(cipher_name:, app_name:, environment:, key_path:, version: 0, regions: Utils::Aws::AWS_US_REGIONS, dek: nil, **_args) # TODO: Also support generating environment variables instead of files. version >= 255 ? (version = 1) : (version += 1) regions = Array(regions).dup master_key_alias = master_key_alias(app_name, environment) # File per region for holding the encrypted data key key_files = regions.collect do |region| file_name = "#{app_name}_#{environment}_#{region}_v#{version}.encrypted_key" {region: region, file_name: ::File.join(key_path, file_name)} end keystore = new(key_files: key_files, master_key_alias: master_key_alias) unless dek data_key = keystore.aws(regions.first).generate_data_key(cipher_name) dek = Key.new(key: data_key, cipher_name: cipher_name) end keystore.write(dek.key) { keystore: :aws, cipher_name: dek.cipher_name, version: version, master_key_alias: master_key_alias, key_files: key_files, iv: dek.iv } end |
.master_key_alias(app_name, environment) ⇒ Object
Alias pointing to the active version of the master key for that region.
109 110 111 |
# File 'lib/symmetric_encryption/keystore/aws.rb', line 109 def self.master_key_alias(app_name, environment) @master_key_alias ||= "alias/symmetric-encryption/#{app_name}/#{environment}" end |
Instance Method Details
#aws(region) ⇒ Object
150 151 152 |
# File 'lib/symmetric_encryption/keystore/aws.rb', line 150 def aws(region) Utils::Aws.new(region: region, master_key_alias: master_key_alias) end |
#read ⇒ Object
Reads the data key environment variable, if present, otherwise a file. Decrypts the key using the master key for this region.
127 128 129 130 131 132 133 134 135 |
# File 'lib/symmetric_encryption/keystore/aws.rb', line 127 def read key_file = key_files.find { |i| i[:region] == region } raise(SymmetricEncryption::ConfigError, "region: #{region} not available in the supplied key_files") unless key_file file_name = key_file[:file_name] encrypted_data_key = read_file_and_decode(file_name) aws(region).decrypt(encrypted_data_key) end |
#write(data_key) ⇒ Object
Encrypt and write the data key to the file for each region.
138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/symmetric_encryption/keystore/aws.rb', line 138 def write(data_key) key_files.each do |key_file| region = key_file[:region] file_name = key_file[:file_name] raise(ArgumentError, "region and file_name are mandatory for each key_file entry") unless region && file_name encrypted_data_key = aws(region).encrypt(data_key) write_encoded_to_file(file_name, encrypted_data_key) end end |