1# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"). You
4# may not use this file except in compliance with the License. A copy of
5# the License is located at
6#
7# http://aws.amazon.com/apache2.0/
8#
9# or in the "license" file accompanying this file. This file is
10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11# ANY KIND, either express or implied. See the License for the specific
12# language governing permissions and limitations under the License.
13"""Resolves regions and endpoints.
14
15This module implements endpoint resolution, including resolving endpoints for a
16given service and region and resolving the available endpoints for a service
17in a specific AWS partition.
18"""
19
20import copy
21import logging
22import re
23from enum import Enum
24
25import jmespath
26
27from botocore import UNSIGNED, xform_name
28from botocore.auth import AUTH_TYPE_MAPS, HAS_CRT
29from botocore.crt import CRT_SUPPORTED_AUTH_TYPES
30from botocore.endpoint_provider import EndpointProvider
31from botocore.exceptions import (
32 EndpointProviderError,
33 EndpointVariantError,
34 InvalidEndpointConfigurationError,
35 InvalidHostLabelError,
36 MissingDependencyException,
37 NoRegionError,
38 ParamValidationError,
39 UnknownEndpointResolutionBuiltInName,
40 UnknownRegionError,
41 UnknownSignatureVersionError,
42 UnsupportedS3AccesspointConfigurationError,
43 UnsupportedS3ConfigurationError,
44 UnsupportedS3ControlArnError,
45 UnsupportedS3ControlConfigurationError,
46)
47from botocore.useragent import register_feature_id
48from botocore.utils import ensure_boolean, instance_cache
49
50LOG = logging.getLogger(__name__)
51DEFAULT_URI_TEMPLATE = '{service}.{region}.{dnsSuffix}' # noqa
52DEFAULT_SERVICE_DATA = {'endpoints': {}}
53
54
55class BaseEndpointResolver:
56 """Resolves regions and endpoints. Must be subclassed."""
57
58 def construct_endpoint(self, service_name, region_name=None):
59 """Resolves an endpoint for a service and region combination.
60
61 :type service_name: string
62 :param service_name: Name of the service to resolve an endpoint for
63 (e.g., s3)
64
65 :type region_name: string
66 :param region_name: Region/endpoint name to resolve (e.g., us-east-1)
67 if no region is provided, the first found partition-wide endpoint
68 will be used if available.
69
70 :rtype: dict
71 :return: Returns a dict containing the following keys:
72 - partition: (string, required) Resolved partition name
73 - endpointName: (string, required) Resolved endpoint name
74 - hostname: (string, required) Hostname to use for this endpoint
75 - sslCommonName: (string) sslCommonName to use for this endpoint.
76 - credentialScope: (dict) Signature version 4 credential scope
77 - region: (string) region name override when signing.
78 - service: (string) service name override when signing.
79 - signatureVersions: (list<string>) A list of possible signature
80 versions, including s3, v4, v2, and s3v4
81 - protocols: (list<string>) A list of supported protocols
82 (e.g., http, https)
83 - ...: Other keys may be included as well based on the metadata
84 """
85 raise NotImplementedError
86
87 def get_available_partitions(self):
88 """Lists the partitions available to the endpoint resolver.
89
90 :return: Returns a list of partition names (e.g., ["aws", "aws-cn"]).
91 """
92 raise NotImplementedError
93
94 def get_available_endpoints(
95 self, service_name, partition_name='aws', allow_non_regional=False
96 ):
97 """Lists the endpoint names of a particular partition.
98
99 :type service_name: string
100 :param service_name: Name of a service to list endpoint for (e.g., s3)
101
102 :type partition_name: string
103 :param partition_name: Name of the partition to limit endpoints to.
104 (e.g., aws for the public AWS endpoints, aws-cn for AWS China
105 endpoints, aws-us-gov for AWS GovCloud (US) Endpoints, etc.
106
107 :type allow_non_regional: bool
108 :param allow_non_regional: Set to True to include endpoints that are
109 not regional endpoints (e.g., s3-external-1,
110 fips-us-gov-west-1, etc).
111 :return: Returns a list of endpoint names (e.g., ["us-east-1"]).
112 """
113 raise NotImplementedError
114
115
116class EndpointResolver(BaseEndpointResolver):
117 """Resolves endpoints based on partition endpoint metadata"""
118
119 _UNSUPPORTED_DUALSTACK_PARTITIONS = ['aws-iso', 'aws-iso-b']
120
121 def __init__(self, endpoint_data, uses_builtin_data=False):
122 """
123 :type endpoint_data: dict
124 :param endpoint_data: A dict of partition data.
125
126 :type uses_builtin_data: boolean
127 :param uses_builtin_data: Whether the endpoint data originates in the
128 package's data directory.
129 """
130 if 'partitions' not in endpoint_data:
131 raise ValueError('Missing "partitions" in endpoint data')
132 self._endpoint_data = endpoint_data
133 self.uses_builtin_data = uses_builtin_data
134
135 def get_service_endpoints_data(self, service_name, partition_name='aws'):
136 for partition in self._endpoint_data['partitions']:
137 if partition['partition'] != partition_name:
138 continue
139 services = partition['services']
140 if service_name not in services:
141 continue
142 return services[service_name]['endpoints']
143
144 def get_available_partitions(self):
145 result = []
146 for partition in self._endpoint_data['partitions']:
147 result.append(partition['partition'])
148 return result
149
150 def get_available_endpoints(
151 self,
152 service_name,
153 partition_name='aws',
154 allow_non_regional=False,
155 endpoint_variant_tags=None,
156 ):
157 result = []
158 for partition in self._endpoint_data['partitions']:
159 if partition['partition'] != partition_name:
160 continue
161 services = partition['services']
162 if service_name not in services:
163 continue
164 service_endpoints = services[service_name]['endpoints']
165 for endpoint_name in service_endpoints:
166 is_regional_endpoint = endpoint_name in partition['regions']
167 # Only regional endpoints can be modeled with variants
168 if endpoint_variant_tags and is_regional_endpoint:
169 variant_data = self._retrieve_variant_data(
170 service_endpoints[endpoint_name], endpoint_variant_tags
171 )
172 if variant_data:
173 result.append(endpoint_name)
174 elif allow_non_regional or is_regional_endpoint:
175 result.append(endpoint_name)
176 return result
177
178 def get_partition_dns_suffix(
179 self, partition_name, endpoint_variant_tags=None
180 ):
181 for partition in self._endpoint_data['partitions']:
182 if partition['partition'] == partition_name:
183 if endpoint_variant_tags:
184 variant = self._retrieve_variant_data(
185 partition.get('defaults'), endpoint_variant_tags
186 )
187 if variant and 'dnsSuffix' in variant:
188 return variant['dnsSuffix']
189 else:
190 return partition['dnsSuffix']
191 return None
192
193 def construct_endpoint(
194 self,
195 service_name,
196 region_name=None,
197 partition_name=None,
198 use_dualstack_endpoint=False,
199 use_fips_endpoint=False,
200 ):
201 if (
202 service_name == 's3'
203 and use_dualstack_endpoint
204 and region_name is None
205 ):
206 region_name = 'us-east-1'
207
208 if partition_name is not None:
209 valid_partition = None
210 for partition in self._endpoint_data['partitions']:
211 if partition['partition'] == partition_name:
212 valid_partition = partition
213
214 if valid_partition is not None:
215 result = self._endpoint_for_partition(
216 valid_partition,
217 service_name,
218 region_name,
219 use_dualstack_endpoint,
220 use_fips_endpoint,
221 True,
222 )
223 return result
224 return None
225
226 # Iterate over each partition until a match is found.
227 for partition in self._endpoint_data['partitions']:
228 if use_dualstack_endpoint and (
229 partition['partition']
230 in self._UNSUPPORTED_DUALSTACK_PARTITIONS
231 ):
232 continue
233 result = self._endpoint_for_partition(
234 partition,
235 service_name,
236 region_name,
237 use_dualstack_endpoint,
238 use_fips_endpoint,
239 )
240 if result:
241 return result
242
243 def get_partition_for_region(self, region_name):
244 for partition in self._endpoint_data['partitions']:
245 if self._region_match(partition, region_name):
246 return partition['partition']
247 raise UnknownRegionError(
248 region_name=region_name,
249 error_msg='No partition found for provided region_name.',
250 )
251
252 def _endpoint_for_partition(
253 self,
254 partition,
255 service_name,
256 region_name,
257 use_dualstack_endpoint,
258 use_fips_endpoint,
259 force_partition=False,
260 ):
261 partition_name = partition["partition"]
262 if (
263 use_dualstack_endpoint
264 and partition_name in self._UNSUPPORTED_DUALSTACK_PARTITIONS
265 ):
266 error_msg = (
267 "Dualstack endpoints are currently not supported"
268 f" for {partition_name} partition"
269 )
270 raise EndpointVariantError(tags=['dualstack'], error_msg=error_msg)
271
272 # Get the service from the partition, or an empty template.
273 service_data = partition['services'].get(
274 service_name, DEFAULT_SERVICE_DATA
275 )
276 # Use the partition endpoint if no region is supplied.
277 if region_name is None:
278 if 'partitionEndpoint' in service_data:
279 region_name = service_data['partitionEndpoint']
280 else:
281 raise NoRegionError()
282
283 resolve_kwargs = {
284 'partition': partition,
285 'service_name': service_name,
286 'service_data': service_data,
287 'endpoint_name': region_name,
288 'use_dualstack_endpoint': use_dualstack_endpoint,
289 'use_fips_endpoint': use_fips_endpoint,
290 }
291
292 # Attempt to resolve the exact region for this partition.
293 if region_name in service_data['endpoints']:
294 return self._resolve(**resolve_kwargs)
295
296 # Check to see if the endpoint provided is valid for the partition.
297 if self._region_match(partition, region_name) or force_partition:
298 # Use the partition endpoint if set and not regionalized.
299 partition_endpoint = service_data.get('partitionEndpoint')
300 is_regionalized = service_data.get('isRegionalized', True)
301 if partition_endpoint and not is_regionalized:
302 LOG.debug(
303 'Using partition endpoint for %s, %s: %s',
304 service_name,
305 region_name,
306 partition_endpoint,
307 )
308 resolve_kwargs['endpoint_name'] = partition_endpoint
309 return self._resolve(**resolve_kwargs)
310 LOG.debug(
311 'Creating a regex based endpoint for %s, %s',
312 service_name,
313 region_name,
314 )
315 return self._resolve(**resolve_kwargs)
316
317 def _region_match(self, partition, region_name):
318 if region_name in partition['regions']:
319 return True
320 if 'regionRegex' in partition:
321 return re.compile(partition['regionRegex']).match(region_name)
322 return False
323
324 def _retrieve_variant_data(self, endpoint_data, tags):
325 variants = endpoint_data.get('variants', [])
326 for variant in variants:
327 if set(variant['tags']) == set(tags):
328 result = variant.copy()
329 return result
330
331 def _create_tag_list(self, use_dualstack_endpoint, use_fips_endpoint):
332 tags = []
333 if use_dualstack_endpoint:
334 tags.append('dualstack')
335 if use_fips_endpoint:
336 tags.append('fips')
337 return tags
338
339 def _resolve_variant(
340 self, tags, endpoint_data, service_defaults, partition_defaults
341 ):
342 result = {}
343 for variants in [endpoint_data, service_defaults, partition_defaults]:
344 variant = self._retrieve_variant_data(variants, tags)
345 if variant:
346 self._merge_keys(variant, result)
347 return result
348
349 def _resolve(
350 self,
351 partition,
352 service_name,
353 service_data,
354 endpoint_name,
355 use_dualstack_endpoint,
356 use_fips_endpoint,
357 ):
358 endpoint_data = service_data.get('endpoints', {}).get(
359 endpoint_name, {}
360 )
361
362 if endpoint_data.get('deprecated'):
363 LOG.warning(
364 'Client is configured with the deprecated endpoint: %s',
365 endpoint_name,
366 )
367
368 service_defaults = service_data.get('defaults', {})
369 partition_defaults = partition.get('defaults', {})
370 tags = self._create_tag_list(use_dualstack_endpoint, use_fips_endpoint)
371
372 if tags:
373 result = self._resolve_variant(
374 tags, endpoint_data, service_defaults, partition_defaults
375 )
376 if result == {}:
377 error_msg = (
378 f"Endpoint does not exist for {service_name} "
379 f"in region {endpoint_name}"
380 )
381 raise EndpointVariantError(tags=tags, error_msg=error_msg)
382 self._merge_keys(endpoint_data, result)
383 else:
384 result = endpoint_data
385
386 # If dnsSuffix has not already been consumed from a variant definition
387 if 'dnsSuffix' not in result:
388 result['dnsSuffix'] = partition['dnsSuffix']
389
390 result['partition'] = partition['partition']
391 result['endpointName'] = endpoint_name
392
393 # Merge in the service defaults then the partition defaults.
394 self._merge_keys(service_defaults, result)
395 self._merge_keys(partition_defaults, result)
396
397 result['hostname'] = self._expand_template(
398 partition,
399 result['hostname'],
400 service_name,
401 endpoint_name,
402 result['dnsSuffix'],
403 )
404 if 'sslCommonName' in result:
405 result['sslCommonName'] = self._expand_template(
406 partition,
407 result['sslCommonName'],
408 service_name,
409 endpoint_name,
410 result['dnsSuffix'],
411 )
412
413 return result
414
415 def _merge_keys(self, from_data, result):
416 for key in from_data:
417 if key not in result:
418 result[key] = from_data[key]
419
420 def _expand_template(
421 self, partition, template, service_name, endpoint_name, dnsSuffix
422 ):
423 return template.format(
424 service=service_name, region=endpoint_name, dnsSuffix=dnsSuffix
425 )
426
427
428class EndpointResolverBuiltins(str, Enum):
429 # The AWS Region configured for the SDK client (str)
430 AWS_REGION = "AWS::Region"
431 # Whether the UseFIPSEndpoint configuration option has been enabled for
432 # the SDK client (bool)
433 AWS_USE_FIPS = "AWS::UseFIPS"
434 # Whether the UseDualStackEndpoint configuration option has been enabled
435 # for the SDK client (bool)
436 AWS_USE_DUALSTACK = "AWS::UseDualStack"
437 # Whether the global endpoint should be used with STS, rather than the
438 # regional endpoint for us-east-1 (bool)
439 AWS_STS_USE_GLOBAL_ENDPOINT = "AWS::STS::UseGlobalEndpoint"
440 # Whether the global endpoint should be used with S3, rather than the
441 # regional endpoint for us-east-1 (bool)
442 AWS_S3_USE_GLOBAL_ENDPOINT = "AWS::S3::UseGlobalEndpoint"
443 # Whether S3 Transfer Acceleration has been requested (bool)
444 AWS_S3_ACCELERATE = "AWS::S3::Accelerate"
445 # Whether S3 Force Path Style has been enabled (bool)
446 AWS_S3_FORCE_PATH_STYLE = "AWS::S3::ForcePathStyle"
447 # Whether to use the ARN region or raise an error when ARN and client
448 # region differ (for s3 service only, bool)
449 AWS_S3_USE_ARN_REGION = "AWS::S3::UseArnRegion"
450 # Whether to use the ARN region or raise an error when ARN and client
451 # region differ (for s3-control service only, bool)
452 AWS_S3CONTROL_USE_ARN_REGION = 'AWS::S3Control::UseArnRegion'
453 # Whether multi-region access points (MRAP) should be disabled (bool)
454 AWS_S3_DISABLE_MRAP = "AWS::S3::DisableMultiRegionAccessPoints"
455 # Whether a custom endpoint has been configured (str)
456 SDK_ENDPOINT = "SDK::Endpoint"
457 # An AWS account ID that can be optionally configured for the SDK client (str)
458 ACCOUNT_ID = "AWS::Auth::AccountId"
459 # Whether an endpoint should include an account ID (str)
460 ACCOUNT_ID_ENDPOINT_MODE = "AWS::Auth::AccountIdEndpointMode"
461
462
463class EndpointRulesetResolver:
464 """Resolves endpoints using a service's endpoint ruleset"""
465
466 def __init__(
467 self,
468 endpoint_ruleset_data,
469 partition_data,
470 service_model,
471 builtins,
472 client_context,
473 event_emitter,
474 use_ssl=True,
475 requested_auth_scheme=None,
476 ):
477 self._provider = EndpointProvider(
478 ruleset_data=endpoint_ruleset_data,
479 partition_data=partition_data,
480 )
481 self._param_definitions = self._provider.ruleset.parameters
482 self._service_model = service_model
483 self._builtins = builtins
484 self._client_context = client_context
485 self._event_emitter = event_emitter
486 self._use_ssl = use_ssl
487 self._requested_auth_scheme = requested_auth_scheme
488 self._instance_cache = {}
489
490 def construct_endpoint(
491 self,
492 operation_model,
493 call_args,
494 request_context,
495 ):
496 """Invokes the provider with params defined in the service's ruleset"""
497 if call_args is None:
498 call_args = {}
499
500 if request_context is None:
501 request_context = {}
502
503 provider_params = self._get_provider_params(
504 operation_model, call_args, request_context
505 )
506 LOG.debug(
507 'Calling endpoint provider with parameters: %s', provider_params
508 )
509 try:
510 provider_result = self._provider.resolve_endpoint(
511 **provider_params
512 )
513 except EndpointProviderError as ex:
514 botocore_exception = self.ruleset_error_to_botocore_exception(
515 ex, provider_params
516 )
517 if botocore_exception is None:
518 raise
519 else:
520 raise botocore_exception from ex
521 LOG.debug('Endpoint provider result: %s', provider_result.url)
522
523 # The endpoint provider does not support non-secure transport.
524 if (
525 not self._use_ssl
526 and provider_result.url.startswith('https://')
527 and 'Endpoint' not in provider_params
528 ):
529 provider_result = provider_result._replace(
530 url=f'http://{provider_result.url[8:]}'
531 )
532
533 # Multi-valued headers are not supported in botocore. Replace the list
534 # of values returned for each header with just its first entry,
535 # dropping any additionally entries.
536 provider_result = provider_result._replace(
537 headers={
538 key: val[0] for key, val in provider_result.headers.items()
539 }
540 )
541
542 return provider_result
543
544 def _get_provider_params(
545 self, operation_model, call_args, request_context
546 ):
547 """Resolve a value for each parameter defined in the service's ruleset
548
549 The resolution order for parameter values is:
550 1. Operation-specific static context values from the service definition
551 2. Operation-specific dynamic context values from API parameters
552 3. Client-specific context parameters
553 4. Built-in values such as region, FIPS usage, ...
554 """
555 provider_params = {}
556 # Builtin values can be customized for each operation by hooks
557 # subscribing to the ``before-endpoint-resolution.*`` event.
558 customized_builtins = self._get_customized_builtins(
559 operation_model, call_args, request_context
560 )
561 for param_name, param_def in self._param_definitions.items():
562 param_val = self._resolve_param_from_context(
563 param_name=param_name,
564 operation_model=operation_model,
565 call_args=call_args,
566 )
567 if param_val is None and param_def.builtin is not None:
568 param_val = self._resolve_param_as_builtin(
569 builtin_name=param_def.builtin,
570 builtins=customized_builtins,
571 )
572 if param_val is not None:
573 provider_params[param_name] = param_val
574 self._register_endpoint_feature_ids(param_name, param_val)
575
576 return provider_params
577
578 def _resolve_param_from_context(
579 self, param_name, operation_model, call_args
580 ):
581 static = self._resolve_param_as_static_context_param(
582 param_name, operation_model
583 )
584 if static is not None:
585 return static
586 dynamic = self._resolve_param_as_dynamic_context_param(
587 param_name, operation_model, call_args
588 )
589 if dynamic is not None:
590 return dynamic
591 operation_context_params = (
592 self._resolve_param_as_operation_context_param(
593 param_name, operation_model, call_args
594 )
595 )
596 if operation_context_params is not None:
597 return operation_context_params
598 return self._resolve_param_as_client_context_param(param_name)
599
600 def _resolve_param_as_static_context_param(
601 self, param_name, operation_model
602 ):
603 static_ctx_params = self._get_static_context_params(operation_model)
604 return static_ctx_params.get(param_name)
605
606 def _resolve_param_as_dynamic_context_param(
607 self, param_name, operation_model, call_args
608 ):
609 dynamic_ctx_params = self._get_dynamic_context_params(operation_model)
610 if param_name in dynamic_ctx_params:
611 member_name = dynamic_ctx_params[param_name]
612 return call_args.get(member_name)
613
614 def _resolve_param_as_client_context_param(self, param_name):
615 client_ctx_params = self._get_client_context_params()
616 if param_name in client_ctx_params:
617 client_ctx_varname = client_ctx_params[param_name]
618 return self._client_context.get(client_ctx_varname)
619
620 def _resolve_param_as_operation_context_param(
621 self, param_name, operation_model, call_args
622 ):
623 operation_ctx_params = operation_model.operation_context_parameters
624 if param_name in operation_ctx_params:
625 path = operation_ctx_params[param_name]['path']
626 return jmespath.search(path, call_args)
627
628 def _resolve_param_as_builtin(self, builtin_name, builtins):
629 if builtin_name not in EndpointResolverBuiltins.__members__.values():
630 raise UnknownEndpointResolutionBuiltInName(name=builtin_name)
631 builtin = builtins.get(builtin_name)
632 if callable(builtin):
633 return builtin()
634 return builtin
635
636 @instance_cache
637 def _get_static_context_params(self, operation_model):
638 """Mapping of param names to static param value for an operation"""
639 return {
640 param.name: param.value
641 for param in operation_model.static_context_parameters
642 }
643
644 @instance_cache
645 def _get_dynamic_context_params(self, operation_model):
646 """Mapping of param names to member names for an operation"""
647 return {
648 param.name: param.member_name
649 for param in operation_model.context_parameters
650 }
651
652 @instance_cache
653 def _get_client_context_params(self):
654 """Mapping of param names to client configuration variable"""
655 return {
656 param.name: xform_name(param.name)
657 for param in self._service_model.client_context_parameters
658 }
659
660 def _get_customized_builtins(
661 self, operation_model, call_args, request_context
662 ):
663 service_id = self._service_model.service_id.hyphenize()
664 customized_builtins = copy.copy(self._builtins)
665 # Handlers are expected to modify the builtins dict in place.
666 self._event_emitter.emit(
667 f'before-endpoint-resolution.{service_id}',
668 builtins=customized_builtins,
669 model=operation_model,
670 params=call_args,
671 context=request_context,
672 )
673 return customized_builtins
674
675 def auth_schemes_to_signing_ctx(self, auth_schemes):
676 """Convert an Endpoint's authSchemes property to a signing_context dict
677
678 :type auth_schemes: list
679 :param auth_schemes: A list of dictionaries taken from the
680 ``authSchemes`` property of an Endpoint object returned by
681 ``EndpointProvider``.
682
683 :rtype: str, dict
684 :return: Tuple of auth type string (to be used in
685 ``request_context['auth_type']``) and signing context dict (for use
686 in ``request_context['signing']``).
687 """
688 if not isinstance(auth_schemes, list) or len(auth_schemes) == 0:
689 raise TypeError("auth_schemes must be a non-empty list.")
690
691 LOG.debug(
692 'Selecting from endpoint provider\'s list of auth schemes: %s. '
693 'User selected auth scheme is: "%s"',
694 ', '.join([f'"{s.get("name")}"' for s in auth_schemes]),
695 self._requested_auth_scheme,
696 )
697
698 if self._requested_auth_scheme == UNSIGNED:
699 return 'none', {}
700
701 auth_schemes = [
702 {**scheme, 'name': self._strip_sig_prefix(scheme['name'])}
703 for scheme in auth_schemes
704 ]
705 if self._requested_auth_scheme is not None:
706 try:
707 # Use the first scheme that matches the requested scheme,
708 # after accounting for naming differences between botocore and
709 # endpoint rulesets. Keep the requested name.
710 name, scheme = next(
711 (self._requested_auth_scheme, s)
712 for s in auth_schemes
713 if self._does_botocore_authname_match_ruleset_authname(
714 self._requested_auth_scheme, s['name']
715 )
716 )
717 except StopIteration:
718 # For legacy signers, no match will be found. Do not raise an
719 # exception, instead default to the logic in botocore
720 # customizations.
721 return None, {}
722 else:
723 try:
724 name, scheme = next(
725 (s['name'], s)
726 for s in auth_schemes
727 if s['name'] in AUTH_TYPE_MAPS
728 )
729 except StopIteration:
730 # If no auth scheme was specifically requested and an
731 # authSchemes list is present in the Endpoint object but none
732 # of the entries are supported, raise an exception.
733 fixable_with_crt = False
734 auth_type_options = [s['name'] for s in auth_schemes]
735 if not HAS_CRT:
736 fixable_with_crt = any(
737 scheme in CRT_SUPPORTED_AUTH_TYPES
738 for scheme in auth_type_options
739 )
740
741 if fixable_with_crt:
742 raise MissingDependencyException(
743 msg='This operation requires an additional dependency.'
744 ' Use pip install botocore[crt] before proceeding.'
745 )
746 else:
747 raise UnknownSignatureVersionError(
748 signature_version=', '.join(auth_type_options)
749 )
750
751 signing_context = {}
752 if 'signingRegion' in scheme:
753 signing_context['region'] = scheme['signingRegion']
754 elif 'signingRegionSet' in scheme:
755 if len(scheme['signingRegionSet']) > 0:
756 signing_context['region'] = ','.join(
757 scheme['signingRegionSet']
758 )
759 if 'signingName' in scheme:
760 signing_context.update(signing_name=scheme['signingName'])
761 if 'disableDoubleEncoding' in scheme:
762 signing_context['disableDoubleEncoding'] = ensure_boolean(
763 scheme['disableDoubleEncoding']
764 )
765
766 LOG.debug(
767 'Selected auth type "%s" as "%s" with signing context params: %s',
768 scheme['name'], # original name without "sig"
769 name, # chosen name can differ when `signature_version` is set
770 signing_context,
771 )
772 return name, signing_context
773
774 def _strip_sig_prefix(self, auth_name):
775 """Normalize auth type names by removing any "sig" prefix"""
776 return auth_name[3:] if auth_name.startswith('sig') else auth_name
777
778 def _does_botocore_authname_match_ruleset_authname(self, botoname, rsname):
779 """
780 Whether a valid string provided as signature_version parameter for
781 client construction refers to the same auth methods as a string
782 returned by the endpoint ruleset provider. This accounts for:
783
784 * The ruleset prefixes auth names with "sig"
785 * The s3 and s3control rulesets don't distinguish between v4[a] and
786 s3v4[a] signers
787 * The v2, v3, and HMAC v1 based signers (s3, s3-*) are botocore legacy
788 features and do not exist in the rulesets
789 * Only characters up to the first dash are considered
790
791 Example matches:
792 * v4, sigv4
793 * v4, v4
794 * s3v4, sigv4
795 * s3v7, sigv7 (hypothetical example)
796 * s3v4a, sigv4a
797 * s3v4-query, sigv4
798
799 Example mismatches:
800 * v4a, sigv4
801 * s3, sigv4
802 * s3-presign-post, sigv4
803 """
804 rsname = self._strip_sig_prefix(rsname)
805 botoname = botoname.split('-')[0]
806 if botoname != 's3' and botoname.startswith('s3'):
807 botoname = botoname[2:]
808 return rsname == botoname
809
810 def ruleset_error_to_botocore_exception(self, ruleset_exception, params):
811 """Attempts to translate ruleset errors to pre-existing botocore
812 exception types by string matching exception strings.
813 """
814 msg = ruleset_exception.kwargs.get('msg')
815 if msg is None:
816 return
817
818 if msg.startswith('Invalid region in ARN: '):
819 # Example message:
820 # "Invalid region in ARN: `us-we$t-2` (invalid DNS name)"
821 try:
822 label = msg.split('`')[1]
823 except IndexError:
824 label = msg
825 return InvalidHostLabelError(label=label)
826
827 service_name = self._service_model.service_name
828 if service_name == 's3':
829 if (
830 msg == 'S3 Object Lambda does not support S3 Accelerate'
831 or msg == 'Accelerate cannot be used with FIPS'
832 ):
833 return UnsupportedS3ConfigurationError(msg=msg)
834 if (
835 msg.startswith('S3 Outposts does not support')
836 or msg.startswith('S3 MRAP does not support')
837 or msg.startswith('S3 Object Lambda does not support')
838 or msg.startswith('Access Points do not support')
839 or msg.startswith('Invalid configuration:')
840 or msg.startswith('Client was configured for partition')
841 ):
842 return UnsupportedS3AccesspointConfigurationError(msg=msg)
843 if msg.lower().startswith('invalid arn:'):
844 return ParamValidationError(report=msg)
845 if service_name == 's3control':
846 if msg.startswith('Invalid ARN:'):
847 arn = params.get('Bucket')
848 return UnsupportedS3ControlArnError(arn=arn, msg=msg)
849 if msg.startswith('Invalid configuration:') or msg.startswith(
850 'Client was configured for partition'
851 ):
852 return UnsupportedS3ControlConfigurationError(msg=msg)
853 if msg == "AccountId is required but not set":
854 return ParamValidationError(report=msg)
855 if service_name == 'events':
856 if msg.startswith(
857 'Invalid Configuration: FIPS is not supported with '
858 'EventBridge multi-region endpoints.'
859 ):
860 return InvalidEndpointConfigurationError(msg=msg)
861 if msg == 'EndpointId must be a valid host label.':
862 return InvalidEndpointConfigurationError(msg=msg)
863 return None
864
865 def _register_endpoint_feature_ids(self, param_name, param_val):
866 if param_name == 'AccountIdEndpointMode':
867 register_feature_id(f'ACCOUNT_ID_MODE_{param_val.upper()}')
868 elif param_name == 'AccountId':
869 register_feature_id('RESOLVED_ACCOUNT_ID')