aws_advanced_ruby_driver_wrapper 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +175 -0
- data/NOTICE +1 -0
- data/README.md +168 -0
- data/THIRD-PARTY-LICENSES +473 -0
- data/aws_advanced_ruby_driver_wrapper.gemspec +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +73 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/custom_configuration.rb +58 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_mysql_dialect.rb +103 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_pg_dialect.rb +124 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/dialect_codes.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_mysql_dialect.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_pg_dialect.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_mysql_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/mysql_dialect.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/pg_dialect.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_mysql_dialect.rb +88 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_pg_dialect.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/unknown_dialect.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/utils/dialect_utils.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +154 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +55 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +165 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +201 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +80 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/pg_error_handler.rb +126 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +59 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/connection_string_host_list_provider.rb +95 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/global_aurora_host_list_provider.rb +65 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability.rb +24 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability_strategy.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_info.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/host_role.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/random_host_selector.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/host/rds_host_list_provider.rb +206 -0
- data/lib/aws_advanced_ruby_driver_wrapper/logging.rb +110 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +709 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/global_cluster_topology_monitor.rb +72 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +57 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_state.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +429 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +205 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +132 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/iam_host_tracker.rb +84 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interim_status.rb +92 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interval_rate.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase.rb +69 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_event_log.rb +85 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_time_info.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/role.rb +38 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/reject_connect_routing.rb +40 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +136 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +53 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +83 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +244 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_info.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +564 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +414 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_state.rb +98 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_timer.rb +46 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +266 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +158 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/info.rb +111 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/member_list_type.rb +31 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/role.rb +45 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_mode.rb +43 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +467 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_mode.rb +68 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +403 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +485 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/audit_logger.rb +157 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_cipher.rb +159 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_encryption_config.rb +61 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/connection_source.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/data_key_cache.rb +220 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_algorithm.rb +75 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_config.rb +146 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_service.rb +391 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/error_context.rb +198 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/errors.rb +259 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +435 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +378 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_metadata.rb +86 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +890 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_utility.rb +281 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/metadata_manager.rb +332 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sanitizer.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_name.rb +70 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_validator.rb +211 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql_runner.rb +147 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/type_marker.rb +109 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +358 -0
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +659 -0
- data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +409 -0
- data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +122 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/connection_service.rb +143 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +267 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +199 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +186 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_call_context.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +273 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +30 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +78 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/session_state_service.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/accessible_regions.rb +52 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb +25 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aurora_topology_utils.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/aws_credentials_utils.rb +62 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +91 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +368 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/conversion_utils.rb +51 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +119 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/data_access_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/events/monitor_reset_event.rb +26 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +185 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/host_list_utils.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +112 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/multi_az_topology_utils.rb +117 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +99 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +641 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +502 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +63 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_type.rb +35 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint.rb +27 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint_parser.rb +50 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +139 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_url_type.rb +71 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_utils.rb +575 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +153 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_encoding.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_method_analyzer.rb +195 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/cache_entry.rb +56 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/expiration_cache.rb +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/sliding_expiration_cache.rb +137 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +172 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +127 -0
- data/lib/aws_advanced_ruby_driver_wrapper/version.rb +19 -0
- data/lib/aws_advanced_ruby_driver_wrapper/wrapper_property.rb +64 -0
- data/lib/aws_advanced_ruby_driver_wrapper.rb +116 -0
- metadata +227 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require_relative 'encryption_service'
|
|
18
|
+
require_relative 'errors'
|
|
19
|
+
|
|
20
|
+
module AwsAdvancedRubyDriverWrapper
|
|
21
|
+
module Plugins
|
|
22
|
+
module Encryption
|
|
23
|
+
# Encrypts and decrypts single column values for the duration of one call.
|
|
24
|
+
#
|
|
25
|
+
# A cipher is created for each intercepted call and {#release}d when the call returns. That
|
|
26
|
+
# scope is what keeps plaintext data keys short lived: a key is decrypted at most once per
|
|
27
|
+
# column per call, however many values that call touches, and every copy is zeroed again on
|
|
28
|
+
# release.
|
|
29
|
+
#
|
|
30
|
+
# Values are returned as strings, matching what both drivers hand back for a text column.
|
|
31
|
+
class ColumnCipher
|
|
32
|
+
# @param key_manager [KeyManager]
|
|
33
|
+
# @param sql_runner [SqlRunner] used to read a bytea or blob column into binary data
|
|
34
|
+
def initialize(key_manager:, sql_runner:)
|
|
35
|
+
@key_manager = key_manager
|
|
36
|
+
@sql = sql_runner
|
|
37
|
+
@data_keys = {}
|
|
38
|
+
# Key material resolved by the id embedded in a value, cached for the cipher's lifetime so
|
|
39
|
+
# a result full of rows written under a rotated-away key costs one lookup, not one per row.
|
|
40
|
+
@key_metadata_by_id = {}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Encrypts one value for the column the configuration describes.
|
|
44
|
+
#
|
|
45
|
+
# @param value [Object, nil] the plaintext value
|
|
46
|
+
# @param config [ColumnEncryptionConfig] the column's kms_encryption configuration
|
|
47
|
+
# @return [String, nil] the binary payload to store, nil when value is nil
|
|
48
|
+
# @raise [Errors::EncryptionError] if the value cannot be encrypted
|
|
49
|
+
def encrypt(value, config)
|
|
50
|
+
return nil if value.nil?
|
|
51
|
+
|
|
52
|
+
metadata = key_metadata!(config)
|
|
53
|
+
EncryptionService.encrypt(
|
|
54
|
+
value, data_key_for(metadata), hmac_key_for(metadata, config), config.algorithm, key_id: metadata.id
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Decrypts one value read from the column the configuration describes.
|
|
59
|
+
#
|
|
60
|
+
# The read fails closed: every value of a column configured for kms_encryption is expected to
|
|
61
|
+
# be an encrypted payload, so a value that cannot be verified and decrypted - too short to be
|
|
62
|
+
# a payload, an integrity tag that does not match, or a stored key with no HMAC key - is
|
|
63
|
+
# raised on rather than returned, so the wrapper never hands the application a value it cannot
|
|
64
|
+
# vouch for. A null column value reads back as nil.
|
|
65
|
+
#
|
|
66
|
+
# @param raw [Object, nil] the raw column value
|
|
67
|
+
# @param config [ColumnEncryptionConfig] the column's kms_encryption configuration
|
|
68
|
+
# @return [Object, nil] the decrypted value, or nil when the column value is null
|
|
69
|
+
# @raise [Errors::EncryptionError] if the value cannot be verified and decrypted, is not the
|
|
70
|
+
# stored bytes of an encrypted column, or the column has no usable key material
|
|
71
|
+
def decrypt(raw, config)
|
|
72
|
+
return nil if raw.nil?
|
|
73
|
+
|
|
74
|
+
# A value read from an encrypted (binary) column is a String; nil, handled above, is the
|
|
75
|
+
# only other shape a NULL produces. Any other type is not the stored bytes of an encrypted
|
|
76
|
+
# column, so it is refused rather than returned unverified.
|
|
77
|
+
raise Errors::EncryptionError.decryption_failed("Cannot decrypt a #{raw.class} value") unless raw.is_a?(String)
|
|
78
|
+
|
|
79
|
+
# A column configured for kms_encryption must have key material; its absence is a
|
|
80
|
+
# misconfiguration worth failing on, as on the encrypt side.
|
|
81
|
+
key_metadata!(config)
|
|
82
|
+
|
|
83
|
+
bytes = @sql.read_binary(raw)
|
|
84
|
+
# Resolve the key the value was written with from the id in its payload, so a value written
|
|
85
|
+
# before a key rotation still decrypts. A value whose key cannot be resolved falls back to
|
|
86
|
+
# the current key, against which it then fails its integrity check and is refused.
|
|
87
|
+
metadata = key_metadata_for_value(bytes, config)
|
|
88
|
+
hmac_key = hmac_key_for(metadata, config)
|
|
89
|
+
|
|
90
|
+
EncryptionService.decrypt(bytes, data_key_for(metadata), hmac_key, config.algorithm, target_type: String)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Zeroes every plaintext data key this cipher decrypted.
|
|
94
|
+
# @return [void]
|
|
95
|
+
def release
|
|
96
|
+
@data_keys.each_value { |data_key| EncryptionService.wipe(data_key) }
|
|
97
|
+
@data_keys.clear
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
# The key a stored value was written with. The payload records the +key_storage+ id, so a
|
|
104
|
+
# value keeps decrypting after its column's key has been rotated: the current key is used
|
|
105
|
+
# when the ids match (the common case, and the only one that needs no extra lookup), and any
|
|
106
|
+
# other id is fetched from +key_storage+ and cached. A value with no embedded id (legacy
|
|
107
|
+
# data) or one whose key is gone falls back to the current key, against which it then fails
|
|
108
|
+
# its integrity check and is refused by {#decrypt}.
|
|
109
|
+
#
|
|
110
|
+
# @return [KeyMetadata, nil]
|
|
111
|
+
def key_metadata_for_value(bytes, config)
|
|
112
|
+
current = config.key_metadata
|
|
113
|
+
key_id = EncryptionService.key_id_from_payload(bytes)
|
|
114
|
+
return current if key_id.nil? || (current && current.id == key_id)
|
|
115
|
+
|
|
116
|
+
@key_metadata_by_id[key_id] ||= resolve_key_metadata(key_id) || current
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Looks a key up by the id embedded in a value. The lookup is opportunistic: a value that is
|
|
120
|
+
# not really an encrypted payload (legacy data, tampered bytes) can carry an arbitrary id, and
|
|
121
|
+
# a value written before this format carries none, so a lookup that finds nothing or fails must
|
|
122
|
+
# not surface as an error - the caller falls back to the current key, against which the value
|
|
123
|
+
# then fails its integrity check and is refused by {#decrypt}.
|
|
124
|
+
def resolve_key_metadata(key_id)
|
|
125
|
+
@key_manager.key_metadata_by_id(key_id)
|
|
126
|
+
rescue Errors::EncryptionPluginError
|
|
127
|
+
nil
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def data_key_for(metadata)
|
|
131
|
+
@data_keys[metadata.encrypted_data_key] ||=
|
|
132
|
+
@key_manager.decrypt_data_key(metadata.encrypted_data_key, metadata.master_key_arn)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def hmac_key_for(metadata, config)
|
|
136
|
+
hmac_key = metadata.hmac_key
|
|
137
|
+
return hmac_key unless hmac_key.nil? || hmac_key.empty?
|
|
138
|
+
|
|
139
|
+
raise Errors::EncryptionError
|
|
140
|
+
.invalid_key('The stored key has no HMAC key')
|
|
141
|
+
.with_table(config.table_name)
|
|
142
|
+
.with_column(config.column_name)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def key_metadata!(config)
|
|
146
|
+
metadata = config.key_metadata
|
|
147
|
+
if metadata.nil?
|
|
148
|
+
raise Errors::EncryptionError
|
|
149
|
+
.invalid_key('The column has no key material')
|
|
150
|
+
.with_table(config.table_name)
|
|
151
|
+
.with_column(config.column_name)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
metadata
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require_relative 'encryption_algorithm'
|
|
18
|
+
require_relative 'key_metadata'
|
|
19
|
+
|
|
20
|
+
module AwsAdvancedRubyDriverWrapper
|
|
21
|
+
module Plugins
|
|
22
|
+
module Encryption
|
|
23
|
+
# One row of the +encryption_metadata+ table joined with the key it points at: the
|
|
24
|
+
# algorithm to use for a single table column, and the key material to use with it.
|
|
25
|
+
ColumnEncryptionConfig = Data.define(
|
|
26
|
+
:table_name,
|
|
27
|
+
:column_name,
|
|
28
|
+
:algorithm,
|
|
29
|
+
:key_id,
|
|
30
|
+
:key_metadata,
|
|
31
|
+
:created_at,
|
|
32
|
+
:updated_at
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
class ColumnEncryptionConfig
|
|
36
|
+
# @param table_name [String]
|
|
37
|
+
# @param column_name [String]
|
|
38
|
+
# @param algorithm [String] an {EncryptionAlgorithm} name
|
|
39
|
+
# @param key_id [Integer, nil] the +encryption_metadata.key_id+ foreign key
|
|
40
|
+
# @param key_metadata [KeyMetadata, nil] the joined +key_storage+ row
|
|
41
|
+
# @param created_at [Time, nil]
|
|
42
|
+
# @param updated_at [Time, nil]
|
|
43
|
+
def initialize(table_name:, column_name:, algorithm: EncryptionAlgorithm::DEFAULT, key_id: nil,
|
|
44
|
+
key_metadata: nil, created_at: nil, updated_at: nil)
|
|
45
|
+
super
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The cache key for this column, e.g. 'users.ssn'.
|
|
49
|
+
# @return [String]
|
|
50
|
+
def column_identifier
|
|
51
|
+
"#{table_name}.#{column_name}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Boolean] true when the column has key material and a supported algorithm
|
|
55
|
+
def usable?
|
|
56
|
+
EncryptionAlgorithm.supported?(algorithm) && !key_metadata.nil? && key_metadata.valid?
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require_relative '../../errors'
|
|
18
|
+
|
|
19
|
+
module AwsAdvancedRubyDriverWrapper
|
|
20
|
+
module Plugins
|
|
21
|
+
module Encryption
|
|
22
|
+
# Gives the encryption components (KeyManager, MetadataManager, KeyManagementUtility) a
|
|
23
|
+
# connection to run a statement on. It is supplied one of two ways, and exactly one must be set:
|
|
24
|
+
#
|
|
25
|
+
# * +connection+ - a caller-owned connection, reused for every operation and never closed here.
|
|
26
|
+
# This is how {KeyManagementUtility} wires things for a user with a connection in hand.
|
|
27
|
+
# * +service_container+ - the plugin's runtime source: each call opens a short-lived connection
|
|
28
|
+
# of its own through the wrapper's connect pipeline and closes it afterward, so metadata and
|
|
29
|
+
# key reads never ride the application's connection.
|
|
30
|
+
module ConnectionSource
|
|
31
|
+
# Records the connection source from a constructor. Exactly one argument must be non-nil.
|
|
32
|
+
#
|
|
33
|
+
# @raise [ArgumentError] unless exactly one of the two is given
|
|
34
|
+
def use_connection_source(connection:, service_container:)
|
|
35
|
+
if [connection, service_container].compact.size != 1
|
|
36
|
+
raise ArgumentError, 'provide exactly one of connection: or service_container:'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@connection = connection
|
|
40
|
+
@service_container = service_container
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Yields a usable connection. A supplied connection is yielded as-is and left open; otherwise a
|
|
44
|
+
# short-lived connection is opened for the call and closed afterward.
|
|
45
|
+
#
|
|
46
|
+
# @param operation [String, nil] operation name, for error context
|
|
47
|
+
# @yieldparam connection [Object] a pg or mysql2 connection
|
|
48
|
+
# @return [Object] whatever the block returns
|
|
49
|
+
# @raise [Errors::AwsError] if a runtime connection cannot be opened
|
|
50
|
+
def with_connection(operation: nil)
|
|
51
|
+
return yield(@connection) if @connection
|
|
52
|
+
|
|
53
|
+
connection = open_runtime_connection(operation)
|
|
54
|
+
begin
|
|
55
|
+
yield connection
|
|
56
|
+
ensure
|
|
57
|
+
close_runtime_connection(connection)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# Opens a short-lived connection through the connect pipeline, the way the plugin's runtime
|
|
64
|
+
# reads its metadata and keys (independent of the application's connection).
|
|
65
|
+
def open_runtime_connection(operation)
|
|
66
|
+
connection_service = @service_container.connection_service
|
|
67
|
+
connection = @service_container.plugin_manager.internal_connect(
|
|
68
|
+
connection_service.current_host_info,
|
|
69
|
+
connection_service.driver_props.dup,
|
|
70
|
+
connection_service.wrapper_props,
|
|
71
|
+
false
|
|
72
|
+
)
|
|
73
|
+
if connection.nil?
|
|
74
|
+
raise AwsAdvancedRubyDriverWrapper::Errors::AwsError,
|
|
75
|
+
"The connect pipeline returned no connection for #{operation || 'a metadata query'}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
connection
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def close_runtime_connection(connection)
|
|
82
|
+
return if connection.nil?
|
|
83
|
+
|
|
84
|
+
@service_container.dialect_service.driver_dialect.close_connection(connection)
|
|
85
|
+
rescue StandardError
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require_relative '../../logging'
|
|
18
|
+
require_relative 'encryption_service'
|
|
19
|
+
|
|
20
|
+
module AwsAdvancedRubyDriverWrapper
|
|
21
|
+
module Plugins
|
|
22
|
+
module Encryption
|
|
23
|
+
# An in-memory cache of plaintext data keys, so that reading an encrypted column does
|
|
24
|
+
# not need a KMS Decrypt call per statement.
|
|
25
|
+
#
|
|
26
|
+
# This deliberately does not use the shared StorageService: data keys need a bounded
|
|
27
|
+
# size with oldest-first eviction, their bytes have to be zeroed when they leave the
|
|
28
|
+
# cache, and every entry is handed out as a copy so that a caller wiping its own copy
|
|
29
|
+
# cannot corrupt the cached one.
|
|
30
|
+
class DataKeyCache
|
|
31
|
+
include Logging
|
|
32
|
+
|
|
33
|
+
MIN_CLEANUP_INTERVAL_SEC = 30
|
|
34
|
+
CLEANUP_INTERVAL_DIVISOR = 4
|
|
35
|
+
|
|
36
|
+
# A cached plaintext data key. The stored string is private to the cache.
|
|
37
|
+
CacheEntry = Data.define(:data_key, :created_at) do
|
|
38
|
+
def expired?(ttl_sec, now = Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
|
39
|
+
now >= created_at + ttl_sec
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Redact the plaintext data key so it is never exposed if an instance is
|
|
43
|
+
# logged, interpolated, or rendered in a backtrace.
|
|
44
|
+
def inspect
|
|
45
|
+
"#<data CacheEntry data_key=#{AwsAdvancedRubyDriverWrapper::REDACTED.inspect}, created_at=#{created_at.inspect}>"
|
|
46
|
+
end
|
|
47
|
+
alias_method :to_s, :inspect
|
|
48
|
+
|
|
49
|
+
# `pp` / PrettyPrint does not call #inspect; route them through the
|
|
50
|
+
# redacted representation so `pp entry` cannot leak the data key.
|
|
51
|
+
def pretty_print(pp)
|
|
52
|
+
pp.text(inspect)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# A point-in-time snapshot of the cache counters.
|
|
57
|
+
CacheStats = Data.define(:size, :hits, :misses, :evictions) do
|
|
58
|
+
# @return [Float] the percentage of lookups that were served from the cache
|
|
59
|
+
def hit_rate
|
|
60
|
+
total = hits + misses
|
|
61
|
+
total.zero? ? 0.0 : (hits.to_f / total) * 100
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_s
|
|
65
|
+
format('CacheStats{size=%<size>d, hits=%<hits>d, misses=%<misses>d, evictions=%<evictions>d, ' \
|
|
66
|
+
'hit_rate=%<hit_rate>.2f%%}',
|
|
67
|
+
size: size, hits: hits, misses: misses, evictions: evictions, hit_rate: hit_rate)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @param max_size [Integer] the maximum number of data keys held at once
|
|
72
|
+
# @param ttl_sec [Numeric] how long a data key stays cached, in seconds
|
|
73
|
+
# @param enabled [Boolean] when false every lookup misses and nothing is stored
|
|
74
|
+
def initialize(max_size:, ttl_sec:, enabled: true)
|
|
75
|
+
@max_size = max_size
|
|
76
|
+
@ttl_sec = ttl_sec
|
|
77
|
+
@enabled = enabled
|
|
78
|
+
@cache = {}
|
|
79
|
+
@lock = Mutex.new
|
|
80
|
+
@hits = 0
|
|
81
|
+
@misses = 0
|
|
82
|
+
@evictions = 0
|
|
83
|
+
@running = enabled
|
|
84
|
+
@cleanup_thread = start_cleanup_thread if enabled
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @return [Boolean]
|
|
88
|
+
def enabled?
|
|
89
|
+
@enabled
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @param key [String] the cached data key, see {KeyManager#data_key_cache_key}
|
|
93
|
+
# @return [String, nil] a copy of the cached data key, or nil on a miss
|
|
94
|
+
def get(key)
|
|
95
|
+
return nil unless @enabled
|
|
96
|
+
|
|
97
|
+
@lock.synchronize do
|
|
98
|
+
entry = @cache[key]
|
|
99
|
+
|
|
100
|
+
if entry.nil?
|
|
101
|
+
@misses += 1
|
|
102
|
+
nil
|
|
103
|
+
elsif entry.expired?(@ttl_sec)
|
|
104
|
+
@cache.delete(key)
|
|
105
|
+
EncryptionService.wipe(entry.data_key)
|
|
106
|
+
@misses += 1
|
|
107
|
+
nil
|
|
108
|
+
else
|
|
109
|
+
@hits += 1
|
|
110
|
+
entry.data_key.dup
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Stores a copy of the given data key, evicting the oldest entry first when full.
|
|
116
|
+
#
|
|
117
|
+
# @param key [String]
|
|
118
|
+
# @param data_key [String] the plaintext data key; the caller keeps ownership of it
|
|
119
|
+
# @return [void]
|
|
120
|
+
def put(key, data_key)
|
|
121
|
+
return unless @enabled && data_key
|
|
122
|
+
|
|
123
|
+
@lock.synchronize do
|
|
124
|
+
existing = @cache.delete(key)
|
|
125
|
+
EncryptionService.wipe(existing.data_key) if existing
|
|
126
|
+
evict_oldest if @cache.size >= @max_size
|
|
127
|
+
|
|
128
|
+
@cache[key] = CacheEntry.new(data_key: data_key.dup, created_at: Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @param key [String]
|
|
133
|
+
# @return [void]
|
|
134
|
+
def remove(key)
|
|
135
|
+
@lock.synchronize do
|
|
136
|
+
entry = @cache.delete(key)
|
|
137
|
+
EncryptionService.wipe(entry.data_key) if entry
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Zeroes and drops every cached data key.
|
|
142
|
+
# @return [void]
|
|
143
|
+
def clear
|
|
144
|
+
@lock.synchronize do
|
|
145
|
+
@cache.each_value { |entry| EncryptionService.wipe(entry.data_key) }
|
|
146
|
+
@cache.clear
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Drops every expired entry. Called on a schedule by the cleanup thread.
|
|
151
|
+
# @return [Integer] the number of entries removed
|
|
152
|
+
def remove_expired_entries
|
|
153
|
+
@lock.synchronize do
|
|
154
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
155
|
+
expired = @cache.select { |_, entry| entry.expired?(@ttl_sec, now) }
|
|
156
|
+
expired.each_key do |key|
|
|
157
|
+
EncryptionService.wipe(@cache.delete(key).data_key)
|
|
158
|
+
end
|
|
159
|
+
expired.size
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# @return [Integer]
|
|
164
|
+
def size
|
|
165
|
+
@lock.synchronize { @cache.size }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @return [CacheStats]
|
|
169
|
+
def stats
|
|
170
|
+
@lock.synchronize { CacheStats.new(size: @cache.size, hits: @hits, misses: @misses, evictions: @evictions) }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Stops the cleanup thread and zeroes every cached key.
|
|
174
|
+
# @return [void]
|
|
175
|
+
def shutdown
|
|
176
|
+
@running = false
|
|
177
|
+
begin
|
|
178
|
+
@cleanup_thread&.wakeup
|
|
179
|
+
rescue ThreadError
|
|
180
|
+
nil
|
|
181
|
+
end
|
|
182
|
+
@cleanup_thread&.join(5)
|
|
183
|
+
clear
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
private
|
|
187
|
+
|
|
188
|
+
# Removes the entry that has been cached the longest. Callers already hold the lock.
|
|
189
|
+
def evict_oldest
|
|
190
|
+
oldest_key, oldest_entry = @cache.min_by { |_, entry| entry.created_at }
|
|
191
|
+
return if oldest_key.nil?
|
|
192
|
+
|
|
193
|
+
@cache.delete(oldest_key)
|
|
194
|
+
EncryptionService.wipe(oldest_entry.data_key)
|
|
195
|
+
@evictions += 1
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def cleanup_interval_sec
|
|
199
|
+
[@ttl_sec.to_f / CLEANUP_INTERVAL_DIVISOR, MIN_CLEANUP_INTERVAL_SEC].max
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def start_cleanup_thread
|
|
203
|
+
interval = cleanup_interval_sec
|
|
204
|
+
thread = Thread.new do
|
|
205
|
+
while @running
|
|
206
|
+
sleep(interval)
|
|
207
|
+
begin
|
|
208
|
+
remove_expired_entries if @running
|
|
209
|
+
rescue StandardError => e
|
|
210
|
+
logger.debug("DataKeyCache cleanup failed: #{e.message}")
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
thread.name = 'kms_encryption-data-key-cache-cleanup'
|
|
215
|
+
thread
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
6
|
+
# You may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
require_relative 'errors'
|
|
18
|
+
|
|
19
|
+
module AwsAdvancedRubyDriverWrapper
|
|
20
|
+
module Plugins
|
|
21
|
+
module Encryption
|
|
22
|
+
# The symmetric algorithms the plugin can use to encrypt column values. The names are
|
|
23
|
+
# the ones stored in the +encryption_metadata.encryption_algorithm+ column.
|
|
24
|
+
module EncryptionAlgorithm
|
|
25
|
+
AES_256_GCM = 'AES-256-GCM'
|
|
26
|
+
AES_128_GCM = 'AES-128-GCM'
|
|
27
|
+
DEFAULT = AES_256_GCM
|
|
28
|
+
|
|
29
|
+
# Expected data key length in bytes for each algorithm.
|
|
30
|
+
KEY_LENGTHS = {
|
|
31
|
+
AES_256_GCM => 32,
|
|
32
|
+
AES_128_GCM => 16
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
# OpenSSL cipher names for each algorithm.
|
|
36
|
+
CIPHERS = {
|
|
37
|
+
AES_256_GCM => 'aes-256-gcm',
|
|
38
|
+
AES_128_GCM => 'aes-128-gcm'
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
ALL = KEY_LENGTHS.keys.freeze
|
|
42
|
+
|
|
43
|
+
module_function
|
|
44
|
+
|
|
45
|
+
# @param name [String] an algorithm name, e.g. 'AES-256-GCM'
|
|
46
|
+
# @return [Integer] the required data key length in bytes
|
|
47
|
+
# @raise [Errors::EncryptionError] if the algorithm is not supported
|
|
48
|
+
def key_length(name)
|
|
49
|
+
KEY_LENGTHS.fetch(name) { raise unsupported(name) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @param name [String] an algorithm name, e.g. 'AES-256-GCM'
|
|
53
|
+
# @return [String] the OpenSSL cipher name
|
|
54
|
+
# @raise [Errors::EncryptionError] if the algorithm is not supported
|
|
55
|
+
def cipher_name(name)
|
|
56
|
+
CIPHERS.fetch(name) { raise unsupported(name) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param name [String, nil]
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def supported?(name)
|
|
62
|
+
KEY_LENGTHS.key?(name)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param name [String, nil]
|
|
66
|
+
# @return [Errors::EncryptionError]
|
|
67
|
+
def unsupported(name)
|
|
68
|
+
Errors::EncryptionError
|
|
69
|
+
.invalid_algorithm("Unsupported kms_encryption algorithm: #{name.inspect}. Supported algorithms: #{ALL.join(', ')}")
|
|
70
|
+
.with_algorithm(name)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|