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.
Files changed (150) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE +175 -0
  4. data/NOTICE +1 -0
  5. data/README.md +168 -0
  6. data/THIRD-PARTY-LICENSES +473 -0
  7. data/aws_advanced_ruby_driver_wrapper.gemspec +73 -0
  8. data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_mysql2_adapter.rb +73 -0
  9. data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +95 -0
  10. data/lib/aws_advanced_ruby_driver_wrapper/custom_configuration.rb +58 -0
  11. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_mysql_dialect.rb +103 -0
  12. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/aurora_pg_dialect.rb +124 -0
  13. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/dialect_codes.rb +38 -0
  14. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_mysql_dialect.rb +91 -0
  15. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/global_pg_dialect.rb +92 -0
  16. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_mysql_dialect.rb +95 -0
  17. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/multi_az_cluster_pg_dialect.rb +86 -0
  18. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/mysql_dialect.rb +98 -0
  19. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/pg_dialect.rb +95 -0
  20. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_mysql_dialect.rb +88 -0
  21. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/rds_pg_dialect.rb +86 -0
  22. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/unknown_dialect.rb +72 -0
  23. data/lib/aws_advanced_ruby_driver_wrapper/db_dialects/utils/dialect_utils.rb +71 -0
  24. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +154 -0
  25. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +55 -0
  26. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +165 -0
  27. data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +201 -0
  28. data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +62 -0
  29. data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +80 -0
  30. data/lib/aws_advanced_ruby_driver_wrapper/errors/pg_error_handler.rb +126 -0
  31. data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +59 -0
  32. data/lib/aws_advanced_ruby_driver_wrapper/host/connection_string_host_list_provider.rb +95 -0
  33. data/lib/aws_advanced_ruby_driver_wrapper/host/global_aurora_host_list_provider.rb +65 -0
  34. data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability.rb +24 -0
  35. data/lib/aws_advanced_ruby_driver_wrapper/host/host_availability_strategy.rb +27 -0
  36. data/lib/aws_advanced_ruby_driver_wrapper/host/host_info.rb +137 -0
  37. data/lib/aws_advanced_ruby_driver_wrapper/host/host_role.rb +25 -0
  38. data/lib/aws_advanced_ruby_driver_wrapper/host/random_host_selector.rb +40 -0
  39. data/lib/aws_advanced_ruby_driver_wrapper/host/rds_host_list_provider.rb +206 -0
  40. data/lib/aws_advanced_ruby_driver_wrapper/logging.rb +110 -0
  41. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +709 -0
  42. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/global_cluster_topology_monitor.rb +72 -0
  43. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor.rb +99 -0
  44. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_connection.rb +57 -0
  45. data/lib/aws_advanced_ruby_driver_wrapper/monitoring/monitor_state.rb +25 -0
  46. data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +429 -0
  47. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +205 -0
  48. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +132 -0
  49. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/iam_host_tracker.rb +84 -0
  50. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interim_status.rb +92 -0
  51. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/interval_rate.rb +27 -0
  52. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase.rb +69 -0
  53. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_event_log.rb +85 -0
  54. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/phase_time_info.rb +25 -0
  55. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/role.rb +38 -0
  56. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +83 -0
  57. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/reject_connect_routing.rb +40 -0
  58. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +136 -0
  59. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +53 -0
  60. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +52 -0
  61. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +83 -0
  62. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status.rb +68 -0
  63. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +244 -0
  64. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_info.rb +30 -0
  65. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +564 -0
  66. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +414 -0
  67. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_state.rb +98 -0
  68. data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/switchover_timer.rb +46 -0
  69. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +266 -0
  70. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +158 -0
  71. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/info.rb +111 -0
  72. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/member_list_type.rb +31 -0
  73. data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/role.rb +45 -0
  74. data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +108 -0
  75. data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_mode.rb +43 -0
  76. data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +467 -0
  77. data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_mode.rb +68 -0
  78. data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +403 -0
  79. data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +159 -0
  80. data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +485 -0
  81. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/audit_logger.rb +157 -0
  82. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_cipher.rb +159 -0
  83. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/column_encryption_config.rb +61 -0
  84. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/connection_source.rb +91 -0
  85. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/data_key_cache.rb +220 -0
  86. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_algorithm.rb +75 -0
  87. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_config.rb +146 -0
  88. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/encryption_service.rb +391 -0
  89. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/error_context.rb +198 -0
  90. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/errors.rb +259 -0
  91. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +435 -0
  92. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +378 -0
  93. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_metadata.rb +86 -0
  94. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +890 -0
  95. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_utility.rb +281 -0
  96. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/metadata_manager.rb +332 -0
  97. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sanitizer.rb +147 -0
  98. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_name.rb +70 -0
  99. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/schema_validator.rb +211 -0
  100. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql_runner.rb +147 -0
  101. data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/type_marker.rb +109 -0
  102. data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +358 -0
  103. data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +659 -0
  104. data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +409 -0
  105. data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +122 -0
  106. data/lib/aws_advanced_ruby_driver_wrapper/services/connection_service.rb +143 -0
  107. data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +267 -0
  108. data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +199 -0
  109. data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +186 -0
  110. data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_call_context.rb +63 -0
  111. data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +273 -0
  112. data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +30 -0
  113. data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +78 -0
  114. data/lib/aws_advanced_ruby_driver_wrapper/services/session_state_service.rb +56 -0
  115. data/lib/aws_advanced_ruby_driver_wrapper/utils/accessible_regions.rb +52 -0
  116. data/lib/aws_advanced_ruby_driver_wrapper/utils/ar_constants.rb +25 -0
  117. data/lib/aws_advanced_ruby_driver_wrapper/utils/aurora_topology_utils.rb +99 -0
  118. data/lib/aws_advanced_ruby_driver_wrapper/utils/aws_credentials_utils.rb +62 -0
  119. data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +91 -0
  120. data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +368 -0
  121. data/lib/aws_advanced_ruby_driver_wrapper/utils/conversion_utils.rb +51 -0
  122. data/lib/aws_advanced_ruby_driver_wrapper/utils/events/batching_event_publisher.rb +119 -0
  123. data/lib/aws_advanced_ruby_driver_wrapper/utils/events/data_access_event.rb +26 -0
  124. data/lib/aws_advanced_ruby_driver_wrapper/utils/events/monitor_reset_event.rb +26 -0
  125. data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +185 -0
  126. data/lib/aws_advanced_ruby_driver_wrapper/utils/host_list_utils.rb +27 -0
  127. data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +112 -0
  128. data/lib/aws_advanced_ruby_driver_wrapper/utils/multi_az_topology_utils.rb +117 -0
  129. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +99 -0
  130. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +641 -0
  131. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +502 -0
  132. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +63 -0
  133. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_type.rb +35 -0
  134. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint.rb +27 -0
  135. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/routing_hint_parser.rb +50 -0
  136. data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +139 -0
  137. data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_url_type.rb +71 -0
  138. data/lib/aws_advanced_ruby_driver_wrapper/utils/rds_utils.rb +575 -0
  139. data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +153 -0
  140. data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_encoding.rb +56 -0
  141. data/lib/aws_advanced_ruby_driver_wrapper/utils/sql_method_analyzer.rb +195 -0
  142. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/cache_entry.rb +56 -0
  143. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/expiration_cache.rb +108 -0
  144. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/sliding_expiration_cache.rb +137 -0
  145. data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +172 -0
  146. data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +127 -0
  147. data/lib/aws_advanced_ruby_driver_wrapper/version.rb +19 -0
  148. data/lib/aws_advanced_ruby_driver_wrapper/wrapper_property.rb +64 -0
  149. data/lib/aws_advanced_ruby_driver_wrapper.rb +116 -0
  150. metadata +227 -0
@@ -0,0 +1,378 @@
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 'base64'
18
+ require 'digest'
19
+ require 'securerandom'
20
+ require_relative '../../logging'
21
+ require_relative '../../property_definition'
22
+ require_relative '../../utils/conversion_utils'
23
+ require_relative 'connection_source'
24
+ require_relative 'errors'
25
+ require_relative 'key_metadata'
26
+
27
+ module AwsAdvancedRubyDriverWrapper
28
+ module Plugins
29
+ module Encryption
30
+ # Owns the data keys: generates them through KMS, stores them in +key_storage+, and
31
+ # decrypts them again on the way back, caching the plaintext keys in memory.
32
+ #
33
+ # Plaintext data keys never reach the database. Only the KMS encrypted form is stored,
34
+ # and the plaintext copies handed out here are zeroed by their callers once used.
35
+ class KeyManager
36
+ include Logging
37
+ include Utils::ConversionUtils
38
+ include ConnectionSource
39
+
40
+ DATA_KEY_CACHE_PREFIX = 'datakey_'
41
+ HMAC_KEY_LENGTH = 32
42
+ KMS_KEY_SPEC = 'AES_256'
43
+ KMS_MASTER_KEY_SPEC = 'SYMMETRIC_DEFAULT'
44
+ KMS_KEY_USAGE = 'ENCRYPT_DECRYPT'
45
+ JITTER_RATIO = 0.25
46
+ MAX_BACKOFF_SEC = 20.0
47
+
48
+ # KMS error codes that are worth retrying.
49
+ RETRYABLE_ERROR_CODES = %w[
50
+ ThrottlingException
51
+ LimitExceededException
52
+ KMSInternalException
53
+ KeyUnavailableException
54
+ DependencyTimeoutException
55
+ RequestTimeout
56
+ ServiceUnavailable
57
+ InternalFailure
58
+ ].freeze
59
+
60
+ # A freshly generated data key, in both its plaintext and stored forms.
61
+ GeneratedDataKey = Data.define(:plaintext, :encrypted_data_key, :hmac_key)
62
+
63
+ # Exactly one of +connection+ or +service_container+ must be given (see {ConnectionSource}).
64
+ #
65
+ # @param kms_client [Aws::KMS::Client]
66
+ # @param sql_runner [SqlRunner]
67
+ # @param config [EncryptionConfig]
68
+ # @param data_key_cache [DataKeyCache]
69
+ # @param connection [Object, nil] a caller-owned connection used for every operation
70
+ # @param service_container [Services::ServiceContainer, nil] opens a short-lived connection per operation
71
+ # @param audit_logger [AuditLogger, nil]
72
+ def initialize(kms_client:, sql_runner:, config:, data_key_cache:, connection: nil, service_container: nil,
73
+ audit_logger: nil)
74
+ @kms_client = kms_client
75
+ use_connection_source(connection: connection, service_container: service_container)
76
+ @sql = sql_runner
77
+ @config = config
78
+ @cache = data_key_cache
79
+ @audit_logger = audit_logger
80
+ @schema = config.metadata_schema
81
+ end
82
+
83
+ # Decrypts a stored data key, using the in-memory cache when possible.
84
+ #
85
+ # The master key ARN comes from +key_storage+, so it is checked against the configured
86
+ # allow-list before anything else: a row repointed at another master key is refused rather
87
+ # than decrypted. KMS is then told which key to use, so a data key wrapped under a different
88
+ # key fails there too.
89
+ #
90
+ # @param encrypted_data_key [String] the base64 encoded, KMS encrypted data key
91
+ # @param master_key_arn [String, nil] the ARN of the master key the data key was wrapped with
92
+ # @return [String] the plaintext data key; the caller should wipe it once used
93
+ # @raise [Errors::KeyManagementError] if the master key is not allowed, or KMS rejects the request
94
+ def decrypt_data_key(encrypted_data_key, master_key_arn = nil)
95
+ authorize_master_key!(master_key_arn)
96
+ if encrypted_data_key.nil? || encrypted_data_key.to_s.strip.empty?
97
+ raise Errors::KeyManagementError
98
+ .invalid_key_metadata('The stored key metadata has no encrypted data key')
99
+ .with_master_key_arn(master_key_arn)
100
+ end
101
+
102
+ cache_key = data_key_cache_key(encrypted_data_key)
103
+ cached = @cache.get(cache_key)
104
+ return cached if cached
105
+
106
+ plaintext = with_retry('DECRYPT_DATA_KEY') do
107
+ response = @kms_client.decrypt(
108
+ ciphertext_blob: Base64.strict_decode64(encrypted_data_key.to_s.delete("\n")),
109
+ **(master_key_arn ? { key_id: master_key_arn } : {})
110
+ )
111
+ response.plaintext.dup.b
112
+ end
113
+
114
+ @cache.put(cache_key, plaintext)
115
+ @audit_logger&.log_data_key_decryption(master_key_arn: master_key_arn, success: true)
116
+ plaintext
117
+ rescue Errors::KeyManagementError => e
118
+ @audit_logger&.log_data_key_decryption(master_key_arn: master_key_arn, success: false, error_message: e.message)
119
+ raise
120
+ end
121
+
122
+ # Asks KMS for a new data key and generates the HMAC key that goes with it.
123
+ #
124
+ # @param master_key_arn [String] the ARN of the master key to wrap the data key with
125
+ # @return [GeneratedDataKey]
126
+ # @raise [Errors::KeyManagementError] if the master key is not allowed, or KMS rejects the request
127
+ def generate_data_key(master_key_arn)
128
+ authorize_master_key!(master_key_arn)
129
+ result = with_retry('GENERATE_DATA_KEY') do
130
+ response = @kms_client.generate_data_key(key_id: master_key_arn, key_spec: KMS_KEY_SPEC)
131
+ GeneratedDataKey.new(
132
+ plaintext: response.plaintext.dup.b,
133
+ encrypted_data_key: Base64.strict_encode64(response.ciphertext_blob),
134
+ hmac_key: SecureRandom.bytes(HMAC_KEY_LENGTH)
135
+ )
136
+ end
137
+
138
+ @audit_logger&.log_data_key_generation(master_key_arn: master_key_arn, success: true)
139
+ result
140
+ rescue Errors::KeyManagementError => e
141
+ @audit_logger&.log_data_key_generation(master_key_arn: master_key_arn, success: false, error_message: e.message)
142
+ raise
143
+ end
144
+
145
+ # Creates a new KMS master key.
146
+ #
147
+ # @param description [String] the key description
148
+ # @return [String] the ARN of the new key
149
+ # @raise [Errors::KeyManagementError] if KMS rejects the request
150
+ def create_master_key(description)
151
+ arn = with_retry('CREATE_MASTER_KEY') do
152
+ response = @kms_client.create_key(
153
+ description: description,
154
+ key_usage: KMS_KEY_USAGE,
155
+ key_spec: KMS_MASTER_KEY_SPEC
156
+ )
157
+ response.key_metadata.arn
158
+ end
159
+
160
+ @audit_logger&.log_key_creation(master_key_arn: arn, description: description, success: true)
161
+ arn
162
+ rescue Errors::KeyManagementError => e
163
+ @audit_logger&.log_key_creation(master_key_arn: nil, description: description, success: false, error_message: e.message)
164
+ raise
165
+ end
166
+
167
+ # Checks that a master key exists, is enabled, and can encrypt and decrypt.
168
+ #
169
+ # @param master_key_arn [String]
170
+ # @return [Boolean]
171
+ def validate_master_key(master_key_arn)
172
+ metadata = with_retry('DESCRIBE_MASTER_KEY') { @kms_client.describe_key(key_id: master_key_arn).key_metadata }
173
+ metadata.enabled && metadata.key_state == 'Enabled' && metadata.key_usage == KMS_KEY_USAGE
174
+ rescue Errors::KeyManagementError => e
175
+ logger.warn("Master key validation failed: #{e.message}")
176
+ false
177
+ end
178
+
179
+ # Inserts a row into +key_storage+.
180
+ #
181
+ # @param key_metadata [KeyMetadata] the key to store; its +id+ is ignored
182
+ # @return [KeyMetadata] the stored key, with +id+ and timestamps filled in
183
+ # @raise [Errors::KeyManagementError] if the row cannot be written
184
+ def store_key_metadata(key_metadata)
185
+ unless key_metadata.valid?
186
+ raise Errors::KeyManagementError.invalid_key_metadata(
187
+ 'Refusing to store key metadata that names no master key or carries no encrypted data key'
188
+ )
189
+ end
190
+
191
+ now = Time.now
192
+ to_store = key_metadata.with(
193
+ key_id: key_metadata.key_id || generate_key_id,
194
+ created_at: key_metadata.created_at || now,
195
+ last_used_at: key_metadata.last_used_at || now
196
+ )
197
+
198
+ id = with_connection(operation: 'STORE_KEY_METADATA') do |connection|
199
+ @sql.insert_returning_id(connection, insert_key_sql, [
200
+ to_store.key_id,
201
+ to_store.key_name,
202
+ to_store.master_key_arn,
203
+ to_store.encrypted_data_key,
204
+ @sql.binary_param(to_store.hmac_key),
205
+ to_store.key_spec,
206
+ to_store.created_at,
207
+ to_store.last_used_at
208
+ ])
209
+ end
210
+
211
+ to_store.with(id: id)
212
+ rescue StandardError => e
213
+ raise wrap_storage_error(e, to_store&.key_id)
214
+ end
215
+
216
+ # Reads one row of +key_storage+ by its surrogate id.
217
+ #
218
+ # @param id [Integer] the +key_storage.id+ value
219
+ # @return [KeyMetadata, nil]
220
+ # @raise [Errors::KeyManagementError] if the row cannot be read
221
+ def key_metadata_by_id(id)
222
+ row = with_connection(operation: 'GET_KEY_METADATA') do |connection|
223
+ @sql.query(connection, select_key_sql, [id]).first
224
+ end
225
+ row && to_key_metadata(row)
226
+ rescue StandardError => e
227
+ raise Errors::KeyManagementError
228
+ .key_retrieval_failed("Failed to read key metadata: #{e.message}")
229
+ .with_key_id(id.to_s)
230
+ end
231
+
232
+ # Stamps a key as used. Failures are logged and swallowed: this is bookkeeping only.
233
+ #
234
+ # @param key_id [String] the +key_storage.key_id+ value
235
+ # @return [void]
236
+ def touch_key(key_id)
237
+ with_connection(operation: 'UPDATE_KEY_LAST_USED') do |connection|
238
+ @sql.execute(connection, update_last_used_sql, [Time.now, key_id])
239
+ end
240
+ nil
241
+ rescue StandardError => e
242
+ logger.debug("Failed to update last_used_at for key #{key_id}: #{e.message}")
243
+ nil
244
+ end
245
+
246
+ # Builds a {KeyMetadata} from a +key_storage+ row.
247
+ #
248
+ # @param row [Hash{String => Object}]
249
+ # @return [KeyMetadata]
250
+ def to_key_metadata(row)
251
+ KeyMetadata.new(
252
+ id: row['id']&.to_i,
253
+ key_id: row['key_id'],
254
+ key_name: row['name'],
255
+ master_key_arn: row['master_key_arn'],
256
+ encrypted_data_key: row['encrypted_data_key'],
257
+ hmac_key: @sql.read_binary(row['hmac_key']),
258
+ key_spec: row['key_spec'] || KeyMetadata::DEFAULT_KEY_SPEC,
259
+ created_at: row['created_at'] && to_time(row['created_at']),
260
+ last_used_at: row['last_used_at'] && to_time(row['last_used_at'])
261
+ )
262
+ end
263
+
264
+ # @return [String] a new identifier for the +key_storage.key_id+ column
265
+ def generate_key_id
266
+ SecureRandom.uuid
267
+ end
268
+
269
+ # The cache key for a stored data key. Hashing keeps the encrypted key itself out of
270
+ # the cache keys, which can end up in log messages.
271
+ #
272
+ # @param encrypted_data_key [String]
273
+ # @return [String]
274
+ def data_key_cache_key(encrypted_data_key)
275
+ "#{DATA_KEY_CACHE_PREFIX}#{Base64.strict_encode64(Digest::SHA256.digest(encrypted_data_key.to_s))}"
276
+ end
277
+
278
+ private
279
+
280
+ def authorize_master_key!(master_key_arn)
281
+ return if @config.master_key_allowed?(master_key_arn)
282
+
283
+ raise Errors::KeyManagementError
284
+ .unauthorized_master_key(
285
+ "The master key is not listed in #{PropertyDefinition::ENCRYPTION_ALLOWED_MASTER_KEY_ARNS.name}; " \
286
+ 'refusing to use it'
287
+ )
288
+ .with_master_key_arn(master_key_arn)
289
+ end
290
+
291
+ def insert_key_sql
292
+ "INSERT INTO #{@schema}.key_storage " \
293
+ '(key_id, name, master_key_arn, encrypted_data_key, hmac_key, key_spec, created_at, last_used_at) ' \
294
+ 'VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
295
+ end
296
+
297
+ def select_key_sql
298
+ 'SELECT id, key_id, name, master_key_arn, encrypted_data_key, hmac_key, key_spec, created_at, last_used_at ' \
299
+ "FROM #{@schema}.key_storage WHERE id #{eq} ?"
300
+ end
301
+
302
+ def update_last_used_sql
303
+ "UPDATE #{@schema}.key_storage SET last_used_at = ? WHERE key_id #{eq} ?"
304
+ end
305
+
306
+ def eq
307
+ @sql.equals_operator
308
+ end
309
+
310
+ # Runs a KMS call, retrying throttling and transient service errors with exponential
311
+ # backoff and jitter.
312
+ def with_retry(operation)
313
+ attempt = 0
314
+ max_retries = @config.key_management_max_retries
315
+
316
+ loop do
317
+ return yield
318
+ rescue StandardError => e
319
+ raise kms_error(e, operation, attempt, max_retries) if attempt >= max_retries || !retryable?(e)
320
+
321
+ delay = backoff_sec(attempt)
322
+ logger.debug do
323
+ "#{operation} failed (attempt #{attempt + 1}/#{max_retries + 1}), retrying in #{format('%.3f', delay)}s: #{e.message}"
324
+ end
325
+ sleep(delay)
326
+ attempt += 1
327
+ end
328
+ end
329
+
330
+ def retryable?(error)
331
+ return true if networking_error?(error)
332
+ return false unless error.respond_to?(:context)
333
+
334
+ status = error.context&.http_response&.status_code
335
+ return true if status && (status >= 500 || status == 429)
336
+
337
+ RETRYABLE_ERROR_CODES.include?(error.class.name.split('::').last)
338
+ end
339
+
340
+ def networking_error?(error)
341
+ (defined?(Seahorse::Client::NetworkingError) && error.is_a?(Seahorse::Client::NetworkingError)) ||
342
+ error.is_a?(Errno::ECONNREFUSED) || error.is_a?(Errno::ETIMEDOUT) || error.is_a?(Timeout::Error) ||
343
+ error.is_a?(SocketError) || error.is_a?(IOError)
344
+ end
345
+
346
+ # base * 2**attempt, plus or minus a quarter, at least the base delay.
347
+ def backoff_sec(attempt)
348
+ base_sec = @config.key_management_retry_backoff_base_sec
349
+ exponential = base_sec * (2**attempt)
350
+ jitter = exponential * JITTER_RATIO * ((SecureRandom.random_number * 2) - 1)
351
+ # Not clamp: the configured base delay is allowed to be longer than the cap, and the cap
352
+ # wins when it is.
353
+ delay_sec = [exponential + jitter, base_sec].max
354
+ [delay_sec, MAX_BACKOFF_SEC].min
355
+ end
356
+
357
+ def kms_error(error, operation, attempt, max_retries)
358
+ return error if error.is_a?(Errors::KeyManagementError)
359
+
360
+ message = "#{operation} failed: #{error.message}"
361
+ error_class = networking_error?(error) ? :kms_connection_failed : :key_decryption_failed
362
+ Errors::KeyManagementError
363
+ .public_send(error_class, message)
364
+ .with_operation(operation)
365
+ .with_retry_info(attempt + 1, max_retries + 1)
366
+ end
367
+
368
+ def wrap_storage_error(error, key_id)
369
+ return error if error.is_a?(Errors::KeyManagementError)
370
+
371
+ Errors::KeyManagementError
372
+ .key_storage_failed("Failed to store key metadata: #{error.message}")
373
+ .with_key_id(key_id.to_s)
374
+ end
375
+ end
376
+ end
377
+ end
378
+ end
@@ -0,0 +1,86 @@
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 'sanitizer'
18
+
19
+ module AwsAdvancedRubyDriverWrapper
20
+ module Plugins
21
+ module Encryption
22
+ # One row of the +key_storage+ table: an encrypted data key, the master key that wraps
23
+ # it, and the HMAC key used to sign the payloads it encrypts.
24
+ #
25
+ # The data key is only ever held here in its encrypted form. The plaintext key exists
26
+ # only for the duration of a single encrypt or decrypt call and is zeroed afterward.
27
+ KeyMetadata = Data.define(
28
+ :id,
29
+ :key_id,
30
+ :key_name,
31
+ :master_key_arn,
32
+ :encrypted_data_key,
33
+ :hmac_key,
34
+ :key_spec,
35
+ :created_at,
36
+ :last_used_at
37
+ )
38
+
39
+ class KeyMetadata
40
+ DEFAULT_KEY_SPEC = 'AES_256'
41
+
42
+ # @param id [Integer, nil] the +key_storage.id+ surrogate key, nil before the row is inserted
43
+ # @param key_id [String, nil] the +key_storage.key_id+ identifier
44
+ # @param key_name [String, nil] a human-readable name for the key
45
+ # @param master_key_arn [String, nil] the ARN of the KMS master key wrapping the data key
46
+ # @param encrypted_data_key [String, nil] the base64 encoded, KMS encrypted data key
47
+ # @param hmac_key [String, nil] the binary HMAC-SHA256 key used for integrity protection
48
+ # @param key_spec [String] the KMS data key spec, e.g. 'AES_256'
49
+ # @param created_at [Time, nil]
50
+ # @param last_used_at [Time, nil]
51
+ def initialize(id: nil, key_id: nil, key_name: nil, master_key_arn: nil, encrypted_data_key: nil,
52
+ hmac_key: nil, key_spec: DEFAULT_KEY_SPEC, created_at: nil, last_used_at: nil)
53
+ super
54
+ end
55
+
56
+ # @param now [Time]
57
+ # @return [KeyMetadata] a copy stamped with a new last used time
58
+ def with_updated_last_used(now = Time.now)
59
+ with(last_used_at: now)
60
+ end
61
+
62
+ # A key is usable once it names a master key, carries an encrypted data key, and states the
63
+ # key spec it was generated under.
64
+ # @return [Boolean]
65
+ def valid?
66
+ !blank?(master_key_arn) && !blank?(encrypted_data_key) && !blank?(key_spec)
67
+ end
68
+
69
+ # @return [String] a description with the key material redacted
70
+ def to_s
71
+ "KeyMetadata{id=#{id.inspect}, key_id=#{key_id.inspect}, key_name=#{key_name.inspect}, " \
72
+ "master_key_arn=#{Sanitizer.arn(master_key_arn).inspect}, encrypted_data_key='[REDACTED]', " \
73
+ "hmac_key=#{hmac_key.nil? ? 'nil' : "'[REDACTED]'"}, key_spec=#{key_spec.inspect}, " \
74
+ "created_at=#{created_at.inspect}, last_used_at=#{last_used_at.inspect}}"
75
+ end
76
+ alias inspect to_s
77
+
78
+ private
79
+
80
+ def blank?(value)
81
+ value.nil? || value.to_s.strip.empty?
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end