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

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""" 

19import copy 

20import logging 

21import re 

22from enum import Enum 

23 

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 

45 

46LOG = logging.getLogger(__name__) 

47DEFAULT_URI_TEMPLATE = '{service}.{region}.{dnsSuffix}' # noqa 

48DEFAULT_SERVICE_DATA = {'endpoints': {}} 

49 

50 

51class BaseEndpointResolver: 

52 """Resolves regions and endpoints. Must be subclassed.""" 

53 

54 def construct_endpoint(self, service_name, region_name=None): 

55 """Resolves an endpoint for a service and region combination. 

56 

57 :type service_name: string 

58 :param service_name: Name of the service to resolve an endpoint for 

59 (e.g., s3) 

60 

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. 

65 

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 

82 

83 def get_available_partitions(self): 

84 """Lists the partitions available to the endpoint resolver. 

85 

86 :return: Returns a list of partition names (e.g., ["aws", "aws-cn"]). 

87 """ 

88 raise NotImplementedError 

89 

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. 

94 

95 :type service_name: string 

96 :param service_name: Name of a service to list endpoint for (e.g., s3) 

97 

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. 

102 

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 

110 

111 

112class EndpointResolver(BaseEndpointResolver): 

113 """Resolves endpoints based on partition endpoint metadata""" 

114 

115 _UNSUPPORTED_DUALSTACK_PARTITIONS = ['aws-iso', 'aws-iso-b'] 

116 

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. 

121 

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 

130 

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'] 

139 

140 def get_available_partitions(self): 

141 result = [] 

142 for partition in self._endpoint_data['partitions']: 

143 result.append(partition['partition']) 

144 return result 

145 

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 

173 

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 

188 

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' 

203 

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 

209 

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 

221 

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 

238 

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 ) 

247 

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) 

267 

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() 

278 

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 } 

287 

288 # Attempt to resolve the exact region for this partition. 

289 if region_name in service_data['endpoints']: 

290 return self._resolve(**resolve_kwargs) 

291 

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) 

312 

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 

319 

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 

326 

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 

334 

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 

344 

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 ) 

357 

358 if endpoint_data.get('deprecated'): 

359 LOG.warning( 

360 'Client is configured with the deprecated endpoint: %s' 

361 % (endpoint_name) 

362 ) 

363 

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) 

367 

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 

381 

382 # If dnsSuffix has not already been consumed from a variant definition 

383 if 'dnsSuffix' not in result: 

384 result['dnsSuffix'] = partition['dnsSuffix'] 

385 

386 result['partition'] = partition['partition'] 

387 result['endpointName'] = endpoint_name 

388 

389 # Merge in the service defaults then the partition defaults. 

390 self._merge_keys(service_defaults, result) 

391 self._merge_keys(partition_defaults, result) 

392 

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 ) 

408 

409 return result 

410 

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] 

415 

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 ) 

422 

423 

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" 

453 

454 

455class EndpointRulesetResolver: 

456 """Resolves endpoints using a service's endpoint ruleset""" 

457 

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 = {} 

481 

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 = {} 

491 

492 if request_context is None: 

493 request_context = {} 

494 

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) 

514 

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 ) 

520 

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 ) 

529 

530 return provider_result 

531 

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 

536 

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 

562 

563 return provider_params 

564 

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) 

579 

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) 

585 

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) 

593 

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) 

599 

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) 

604 

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 } 

612 

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 } 

620 

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 } 

628 

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 

643 

644 def auth_schemes_to_signing_ctx(self, auth_schemes): 

645 """Convert an Endpoint's authSchemes property to a signing_context dict 

646 

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``. 

651 

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.") 

659 

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 ) 

666 

667 if self._requested_auth_scheme == UNSIGNED: 

668 return 'none', {} 

669 

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 ) 

709 

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 ) 

719 

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 ) 

732 

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 

740 

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 

744 

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: 

750 

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 

757 

758 Example matches: 

759 * v4, sigv4 

760 * v4, v4 

761 * s3v4, sigv4 

762 * s3v7, sigv7 (hypothetical example) 

763 * s3v4a, sigv4a 

764 * s3v4-query, sigv4 

765 

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 

776 

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 

784 

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) 

793 

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