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,281 @@
|
|
|
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 '../../property_definition'
|
|
19
|
+
require_relative 'audit_logger'
|
|
20
|
+
require_relative 'data_key_cache'
|
|
21
|
+
require_relative 'encryption_config'
|
|
22
|
+
require_relative 'errors'
|
|
23
|
+
require_relative 'key_manager'
|
|
24
|
+
require_relative 'metadata_manager'
|
|
25
|
+
require_relative 'schema_validator'
|
|
26
|
+
require_relative 'sql_runner'
|
|
27
|
+
|
|
28
|
+
module AwsAdvancedRubyDriverWrapper
|
|
29
|
+
module Plugins
|
|
30
|
+
module Encryption
|
|
31
|
+
# Wires up everything the kms_encryption plugin needs and owns its lifecycle.
|
|
32
|
+
#
|
|
33
|
+
# Construction is split in two, because the two halves become available at different times:
|
|
34
|
+
#
|
|
35
|
+
# * Everything that only needs properties (the configuration, the audit logger, the data key
|
|
36
|
+
# cache, the KMS client) is built up front, so that a misconfiguration is reported when the
|
|
37
|
+
# connection is opened rather than in the middle of a query.
|
|
38
|
+
# * Everything that needs to talk to the database (the metadata manager and the key manager,
|
|
39
|
+
# both of which read the +encryption_metadata+ and +key_storage+ tables) is built the first
|
|
40
|
+
# time a statement could touch an encrypted column, through {#ensure_initialized}. At that
|
|
41
|
+
# point the connect pipeline has finished, so the plugin can open its own connections.
|
|
42
|
+
#
|
|
43
|
+
# The KMS client is created lazily as well, so that an application that never touches an
|
|
44
|
+
# encrypted column never has to reach KMS.
|
|
45
|
+
class KmsEncryptionUtility
|
|
46
|
+
include Logging
|
|
47
|
+
|
|
48
|
+
PLUGIN_NAME = 'KmsEncryptionPlugin'
|
|
49
|
+
|
|
50
|
+
# Logged at most once per process, so disabling the metadata cache does not warn on every
|
|
51
|
+
# connection.
|
|
52
|
+
@metadata_cache_warning_logged = false
|
|
53
|
+
class << self
|
|
54
|
+
attr_accessor :metadata_cache_warning_logged
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
attr_reader :config, :audit_logger, :data_key_cache
|
|
58
|
+
|
|
59
|
+
# @param service_container [Services::ServiceContainer]
|
|
60
|
+
# @param props [Concurrent::Map, Hash] the wrapper properties
|
|
61
|
+
# @param kms_client [Aws::KMS::Client, nil] a client to use instead of building one
|
|
62
|
+
# @raise [ArgumentError] if the kms_encryption properties are invalid, or no master keys are allowed
|
|
63
|
+
def initialize(service_container, props, kms_client: nil)
|
|
64
|
+
raise ArgumentError, 'service_container is required' if service_container.nil?
|
|
65
|
+
|
|
66
|
+
@service_container = service_container
|
|
67
|
+
@props = props
|
|
68
|
+
@config = EncryptionConfig.from_props(props)
|
|
69
|
+
require_master_key_allow_list!
|
|
70
|
+
@kms_client = kms_client
|
|
71
|
+
@lock = Mutex.new
|
|
72
|
+
@initialized = false
|
|
73
|
+
@closed = false
|
|
74
|
+
|
|
75
|
+
@audit_logger = AuditLogger.new(@config.audit_logging_enabled)
|
|
76
|
+
@data_key_cache = DataKeyCache.new(
|
|
77
|
+
max_size: @config.data_key_cache_max_size,
|
|
78
|
+
ttl_sec: @config.data_key_cache_expiration_sec,
|
|
79
|
+
enabled: @config.data_key_cache_enabled
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
logger.debug do
|
|
83
|
+
"Loaded the kms_encryption configuration: region=#{@config.kms_region}, " \
|
|
84
|
+
"schema=#{@config.metadata_schema}, metadata cache=#{@config.metadata_cache_enabled}, " \
|
|
85
|
+
"max retries=#{@config.key_management_max_retries}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
warn_if_metadata_cache_disabled
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @return [String] the name the plugin is known by
|
|
92
|
+
def plugin_name
|
|
93
|
+
PLUGIN_NAME
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @return [Boolean] whether the database backed components have been built
|
|
97
|
+
def initialized?
|
|
98
|
+
@lock.synchronize { @initialized }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @return [Boolean] whether {#cleanup} has run
|
|
102
|
+
def closed?
|
|
103
|
+
@lock.synchronize { @closed }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Builds the components that need a database connection, the first time one is needed.
|
|
107
|
+
#
|
|
108
|
+
# @return [void]
|
|
109
|
+
# @raise [Errors::MetadataError] if the initial metadata load fails
|
|
110
|
+
# @raise [Errors::EncryptionPluginError] if the components cannot be built
|
|
111
|
+
def ensure_initialized
|
|
112
|
+
return if @lock.synchronize { @initialized || @closed }
|
|
113
|
+
|
|
114
|
+
@lock.synchronize do
|
|
115
|
+
next if @initialized || @closed
|
|
116
|
+
|
|
117
|
+
build_database_components
|
|
118
|
+
@initialized = true
|
|
119
|
+
end
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @return [MetadataManager, nil] nil until {#ensure_initialized} has run
|
|
124
|
+
def metadata_manager
|
|
125
|
+
@lock.synchronize { @metadata_manager }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @return [KeyManager, nil] nil until {#ensure_initialized} has run
|
|
129
|
+
def key_manager
|
|
130
|
+
@lock.synchronize { @key_manager }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# @return [SqlRunner, nil] nil until {#ensure_initialized} has run
|
|
134
|
+
def sql_runner
|
|
135
|
+
@lock.synchronize { @sql_runner }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Checks that the +encryption_metadata+ and +key_storage+ tables look the way the plugin
|
|
139
|
+
# expects. Nothing calls this automatically: it is meant for setup scripts and diagnostics.
|
|
140
|
+
#
|
|
141
|
+
# @return [SchemaValidator::ValidationResult]
|
|
142
|
+
# @raise [Errors::EncryptionPluginError] if the components cannot be built
|
|
143
|
+
def validate_schema
|
|
144
|
+
ensure_initialized
|
|
145
|
+
validator, metadata_manager = @lock.synchronize { [@schema_validator, @metadata_manager] }
|
|
146
|
+
|
|
147
|
+
metadata_manager.with_connection(operation: 'VALIDATE_SCHEMA') { |connection| validator.validate(connection) }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# The KMS client, created on first use.
|
|
151
|
+
#
|
|
152
|
+
# @return [Aws::KMS::Client]
|
|
153
|
+
def kms_client
|
|
154
|
+
@lock.synchronize { @kms_client ||= create_kms_client }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @return [String] a description of how metadata is being read
|
|
158
|
+
def connection_mode_status
|
|
159
|
+
if initialized?
|
|
160
|
+
'The kms_encryption plugin is reading its metadata over its own short-lived connections'
|
|
161
|
+
else
|
|
162
|
+
'The kms_encryption plugin has not opened a metadata connection yet'
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Logs the connection mode, for troubleshooting.
|
|
167
|
+
# @return [void]
|
|
168
|
+
def log_current_status
|
|
169
|
+
logger.info("#{PLUGIN_NAME} status report")
|
|
170
|
+
logger.info(connection_mode_status)
|
|
171
|
+
nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Releases everything the plugin holds: the background metadata refresh, the cached data
|
|
175
|
+
# keys, and the KMS client.
|
|
176
|
+
#
|
|
177
|
+
# @return [void]
|
|
178
|
+
def cleanup
|
|
179
|
+
return if @lock.synchronize { @closed }
|
|
180
|
+
|
|
181
|
+
logger.debug("Cleaning up #{PLUGIN_NAME}")
|
|
182
|
+
|
|
183
|
+
metadata_manager, data_key_cache, kms_client = @lock.synchronize do
|
|
184
|
+
@closed = true
|
|
185
|
+
@initialized = false
|
|
186
|
+
[@metadata_manager, @data_key_cache, @kms_client]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
quietly('stop the metadata refresh') { metadata_manager&.shutdown }
|
|
190
|
+
quietly('clear the data key cache') { data_key_cache&.shutdown }
|
|
191
|
+
quietly('close the KMS client') { kms_client.close if kms_client.respond_to?(:close) }
|
|
192
|
+
|
|
193
|
+
logger.debug("Finished cleaning up #{PLUGIN_NAME}")
|
|
194
|
+
nil
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def require_master_key_allow_list!
|
|
200
|
+
return if @config.restricts_master_keys?
|
|
201
|
+
|
|
202
|
+
raise ArgumentError,
|
|
203
|
+
"#{PropertyDefinition::ENCRYPTION_ALLOWED_MASTER_KEY_ARNS.name} is required: list the KMS master key " \
|
|
204
|
+
'ARNs the kms_encryption plugin may use'
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Runs under @lock.
|
|
208
|
+
def build_database_components
|
|
209
|
+
@sql_runner = SqlRunner.new(@service_container.dialect_service.driver_dialect)
|
|
210
|
+
@audit_logger.log_connection_parameter_extraction(
|
|
211
|
+
strategy: 'ServiceContainer', connection_type: 'INDEPENDENT_CONNECTION'
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
@kms_client ||= create_kms_client
|
|
215
|
+
@key_manager = KeyManager.new(
|
|
216
|
+
kms_client: @kms_client,
|
|
217
|
+
service_container: @service_container,
|
|
218
|
+
sql_runner: @sql_runner,
|
|
219
|
+
config: @config,
|
|
220
|
+
data_key_cache: @data_key_cache,
|
|
221
|
+
audit_logger: @audit_logger
|
|
222
|
+
)
|
|
223
|
+
@metadata_manager = MetadataManager.new(
|
|
224
|
+
service_container: @service_container,
|
|
225
|
+
sql_runner: @sql_runner,
|
|
226
|
+
config: @config,
|
|
227
|
+
audit_logger: @audit_logger
|
|
228
|
+
)
|
|
229
|
+
@schema_validator = SchemaValidator.new(@config.metadata_schema, @sql_runner)
|
|
230
|
+
|
|
231
|
+
@metadata_manager.start
|
|
232
|
+
logger.debug('The kms_encryption plugin is ready to encrypt and decrypt column values')
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Warns once per process when the metadata cache is off, since that makes the plugin open a
|
|
236
|
+
# short-lived metadata connection for every statement that touches an encrypted column.
|
|
237
|
+
def warn_if_metadata_cache_disabled
|
|
238
|
+
return if @config.metadata_cache_enabled
|
|
239
|
+
return if self.class.metadata_cache_warning_logged
|
|
240
|
+
|
|
241
|
+
self.class.metadata_cache_warning_logged = true
|
|
242
|
+
logger.warn(
|
|
243
|
+
'The kms_encryption metadata cache is disabled (encryption_metadata_cache_enabled=false): the ' \
|
|
244
|
+
'plugin opens a short-lived metadata connection for every statement that touches an encrypted ' \
|
|
245
|
+
'column. Enable it in production and lower encryption_metadata_cache_refresh_interval_sec if ' \
|
|
246
|
+
'you need fresher metadata.'
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def create_kms_client
|
|
251
|
+
ensure_sdk!
|
|
252
|
+
logger.debug { "Creating a KMS client for region #{@config.kms_region}" }
|
|
253
|
+
|
|
254
|
+
options = {
|
|
255
|
+
region: @config.kms_region,
|
|
256
|
+
credentials: PropertyDefinition::AWS_CREDENTIALS_PROVIDER.get(@props) ||
|
|
257
|
+
Aws::CredentialProviderChain.new.resolve
|
|
258
|
+
}
|
|
259
|
+
options[:endpoint] = @config.kms_endpoint unless @config.kms_endpoint.nil?
|
|
260
|
+
|
|
261
|
+
Aws::KMS::Client.new(**options)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def ensure_sdk!
|
|
265
|
+
require 'aws-sdk-kms'
|
|
266
|
+
rescue LoadError
|
|
267
|
+
raise LoadError,
|
|
268
|
+
"The KMS kms_encryption plugin requires 'aws-sdk-kms'. " \
|
|
269
|
+
"Add it to your Gemfile: gem 'aws-sdk-kms'"
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Cleanup must release everything it can, so one failing step cannot stop the others.
|
|
273
|
+
def quietly(description)
|
|
274
|
+
yield
|
|
275
|
+
rescue StandardError => e
|
|
276
|
+
logger.warn("Failed to #{description} while cleaning up the kms_encryption plugin: #{e.message}")
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
@@ -0,0 +1,332 @@
|
|
|
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 '../../utils/conversion_utils'
|
|
19
|
+
require_relative '../../utils/sql_encoding'
|
|
20
|
+
require_relative 'column_encryption_config'
|
|
21
|
+
require_relative 'connection_source'
|
|
22
|
+
require_relative 'encryption_algorithm'
|
|
23
|
+
require_relative 'errors'
|
|
24
|
+
require_relative 'key_metadata'
|
|
25
|
+
|
|
26
|
+
module AwsAdvancedRubyDriverWrapper
|
|
27
|
+
module Plugins
|
|
28
|
+
module Encryption
|
|
29
|
+
# Reads the +encryption_metadata+ table, which says which columns are encrypted and with
|
|
30
|
+
# which key, and keeps the answers in a cache.
|
|
31
|
+
#
|
|
32
|
+
# The cache is loaded once at startup and then refreshed on a background thread every
|
|
33
|
+
# +encryption_metadata_cache_refresh_interval_sec+, so that a column can be added to or
|
|
34
|
+
# removed from the kms_encryption configuration without restarting the application. Lookups
|
|
35
|
+
# fall back to querying the database directly when caching is disabled or the cache has
|
|
36
|
+
# expired.
|
|
37
|
+
class MetadataManager
|
|
38
|
+
include Logging
|
|
39
|
+
include Utils::ConversionUtils
|
|
40
|
+
include ConnectionSource
|
|
41
|
+
|
|
42
|
+
# How long {shutdown} waits for the refresh thread to finish.
|
|
43
|
+
SHUTDOWN_TIMEOUT_SEC = 5
|
|
44
|
+
|
|
45
|
+
# Exactly one of +connection+ or +service_container+ must be given (see {ConnectionSource}).
|
|
46
|
+
#
|
|
47
|
+
# @param sql_runner [SqlRunner]
|
|
48
|
+
# @param config [EncryptionConfig]
|
|
49
|
+
# @param connection [Object, nil] a caller-owned connection used for every operation
|
|
50
|
+
# @param service_container [Services::ServiceContainer, nil] opens a short-lived connection per operation
|
|
51
|
+
# @param audit_logger [AuditLogger, nil]
|
|
52
|
+
def initialize(sql_runner:, config:, connection: nil, service_container: nil, audit_logger: nil)
|
|
53
|
+
use_connection_source(connection: connection, service_container: service_container)
|
|
54
|
+
@sql = sql_runner
|
|
55
|
+
@config = config
|
|
56
|
+
@audit_logger = audit_logger
|
|
57
|
+
@cache = {}
|
|
58
|
+
@lock = Mutex.new
|
|
59
|
+
@last_refresh_time = nil
|
|
60
|
+
@running = false
|
|
61
|
+
@refresh_thread = nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Loads the cache and starts the background refresh.
|
|
65
|
+
#
|
|
66
|
+
# @return [void]
|
|
67
|
+
# @raise [Errors::MetadataError] if the initial load fails
|
|
68
|
+
def start
|
|
69
|
+
logger.debug('Initializing the kms_encryption metadata manager')
|
|
70
|
+
refresh if @config.metadata_cache_enabled
|
|
71
|
+
start_refresh_thread if @config.background_refresh_enabled?
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Reloads every column configuration from the database and replaces the cache.
|
|
76
|
+
#
|
|
77
|
+
# @return [Integer] the number of cached column configurations
|
|
78
|
+
# @raise [Errors::MetadataError] if the load fails
|
|
79
|
+
def refresh
|
|
80
|
+
metadata = load_metadata
|
|
81
|
+
|
|
82
|
+
@lock.synchronize do
|
|
83
|
+
@cache = metadata
|
|
84
|
+
@last_refresh_time = Time.now
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
logger.debug { "Refreshed the kms_encryption metadata cache with #{metadata.size} column configuration(s)" }
|
|
88
|
+
@audit_logger&.log_metadata_operation(operation: 'refresh', success: true)
|
|
89
|
+
metadata.size
|
|
90
|
+
rescue Errors::MetadataError => e
|
|
91
|
+
@audit_logger&.log_metadata_operation(operation: 'refresh', success: false, error_message: e.message)
|
|
92
|
+
raise
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Reads every column configuration from the database, without touching the cache.
|
|
96
|
+
#
|
|
97
|
+
# @return [Hash{String => ColumnEncryptionConfig}] keyed by +"table.column"+
|
|
98
|
+
# @raise [Errors::MetadataError] if the load fails
|
|
99
|
+
def load_metadata
|
|
100
|
+
rows = with_connection(operation: 'LOAD_ENCRYPTION_METADATA') do |connection|
|
|
101
|
+
@sql.query(connection, load_metadata_sql)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
rows.each_with_object({}) do |row, metadata|
|
|
105
|
+
column_config = to_column_config(row)
|
|
106
|
+
metadata[column_config.column_identifier] = column_config
|
|
107
|
+
end
|
|
108
|
+
rescue StandardError => e
|
|
109
|
+
raise e if e.is_a?(Errors::MetadataError)
|
|
110
|
+
|
|
111
|
+
raise Errors::MetadataError.load_failed("Failed to load kms_encryption metadata: #{e.message}")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @param table_name [String, nil]
|
|
115
|
+
# @param column_name [String, nil]
|
|
116
|
+
# @return [Boolean] whether the column is configured for kms_encryption
|
|
117
|
+
# @raise [Errors::MetadataError] if the lookup has to hit the database and that fails
|
|
118
|
+
def column_encrypted?(table_name, column_name)
|
|
119
|
+
return false if table_name.nil? || column_name.nil?
|
|
120
|
+
|
|
121
|
+
if cache_usable?
|
|
122
|
+
hit = @lock.synchronize { @cache.key?(column_identifier(table_name, column_name)) }
|
|
123
|
+
return hit
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
column_encrypted_in_database?(table_name, column_name)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @param table_name [String, nil]
|
|
130
|
+
# @param column_name [String, nil]
|
|
131
|
+
# @return [ColumnEncryptionConfig, nil] the column's configuration, nil when not encrypted
|
|
132
|
+
# @raise [Errors::MetadataError] if the lookup has to hit the database and that fails
|
|
133
|
+
def column_config(table_name, column_name)
|
|
134
|
+
return nil if table_name.nil? || column_name.nil?
|
|
135
|
+
|
|
136
|
+
return @lock.synchronize { @cache[column_identifier(table_name, column_name)] } if cache_usable?
|
|
137
|
+
|
|
138
|
+
column_config_from_database(table_name, column_name)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Every encrypted column of one table.
|
|
142
|
+
#
|
|
143
|
+
# This is what a read is planned from: the statement says which tables it touches, and the
|
|
144
|
+
# configuration says which of their columns will come back encrypted.
|
|
145
|
+
#
|
|
146
|
+
# @param table_name [String, nil]
|
|
147
|
+
# @return [Array<ColumnEncryptionConfig>] empty when no column of the table is encrypted
|
|
148
|
+
# @raise [Errors::MetadataError] if the lookup has to hit the database and that fails
|
|
149
|
+
def table_configs(table_name)
|
|
150
|
+
return [] if table_name.nil?
|
|
151
|
+
|
|
152
|
+
return @lock.synchronize { cached_table_configs(table_name) } if cache_usable?
|
|
153
|
+
|
|
154
|
+
table_configs_from_database(table_name)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# @return [Time, nil] when the cache was last refreshed, nil when it never was
|
|
158
|
+
def last_refresh_time
|
|
159
|
+
@lock.synchronize { @last_refresh_time }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @return [Integer] the number of cached column configurations
|
|
163
|
+
def cache_size
|
|
164
|
+
@lock.synchronize { @cache.size }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Stops the background refresh and empties the cache.
|
|
168
|
+
# @return [void]
|
|
169
|
+
def shutdown
|
|
170
|
+
@running = false
|
|
171
|
+
thread = @refresh_thread
|
|
172
|
+
if thread
|
|
173
|
+
thread.wakeup if thread.alive?
|
|
174
|
+
thread.join(SHUTDOWN_TIMEOUT_SEC)
|
|
175
|
+
@refresh_thread = nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
@lock.synchronize do
|
|
179
|
+
@cache = {}
|
|
180
|
+
@last_refresh_time = nil
|
|
181
|
+
end
|
|
182
|
+
nil
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
|
|
187
|
+
def column_identifier(table_name, column_name)
|
|
188
|
+
"#{table_name}.#{column_name}"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# The cache can only answer a lookup when it is enabled, has been loaded, and has not
|
|
192
|
+
# expired.
|
|
193
|
+
def cache_usable?
|
|
194
|
+
return false unless @config.metadata_cache_enabled
|
|
195
|
+
|
|
196
|
+
last_refresh = @lock.synchronize { @last_refresh_time }
|
|
197
|
+
!last_refresh.nil? && (Time.now - last_refresh) < @config.metadata_cache_expiration_sec
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def column_encrypted_in_database?(table_name, column_name)
|
|
201
|
+
row = with_connection(operation: 'CHECK_COLUMN_ENCRYPTED') do |connection|
|
|
202
|
+
@sql.query(connection, check_column_encrypted_sql, [table_name, column_name]).first
|
|
203
|
+
end
|
|
204
|
+
!row.nil?
|
|
205
|
+
rescue StandardError => e
|
|
206
|
+
raise lookup_error(e, table_name, column_name)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def column_config_from_database(table_name, column_name)
|
|
210
|
+
row = with_connection(operation: 'GET_COLUMN_CONFIG') do |connection|
|
|
211
|
+
@sql.query(connection, column_config_sql, [table_name, column_name]).first
|
|
212
|
+
end
|
|
213
|
+
row && to_column_config(row)
|
|
214
|
+
rescue StandardError => e
|
|
215
|
+
raise lookup_error(e, table_name, column_name)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Callers hold {@lock}.
|
|
219
|
+
def cached_table_configs(table_name)
|
|
220
|
+
@cache.each_value.select { |config| config.table_name == table_name }
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def table_configs_from_database(table_name)
|
|
224
|
+
rows = with_connection(operation: 'GET_TABLE_CONFIGS') do |connection|
|
|
225
|
+
@sql.query(connection, table_configs_sql, [table_name])
|
|
226
|
+
end
|
|
227
|
+
rows.map { |row| to_column_config(row) }
|
|
228
|
+
rescue StandardError => e
|
|
229
|
+
raise lookup_error(e, table_name, nil)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def lookup_error(error, table_name, column_name)
|
|
233
|
+
return error if error.is_a?(Errors::MetadataError)
|
|
234
|
+
|
|
235
|
+
Errors::MetadataError
|
|
236
|
+
.lookup_failed("Failed to load the kms_encryption configuration: #{error.message}")
|
|
237
|
+
.with_table(table_name)
|
|
238
|
+
.with_column(column_name)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Builds a {ColumnEncryptionConfig}, and the {KeyMetadata} it points at, from one joined row.
|
|
242
|
+
#
|
|
243
|
+
# The row names its table and column in the connection's encoding, while the names they are
|
|
244
|
+
# looked up by are read from the application's SQL as UTF-8. They are kept as UTF-8 so the two
|
|
245
|
+
# compare equal whatever the connection's encoding is.
|
|
246
|
+
def to_column_config(row)
|
|
247
|
+
key_metadata = KeyMetadata.new(
|
|
248
|
+
id: row['key_id']&.to_i,
|
|
249
|
+
key_id: row['key_uuid'],
|
|
250
|
+
key_name: row['name'],
|
|
251
|
+
master_key_arn: row['master_key_arn'],
|
|
252
|
+
encrypted_data_key: row['encrypted_data_key'],
|
|
253
|
+
hmac_key: @sql.read_binary(row['hmac_key']),
|
|
254
|
+
key_spec: row['key_spec'] || KeyMetadata::DEFAULT_KEY_SPEC,
|
|
255
|
+
created_at: row['key_created_at'] && to_time(row['key_created_at']),
|
|
256
|
+
last_used_at: row['last_used_at'] && to_time(row['last_used_at'])
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
ColumnEncryptionConfig.new(
|
|
260
|
+
table_name: Utils::SqlEncoding.inspectable(row['table_name']),
|
|
261
|
+
column_name: Utils::SqlEncoding.inspectable(row['column_name']),
|
|
262
|
+
algorithm: row['encryption_algorithm'] || EncryptionAlgorithm::DEFAULT,
|
|
263
|
+
key_id: row['key_id']&.to_i,
|
|
264
|
+
key_metadata: key_metadata,
|
|
265
|
+
created_at: row['created_at'] && to_time(row['created_at']),
|
|
266
|
+
updated_at: row['updated_at'] && to_time(row['updated_at'])
|
|
267
|
+
)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def load_metadata_sql
|
|
271
|
+
"#{joined_columns_sql} ORDER BY em.table_name, em.column_name"
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def column_config_sql
|
|
275
|
+
"#{joined_columns_sql} WHERE em.table_name #{eq} ? AND em.column_name #{eq} ?"
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def table_configs_sql
|
|
279
|
+
"#{joined_columns_sql} WHERE em.table_name #{eq} ?"
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def check_column_encrypted_sql
|
|
283
|
+
"SELECT 1 FROM #{@config.metadata_schema}.encryption_metadata " \
|
|
284
|
+
"WHERE table_name #{eq} ? AND column_name #{eq} ?"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# +ks.key_id+ is aliased because +em.key_id+ already occupies that name in the row: the
|
|
288
|
+
# metadata table's key_id is the integer foreign key into key_storage.id, while the key
|
|
289
|
+
# storage table's own key_id is the external identifier of the key.
|
|
290
|
+
#
|
|
291
|
+
# The join is an outer one so that a column whose key row is missing is still reported as
|
|
292
|
+
# configured for kms_encryption. An inner join would drop it, the column would look like an
|
|
293
|
+
# ordinary one, and a write would store the plaintext. Kept this way, its key metadata comes
|
|
294
|
+
# back empty, which fails validation and so fails the write instead.
|
|
295
|
+
def joined_columns_sql
|
|
296
|
+
schema = @config.metadata_schema
|
|
297
|
+
'SELECT em.table_name, em.column_name, em.encryption_algorithm, em.key_id, ' \
|
|
298
|
+
'em.created_at, em.updated_at, ' \
|
|
299
|
+
'ks.key_id AS key_uuid, ks.name, ks.master_key_arn, ks.encrypted_data_key, ks.hmac_key, ks.key_spec, ' \
|
|
300
|
+
'ks.created_at AS key_created_at, ks.last_used_at ' \
|
|
301
|
+
"FROM #{schema}.encryption_metadata em " \
|
|
302
|
+
"LEFT JOIN #{schema}.key_storage ks ON em.key_id #{eq} ks.id"
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def eq
|
|
306
|
+
@sql.equals_operator
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def start_refresh_thread
|
|
310
|
+
@running = true
|
|
311
|
+
interval = @config.metadata_cache_refresh_interval_sec
|
|
312
|
+
|
|
313
|
+
thread = Thread.new do
|
|
314
|
+
while @running
|
|
315
|
+
sleep(interval)
|
|
316
|
+
break unless @running
|
|
317
|
+
|
|
318
|
+
begin
|
|
319
|
+
refresh
|
|
320
|
+
rescue StandardError => e
|
|
321
|
+
logger.warn("Failed to refresh the kms_encryption metadata cache: #{e.message}")
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
thread.name = 'kms_encryption-metadata-refresh'
|
|
326
|
+
@refresh_thread = thread
|
|
327
|
+
logger.debug { "Started the kms_encryption metadata refresh thread with a #{interval}s interval" }
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|