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,147 @@
|
|
|
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
|
+
module AwsAdvancedRubyDriverWrapper
|
|
18
|
+
module Plugins
|
|
19
|
+
module Encryption
|
|
20
|
+
# Redaction helpers shared by the audit logger, the error context builder, and the
|
|
21
|
+
# kms_encryption error classes. Every method returns nil when given nil so that callers
|
|
22
|
+
# can decide how to render a missing value.
|
|
23
|
+
module Sanitizer
|
|
24
|
+
# Matches "password=...", "secret=...", "key=...", "token=..." assignments.
|
|
25
|
+
SENSITIVE_ASSIGNMENT_PATTERN = /(password|secret|key|token)=\S+/i
|
|
26
|
+
# Same as above, plus "credential=...", and stops at commas and closing braces so that
|
|
27
|
+
# it works on rendered hashes.
|
|
28
|
+
CREDENTIAL_ASSIGNMENT_PATTERN = /(password|secret|key|token|credential)=[^\s,}]+/i
|
|
29
|
+
# The region and account exclude '*' so that an ARN masked by {.arn} is not matched again,
|
|
30
|
+
# which would garble its partly masked key id. An unmasked ARN never contains a '*'.
|
|
31
|
+
KMS_ARN_PATTERN = %r{arn:aws:kms:[^:*]+:[^:*]+:key/[a-f0-9-]+}i
|
|
32
|
+
URL_PASSWORD_PATTERN = /[?&]password=[^&]*/i
|
|
33
|
+
URL_PWD_PATTERN = /[?&]pwd=[^&]*/i
|
|
34
|
+
# The password run excludes '/' and whitespace as well as '@', so it cannot scan past the
|
|
35
|
+
# start of the next authority; without that a string of "://x:" segments is quadratic to
|
|
36
|
+
# match. A URL's userinfo cannot contain an unencoded '/' anyway.
|
|
37
|
+
URL_USER_INFO_PATTERN = %r{://[^:/@\s]+:[^@/\s]+@}
|
|
38
|
+
SQL_STRING_LITERAL_PATTERN = /'[^']*'/
|
|
39
|
+
SQL_NUMERIC_LITERAL_PATTERN = /\b\d+\b/
|
|
40
|
+
|
|
41
|
+
MAX_NAME_LENGTH = 50
|
|
42
|
+
MAX_DESCRIPTION_LENGTH = 100
|
|
43
|
+
MAX_CONFIG_DETAILS_LENGTH = 150
|
|
44
|
+
MAX_ERROR_MESSAGE_LENGTH = 200
|
|
45
|
+
MAX_SQL_LENGTH = 100
|
|
46
|
+
|
|
47
|
+
module_function
|
|
48
|
+
|
|
49
|
+
# Masks the account and region of a KMS key ARN, and masks the middle of the trailing key id
|
|
50
|
+
# the same way {.key_id} does, so a key id is redacted consistently wherever it appears.
|
|
51
|
+
# @param value [String, nil]
|
|
52
|
+
# @return [String, nil]
|
|
53
|
+
def arn(value)
|
|
54
|
+
return nil if value.nil?
|
|
55
|
+
|
|
56
|
+
str = value.to_s
|
|
57
|
+
last_slash = str.rindex('/')
|
|
58
|
+
return 'arn:aws:kms:***:***:key/***' if last_slash.nil? || last_slash.zero? || last_slash == str.length - 1
|
|
59
|
+
|
|
60
|
+
"arn:aws:kms:***:***:key/#{key_id(str[(last_slash + 1)..])}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Keeps the first and last four characters of a key id, masking the middle.
|
|
64
|
+
# @param value [String, nil]
|
|
65
|
+
# @return [String, nil]
|
|
66
|
+
def key_id(value)
|
|
67
|
+
return nil if value.nil?
|
|
68
|
+
|
|
69
|
+
str = value.to_s
|
|
70
|
+
str.length > 8 ? "#{str[0, 4]}***#{str[-4..]}" : '***'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @param value [String, nil]
|
|
74
|
+
# @return [String, nil]
|
|
75
|
+
def table_name(value)
|
|
76
|
+
truncate(value, MAX_NAME_LENGTH)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @param value [String, nil]
|
|
80
|
+
# @return [String, nil]
|
|
81
|
+
def column_name(value)
|
|
82
|
+
truncate(value, MAX_NAME_LENGTH)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @param value [String, nil]
|
|
86
|
+
# @return [String, nil]
|
|
87
|
+
def description(value)
|
|
88
|
+
return nil if value.nil?
|
|
89
|
+
|
|
90
|
+
truncate(value.gsub(SENSITIVE_ASSIGNMENT_PATTERN, '\1=***'), MAX_DESCRIPTION_LENGTH)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @param value [String, nil]
|
|
94
|
+
# @return [String, nil]
|
|
95
|
+
def error_message(value)
|
|
96
|
+
return nil if value.nil?
|
|
97
|
+
|
|
98
|
+
masked = value.gsub(SENSITIVE_ASSIGNMENT_PATTERN, '\1=***')
|
|
99
|
+
.gsub(KMS_ARN_PATTERN, 'arn:aws:kms:***:***:key/***')
|
|
100
|
+
truncate(masked, MAX_ERROR_MESSAGE_LENGTH)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @param value [String, nil]
|
|
104
|
+
# @return [String, nil]
|
|
105
|
+
def config_details(value)
|
|
106
|
+
return nil if value.nil?
|
|
107
|
+
|
|
108
|
+
masked = value.gsub(CREDENTIAL_ASSIGNMENT_PATTERN, '\1=***')
|
|
109
|
+
.gsub(KMS_ARN_PATTERN, 'arn:aws:kms:***:***:key/***')
|
|
110
|
+
truncate(masked, MAX_CONFIG_DETAILS_LENGTH)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Masks credentials embedded in a connection URL, both as query parameters and as user info.
|
|
114
|
+
# @param value [String, nil]
|
|
115
|
+
# @return [String, nil]
|
|
116
|
+
def connection_url(value)
|
|
117
|
+
return nil if value.nil?
|
|
118
|
+
|
|
119
|
+
value.gsub(URL_PASSWORD_PATTERN, '?password=***')
|
|
120
|
+
.gsub(URL_PWD_PATTERN, '?pwd=***')
|
|
121
|
+
.gsub(URL_USER_INFO_PATTERN, '://***:***@')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Replaces string and numeric literals so that SQL can be logged without leaking values.
|
|
125
|
+
# @param value [String, nil]
|
|
126
|
+
# @return [String, nil]
|
|
127
|
+
def sql(value)
|
|
128
|
+
return nil if value.nil?
|
|
129
|
+
|
|
130
|
+
masked = value.gsub(SQL_STRING_LITERAL_PATTERN, "'***'")
|
|
131
|
+
.gsub(SQL_NUMERIC_LITERAL_PATTERN, '***')
|
|
132
|
+
truncate(masked, MAX_SQL_LENGTH)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @param value [String, nil]
|
|
136
|
+
# @param limit [Integer]
|
|
137
|
+
# @return [String, nil]
|
|
138
|
+
def truncate(value, limit)
|
|
139
|
+
return nil if value.nil?
|
|
140
|
+
|
|
141
|
+
str = value.to_s
|
|
142
|
+
str.length > limit ? "#{str[0, limit - 3]}..." : str
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
module AwsAdvancedRubyDriverWrapper
|
|
18
|
+
module Plugins
|
|
19
|
+
module Encryption
|
|
20
|
+
# A validated database schema name.
|
|
21
|
+
#
|
|
22
|
+
# Schema names cannot be bound as statement parameters, so the plugin has to
|
|
23
|
+
# interpolate them into the metadata queries. Wrapping the name in this type keeps
|
|
24
|
+
# that interpolation safe: only plain identifiers are accepted, which rules out
|
|
25
|
+
# quoting, whitespace, comments, and statement separators.
|
|
26
|
+
class SchemaName
|
|
27
|
+
VALID_PATTERN = /\A[a-zA-Z_][a-zA-Z0-9_]*\z/
|
|
28
|
+
|
|
29
|
+
attr_reader :value
|
|
30
|
+
|
|
31
|
+
# @param name [String, Symbol, SchemaName] the schema name to validate
|
|
32
|
+
# @return [SchemaName]
|
|
33
|
+
# @raise [ArgumentError] if the name is empty or is not a plain SQL identifier
|
|
34
|
+
def self.of(name)
|
|
35
|
+
name.is_a?(SchemaName) ? name : new(name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param value [String, Symbol]
|
|
39
|
+
# @raise [ArgumentError] if the name is empty or is not a plain SQL identifier
|
|
40
|
+
def initialize(value)
|
|
41
|
+
name = value.to_s
|
|
42
|
+
raise ArgumentError, 'Schema name cannot be empty' if value.nil? || name.strip.empty?
|
|
43
|
+
|
|
44
|
+
unless VALID_PATTERN.match?(name)
|
|
45
|
+
raise ArgumentError,
|
|
46
|
+
"Invalid schema name: #{name.inspect}. Schema names must start with a letter or underscore and " \
|
|
47
|
+
'contain only letters, digits, and underscores.'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
@value = name.freeze
|
|
51
|
+
freeze
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to_s
|
|
55
|
+
@value
|
|
56
|
+
end
|
|
57
|
+
alias to_str to_s
|
|
58
|
+
|
|
59
|
+
def ==(other)
|
|
60
|
+
other.is_a?(SchemaName) && other.value == @value
|
|
61
|
+
end
|
|
62
|
+
alias eql? ==
|
|
63
|
+
|
|
64
|
+
def hash
|
|
65
|
+
[self.class, @value].hash
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
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 'schema_name'
|
|
18
|
+
|
|
19
|
+
module AwsAdvancedRubyDriverWrapper
|
|
20
|
+
module Plugins
|
|
21
|
+
module Encryption
|
|
22
|
+
# Checks that the +encryption_metadata+ and +key_storage+ tables exist and look the way the
|
|
23
|
+
# plugin expects before any kms_encryption is attempted.
|
|
24
|
+
#
|
|
25
|
+
# Everything is read from +information_schema+, which both PostgreSQL and MySQL provide, so
|
|
26
|
+
# the same queries work for either driver. Only the foreign key lookup differs: MySQL keeps
|
|
27
|
+
# the referenced table on +key_column_usage+, while PostgreSQL exposes it through
|
|
28
|
+
# +constraint_column_usage+.
|
|
29
|
+
class SchemaValidator
|
|
30
|
+
REQUIRED_ENCRYPTION_METADATA_COLUMNS = %w[
|
|
31
|
+
table_name column_name encryption_algorithm key_id created_at updated_at
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
REQUIRED_KEY_STORAGE_COLUMNS = %w[
|
|
35
|
+
id key_id name master_key_arn encrypted_data_key hmac_key key_spec created_at last_used_at
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
ENCRYPTION_METADATA_TABLE = 'encryption_metadata'
|
|
39
|
+
KEY_STORAGE_TABLE = 'key_storage'
|
|
40
|
+
UNIQUE_METADATA_COLUMNS = %w[table_name column_name].freeze
|
|
41
|
+
|
|
42
|
+
# The outcome of a validation run.
|
|
43
|
+
ValidationResult = Data.define(:valid, :issues)
|
|
44
|
+
|
|
45
|
+
# Reopened so that the constant below is scoped to the result class.
|
|
46
|
+
class ValidationResult
|
|
47
|
+
def initialize(valid: nil, issues: [])
|
|
48
|
+
frozen_issues = issues.freeze
|
|
49
|
+
super(valid: valid.nil? ? frozen_issues.empty? : valid, issues: frozen_issues)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
def valid?
|
|
54
|
+
valid
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_s
|
|
58
|
+
valid? ? 'Schema validation passed' : "Schema validation failed: #{issues.join(', ')}"
|
|
59
|
+
end
|
|
60
|
+
alias inspect to_s
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @param metadata_schema [SchemaName, String] the schema holding the two tables
|
|
64
|
+
# @param sql_runner [SqlRunner]
|
|
65
|
+
def initialize(metadata_schema, sql_runner)
|
|
66
|
+
raise ArgumentError, 'metadata_schema is required' if metadata_schema.nil?
|
|
67
|
+
|
|
68
|
+
@schema = SchemaName.of(metadata_schema)
|
|
69
|
+
@sql = sql_runner
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Validates the schema over an existing connection.
|
|
73
|
+
#
|
|
74
|
+
# @param connection [Object] a pg or mysql2 connection
|
|
75
|
+
# @return [ValidationResult]
|
|
76
|
+
def validate(connection)
|
|
77
|
+
issues = []
|
|
78
|
+
|
|
79
|
+
issues.concat(validate_table(connection, ENCRYPTION_METADATA_TABLE, REQUIRED_ENCRYPTION_METADATA_COLUMNS) do
|
|
80
|
+
unique_constraint_issues(connection)
|
|
81
|
+
end)
|
|
82
|
+
|
|
83
|
+
issues.concat(validate_table(connection, KEY_STORAGE_TABLE, REQUIRED_KEY_STORAGE_COLUMNS) do
|
|
84
|
+
primary_key_issues(connection)
|
|
85
|
+
end)
|
|
86
|
+
|
|
87
|
+
# Only worth checking once both tables are known to be sound.
|
|
88
|
+
issues.concat(foreign_key_issues(connection)) if issues.empty?
|
|
89
|
+
|
|
90
|
+
ValidationResult.new(issues: issues)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
# Checks that a table exists and has every required column, then runs the table's own
|
|
96
|
+
# constraint checks.
|
|
97
|
+
def validate_table(connection, table, required_columns)
|
|
98
|
+
qualified = qualify(table)
|
|
99
|
+
return ["Table '#{qualified}' does not exist"] unless table_exists?(connection, table)
|
|
100
|
+
|
|
101
|
+
issues = missing_columns(connection, table, required_columns).map do |column|
|
|
102
|
+
"Table '#{qualified}' is missing required column '#{column}'"
|
|
103
|
+
end
|
|
104
|
+
issues.concat(yield)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def table_exists?(connection, table)
|
|
108
|
+
rows = @sql.query(
|
|
109
|
+
connection,
|
|
110
|
+
"SELECT 1 AS present FROM information_schema.tables WHERE table_schema #{eq} ? AND table_name #{eq} ?",
|
|
111
|
+
[@schema.to_s, table]
|
|
112
|
+
)
|
|
113
|
+
!rows.empty?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def missing_columns(connection, table, required_columns)
|
|
117
|
+
rows = @sql.query(
|
|
118
|
+
connection,
|
|
119
|
+
'SELECT column_name AS name FROM information_schema.columns ' \
|
|
120
|
+
"WHERE table_schema #{eq} ? AND table_name #{eq} ?",
|
|
121
|
+
[@schema.to_s, table]
|
|
122
|
+
)
|
|
123
|
+
existing = rows.to_set { |row| value(row, 'name').to_s.downcase }
|
|
124
|
+
|
|
125
|
+
required_columns.reject { |column| existing.include?(column.downcase) }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def unique_constraint_issues(connection)
|
|
129
|
+
columns = constrained_columns(connection, ENCRYPTION_METADATA_TABLE, ['PRIMARY KEY', 'UNIQUE'])
|
|
130
|
+
expected = UNIQUE_METADATA_COLUMNS.to_set
|
|
131
|
+
return [] if columns.include?(expected)
|
|
132
|
+
|
|
133
|
+
["Table '#{qualify(ENCRYPTION_METADATA_TABLE)}' is missing a unique constraint on " \
|
|
134
|
+
"(#{UNIQUE_METADATA_COLUMNS.join(', ')})"]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def primary_key_issues(connection)
|
|
138
|
+
columns = constrained_columns(connection, KEY_STORAGE_TABLE, ['PRIMARY KEY'])
|
|
139
|
+
return [] if columns.any? { |constraint_columns| constraint_columns.include?('id') }
|
|
140
|
+
|
|
141
|
+
["Table '#{qualify(KEY_STORAGE_TABLE)}' is missing a primary key on 'id'"]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def foreign_key_issues(connection)
|
|
145
|
+
references = foreign_keys(connection, ENCRYPTION_METADATA_TABLE)
|
|
146
|
+
matched = references.any? do |reference|
|
|
147
|
+
reference[:from_column] == 'key_id' &&
|
|
148
|
+
reference[:to_table] == KEY_STORAGE_TABLE &&
|
|
149
|
+
reference[:to_column] == 'id'
|
|
150
|
+
end
|
|
151
|
+
return [] if matched
|
|
152
|
+
|
|
153
|
+
["Missing foreign key constraint from #{qualify(ENCRYPTION_METADATA_TABLE)}.key_id " \
|
|
154
|
+
"to #{qualify(KEY_STORAGE_TABLE)}.id"]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# The column sets of every constraint of the given types, one set per constraint.
|
|
158
|
+
#
|
|
159
|
+
# @return [Array<Set<String>>]
|
|
160
|
+
def constrained_columns(connection, table, constraint_types)
|
|
161
|
+
# Spelled out as ORs rather than IN, since IN compares with an unqualified operator.
|
|
162
|
+
type_matches = Array.new(constraint_types.size, "tc.constraint_type #{eq} ?").join(' OR ')
|
|
163
|
+
rows = @sql.query(
|
|
164
|
+
connection,
|
|
165
|
+
'SELECT tc.constraint_name AS constraint_name, kcu.column_name AS column_name ' \
|
|
166
|
+
'FROM information_schema.table_constraints tc ' \
|
|
167
|
+
'JOIN information_schema.key_column_usage kcu ' \
|
|
168
|
+
"ON tc.constraint_name #{eq} kcu.constraint_name AND tc.table_schema #{eq} kcu.table_schema " \
|
|
169
|
+
"AND tc.table_name #{eq} kcu.table_name WHERE (#{type_matches}) " \
|
|
170
|
+
"AND tc.table_schema #{eq} ? AND tc.table_name #{eq} ?",
|
|
171
|
+
constraint_types + [@schema.to_s, table]
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
grouped = rows.group_by { |row| value(row, 'constraint_name') }
|
|
175
|
+
grouped.values.map { |group| group.to_set { |row| value(row, 'column_name').to_s.downcase } }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @return [Array<Hash>] one entry per foreign key column of the table
|
|
179
|
+
def foreign_keys(connection, table)
|
|
180
|
+
rows = @sql.query(connection, foreign_key_sql, [@schema.to_s, table])
|
|
181
|
+
|
|
182
|
+
rows.map do |row|
|
|
183
|
+
{
|
|
184
|
+
from_column: value(row, 'from_column').to_s.downcase,
|
|
185
|
+
to_table: value(row, 'to_table').to_s.downcase,
|
|
186
|
+
to_column: value(row, 'to_column').to_s.downcase
|
|
187
|
+
}
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def foreign_key_sql
|
|
192
|
+
@sql.foreign_key_query
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# MySQL reports information_schema column names in upper case on some versions, so every
|
|
196
|
+
# lookup accepts either case.
|
|
197
|
+
def value(row, key)
|
|
198
|
+
row[key] || row[key.upcase]
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def qualify(table)
|
|
202
|
+
"#{@schema}.#{table}"
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def eq
|
|
206
|
+
@sql.equals_operator
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
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/pg_driver_dialect'
|
|
18
|
+
|
|
19
|
+
module AwsAdvancedRubyDriverWrapper
|
|
20
|
+
module Plugins
|
|
21
|
+
module Encryption
|
|
22
|
+
# Runs the plugin's own metadata and key storage queries against either driver.
|
|
23
|
+
#
|
|
24
|
+
# The plugin needs to talk to +encryption_metadata+ and +key_storage+ itself, and the two
|
|
25
|
+
# drivers disagree on almost every detail of how to do that: pg wants +$1+ placeholders
|
|
26
|
+
# and hex escaped bytea, mysql2 wants +?+ placeholders and raw binary, and only pg can
|
|
27
|
+
# return a generated id from an INSERT. This isolates those differences so that the
|
|
28
|
+
# managers above it can be written once.
|
|
29
|
+
#
|
|
30
|
+
# SQL is always written with +?+ placeholders and translated here.
|
|
31
|
+
class SqlRunner
|
|
32
|
+
# @param driver_dialect [DriverDialects::DriverDialect] the dialect of the connection in use
|
|
33
|
+
def initialize(driver_dialect)
|
|
34
|
+
@driver_dialect = driver_dialect
|
|
35
|
+
@pg = driver_dialect.is_a?(DriverDialects::PgDriverDialect)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [Boolean] true when the underlying driver is pg
|
|
39
|
+
def pg?
|
|
40
|
+
@pg
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Runs a query and returns its rows.
|
|
44
|
+
#
|
|
45
|
+
# @param connection [Object] a pg or mysql2 connection
|
|
46
|
+
# @param template [String] SQL using +?+ placeholders
|
|
47
|
+
# @param params [Array] the bind parameters
|
|
48
|
+
# @return [Array<Hash{String => Object}>] the result rows, empty for statements without one
|
|
49
|
+
def query(connection, template, params = [])
|
|
50
|
+
rows(raw_execute(connection, template, params))
|
|
51
|
+
end
|
|
52
|
+
alias execute query
|
|
53
|
+
|
|
54
|
+
# Runs an INSERT, UPDATE, or DELETE and returns how many rows it changed.
|
|
55
|
+
#
|
|
56
|
+
# @param connection [Object] a pg or mysql2 connection
|
|
57
|
+
# @param template [String] SQL using +?+ placeholders
|
|
58
|
+
# @param params [Array] the bind parameters
|
|
59
|
+
# @return [Integer] the number of affected rows
|
|
60
|
+
def update(connection, template, params = [])
|
|
61
|
+
result = raw_execute(connection, template, params)
|
|
62
|
+
@driver_dialect.affected_rows(connection, result)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Runs an INSERT and returns the generated +id+.
|
|
66
|
+
#
|
|
67
|
+
# @param connection [Object] a pg or mysql2 connection
|
|
68
|
+
# @param template [String] an INSERT using +?+ placeholders, without a RETURNING clause
|
|
69
|
+
# @param params [Array] the bind parameters
|
|
70
|
+
# @param id_column [String] the generated column to return
|
|
71
|
+
# @return [Integer, nil] the generated id
|
|
72
|
+
def insert_returning_id(connection, template, params, id_column: 'id')
|
|
73
|
+
@driver_dialect.insert_returning_id(connection, translate(template), params, id_column)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Wraps a binary value so that it can be bound to a bytea or blob parameter.
|
|
77
|
+
#
|
|
78
|
+
# @param bytes [String, nil] binary data
|
|
79
|
+
# @return [Object, nil] the driver specific bind value
|
|
80
|
+
def binary_param(bytes)
|
|
81
|
+
return nil if bytes.nil?
|
|
82
|
+
|
|
83
|
+
@driver_dialect.binary_param(bytes)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Reads a bytea or blob column back into binary data.
|
|
87
|
+
#
|
|
88
|
+
# @param value [String, nil] the raw column value
|
|
89
|
+
# @return [String, nil] binary data
|
|
90
|
+
def read_binary(value)
|
|
91
|
+
return nil if value.nil?
|
|
92
|
+
|
|
93
|
+
@driver_dialect.read_binary(value)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Translates +?+ placeholders into the driver's own placeholder syntax.
|
|
97
|
+
#
|
|
98
|
+
# @param template [String]
|
|
99
|
+
# @return [String]
|
|
100
|
+
def translate(template)
|
|
101
|
+
@driver_dialect.translate_placeholders(template)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# The driver-specific trailing upsert clause for an INSERT.
|
|
105
|
+
#
|
|
106
|
+
# @param conflict_columns [Array<String>] the columns whose conflict triggers the update
|
|
107
|
+
# @param update_columns [Array<String>] the columns to overwrite from the incoming row
|
|
108
|
+
# @return [String]
|
|
109
|
+
def upsert_clause(conflict_columns, update_columns)
|
|
110
|
+
@driver_dialect.upsert_clause(conflict_columns, update_columns)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The driver-specific equality operator. Every comparison in the plugin's own SQL uses it,
|
|
114
|
+
# so that on pg a user-defined operator earlier in the search_path cannot take its place.
|
|
115
|
+
#
|
|
116
|
+
# @return [String]
|
|
117
|
+
def equals_operator
|
|
118
|
+
@driver_dialect.equals_operator
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# The driver-specific query for a table's foreign keys, using +?+ placeholders for the
|
|
122
|
+
# schema and table names.
|
|
123
|
+
#
|
|
124
|
+
# @return [String]
|
|
125
|
+
def foreign_key_query
|
|
126
|
+
@driver_dialect.foreign_key_query
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def raw_execute(connection, template, params)
|
|
132
|
+
if params.empty?
|
|
133
|
+
@driver_dialect.execute(connection, translate(template))
|
|
134
|
+
else
|
|
135
|
+
@driver_dialect.execute_with_params(connection, translate(template), params)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def rows(result)
|
|
140
|
+
return [] if result.nil? || !result.respond_to?(:each)
|
|
141
|
+
|
|
142
|
+
result.to_a
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
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 'bigdecimal'
|
|
18
|
+
require 'date'
|
|
19
|
+
|
|
20
|
+
module AwsAdvancedRubyDriverWrapper
|
|
21
|
+
module Plugins
|
|
22
|
+
module Encryption
|
|
23
|
+
# The single byte written ahead of the ciphertext that records how the plaintext was
|
|
24
|
+
# serialized, so that a decrypted payload can be turned back into a Ruby value.
|
|
25
|
+
module TypeMarker
|
|
26
|
+
STRING = 1
|
|
27
|
+
INTEGER = 2
|
|
28
|
+
LONG = 3
|
|
29
|
+
DOUBLE = 4
|
|
30
|
+
FLOAT = 5
|
|
31
|
+
BOOLEAN = 6
|
|
32
|
+
BIG_DECIMAL = 7
|
|
33
|
+
DATE = 8
|
|
34
|
+
TIME = 9
|
|
35
|
+
TIMESTAMP = 10
|
|
36
|
+
LOCAL_DATE = 11
|
|
37
|
+
LOCAL_TIME = 12
|
|
38
|
+
LOCAL_DATE_TIME = 13
|
|
39
|
+
BYTE_ARRAY = 14
|
|
40
|
+
GENERIC = 99
|
|
41
|
+
|
|
42
|
+
NAMES = {
|
|
43
|
+
STRING => 'STRING',
|
|
44
|
+
INTEGER => 'INTEGER',
|
|
45
|
+
LONG => 'LONG',
|
|
46
|
+
DOUBLE => 'DOUBLE',
|
|
47
|
+
FLOAT => 'FLOAT',
|
|
48
|
+
BOOLEAN => 'BOOLEAN',
|
|
49
|
+
BIG_DECIMAL => 'BIG_DECIMAL',
|
|
50
|
+
DATE => 'DATE',
|
|
51
|
+
TIME => 'TIME',
|
|
52
|
+
TIMESTAMP => 'TIMESTAMP',
|
|
53
|
+
LOCAL_DATE => 'LOCAL_DATE',
|
|
54
|
+
LOCAL_TIME => 'LOCAL_TIME',
|
|
55
|
+
LOCAL_DATE_TIME => 'LOCAL_DATE_TIME',
|
|
56
|
+
BYTE_ARRAY => 'BYTE_ARRAY',
|
|
57
|
+
GENERIC => 'GENERIC'
|
|
58
|
+
}.freeze
|
|
59
|
+
|
|
60
|
+
ALL = NAMES.keys.freeze
|
|
61
|
+
|
|
62
|
+
module_function
|
|
63
|
+
|
|
64
|
+
# @param value [Integer] the marker byte read from an encrypted payload
|
|
65
|
+
# @return [Integer] the same marker, once validated
|
|
66
|
+
# @raise [ArgumentError] if the byte is not a known marker
|
|
67
|
+
def from_value(value)
|
|
68
|
+
raise ArgumentError, "Unknown type marker: #{value.inspect}" unless NAMES.key?(value)
|
|
69
|
+
|
|
70
|
+
value
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Picks the marker used to serialize a Ruby value. Unknown classes fall back to
|
|
74
|
+
# {GENERIC}, which is serialized through +to_s+.
|
|
75
|
+
#
|
|
76
|
+
# Ruby has a single unbounded Integer type, so integers are always written as
|
|
77
|
+
# {LONG}. {INTEGER} is still understood on read for payloads written by the wrapper.
|
|
78
|
+
#
|
|
79
|
+
# @param value [Object]
|
|
80
|
+
# @return [Integer]
|
|
81
|
+
def from_object(value)
|
|
82
|
+
case value
|
|
83
|
+
when String then value.encoding == ::Encoding::BINARY ? BYTE_ARRAY : STRING
|
|
84
|
+
when Integer then LONG
|
|
85
|
+
when Float then DOUBLE
|
|
86
|
+
when true, false then BOOLEAN
|
|
87
|
+
when BigDecimal then BIG_DECIMAL
|
|
88
|
+
when ::Time then TIMESTAMP
|
|
89
|
+
when ::DateTime then LOCAL_DATE_TIME
|
|
90
|
+
when ::Date then LOCAL_DATE
|
|
91
|
+
else GENERIC
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @param marker [Integer]
|
|
96
|
+
# @return [String, nil] the human-readable name of the marker
|
|
97
|
+
def name_for(marker)
|
|
98
|
+
NAMES[marker]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @param marker [Integer]
|
|
102
|
+
# @return [Boolean]
|
|
103
|
+
def known?(marker)
|
|
104
|
+
NAMES.key?(marker)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|