aws_advanced_ruby_driver_wrapper 1.0.1 → 1.1.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 +4 -4
- data/CHANGELOG.md +32 -2
- data/README.md +115 -6
- data/THIRD-PARTY-LICENSES +70 -11
- data/aws_advanced_ruby_driver_wrapper.gemspec +9 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_postgresql_adapter.rb +17 -3
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/aws_trilogy_adapter.rb +80 -0
- data/lib/aws_advanced_ruby_driver_wrapper/active_record/type_adapter_alias.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect.rb +43 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/driver_dialect_manager.rb +11 -4
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql2_driver_dialect.rb +129 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/mysql_driver_dialect.rb +9 -91
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/pg_driver_dialect.rb +18 -0
- data/lib/aws_advanced_ruby_driver_wrapper/driver_dialects/trilogy_driver_dialect.rb +118 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/error_handler.rb +16 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors/mysql_error_handler.rb +10 -5
- data/lib/aws_advanced_ruby_driver_wrapper/errors/trilogy_error_handler.rb +127 -0
- data/lib/aws_advanced_ruby_driver_wrapper/errors.rb +1 -0
- data/lib/aws_advanced_ruby_driver_wrapper/monitoring/cluster_topology_monitor.rb +1 -0
- data/lib/aws_advanced_ruby_driver_wrapper/mysql.rb +27 -9
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/blue_green_plugin.rb +115 -7
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/drop_blue_connections_marker.rb +84 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/host_mapper.rb +3 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/base_routing.rb +7 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/substitute_connect_routing.rb +68 -41
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_connect_routing.rb +4 -2
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_execute_routing.rb +5 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/routing/suspend_until_corresponding_host_found_connect_routing.rb +7 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_builder.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_monitor.rb +6 -13
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/blue_green/status_provider.rb +90 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb +11 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_plugin.rb +4 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/default_plugin.rb +14 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/failover_plugin.rb +68 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/federated_auth/base_saml_auth_plugin.rb +266 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/federated_auth/federated_auth_plugin.rb +134 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/federated_auth/okta_auth_plugin.rb +113 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/gdb/gdb_failover_plugin.rb +30 -9
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/iam_auth_plugin.rb +16 -2
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/initial_connection_strategy_plugin.rb +4 -1
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_management_utility.rb +6 -3
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/key_manager.rb +13 -4
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/kms_encryption_plugin.rb +29 -6
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql/trigger_mysql.sql +108 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/kms_encryption/sql/trigger_pg.sql +54 -0
- data/lib/aws_advanced_ruby_driver_wrapper/plugins/secrets_manager_plugin.rb +14 -0
- data/lib/aws_advanced_ruby_driver_wrapper/postgresql.rb +41 -23
- data/lib/aws_advanced_ruby_driver_wrapper/property_definition.rb +63 -1
- data/lib/aws_advanced_ruby_driver_wrapper/ruby_method.rb +3 -0
- data/lib/aws_advanced_ruby_driver_wrapper/services/dialect_service.rb +20 -7
- data/lib/aws_advanced_ruby_driver_wrapper/services/host_service.rb +34 -8
- data/lib/aws_advanced_ruby_driver_wrapper/services/monitor_service.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/services/plugin_manager.rb +80 -29
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_container.rb +10 -2
- data/lib/aws_advanced_ruby_driver_wrapper/services/service_utility.rb +4 -1
- data/lib/aws_advanced_ruby_driver_wrapper/trilogy.rb +167 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper/utils/connection_config_parser.rb +25 -15
- data/lib/aws_advanced_ruby_driver_wrapper/utils/global_aurora_topology_utils.rb +2 -2
- data/lib/aws_advanced_ruby_driver_wrapper/utils/iam_auth_utils.rb +13 -4
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/encryption_annotation_parser.rb +15 -31
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_statement_analyzer.rb +55 -76
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/mysql_text.rb +215 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/pg_statement_analyzer.rb +11 -20
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/query_analysis.rb +2 -3
- data/lib/aws_advanced_ruby_driver_wrapper/utils/parser/sql_parser.rb +21 -14
- data/lib/aws_advanced_ruby_driver_wrapper/utils/retry_util.rb +2 -1
- data/lib/aws_advanced_ruby_driver_wrapper/utils/saml_utils.rb +133 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/storage/storage_service.rb +9 -5
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/null_telemetry.rb +89 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_context.rb +124 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_counter.rb +39 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_factory.rb +122 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/open_telemetry_gauge.rb +36 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/telemetry_const.rb +50 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/telemetry/telemetry_factory.rb +143 -0
- data/lib/aws_advanced_ruby_driver_wrapper/utils/topology_utils.rb +9 -2
- data/lib/aws_advanced_ruby_driver_wrapper/version.rb +1 -1
- data/lib/aws_advanced_ruby_driver_wrapper.rb +9 -2
- metadata +54 -2
|
@@ -18,6 +18,7 @@ require 'concurrent'
|
|
|
18
18
|
require_relative '../../logging'
|
|
19
19
|
require_relative '../../property_definition'
|
|
20
20
|
require_relative '../../utils/rds_utils'
|
|
21
|
+
require_relative 'drop_blue_connections_marker'
|
|
21
22
|
require_relative 'iam_host_tracker'
|
|
22
23
|
require_relative 'interval_rate'
|
|
23
24
|
require_relative 'host_mapper'
|
|
@@ -61,6 +62,10 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
61
62
|
@process_status_lock = Mutex.new
|
|
62
63
|
@monitor_generation = 0
|
|
63
64
|
|
|
65
|
+
@drop_blue_connections = PropertyDefinition::BG_DROP_BLUE_CONNECTIONS.get_bool(wrapper_props)
|
|
66
|
+
# Identity of the drop-blue-connections marker created for the current switchover, if any.
|
|
67
|
+
@drop_marker_id = nil
|
|
68
|
+
|
|
64
69
|
@status_check_interval_map = {
|
|
65
70
|
IntervalRate::BASELINE => PropertyDefinition::BG_INTERVAL_BASELINE_SEC.get_float(wrapper_props) * 1000,
|
|
66
71
|
IntervalRate::INCREASED => PropertyDefinition::BG_INTERVAL_INCREASED_SEC.get_float(wrapper_props) * 1000,
|
|
@@ -176,6 +181,18 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
176
181
|
|
|
177
182
|
logger.warn { 'Blue/Green switchover has timed out.' }
|
|
178
183
|
@summary_status = @rollback ? @builder.created : @builder.completed(@rollback)
|
|
184
|
+
# A timeout means the wrapper stopped waiting, not that the switchover is confirmed. Only
|
|
185
|
+
# activate the marker if the switchover was last observed at POST (past the point of no
|
|
186
|
+
# return); timing out in PREPARATION/IN_PROGRESS could still roll back, and dropping blue
|
|
187
|
+
# connections then would hit a blue that is still primary. In that case discard the pending
|
|
188
|
+
# marker so it does not linger unactivated for the full marker TTL: we have given up waiting
|
|
189
|
+
# on this switchover, and no later phase will arrive to activate it.
|
|
190
|
+
if @latest_status_phase == Phase::POST
|
|
191
|
+
activate_drop_marker
|
|
192
|
+
else
|
|
193
|
+
remove_drop_marker
|
|
194
|
+
@drop_marker_id = nil
|
|
195
|
+
end
|
|
179
196
|
update_monitors
|
|
180
197
|
update_status_cache
|
|
181
198
|
log_current_context
|
|
@@ -191,7 +208,15 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
191
208
|
@monitors = { Role::SOURCE => nil, Role::TARGET => nil }
|
|
192
209
|
old_monitors.each_value { |m| m&.stop }
|
|
193
210
|
|
|
194
|
-
|
|
211
|
+
# A completed switchover's marker must survive this reset so connections opened to the old
|
|
212
|
+
# blue environment are still invalidated on their next use. A rollback leaves blue as primary,
|
|
213
|
+
# so its marker is discarded instead. Either way a later switchover starts a fresh marker.
|
|
214
|
+
remove_drop_marker if @rollback
|
|
215
|
+
@drop_marker_id = nil
|
|
216
|
+
|
|
217
|
+
# IamAuthPlugin, FederatedAuthPlugin, and OktaAuthPlugin all cache tokens under the same
|
|
218
|
+
# shared IAM_TOKEN_CACHE_NAME, so clearing through any one of them clears all of them.
|
|
219
|
+
Plugins::IamAuthPlugin.clear_cache(@storage_service) if iam_token_cache_in_use?
|
|
195
220
|
|
|
196
221
|
@rollback = false
|
|
197
222
|
@green_topology_recognized_logged = false
|
|
@@ -225,12 +250,15 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
225
250
|
@switchover_state.update_dns_flags(@bgd_id, role, interim_status)
|
|
226
251
|
@summary_status = @builder.in_progress(@rollback)
|
|
227
252
|
@switchover_state.trigger_in_progress_monitor_reset
|
|
253
|
+
ensure_drop_marker_pending
|
|
228
254
|
when Phase::POST
|
|
229
255
|
@switchover_state.update_dns_flags(@bgd_id, role, interim_status)
|
|
230
256
|
@summary_status = @builder.post(@rollback)
|
|
257
|
+
activate_drop_marker
|
|
231
258
|
when Phase::COMPLETED
|
|
232
259
|
@switchover_state.update_dns_flags(@bgd_id, role, interim_status)
|
|
233
260
|
@summary_status = @builder.completed(@rollback)
|
|
261
|
+
activate_drop_marker
|
|
234
262
|
else
|
|
235
263
|
raise ArgumentError, "[bgd_id: '#{@bgd_id}'] Unknown BG phase '#{@latest_status_phase}'."
|
|
236
264
|
end
|
|
@@ -350,14 +378,73 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
350
378
|
)
|
|
351
379
|
end
|
|
352
380
|
|
|
381
|
+
# Creates a pending drop-blue-connections marker the first time a switchover reaches IN_PROGRESS.
|
|
382
|
+
# The marker is only confirmed (see #activate_drop_marker) once the switchover passes the point
|
|
383
|
+
# of no return, so nothing is invalidated while it is still in progress and could roll back.
|
|
384
|
+
def ensure_drop_marker_pending
|
|
385
|
+
return unless @drop_blue_connections
|
|
386
|
+
return if @rollback
|
|
387
|
+
return if @drop_marker_id
|
|
388
|
+
|
|
389
|
+
hosts = blue_host_set
|
|
390
|
+
return if hosts.empty?
|
|
391
|
+
|
|
392
|
+
marker = DropBlueConnectionsMarker.new(blue_hosts: hosts, switchover_time_ns: nano_time)
|
|
393
|
+
@drop_marker_id = marker.id
|
|
394
|
+
store_drop_marker(marker)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Confirms the switchover's pending marker so stale blue connections begin to be invalidated.
|
|
398
|
+
# A marker is only created when IN_PROGRESS is observed (see #ensure_drop_marker_pending), since
|
|
399
|
+
# that is the only point with a reliable switchover time. A provider that first sees the
|
|
400
|
+
# deployment at POST/COMPLETED cannot tell which of its connections predate the switchover and
|
|
401
|
+
# would wrongly cover connections opened after switchover, including to green.
|
|
402
|
+
def activate_drop_marker
|
|
403
|
+
return unless @drop_blue_connections
|
|
404
|
+
return if @rollback
|
|
405
|
+
|
|
406
|
+
marker = read_drop_marker
|
|
407
|
+
return if marker.nil? || marker.active?
|
|
408
|
+
|
|
409
|
+
store_drop_marker(marker.activated)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# The blue host set: downcased SOURCE endpoint names plus their pre-switchover IP addresses.
|
|
413
|
+
def blue_host_set
|
|
414
|
+
return Set.new unless @summary_status
|
|
415
|
+
|
|
416
|
+
blue = @summary_status.role_by_host.select { |_host, role| role == Role::SOURCE }.keys
|
|
417
|
+
ips = blue.filter_map { |host| @host_mapper.host_ip_addresses[host] }
|
|
418
|
+
(blue + ips).to_set(&:downcase)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def store_drop_marker(marker)
|
|
422
|
+
@storage_service.set(BlueGreenPlugin::DROP_MARKER_NAME, BlueGreenPlugin.drop_marker_key(@bgd_id), marker)
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def read_drop_marker
|
|
426
|
+
@storage_service.get(BlueGreenPlugin::DROP_MARKER_NAME, BlueGreenPlugin.drop_marker_key(@bgd_id), register_access: false)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def remove_drop_marker
|
|
430
|
+
@storage_service.remove(BlueGreenPlugin::DROP_MARKER_NAME, BlueGreenPlugin.drop_marker_key(@bgd_id))
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def nano_time
|
|
434
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
|
435
|
+
end
|
|
436
|
+
|
|
353
437
|
def switchover_finalized?
|
|
354
438
|
completed = (!@rollback && @summary_status.current_phase == Phase::COMPLETED) ||
|
|
355
439
|
(@rollback && @summary_status.current_phase == Phase::CREATED)
|
|
356
440
|
completed && @event_log.any? { |_, v| v.phase&.active_switchover_or_completed? }
|
|
357
441
|
end
|
|
358
442
|
|
|
359
|
-
|
|
360
|
-
|
|
443
|
+
# True when any auth plugin backed by the shared IAM token cache (IAM or AD FS
|
|
444
|
+
# or Okta) is in use, so a switchover knows to purge that cache regardless
|
|
445
|
+
# of which one is active.
|
|
446
|
+
def iam_token_cache_in_use?
|
|
447
|
+
@service_container.plugin_manager&.iam_token_plugin_in_use? || false
|
|
361
448
|
end
|
|
362
449
|
|
|
363
450
|
def context_hash
|
data/lib/aws_advanced_ruby_driver_wrapper/plugins/custom_endpoint/custom_endpoint_monitor.rb
CHANGED
|
@@ -41,7 +41,14 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
41
41
|
refresh_rate_sec,
|
|
42
42
|
refresh_rate_backoff_factor,
|
|
43
43
|
max_refresh_rate_sec,
|
|
44
|
-
|
|
44
|
+
credentials_provider: nil,
|
|
45
|
+
rds_client_func: lambda { |_, client_region|
|
|
46
|
+
# Pass the configured credentials provider when one is set; otherwise omit :credentials
|
|
47
|
+
# so the SDK resolves from its default chain.
|
|
48
|
+
options = { region: client_region }
|
|
49
|
+
options[:credentials] = credentials_provider if credentials_provider
|
|
50
|
+
Aws::RDS::Client.new(**options)
|
|
51
|
+
}
|
|
45
52
|
)
|
|
46
53
|
super(termination_timeout_sec: TERMINATION_TIMEOUT_SEC)
|
|
47
54
|
|
|
@@ -58,6 +65,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
58
65
|
@refresh_cv = ConditionVariable.new
|
|
59
66
|
@refresh_required = false
|
|
60
67
|
@connection_issue = false
|
|
68
|
+
@info_changed_counter = service_container.telemetry_factory.create_counter('custom_endpoint.info_changed.count')
|
|
61
69
|
end
|
|
62
70
|
|
|
63
71
|
def endpoint_info?
|
|
@@ -161,6 +169,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
161
169
|
logger.debug("[#{@endpoint_id}] Custom endpoint info changed: #{endpoint_info}")
|
|
162
170
|
cache_allowed_blocked(endpoint_info)
|
|
163
171
|
cache_info(endpoint_info)
|
|
172
|
+
@info_changed_counter.inc
|
|
164
173
|
speedup_refresh_rate
|
|
165
174
|
|
|
166
175
|
elapsed = monotonic_time - start
|
|
@@ -241,6 +250,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
241
250
|
end
|
|
242
251
|
|
|
243
252
|
def cached_info
|
|
253
|
+
# Explicitly unregistered: a monitor reading its own data must not keep itself alive.
|
|
244
254
|
storage_service.get(ENDPOINT_INFO_CACHE_NAME, @custom_endpoint_host.url, register_access: false)
|
|
245
255
|
end
|
|
246
256
|
|
|
@@ -38,6 +38,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
38
38
|
@props = props
|
|
39
39
|
@should_wait_for_info = PropertyDefinition::WAIT_FOR_CUSTOM_ENDPOINT_INFO.get_bool(props)
|
|
40
40
|
@wait_timeout_sec = PropertyDefinition::WAIT_FOR_CUSTOM_ENDPOINT_INFO_TIMEOUT_SEC.get_float(props)
|
|
41
|
+
@wait_for_info_counter = service_container.telemetry_factory.create_counter('custom_endpoint.wait_for_info.count')
|
|
41
42
|
|
|
42
43
|
@custom_endpoint_host = nil
|
|
43
44
|
@endpoint_id = nil
|
|
@@ -128,7 +129,8 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
128
129
|
@region,
|
|
129
130
|
PropertyDefinition::CUSTOM_ENDPOINT_INFO_REFRESH_RATE_SEC.get_float(props),
|
|
130
131
|
PropertyDefinition::CUSTOM_ENDPOINT_INFO_REFRESH_RATE_BACKOFF_FACTOR.get_int(props),
|
|
131
|
-
PropertyDefinition::CUSTOM_ENDPOINT_INFO_MAX_REFRESH_RATE_SEC.get_float(props)
|
|
132
|
+
PropertyDefinition::CUSTOM_ENDPOINT_INFO_MAX_REFRESH_RATE_SEC.get_float(props),
|
|
133
|
+
credentials_provider: PropertyDefinition::AWS_CREDENTIALS_PROVIDER.get(props)
|
|
132
134
|
)
|
|
133
135
|
end
|
|
134
136
|
end
|
|
@@ -137,6 +139,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
137
139
|
return if monitor.endpoint_info?
|
|
138
140
|
|
|
139
141
|
monitor.request_endpoint_info_update
|
|
142
|
+
@wait_for_info_counter.inc
|
|
140
143
|
logger.debug("CustomEndpointPlugin: waiting up to #{@wait_timeout_sec}s for endpoint info on #{@custom_endpoint_host.url}")
|
|
141
144
|
|
|
142
145
|
return if monitor.wait_for_info?(@wait_timeout_sec)
|
|
@@ -18,6 +18,7 @@ require 'concurrent'
|
|
|
18
18
|
require_relative '../errors'
|
|
19
19
|
require_relative '../host/host_info'
|
|
20
20
|
require_relative '../host/host_availability'
|
|
21
|
+
require_relative '../utils/telemetry/telemetry_const'
|
|
21
22
|
|
|
22
23
|
module AwsAdvancedRubyDriverWrapper
|
|
23
24
|
module Plugins
|
|
@@ -44,7 +45,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
44
45
|
host_info
|
|
45
46
|
end
|
|
46
47
|
|
|
47
|
-
conn = driver_dialect.connect(target_host_info, driver_props)
|
|
48
|
+
conn = with_driver_span { driver_dialect.connect(target_host_info, driver_props) }
|
|
48
49
|
|
|
49
50
|
if conn.nil?
|
|
50
51
|
raise Errors::AwsError,
|
|
@@ -83,7 +84,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
83
84
|
|
|
84
85
|
def internal_connect(host_info, driver_props, _wrapper_props, _is_initial_connection, _pipeline_callable)
|
|
85
86
|
driver_dialect = @service_container.dialect_service.driver_dialect
|
|
86
|
-
driver_dialect.connect(host_info, driver_props)
|
|
87
|
+
with_driver_span { driver_dialect.connect(host_info, driver_props) }
|
|
87
88
|
end
|
|
88
89
|
|
|
89
90
|
def execute(target_method_name, target_callable, *args, **, &)
|
|
@@ -92,7 +93,7 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
92
93
|
succeeded = false
|
|
93
94
|
|
|
94
95
|
begin
|
|
95
|
-
result = target_callable.call(*args, **, &)
|
|
96
|
+
result = with_driver_span { target_callable.call(*args, **, &) }
|
|
96
97
|
succeeded = true
|
|
97
98
|
result
|
|
98
99
|
ensure
|
|
@@ -103,6 +104,16 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
103
104
|
end
|
|
104
105
|
end
|
|
105
106
|
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
# Records the time spent in the driver itself as a span named after the driver.
|
|
111
|
+
def with_driver_span(&)
|
|
112
|
+
telemetry_factory = @service_container.telemetry_factory
|
|
113
|
+
return yield unless telemetry_factory.traces_in_use?
|
|
114
|
+
|
|
115
|
+
telemetry_factory.in_context(@service_container.dialect_service.driver_dialect.name, Utils::Telemetry::TraceLevel::NESTED, &)
|
|
116
|
+
end
|
|
106
117
|
end
|
|
107
118
|
end
|
|
108
119
|
end
|
|
@@ -20,9 +20,11 @@ require_relative '../host/host_info'
|
|
|
20
20
|
require_relative '../host/host_role'
|
|
21
21
|
require_relative '../host/host_availability'
|
|
22
22
|
require_relative '../logging'
|
|
23
|
+
require_relative '../services/host_service'
|
|
23
24
|
require_relative '../utils/rds_utils'
|
|
24
25
|
require_relative '../utils/rds_url_type'
|
|
25
26
|
require_relative '../utils/retry_util'
|
|
27
|
+
require_relative '../utils/telemetry/telemetry_const'
|
|
26
28
|
require_relative '../wrapper_property'
|
|
27
29
|
require_relative 'failover_mode'
|
|
28
30
|
|
|
@@ -32,14 +34,25 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
32
34
|
include Logging
|
|
33
35
|
|
|
34
36
|
ReaderFailoverResult = Data.define(:connection, :host_info)
|
|
37
|
+
FailoverCounters = Data.define(:triggered, :succeeded, :failed)
|
|
38
|
+
|
|
39
|
+
SUCCESSFUL_FAILOVER_ERRORS = [Errors::FailoverSuccessError, Errors::TransactionStateUnknownError].freeze
|
|
40
|
+
|
|
41
|
+
TELEMETRY_WRITER_FAILOVER = 'failover to writer host'
|
|
42
|
+
TELEMETRY_READER_FAILOVER = 'failover to replica'
|
|
35
43
|
|
|
36
44
|
def initialize(service_container, props = ::Concurrent::Map.new)
|
|
37
45
|
@service_container = service_container
|
|
38
46
|
@wrapper_props = props
|
|
47
|
+
@telemetry_factory = service_container.telemetry_factory
|
|
48
|
+
@telemetry_failover_additional_top_trace = PropertyDefinition::TELEMETRY_FAILOVER_ADDITIONAL_TOP_TRACE.get_bool(props)
|
|
49
|
+
@writer_failover_counters = failover_counters('writer_failover')
|
|
50
|
+
@reader_failover_counters = failover_counters('reader_failover')
|
|
39
51
|
|
|
40
52
|
@retry_util = Utils::RetryUtil.new(service_container)
|
|
41
53
|
@failover_timeout = PropertyDefinition::FAILOVER_TIMEOUT_SEC.get_float(props)
|
|
42
54
|
@reader_selector_strategy = PropertyDefinition::FAILOVER_READER_HOST_SELECTOR_STRATEGY.get(props)
|
|
55
|
+
Services::HostService.validate_strategy!(@reader_selector_strategy)
|
|
43
56
|
@failover_mode = nil
|
|
44
57
|
@rds_url_type = nil
|
|
45
58
|
@closed_explicitly = false
|
|
@@ -235,9 +248,60 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
235
248
|
end
|
|
236
249
|
|
|
237
250
|
if @failover_mode == FailoverMode::STRICT_WRITER
|
|
238
|
-
failover_writer
|
|
251
|
+
with_failover_telemetry(TELEMETRY_WRITER_FAILOVER) { count_failover(@writer_failover_counters) { failover_writer } }
|
|
239
252
|
else
|
|
240
|
-
failover_reader
|
|
253
|
+
with_failover_telemetry(TELEMETRY_READER_FAILOVER) { count_failover(@reader_failover_counters) { failover_reader } }
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def failover_counters(prefix)
|
|
258
|
+
FailoverCounters.new(
|
|
259
|
+
triggered: @telemetry_factory.create_counter("#{prefix}.triggered.count"),
|
|
260
|
+
succeeded: @telemetry_factory.create_counter("#{prefix}.completed.success.count"),
|
|
261
|
+
failed: @telemetry_factory.create_counter("#{prefix}.completed.failed.count")
|
|
262
|
+
)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Records a failover as a span. Failover always ends in an error: a successful one ends in one
|
|
266
|
+
# of SUCCESSFUL_FAILOVER_ERRORS, which marks the span as succeeded, and any other error marks it
|
|
267
|
+
# as failed. With telemetry_failover_additional_top_trace enabled, the span is also submitted as
|
|
268
|
+
# a trace of its own.
|
|
269
|
+
def with_failover_telemetry(span_name)
|
|
270
|
+
telemetry_context = nil
|
|
271
|
+
@telemetry_factory.in_context(span_name, Utils::Telemetry::TraceLevel::NESTED) do |context|
|
|
272
|
+
telemetry_context = context
|
|
273
|
+
yield
|
|
274
|
+
rescue *SUCCESSFUL_FAILOVER_ERRORS => e
|
|
275
|
+
context.success = true
|
|
276
|
+
context.record_exception(e)
|
|
277
|
+
raise
|
|
278
|
+
rescue StandardError => e
|
|
279
|
+
context.success = false
|
|
280
|
+
context.record_exception(e)
|
|
281
|
+
raise
|
|
282
|
+
end
|
|
283
|
+
ensure
|
|
284
|
+
submit_failover_copy(telemetry_context) if telemetry_context && @telemetry_failover_additional_top_trace
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Runs while the failover's outcome is propagating, so an error here must not replace it.
|
|
288
|
+
def submit_failover_copy(telemetry_context)
|
|
289
|
+
@telemetry_factory.post_copy(telemetry_context, Utils::Telemetry::TraceLevel::FORCE_TOP_LEVEL)
|
|
290
|
+
rescue StandardError => e
|
|
291
|
+
logger.debug { "Unable to submit the failover trace as a trace of its own: #{e.message}" }
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Counts a failover as triggered, and then as succeeded or failed according to how it ends.
|
|
295
|
+
def count_failover(counters)
|
|
296
|
+
counters.triggered.inc
|
|
297
|
+
begin
|
|
298
|
+
yield
|
|
299
|
+
rescue *SUCCESSFUL_FAILOVER_ERRORS
|
|
300
|
+
counters.succeeded.inc
|
|
301
|
+
raise
|
|
302
|
+
rescue StandardError
|
|
303
|
+
counters.failed.inc
|
|
304
|
+
raise
|
|
241
305
|
end
|
|
242
306
|
end
|
|
243
307
|
|
|
@@ -414,7 +478,8 @@ module AwsAdvancedRubyDriverWrapper
|
|
|
414
478
|
Host::HostRole::READER,
|
|
415
479
|
@reader_selector_strategy
|
|
416
480
|
)
|
|
417
|
-
rescue StandardError
|
|
481
|
+
rescue StandardError => e
|
|
482
|
+
logger.warn("Failed to select a reader using the '#{@reader_selector_strategy}' host selection strategy: #{e.message}")
|
|
418
483
|
nil
|
|
419
484
|
end
|
|
420
485
|
|
|
@@ -0,0 +1,266 @@
|
|
|
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 'concurrent'
|
|
18
|
+
require_relative '../../errors'
|
|
19
|
+
require_relative '../../property_definition'
|
|
20
|
+
require_relative '../iam_auth_plugin'
|
|
21
|
+
require_relative '../../utils/iam_auth_utils'
|
|
22
|
+
require_relative '../../utils/rds_utils'
|
|
23
|
+
require_relative '../../utils/rds_url_type'
|
|
24
|
+
require_relative '../../utils/saml_utils'
|
|
25
|
+
require_relative '../../utils/telemetry/telemetry_const'
|
|
26
|
+
|
|
27
|
+
module AwsAdvancedRubyDriverWrapper
|
|
28
|
+
module Plugins
|
|
29
|
+
module FederatedAuth
|
|
30
|
+
# Shared connect flow for the SAML-based authentication plugins (AD FS and
|
|
31
|
+
# Okta).
|
|
32
|
+
#
|
|
33
|
+
# A subclass defines +self.fetch_saml_assertion+, +TELEMETRY_PREFIX+ (the prefix of its metric
|
|
34
|
+
# names) and +TELEMETRY_FETCH_SAML+ (the name of the span its SAML assertion fetch is recorded as).
|
|
35
|
+
class BaseSamlAuthPlugin
|
|
36
|
+
SUBSCRIBED_METHODS = Set['connect', 'internal_connect'].freeze
|
|
37
|
+
# Reuse the IAM plugin's cache name.
|
|
38
|
+
IAM_TOKEN_CACHE_NAME = AwsAdvancedRubyDriverWrapper::Plugins::IamAuthPlugin::IAM_TOKEN_CACHE_NAME
|
|
39
|
+
|
|
40
|
+
attr_reader :subscribed_methods
|
|
41
|
+
|
|
42
|
+
def initialize(service_container, props = ::Concurrent::Map.new)
|
|
43
|
+
ensure_aws_sdk!
|
|
44
|
+
@service_container = service_container
|
|
45
|
+
@wrapper_props = props
|
|
46
|
+
expiration = PropertyDefinition::IAM_EXPIRATION_SEC.get_float(props)
|
|
47
|
+
PropertyDefinition::IAM_EXPIRATION_SEC.validate!(expiration)
|
|
48
|
+
service_container.storage_service.register(IAM_TOKEN_CACHE_NAME, ttl: expiration)
|
|
49
|
+
@subscribed_methods = SUBSCRIBED_METHODS
|
|
50
|
+
|
|
51
|
+
# Each subclass names its metrics after itself, and its SAML assertion span after its IdP.
|
|
52
|
+
@telemetry_factory = service_container.telemetry_factory
|
|
53
|
+
@fetch_token_counter = @telemetry_factory.create_counter("#{self.class::TELEMETRY_PREFIX}.fetch_token.count")
|
|
54
|
+
@telemetry_factory.create_gauge(
|
|
55
|
+
"#{self.class::TELEMETRY_PREFIX}.token_cache.size",
|
|
56
|
+
&IamAuthPlugin.token_cache_size(service_container.storage_service)
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def connect(host_info, driver_props, _is_initial_connection, pipeline_callable)
|
|
61
|
+
saml_connect(host_info, driver_props, pipeline_callable)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def internal_connect(host_info, driver_props, wrapper_props_override, _is_initial_connection, pipeline_callable)
|
|
65
|
+
saml_connect(host_info, driver_props, pipeline_callable, wrapper_props_override || @wrapper_props)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.clear_cache(storage_service)
|
|
69
|
+
storage_service.clear(IAM_TOKEN_CACHE_NAME)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Exchanges a SAML assertion for temporary AWS credentials via STS
|
|
73
|
+
# AssumeRoleWithSAML.
|
|
74
|
+
#
|
|
75
|
+
# @return [Aws::Credentials]
|
|
76
|
+
def self.credentials_from_saml(saml_assertion, role_arn:, idp_arn:, region:)
|
|
77
|
+
require 'aws-sdk-core'
|
|
78
|
+
# AssumeRoleWithSAML is unsigned, so no AWS credentials are needed. Passing
|
|
79
|
+
# credentials: nil keeps the SDK from running the default credential chain (which
|
|
80
|
+
# on a credential-less, non-EC2 host wastes ~1s probing IMDS and logs a misleading
|
|
81
|
+
# "Error retrieving instance profile credentials").
|
|
82
|
+
client_opts = { credentials: nil }
|
|
83
|
+
client_opts[:region] = region if region && !region.to_s.empty?
|
|
84
|
+
resp = Aws::STS::Client.new(**client_opts).assume_role_with_saml(
|
|
85
|
+
role_arn:, principal_arn: idp_arn, saml_assertion:
|
|
86
|
+
)
|
|
87
|
+
creds = resp.credentials
|
|
88
|
+
Aws::Credentials.new(creds.access_key_id, creds.secret_access_key, creds.session_token)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def saml_connect(host_info, driver_props, pipeline_callable, wrapper_props_override = nil)
|
|
94
|
+
wrapper_props_override ||= @wrapper_props
|
|
95
|
+
saml_props = wrapper_props_override
|
|
96
|
+
|
|
97
|
+
validate_required!(saml_props)
|
|
98
|
+
|
|
99
|
+
# The connection's driver_props :user/:password double as the fallback IdP login when
|
|
100
|
+
# idp_username / idp_password are not set. We overwrite driver_props with db_user and the
|
|
101
|
+
# RDS token further down, and that hash is reused across reconnects (failover, retry,
|
|
102
|
+
# Blue/Green rerouting), so capture the fallback into the wrapper props once, up front.
|
|
103
|
+
# A later reconnect then still logs in to the IdP with the original credentials rather
|
|
104
|
+
# than db_user and a stale token.
|
|
105
|
+
persist_idp_credentials_fallback(saml_props, driver_props)
|
|
106
|
+
db_user = PropertyDefinition::DB_USER.get(saml_props)
|
|
107
|
+
idp_username = PropertyDefinition::IDP_USERNAME.get(saml_props)
|
|
108
|
+
idp_password = PropertyDefinition::IDP_PASSWORD.get(saml_props)
|
|
109
|
+
|
|
110
|
+
host = Utils::IamAuthUtils.resolve_host(
|
|
111
|
+
PropertyDefinition::IAM_HOST.get(wrapper_props_override), host_info
|
|
112
|
+
)
|
|
113
|
+
port = Utils::IamAuthUtils.resolve_port(
|
|
114
|
+
PropertyDefinition::IAM_PORT.get(wrapper_props_override),
|
|
115
|
+
host_info,
|
|
116
|
+
@service_container.dialect_service.db_dialect.default_port
|
|
117
|
+
)
|
|
118
|
+
rds_type = Utils::RdsUtils.identify_rds_type(host)
|
|
119
|
+
|
|
120
|
+
# Only a global writer cluster with no explicit iam_region needs AWS credentials to look
|
|
121
|
+
# its region up. In that one case the SAML exchange has to happen before the region lookup
|
|
122
|
+
# (and so before the token-cache check); every other case - including a global cluster
|
|
123
|
+
# whose iam_region is set - defers the IdP round-trip to the token-cache miss path, so a
|
|
124
|
+
# pool or failover storm does not trigger one IdP login per connection.
|
|
125
|
+
explicit_region = PropertyDefinition::IAM_REGION.get(wrapper_props_override)
|
|
126
|
+
preassumed_credentials = nil
|
|
127
|
+
if rds_type == Utils::RdsUrlType::RDS_GLOBAL_WRITER_CLUSTER && (explicit_region.nil? || explicit_region.to_s.empty?)
|
|
128
|
+
preassumed_credentials = obtain_credentials(saml_props, nil, idp_username, idp_password)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
region = Utils::IamAuthUtils.region_for(
|
|
132
|
+
host:, props: wrapper_props_override, rds_type:,
|
|
133
|
+
credentials_provider: preassumed_credentials,
|
|
134
|
+
rds_client_func: preassumed_credentials ? -> { Aws::RDS::Client.new(credentials: preassumed_credentials) } : nil
|
|
135
|
+
)
|
|
136
|
+
unless region
|
|
137
|
+
raise Errors::SamlAuthError,
|
|
138
|
+
"#{self.class.name}: unable to determine connection region. " \
|
|
139
|
+
"If you are using a non-standard RDS URL, please set the 'iam_region' property."
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
fingerprint = Utils::SamlUtils.principal_fingerprint(
|
|
143
|
+
PropertyDefinition::IAM_ROLE_ARN.get(saml_props),
|
|
144
|
+
PropertyDefinition::IAM_IDP_ARN.get(saml_props)
|
|
145
|
+
)
|
|
146
|
+
cache_key = Utils::IamAuthUtils.cache_key(region, host, port, db_user, fingerprint)
|
|
147
|
+
entry = @service_container.storage_service.get(IAM_TOKEN_CACHE_NAME, cache_key)
|
|
148
|
+
expiration = PropertyDefinition::IAM_EXPIRATION_SEC.get_float(wrapper_props_override)
|
|
149
|
+
PropertyDefinition::IAM_EXPIRATION_SEC.validate!(expiration)
|
|
150
|
+
|
|
151
|
+
if Utils::IamAuthUtils.valid_entry?(entry)
|
|
152
|
+
token = entry.token
|
|
153
|
+
is_cached_token = true
|
|
154
|
+
else
|
|
155
|
+
token = fetch_and_cache_token(saml_props, region, host, port, db_user, cache_key, expiration,
|
|
156
|
+
idp_username, idp_password, preassumed_credentials)
|
|
157
|
+
is_cached_token = false
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
user_key = @service_container.dialect_service.driver_dialect.user_property_key
|
|
161
|
+
driver_props[user_key] = db_user
|
|
162
|
+
driver_props[:password] = token
|
|
163
|
+
|
|
164
|
+
begin
|
|
165
|
+
pipeline_callable.call
|
|
166
|
+
rescue StandardError => e
|
|
167
|
+
raise unless is_cached_token && @service_container.dialect_service.login_error?(e)
|
|
168
|
+
|
|
169
|
+
driver_props[:password] = fetch_and_cache_token(saml_props, region, host, port, db_user, cache_key,
|
|
170
|
+
expiration, idp_username, idp_password, nil)
|
|
171
|
+
pipeline_callable.call
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Mints a fresh RDS token signed with SAML-derived credentials and caches it.
|
|
176
|
+
def fetch_and_cache_token(saml_props, region, host, port, db_user, cache_key, expiration,
|
|
177
|
+
idp_username, idp_password, credentials)
|
|
178
|
+
credentials ||= obtain_credentials(saml_props, region, idp_username, idp_password)
|
|
179
|
+
@fetch_token_counter.inc
|
|
180
|
+
token = Utils::IamAuthUtils.generate_token(
|
|
181
|
+
region:, hostname: host, port:, user: db_user, credentials_provider: credentials,
|
|
182
|
+
telemetry_factory: @telemetry_factory
|
|
183
|
+
)
|
|
184
|
+
@service_container.storage_service.set(
|
|
185
|
+
IAM_TOKEN_CACHE_NAME, cache_key, Utils::IamAuthUtils.build_token_entry(token, expiration)
|
|
186
|
+
)
|
|
187
|
+
token
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Captures the connection's user/password as the IdP login when idp_username /
|
|
191
|
+
# idp_password are unset, persisting them into the wrapper props so the value survives
|
|
192
|
+
# the driver_props overwrite later in the flow and any subsequent reconnect. Never stores
|
|
193
|
+
# a nil (Concurrent::Map rejects nil values), so a connection with no fallback simply
|
|
194
|
+
# keeps idp_username / idp_password unset.
|
|
195
|
+
def persist_idp_credentials_fallback(saml_props, driver_props)
|
|
196
|
+
if PropertyDefinition::IDP_USERNAME.get(saml_props).nil?
|
|
197
|
+
fallback_user = driver_props[:user] || driver_props[:username]
|
|
198
|
+
PropertyDefinition::IDP_USERNAME.set(saml_props, fallback_user) unless fallback_user.nil?
|
|
199
|
+
end
|
|
200
|
+
return unless PropertyDefinition::IDP_PASSWORD.get(saml_props).nil?
|
|
201
|
+
|
|
202
|
+
fallback_password = driver_props[:password]
|
|
203
|
+
PropertyDefinition::IDP_PASSWORD.set(saml_props, fallback_password) unless fallback_password.nil?
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Fetches a SAML assertion from the IdP and exchanges it for AWS credentials.
|
|
207
|
+
def obtain_credentials(saml_props, region, idp_username, idp_password)
|
|
208
|
+
assertion = @telemetry_factory.in_context(self.class::TELEMETRY_FETCH_SAML, Utils::Telemetry::TraceLevel::NESTED,
|
|
209
|
+
record_failure: true) do
|
|
210
|
+
self.class.fetch_saml_assertion(**saml_fetch_options(saml_props, idp_username, idp_password))
|
|
211
|
+
end
|
|
212
|
+
self.class.credentials_from_saml(
|
|
213
|
+
assertion,
|
|
214
|
+
role_arn: PropertyDefinition::IAM_ROLE_ARN.get(saml_props),
|
|
215
|
+
idp_arn: PropertyDefinition::IAM_IDP_ARN.get(saml_props),
|
|
216
|
+
region:
|
|
217
|
+
)
|
|
218
|
+
rescue Errors::SamlAuthError
|
|
219
|
+
raise
|
|
220
|
+
rescue StandardError => e
|
|
221
|
+
# Surface IdP/STS failures (DNS, TLS, STS errors) as a SamlAuthError.
|
|
222
|
+
raise Errors::SamlAuthError, "#{self.class.name}: failed to obtain credentials via SAML: #{e.message}"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def validate_required!(saml_props)
|
|
226
|
+
missing = required_identity(saml_props).select { |_name, value| value.nil? || value.to_s.empty? }
|
|
227
|
+
return if missing.empty?
|
|
228
|
+
|
|
229
|
+
names = missing.map(&:first)
|
|
230
|
+
raise Errors::SamlAuthError,
|
|
231
|
+
"#{self.class.name}: #{names.join(', ')} #{names.length > 1 ? 'are' : 'is'} required"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# The properties that must be present for this IdP. Subclasses override to
|
|
235
|
+
# append their own (e.g. Okta's app_id).
|
|
236
|
+
def required_identity(saml_props)
|
|
237
|
+
[
|
|
238
|
+
['db_user', PropertyDefinition::DB_USER.get(saml_props)],
|
|
239
|
+
['idp_endpoint', PropertyDefinition::IDP_ENDPOINT.get(saml_props)],
|
|
240
|
+
['iam_role_arn', PropertyDefinition::IAM_ROLE_ARN.get(saml_props)],
|
|
241
|
+
['iam_idp_arn', PropertyDefinition::IAM_IDP_ARN.get(saml_props)]
|
|
242
|
+
]
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# The keyword arguments passed to .fetch_saml_assertion. Subclasses override.
|
|
246
|
+
def saml_fetch_options(saml_props, idp_username, idp_password)
|
|
247
|
+
{
|
|
248
|
+
idp_endpoint: PropertyDefinition::IDP_ENDPOINT.get(saml_props),
|
|
249
|
+
idp_username:,
|
|
250
|
+
idp_password:,
|
|
251
|
+
ssl_insecure: PropertyDefinition::SSL_INSECURE.get_bool(saml_props),
|
|
252
|
+
socket_timeout_sec: PropertyDefinition::HTTP_CLIENT_SOCKET_TIMEOUT_SEC.get_float(saml_props),
|
|
253
|
+
connect_timeout_sec: PropertyDefinition::HTTP_CLIENT_CONNECT_TIMEOUT_SEC.get_float(saml_props)
|
|
254
|
+
}
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def ensure_aws_sdk!
|
|
258
|
+
require 'aws-sdk-rds'
|
|
259
|
+
rescue LoadError
|
|
260
|
+
raise LoadError,
|
|
261
|
+
"The SAML auth plugins require 'aws-sdk-rds'. Add it to your Gemfile: gem 'aws-sdk-rds'"
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|