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