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,435 @@
|
|
|
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 '../../driver_dialects/mysql_driver_dialect'
|
|
18
|
+
require_relative '../../driver_dialects/pg_driver_dialect'
|
|
19
|
+
require_relative '../../logging'
|
|
20
|
+
require_relative '../../property_definition'
|
|
21
|
+
require_relative 'connection_source'
|
|
22
|
+
require_relative 'data_key_cache'
|
|
23
|
+
require_relative 'encryption_algorithm'
|
|
24
|
+
require_relative 'encryption_service'
|
|
25
|
+
require_relative 'errors'
|
|
26
|
+
require_relative 'key_manager'
|
|
27
|
+
require_relative 'key_metadata'
|
|
28
|
+
require_relative 'metadata_manager'
|
|
29
|
+
require_relative 'sanitizer'
|
|
30
|
+
require_relative 'sql_runner'
|
|
31
|
+
|
|
32
|
+
module AwsAdvancedRubyDriverWrapper
|
|
33
|
+
module Plugins
|
|
34
|
+
module Encryption
|
|
35
|
+
# The administrative side of the plugin: creates master keys, turns kms_encryption on or off for a
|
|
36
|
+
# column, rotates data keys, and reports which columns a key is used by.
|
|
37
|
+
#
|
|
38
|
+
# None of this runs during normal query execution. It is meant to be called once, from a
|
|
39
|
+
# migration or a setup script, by whoever administers the kms_encryption configuration. Build
|
|
40
|
+
# it from a plain database connection you already have:
|
|
41
|
+
#
|
|
42
|
+
# kms = Aws::KMS::Client.new(region: 'us-east-1')
|
|
43
|
+
# config = EncryptionConfig.from_props(encryption_kms_region: 'us-east-1',
|
|
44
|
+
# encryption_metadata_schema: 'encrypt')
|
|
45
|
+
# utility = KeyManagementUtility.new(connection: conn, kms_client: kms, config: config)
|
|
46
|
+
# arn = utility.create_master_key('application column kms_encryption')
|
|
47
|
+
# utility.initialize_encryption_for_column('users', 'ssn', arn)
|
|
48
|
+
#
|
|
49
|
+
# Every operation uses the connection you pass, and that connection is never closed here - its
|
|
50
|
+
# lifecycle stays yours.
|
|
51
|
+
#
|
|
52
|
+
# Unlike the plugin, the utility does not require +encryption_allowed_master_key_arns+, so a new
|
|
53
|
+
# master key can be created and put to use in the same script. When the allow-list is configured
|
|
54
|
+
# it is enforced here too, so data keys can only be generated or decrypted under listed master
|
|
55
|
+
# keys. Without it, {#rotate_data_key} must be told which master key to rotate onto, since
|
|
56
|
+
# keeping the current one would mean trusting the ARN recorded in +key_storage+. Either way,
|
|
57
|
+
# add every master key a column uses to the application's allow-list, or the plugin will
|
|
58
|
+
# refuse to read or write that column.
|
|
59
|
+
#
|
|
60
|
+
# Rotating a data key only changes the key that new writes use. Values already written with
|
|
61
|
+
# the previous key stay readable, because each stored value records the id of the key it was
|
|
62
|
+
# written with and the read path resolves that key from +key_storage+ (which keeps the old
|
|
63
|
+
# key). Re-encrypting old values under the new key is optional, and is the application's job;
|
|
64
|
+
# until it is done, retiring the old key from +key_storage+ would make them unreadable.
|
|
65
|
+
class KeyManagementUtility
|
|
66
|
+
include Logging
|
|
67
|
+
include ConnectionSource
|
|
68
|
+
|
|
69
|
+
KEY_SPEC = 'AES_256'
|
|
70
|
+
ALIAS_PREFIX = 'alias/ruby-kms_encryption-'
|
|
71
|
+
|
|
72
|
+
# Builds a utility that runs every operation on the connection you supply, assembling the
|
|
73
|
+
# KeyManager, MetadataManager, and SqlRunner it needs internally. This is purely a user-facing
|
|
74
|
+
# administrative tool, so it takes a plain connection and never closes that connection; its
|
|
75
|
+
# lifecycle stays yours.
|
|
76
|
+
#
|
|
77
|
+
# @param connection [Object] a pg or mysql2 connection, used for every operation
|
|
78
|
+
# @param kms_client [Aws::KMS::Client]
|
|
79
|
+
# @param config [EncryptionConfig]
|
|
80
|
+
# @param driver [Symbol, nil] +:postgresql+ or +:mysql2+; inferred from +connection+ when nil
|
|
81
|
+
# @param audit_logger [AuditLogger, nil]
|
|
82
|
+
# @raise [ArgumentError] if a required argument is missing or the driver cannot be determined
|
|
83
|
+
def initialize(connection:, kms_client:, config:, driver: nil, audit_logger: nil)
|
|
84
|
+
raise ArgumentError, 'connection is required' if connection.nil?
|
|
85
|
+
raise ArgumentError, 'kms_client is required' if kms_client.nil?
|
|
86
|
+
raise ArgumentError, 'config is required' if config.nil?
|
|
87
|
+
|
|
88
|
+
dialect = driver.nil? ? dialect_for_connection(connection) : dialect_for_driver(driver)
|
|
89
|
+
@sql = SqlRunner.new(dialect)
|
|
90
|
+
data_key_cache = DataKeyCache.new(
|
|
91
|
+
max_size: config.data_key_cache_max_size,
|
|
92
|
+
ttl_sec: config.data_key_cache_expiration_sec,
|
|
93
|
+
enabled: config.data_key_cache_enabled
|
|
94
|
+
)
|
|
95
|
+
@key_manager = KeyManager.new(kms_client: kms_client, connection: connection, sql_runner: @sql,
|
|
96
|
+
config: config, data_key_cache: data_key_cache, audit_logger: audit_logger)
|
|
97
|
+
@metadata_manager = MetadataManager.new(connection: connection, sql_runner: @sql,
|
|
98
|
+
config: config, audit_logger: audit_logger)
|
|
99
|
+
use_connection_source(connection: connection, service_container: nil)
|
|
100
|
+
@kms_client = kms_client
|
|
101
|
+
@config = config
|
|
102
|
+
@audit_logger = audit_logger
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Creates a KMS master key for column kms_encryption and gives it an alias.
|
|
106
|
+
#
|
|
107
|
+
# @param description [String] the key description
|
|
108
|
+
# @param key_policy [String, nil] a key policy document; KMS applies its default when omitted
|
|
109
|
+
# @param create_alias [Boolean] whether to also create an alias for the new key
|
|
110
|
+
# @return [String] the ARN of the new master key
|
|
111
|
+
# @raise [Errors::KeyManagementError] if the key cannot be created
|
|
112
|
+
def create_master_key(description, key_policy: nil, create_alias: true)
|
|
113
|
+
raise ArgumentError, 'description is required' if description.nil?
|
|
114
|
+
|
|
115
|
+
logger.info("Creating a KMS master key: #{Sanitizer.description(description)}")
|
|
116
|
+
|
|
117
|
+
request = { description: description, key_usage: 'ENCRYPT_DECRYPT', key_spec: 'SYMMETRIC_DEFAULT' }
|
|
118
|
+
request[:policy] = key_policy unless key_policy.nil? || key_policy.strip.empty?
|
|
119
|
+
|
|
120
|
+
arn = @kms_client.create_key(**request).key_metadata.arn
|
|
121
|
+
add_alias(arn) if create_alias
|
|
122
|
+
|
|
123
|
+
@audit_logger&.log_key_creation(master_key_arn: arn, description: description, success: true)
|
|
124
|
+
arn
|
|
125
|
+
rescue StandardError => e
|
|
126
|
+
raise e if e.is_a?(ArgumentError)
|
|
127
|
+
|
|
128
|
+
@audit_logger&.log_key_creation(master_key_arn: nil, description: description, success: false,
|
|
129
|
+
error_message: e.message)
|
|
130
|
+
raise Errors::KeyManagementError.key_creation_failed("Failed to create the master key: #{e.message}")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Sets up kms_encryption for a column that is not encrypted yet: generates a data key, stores it,
|
|
134
|
+
# and records the column in +encryption_metadata+.
|
|
135
|
+
#
|
|
136
|
+
# @param table_name [String]
|
|
137
|
+
# @param column_name [String]
|
|
138
|
+
# @param master_key_arn [String]
|
|
139
|
+
# @param algorithm [String] one of {EncryptionAlgorithm::ALL}
|
|
140
|
+
# @return [Integer] the +key_storage.id+ of the new data key
|
|
141
|
+
# @raise [Errors::KeyManagementError] if the column is already encrypted or the setup fails
|
|
142
|
+
def initialize_encryption_for_column(table_name, column_name, master_key_arn,
|
|
143
|
+
algorithm = EncryptionAlgorithm::DEFAULT)
|
|
144
|
+
logger.info("Initializing kms_encryption for #{table_name}.#{column_name}")
|
|
145
|
+
|
|
146
|
+
begin
|
|
147
|
+
already_encrypted = @metadata_manager.column_encrypted?(table_name, column_name)
|
|
148
|
+
rescue Errors::MetadataError => e
|
|
149
|
+
raise Errors::KeyManagementError
|
|
150
|
+
.key_creation_failed("Failed to check the kms_encryption status of the column: #{e.message}")
|
|
151
|
+
.with_context(:table, table_name)
|
|
152
|
+
.with_context(:column, column_name)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
if already_encrypted
|
|
156
|
+
raise Errors::KeyManagementError
|
|
157
|
+
.key_creation_failed("Column #{table_name}.#{column_name} is already encrypted")
|
|
158
|
+
.with_context(:table, table_name)
|
|
159
|
+
.with_context(:column, column_name)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
generate_and_store_data_key(table_name, column_name, master_key_arn, algorithm)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Generates a data key for a column and records it, replacing any configuration the column
|
|
166
|
+
# already has.
|
|
167
|
+
#
|
|
168
|
+
# @param table_name [String]
|
|
169
|
+
# @param column_name [String]
|
|
170
|
+
# @param master_key_arn [String]
|
|
171
|
+
# @param algorithm [String]
|
|
172
|
+
# @return [Integer] the +key_storage.id+ of the new data key
|
|
173
|
+
# @raise [Errors::KeyManagementError] if the key cannot be generated or stored
|
|
174
|
+
def generate_and_store_data_key(table_name, column_name, master_key_arn,
|
|
175
|
+
algorithm = EncryptionAlgorithm::DEFAULT)
|
|
176
|
+
raise ArgumentError, 'table_name is required' if table_name.nil?
|
|
177
|
+
raise ArgumentError, 'column_name is required' if column_name.nil?
|
|
178
|
+
raise ArgumentError, 'master_key_arn is required' if master_key_arn.nil?
|
|
179
|
+
|
|
180
|
+
algorithm = EncryptionAlgorithm::DEFAULT if algorithm.nil? || algorithm.to_s.strip.empty?
|
|
181
|
+
raise EncryptionAlgorithm.unsupported(algorithm) unless EncryptionAlgorithm.supported?(algorithm)
|
|
182
|
+
|
|
183
|
+
stored = store_new_data_key(table_name, column_name, master_key_arn)
|
|
184
|
+
store_encryption_metadata(table_name, column_name, algorithm, stored.id)
|
|
185
|
+
@metadata_manager.refresh if @config.metadata_cache_enabled
|
|
186
|
+
|
|
187
|
+
logger.info("Stored a data key for #{table_name}.#{column_name} as key_storage id #{stored.id}")
|
|
188
|
+
stored.id
|
|
189
|
+
rescue StandardError => e
|
|
190
|
+
raise e if e.is_a?(ArgumentError) || e.is_a?(Errors::EncryptionPluginError)
|
|
191
|
+
|
|
192
|
+
raise Errors::KeyManagementError
|
|
193
|
+
.key_creation_failed("Failed to generate and store the data key: #{e.message}")
|
|
194
|
+
.with_context(:table, table_name)
|
|
195
|
+
.with_context(:column, column_name)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Rotates the data key of an already encrypted column. New writes use the new key; values
|
|
199
|
+
# written with the previous key remain readable.
|
|
200
|
+
#
|
|
201
|
+
# Keeping the current master key means reusing the ARN recorded in +key_storage+, so it is
|
|
202
|
+
# only allowed when +encryption_allowed_master_key_arns+ is configured to check that ARN
|
|
203
|
+
# against. Without the allow-list, a row repointed at another key would have the new data key
|
|
204
|
+
# generated under whatever key it names.
|
|
205
|
+
#
|
|
206
|
+
# @param table_name [String]
|
|
207
|
+
# @param column_name [String]
|
|
208
|
+
# @param new_master_key_arn [String, nil] the master key to wrap the new data key with, or nil
|
|
209
|
+
# to keep the current one (requires the allow-list)
|
|
210
|
+
# @return [Integer] the +key_storage.id+ of the new data key
|
|
211
|
+
# @raise [ArgumentError] if no master key is given and no allow-list is configured
|
|
212
|
+
# @raise [Errors::KeyManagementError] if the column is not encrypted, the master key is not
|
|
213
|
+
# allowed, or the rotation fails
|
|
214
|
+
def rotate_data_key(table_name, column_name, new_master_key_arn = nil)
|
|
215
|
+
raise ArgumentError, 'table_name is required' if table_name.nil?
|
|
216
|
+
raise ArgumentError, 'column_name is required' if column_name.nil?
|
|
217
|
+
|
|
218
|
+
if new_master_key_arn.nil? && !@config.restricts_master_keys?
|
|
219
|
+
raise ArgumentError,
|
|
220
|
+
'Pass the master key ARN to rotate onto, or configure ' \
|
|
221
|
+
"#{PropertyDefinition::ENCRYPTION_ALLOWED_MASTER_KEY_ARNS.name} so the current one can be checked"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
logger.info("Rotating the data key for #{table_name}.#{column_name}")
|
|
225
|
+
|
|
226
|
+
current = @metadata_manager.column_config(table_name, column_name)
|
|
227
|
+
if current.nil?
|
|
228
|
+
raise Errors::KeyManagementError
|
|
229
|
+
.key_creation_failed("No kms_encryption configuration exists for #{table_name}.#{column_name}")
|
|
230
|
+
.with_context(:table, table_name)
|
|
231
|
+
.with_context(:column, column_name)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
master_key_arn = new_master_key_arn || current.key_metadata&.master_key_arn
|
|
235
|
+
stored = store_new_data_key(table_name, column_name, master_key_arn)
|
|
236
|
+
update_encryption_metadata_key(table_name, column_name, stored.id)
|
|
237
|
+
@metadata_manager.refresh if @config.metadata_cache_enabled
|
|
238
|
+
|
|
239
|
+
logger.info(
|
|
240
|
+
"Rotated the data key for #{table_name}.#{column_name} from key #{current.key_id} to #{stored.id}"
|
|
241
|
+
)
|
|
242
|
+
stored.id
|
|
243
|
+
rescue StandardError => e
|
|
244
|
+
raise e if e.is_a?(ArgumentError) || e.is_a?(Errors::EncryptionPluginError)
|
|
245
|
+
|
|
246
|
+
raise Errors::KeyManagementError
|
|
247
|
+
.key_creation_failed("Failed to rotate the data key: #{e.message}")
|
|
248
|
+
.with_context(:table, table_name)
|
|
249
|
+
.with_context(:column, column_name)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Stops encrypting a column by deleting its row from +encryption_metadata+.
|
|
253
|
+
#
|
|
254
|
+
# The key itself is deliberately left in +key_storage+, so that values already written can
|
|
255
|
+
# still be decrypted.
|
|
256
|
+
#
|
|
257
|
+
# @param table_name [String]
|
|
258
|
+
# @param column_name [String]
|
|
259
|
+
# @return [Boolean] true when a configuration row was removed
|
|
260
|
+
# @raise [Errors::KeyManagementError] if the row cannot be removed
|
|
261
|
+
def remove_encryption_for_column(table_name, column_name)
|
|
262
|
+
raise ArgumentError, 'table_name is required' if table_name.nil?
|
|
263
|
+
raise ArgumentError, 'column_name is required' if column_name.nil?
|
|
264
|
+
|
|
265
|
+
logger.info("Removing the kms_encryption configuration for #{table_name}.#{column_name}")
|
|
266
|
+
|
|
267
|
+
affected = with_connection(operation: 'DELETE_ENCRYPTION_METADATA') do |connection|
|
|
268
|
+
@sql.update(connection, delete_encryption_metadata_sql, [table_name, column_name])
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
if affected.zero?
|
|
272
|
+
logger.warn("No kms_encryption configuration existed for #{table_name}.#{column_name}")
|
|
273
|
+
else
|
|
274
|
+
logger.info("Removed the kms_encryption configuration for #{table_name}.#{column_name}")
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
@metadata_manager.refresh if @config.metadata_cache_enabled
|
|
278
|
+
@audit_logger&.log_metadata_operation(operation: 'remove', table_name: table_name, column_name: column_name)
|
|
279
|
+
affected.positive?
|
|
280
|
+
rescue StandardError => e
|
|
281
|
+
raise e if e.is_a?(ArgumentError)
|
|
282
|
+
|
|
283
|
+
raise Errors::KeyManagementError
|
|
284
|
+
.key_storage_failed("Failed to remove the kms_encryption configuration: #{e.message}")
|
|
285
|
+
.with_context(:table, table_name)
|
|
286
|
+
.with_context(:column, column_name)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Lists the columns that a stored key is used by, so that the effect of rotating or
|
|
290
|
+
# retiring it can be seen up front.
|
|
291
|
+
#
|
|
292
|
+
# @param key_id [Integer] a +key_storage.id+ value
|
|
293
|
+
# @return [Array<String>] +"table.column"+ identifiers
|
|
294
|
+
# @raise [Errors::KeyManagementError] if the query fails
|
|
295
|
+
def columns_using_key(key_id)
|
|
296
|
+
raise ArgumentError, 'key_id is required' if key_id.nil?
|
|
297
|
+
|
|
298
|
+
rows = with_connection(operation: 'SELECT_COLUMNS_USING_KEY') do |connection|
|
|
299
|
+
@sql.query(connection, select_columns_with_key_sql, [key_id])
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
rows.map { |row| "#{row['table_name']}.#{row['column_name']}" }
|
|
303
|
+
rescue StandardError => e
|
|
304
|
+
raise e if e.is_a?(ArgumentError)
|
|
305
|
+
|
|
306
|
+
raise Errors::KeyManagementError
|
|
307
|
+
.key_retrieval_failed("Failed to find the columns using the key: #{e.message}")
|
|
308
|
+
.with_key_id(key_id.to_s)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# @param master_key_arn [String]
|
|
312
|
+
# @return [Boolean] whether the master key exists, is enabled, and can encrypt and decrypt
|
|
313
|
+
def validate_master_key(master_key_arn)
|
|
314
|
+
raise ArgumentError, 'master_key_arn is required' if master_key_arn.nil?
|
|
315
|
+
|
|
316
|
+
@key_manager.validate_master_key(master_key_arn)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
private
|
|
320
|
+
|
|
321
|
+
# Picks a driver dialect from a connection's class without loading the driver gems. Both a raw
|
|
322
|
+
# driver connection (PG::Connection / Mysql2::Client) and a wrapper connection
|
|
323
|
+
# (WrapperPgConnection / WrapperMysql2Client) are recognized.
|
|
324
|
+
def dialect_for_connection(connection)
|
|
325
|
+
name = connection.class.name.to_s
|
|
326
|
+
return DriverDialects::PgDriverDialect.new if name.start_with?('PG::') || name.end_with?('WrapperPgConnection')
|
|
327
|
+
return DriverDialects::MysqlDriverDialect.new if name.start_with?('Mysql2::') || name.end_with?('WrapperMysql2Client')
|
|
328
|
+
|
|
329
|
+
raise ArgumentError,
|
|
330
|
+
"Cannot infer the driver dialect from #{name.empty? ? connection.class : name}; " \
|
|
331
|
+
'pass driver: :postgresql or :mysql2'
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def dialect_for_driver(driver)
|
|
335
|
+
case driver.to_sym
|
|
336
|
+
when :postgresql, :pg then DriverDialects::PgDriverDialect.new
|
|
337
|
+
when :mysql2, :mysql then DriverDialects::MysqlDriverDialect.new
|
|
338
|
+
else raise ArgumentError, "Unknown driver #{driver.inspect}; use :postgresql or :mysql2"
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Generates a data key through KMS and writes it to +key_storage+. The plaintext key is
|
|
343
|
+
# wiped again immediately: nothing here needs to encrypt with it.
|
|
344
|
+
#
|
|
345
|
+
# @return [KeyMetadata] the stored data key, with its +id+ filled in
|
|
346
|
+
def store_new_data_key(table_name, column_name, master_key_arn)
|
|
347
|
+
generated = @key_manager.generate_data_key(master_key_arn)
|
|
348
|
+
|
|
349
|
+
begin
|
|
350
|
+
@key_manager.store_key_metadata(
|
|
351
|
+
KeyMetadata.new(
|
|
352
|
+
key_name: key_name_for(table_name, column_name),
|
|
353
|
+
master_key_arn: master_key_arn,
|
|
354
|
+
encrypted_data_key: generated.encrypted_data_key,
|
|
355
|
+
hmac_key: generated.hmac_key,
|
|
356
|
+
key_spec: KEY_SPEC
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
ensure
|
|
360
|
+
EncryptionService.wipe(generated.plaintext)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def key_name_for(table_name, column_name)
|
|
365
|
+
"key-#{table_name}-#{column_name}-#{(Time.now.to_f * 1000).to_i}"
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def add_alias(key_arn)
|
|
369
|
+
@kms_client.create_alias(
|
|
370
|
+
alias_name: "#{ALIAS_PREFIX}#{(Time.now.to_f * 1000).to_i}",
|
|
371
|
+
target_key_id: key_arn
|
|
372
|
+
)
|
|
373
|
+
rescue StandardError => e
|
|
374
|
+
# An alias is a convenience, not a requirement: the key is usable by ARN either way.
|
|
375
|
+
logger.warn("Created the master key but could not create an alias for it: #{e.message}")
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def store_encryption_metadata(table_name, column_name, algorithm, key_id)
|
|
379
|
+
now = Time.now
|
|
380
|
+
with_connection(operation: 'STORE_ENCRYPTION_METADATA') do |connection|
|
|
381
|
+
@sql.update(connection, insert_encryption_metadata_sql,
|
|
382
|
+
[table_name, column_name, algorithm, key_id, now, now])
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
@audit_logger&.log_metadata_operation(operation: 'store', table_name: table_name, column_name: column_name)
|
|
386
|
+
nil
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def update_encryption_metadata_key(table_name, column_name, key_id)
|
|
390
|
+
affected = with_connection(operation: 'UPDATE_ENCRYPTION_METADATA') do |connection|
|
|
391
|
+
@sql.update(connection, update_encryption_metadata_key_sql, [key_id, Time.now, table_name, column_name])
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
if affected.zero?
|
|
395
|
+
raise Errors::KeyManagementError
|
|
396
|
+
.key_storage_failed("No kms_encryption configuration row was updated for #{table_name}.#{column_name}")
|
|
397
|
+
.with_context(:table, table_name)
|
|
398
|
+
.with_context(:column, column_name)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
@audit_logger&.log_metadata_operation(operation: 'update', table_name: table_name, column_name: column_name)
|
|
402
|
+
nil
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Upserting keeps this idempotent, so re-running a setup script does not fail on a column
|
|
406
|
+
# that is already configured. The driver dialect supplies its own upsert grammar.
|
|
407
|
+
def insert_encryption_metadata_sql
|
|
408
|
+
base = "INSERT INTO #{@config.metadata_schema}.encryption_metadata " \
|
|
409
|
+
'(table_name, column_name, encryption_algorithm, key_id, created_at, updated_at) ' \
|
|
410
|
+
'VALUES (?, ?, ?, ?, ?, ?)'
|
|
411
|
+
clause = @sql.upsert_clause(%w[table_name column_name], %w[encryption_algorithm key_id updated_at])
|
|
412
|
+
"#{base} #{clause}"
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def update_encryption_metadata_key_sql
|
|
416
|
+
"UPDATE #{@config.metadata_schema}.encryption_metadata SET key_id = ?, updated_at = ? " \
|
|
417
|
+
"WHERE table_name #{eq} ? AND column_name #{eq} ?"
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def select_columns_with_key_sql
|
|
421
|
+
"SELECT table_name, column_name FROM #{@config.metadata_schema}.encryption_metadata WHERE key_id #{eq} ?"
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def delete_encryption_metadata_sql
|
|
425
|
+
"DELETE FROM #{@config.metadata_schema}.encryption_metadata " \
|
|
426
|
+
"WHERE table_name #{eq} ? AND column_name #{eq} ?"
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def eq
|
|
430
|
+
@sql.equals_operator
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|