Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/botocore/regions.py: 21%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

336 statements  

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 

25from botocore import UNSIGNED, xform_name 

26from botocore.auth import AUTH_TYPE_MAPS, HAS_CRT 

27from botocore.crt import CRT_SUPPORTED_AUTH_TYPES 

28from botocore.endpoint_provider import EndpointProvider 

29from botocore.exceptions import ( 

30 EndpointProviderError, 

31 EndpointVariantError, 

32 InvalidEndpointConfigurationError, 

33 InvalidHostLabelError, 

34 MissingDependencyException, 

35 NoRegionError, 

36 ParamValidationError, 

37 UnknownEndpointResolutionBuiltInName, 

38 UnknownRegionError, 

39 UnknownSignatureVersionError, 

40 UnsupportedS3AccesspointConfigurationError, 

41 UnsupportedS3ConfigurationError, 

42 UnsupportedS3ControlArnError, 

43 UnsupportedS3ControlConfigurationError, 

44) 

45from botocore.utils import ensure_boolean, instance_cache 

46 

47LOG = logging.getLogger(__name__) 

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

49DEFAULT_SERVICE_DATA = {'endpoints': {}} 

50 

51 

52class BaseEndpointResolver: 

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

54 

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

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

57 

58 :type service_name: string 

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

60 (e.g., s3) 

61 

62 :type region_name: string 

63 :param region_name: Region/endpoint name to resolve (e.g., us-east-1) 

64 if no region is provided, the first found partition-wide endpoint 

65 will be used if available. 

66 

67 :rtype: dict 

68 :return: Returns a dict containing the following keys: 

69 - partition: (string, required) Resolved partition name 

70 - endpointName: (string, required) Resolved endpoint name 

71 - hostname: (string, required) Hostname to use for this endpoint 

72 - sslCommonName: (string) sslCommonName to use for this endpoint. 

73 - credentialScope: (dict) Signature version 4 credential scope 

74 - region: (string) region name override when signing. 

75 - service: (string) service name override when signing. 

76 - signatureVersions: (list<string>) A list of possible signature 

77 versions, including s3, v4, v2, and s3v4 

78 - protocols: (list<string>) A list of supported protocols 

79 (e.g., http, https) 

80 - ...: Other keys may be included as well based on the metadata 

81 """ 

82 raise NotImplementedError 

83 

84 def get_available_partitions(self): 

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

86 

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

88 """ 

89 raise NotImplementedError 

90 

91 def get_available_endpoints( 

92 self, service_name, partition_name='aws', allow_non_regional=False 

93 ): 

94 """Lists the endpoint names of a particular partition. 

95 

96 :type service_name: string 

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

98 

99 :type partition_name: string 

100 :param partition_name: Name of the partition to limit endpoints to. 

101 (e.g., aws for the public AWS endpoints, aws-cn for AWS China 

102 endpoints, aws-us-gov for AWS GovCloud (US) Endpoints, etc. 

103 

104 :type allow_non_regional: bool 

105 :param allow_non_regional: Set to True to include endpoints that are 

106 not regional endpoints (e.g., s3-external-1, 

107 fips-us-gov-west-1, etc). 

108 :return: Returns a list of endpoint names (e.g., ["us-east-1"]). 

109 """ 

110 raise NotImplementedError 

111 

112 

113class EndpointResolver(BaseEndpointResolver): 

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

115 

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

117 

118 def __init__(self, endpoint_data, uses_builtin_data=False): 

119 """ 

120 :type endpoint_data: dict 

121 :param endpoint_data: A dict of partition data. 

122 

123 :type uses_builtin_data: boolean 

124 :param uses_builtin_data: Whether the endpoint data originates in the 

125 package's data directory. 

126 """ 

127 if 'partitions' not in endpoint_data: 

128 raise ValueError('Missing "partitions" in endpoint data') 

129 self._endpoint_data = endpoint_data 

130 self.uses_builtin_data = uses_builtin_data 

131 

132 def get_service_endpoints_data(self, service_name, partition_name='aws'): 

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

134 if partition['partition'] != partition_name: 

135 continue 

136 services = partition['services'] 

137 if service_name not in services: 

138 continue 

139 return services[service_name]['endpoints'] 

140 

141 def get_available_partitions(self): 

142 result = [] 

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

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

145 return result 

146 

147 def get_available_endpoints( 

148 self, 

149 service_name, 

150 partition_name='aws', 

151 allow_non_regional=False, 

152 endpoint_variant_tags=None, 

153 ): 

154 result = [] 

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

156 if partition['partition'] != partition_name: 

157 continue 

158 services = partition['services'] 

159 if service_name not in services: 

160 continue 

161 service_endpoints = services[service_name]['endpoints'] 

162 for endpoint_name in service_endpoints: 

163 is_regional_endpoint = endpoint_name in partition['regions'] 

164 # Only regional endpoints can be modeled with variants 

165 if endpoint_variant_tags and is_regional_endpoint: 

166 variant_data = self._retrieve_variant_data( 

167 service_endpoints[endpoint_name], endpoint_variant_tags 

168 ) 

169 if variant_data: 

170 result.append(endpoint_name) 

171 elif allow_non_regional or is_regional_endpoint: 

172 result.append(endpoint_name) 

173 return result 

174 

175 def get_partition_dns_suffix( 

176 self, partition_name, endpoint_variant_tags=None 

177 ): 

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

179 if partition['partition'] == partition_name: 

180 if endpoint_variant_tags: 

181 variant = self._retrieve_variant_data( 

182 partition.get('defaults'), endpoint_variant_tags 

183 ) 

184 if variant and 'dnsSuffix' in variant: 

185 return variant['dnsSuffix'] 

186 else: 

187 return partition['dnsSuffix'] 

188 return None 

189 

190 def construct_endpoint( 

191 self, 

192 service_name, 

193 region_name=None, 

194 partition_name=None, 

195 use_dualstack_endpoint=False, 

196 use_fips_endpoint=False, 

197 ): 

198 if ( 

199 service_name == 's3' 

200 and use_dualstack_endpoint 

201 and region_name is None 

202 ): 

203 region_name = 'us-east-1' 

204 

205 if partition_name is not None: 

206 valid_partition = None 

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

208 if partition['partition'] == partition_name: 

209 valid_partition = partition 

210 

211 if valid_partition is not None: 

212 result = self._endpoint_for_partition( 

213 valid_partition, 

214 service_name, 

215 region_name, 

216 use_dualstack_endpoint, 

217 use_fips_endpoint, 

218 True, 

219 ) 

220 return result 

221 return None 

222 

223 # Iterate over each partition until a match is found. 

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

225 if use_dualstack_endpoint and ( 

226 partition['partition'] 

227 in self._UNSUPPORTED_DUALSTACK_PARTITIONS 

228 ): 

229 continue 

230 result = self._endpoint_for_partition( 

231 partition, 

232 service_name, 

233 region_name, 

234 use_dualstack_endpoint, 

235 use_fips_endpoint, 

236 ) 

237 if result: 

238 return result 

239 

240 def get_partition_for_region(self, region_name): 

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

242 if self._region_match(partition, region_name): 

243 return partition['partition'] 

244 raise UnknownRegionError( 

245 region_name=region_name, 

246 error_msg='No partition found for provided region_name.', 

247 ) 

248 

249 def _endpoint_for_partition( 

250 self, 

251 partition, 

252 service_name, 

253 region_name, 

254 use_dualstack_endpoint, 

255 use_fips_endpoint, 

256 force_partition=False, 

257 ): 

258 partition_name = partition["partition"] 

259 if ( 

260 use_dualstack_endpoint 

261 and partition_name in self._UNSUPPORTED_DUALSTACK_PARTITIONS 

262 ): 

263 error_msg = ( 

264 "Dualstack endpoints are currently not supported" 

265 f" for {partition_name} partition" 

266 ) 

267 raise EndpointVariantError(tags=['dualstack'], error_msg=error_msg) 

268 

269 # Get the service from the partition, or an empty template. 

270 service_data = partition['services'].get( 

271 service_name, DEFAULT_SERVICE_DATA 

272 ) 

273 # Use the partition endpoint if no region is supplied. 

274 if region_name is None: 

275 if 'partitionEndpoint' in service_data: 

276 region_name = service_data['partitionEndpoint'] 

277 else: 

278 raise NoRegionError() 

279 

280 resolve_kwargs = { 

281 'partition': partition, 

282 'service_name': service_name, 

283 'service_data': service_data, 

284 'endpoint_name': region_name, 

285 'use_dualstack_endpoint': use_dualstack_endpoint, 

286 'use_fips_endpoint': use_fips_endpoint, 

287 } 

288 

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

290 if region_name in service_data['endpoints']: 

291 return self._resolve(**resolve_kwargs) 

292 

293 # Check to see if the endpoint provided is valid for the partition. 

294 if self._region_match(partition, region_name) or force_partition: 

295 # Use the partition endpoint if set and not regionalized. 

296 partition_endpoint = service_data.get('partitionEndpoint') 

297 is_regionalized = service_data.get('isRegionalized', True) 

298 if partition_endpoint and not is_regionalized: 

299 LOG.debug( 

300 'Using partition endpoint for %s, %s: %s', 

301 service_name, 

302 region_name, 

303 partition_endpoint, 

304 ) 

305 resolve_kwargs['endpoint_name'] = partition_endpoint 

306 return self._resolve(**resolve_kwargs) 

307 LOG.debug( 

308 'Creating a regex based endpoint for %s, %s', 

309 service_name, 

310 region_name, 

311 ) 

312 return self._resolve(**resolve_kwargs) 

313 

314 def _region_match(self, partition, region_name): 

315 if region_name in partition['regions']: 

316 return True 

317 if 'regionRegex' in partition: 

318 return re.compile(partition['regionRegex']).match(region_name) 

319 return False 

320 

321 def _retrieve_variant_data(self, endpoint_data, tags): 

322 variants = endpoint_data.get('variants', []) 

323 for variant in variants: 

324 if set(variant['tags']) == set(tags): 

325 result = variant.copy() 

326 return result 

327 

328 def _create_tag_list(self, use_dualstack_endpoint, use_fips_endpoint): 

329 tags = [] 

330 if use_dualstack_endpoint: 

331 tags.append('dualstack') 

332 if use_fips_endpoint: 

333 tags.append('fips') 

334 return tags 

335 

336 def _resolve_variant( 

337 self, tags, endpoint_data, service_defaults, partition_defaults 

338 ): 

339 result = {} 

340 for variants in [endpoint_data, service_defaults, partition_defaults]: 

341 variant = self._retrieve_variant_data(variants, tags) 

342 if variant: 

343 self._merge_keys(variant, result) 

344 return result 

345 

346 def _resolve( 

347 self, 

348 partition, 

349 service_name, 

350 service_data, 

351 endpoint_name, 

352 use_dualstack_endpoint, 

353 use_fips_endpoint, 

354 ): 

355 endpoint_data = service_data.get('endpoints', {}).get( 

356 endpoint_name, {} 

357 ) 

358 

359 if endpoint_data.get('deprecated'): 

360 LOG.warning( 

361 f'Client is configured with the deprecated endpoint: {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 # Currently not implemented: 

454 ACCOUNT_ID = "AWS::Auth::AccountId" 

455 ACCOUNT_ID_ENDPOINT_MODE = "AWS::Auth::AccountIdEndpointMode" 

456 

457 

458class EndpointRulesetResolver: 

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

460 

461 def __init__( 

462 self, 

463 endpoint_ruleset_data, 

464 partition_data, 

465 service_model, 

466 builtins, 

467 client_context, 

468 event_emitter, 

469 use_ssl=True, 

470 requested_auth_scheme=None, 

471 ): 

472 self._provider = EndpointProvider( 

473 ruleset_data=endpoint_ruleset_data, 

474 partition_data=partition_data, 

475 ) 

476 self._param_definitions = self._provider.ruleset.parameters 

477 self._service_model = service_model 

478 self._builtins = builtins 

479 self._client_context = client_context 

480 self._event_emitter = event_emitter 

481 self._use_ssl = use_ssl 

482 self._requested_auth_scheme = requested_auth_scheme 

483 self._instance_cache = {} 

484 

485 def construct_endpoint( 

486 self, 

487 operation_model, 

488 call_args, 

489 request_context, 

490 ): 

491 """Invokes the provider with params defined in the service's ruleset""" 

492 if call_args is None: 

493 call_args = {} 

494 

495 if request_context is None: 

496 request_context = {} 

497 

498 provider_params = self._get_provider_params( 

499 operation_model, call_args, request_context 

500 ) 

501 LOG.debug( 

502 f'Calling endpoint provider with parameters: {provider_params}' 

503 ) 

504 try: 

505 provider_result = self._provider.resolve_endpoint( 

506 **provider_params 

507 ) 

508 except EndpointProviderError as ex: 

509 botocore_exception = self.ruleset_error_to_botocore_exception( 

510 ex, provider_params 

511 ) 

512 if botocore_exception is None: 

513 raise 

514 else: 

515 raise botocore_exception from ex 

516 LOG.debug(f'Endpoint provider result: {provider_result.url}') 

517 

518 # The endpoint provider does not support non-secure transport. 

519 if not self._use_ssl and provider_result.url.startswith('https://'): 

520 provider_result = provider_result._replace( 

521 url=f'http://{provider_result.url[8:]}' 

522 ) 

523 

524 # Multi-valued headers are not supported in botocore. Replace the list 

525 # of values returned for each header with just its first entry, 

526 # dropping any additionally entries. 

527 provider_result = provider_result._replace( 

528 headers={ 

529 key: val[0] for key, val in provider_result.headers.items() 

530 } 

531 ) 

532 

533 return provider_result 

534 

535 def _get_provider_params( 

536 self, operation_model, call_args, request_context 

537 ): 

538 """Resolve a value for each parameter defined in the service's ruleset 

539 

540 The resolution order for parameter values is: 

541 1. Operation-specific static context values from the service definition 

542 2. Operation-specific dynamic context values from API parameters 

543 3. Client-specific context parameters 

544 4. Built-in values such as region, FIPS usage, ... 

545 """ 

546 provider_params = {} 

547 # Builtin values can be customized for each operation by hooks 

548 # subscribing to the ``before-endpoint-resolution.*`` event. 

549 customized_builtins = self._get_customized_builtins( 

550 operation_model, call_args, request_context 

551 ) 

552 for param_name, param_def in self._param_definitions.items(): 

553 param_val = self._resolve_param_from_context( 

554 param_name=param_name, 

555 operation_model=operation_model, 

556 call_args=call_args, 

557 ) 

558 if param_val is None and param_def.builtin is not None: 

559 param_val = self._resolve_param_as_builtin( 

560 builtin_name=param_def.builtin, 

561 builtins=customized_builtins, 

562 ) 

563 if param_val is not None: 

564 provider_params[param_name] = param_val 

565 

566 return provider_params 

567 

568 def _resolve_param_from_context( 

569 self, param_name, operation_model, call_args 

570 ): 

571 static = self._resolve_param_as_static_context_param( 

572 param_name, operation_model 

573 ) 

574 if static is not None: 

575 return static 

576 dynamic = self._resolve_param_as_dynamic_context_param( 

577 param_name, operation_model, call_args 

578 ) 

579 if dynamic is not None: 

580 return dynamic 

581 return self._resolve_param_as_client_context_param(param_name) 

582 

583 def _resolve_param_as_static_context_param( 

584 self, param_name, operation_model 

585 ): 

586 static_ctx_params = self._get_static_context_params(operation_model) 

587 return static_ctx_params.get(param_name) 

588 

589 def _resolve_param_as_dynamic_context_param( 

590 self, param_name, operation_model, call_args 

591 ): 

592 dynamic_ctx_params = self._get_dynamic_context_params(operation_model) 

593 if param_name in dynamic_ctx_params: 

594 member_name = dynamic_ctx_params[param_name] 

595 return call_args.get(member_name) 

596 

597 def _resolve_param_as_client_context_param(self, param_name): 

598 client_ctx_params = self._get_client_context_params() 

599 if param_name in client_ctx_params: 

600 client_ctx_varname = client_ctx_params[param_name] 

601 return self._client_context.get(client_ctx_varname) 

602 

603 def _resolve_param_as_builtin(self, builtin_name, builtins): 

604 if builtin_name not in EndpointResolverBuiltins.__members__.values(): 

605 raise UnknownEndpointResolutionBuiltInName(name=builtin_name) 

606 return builtins.get(builtin_name) 

607 

608 @instance_cache 

609 def _get_static_context_params(self, operation_model): 

610 """Mapping of param names to static param value for an operation""" 

611 return { 

612 param.name: param.value 

613 for param in operation_model.static_context_parameters 

614 } 

615 

616 @instance_cache 

617 def _get_dynamic_context_params(self, operation_model): 

618 """Mapping of param names to member names for an operation""" 

619 return { 

620 param.name: param.member_name 

621 for param in operation_model.context_parameters 

622 } 

623 

624 @instance_cache 

625 def _get_client_context_params(self): 

626 """Mapping of param names to client configuration variable""" 

627 return { 

628 param.name: xform_name(param.name) 

629 for param in self._service_model.client_context_parameters 

630 } 

631 

632 def _get_customized_builtins( 

633 self, operation_model, call_args, request_context 

634 ): 

635 service_id = self._service_model.service_id.hyphenize() 

636 customized_builtins = copy.copy(self._builtins) 

637 # Handlers are expected to modify the builtins dict in place. 

638 self._event_emitter.emit( 

639 f'before-endpoint-resolution.{service_id}', 

640 builtins=customized_builtins, 

641 model=operation_model, 

642 params=call_args, 

643 context=request_context, 

644 ) 

645 return customized_builtins 

646 

647 def auth_schemes_to_signing_ctx(self, auth_schemes): 

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

649 

650 :type auth_schemes: list 

651 :param auth_schemes: A list of dictionaries taken from the 

652 ``authSchemes`` property of an Endpoint object returned by 

653 ``EndpointProvider``. 

654 

655 :rtype: str, dict 

656 :return: Tuple of auth type string (to be used in 

657 ``request_context['auth_type']``) and signing context dict (for use 

658 in ``request_context['signing']``). 

659 """ 

660 if not isinstance(auth_schemes, list) or len(auth_schemes) == 0: 

661 raise TypeError("auth_schemes must be a non-empty list.") 

662 

663 LOG.debug( 

664 'Selecting from endpoint provider\'s list of auth schemes: %s. ' 

665 'User selected auth scheme is: "%s"', 

666 ', '.join([f'"{s.get("name")}"' for s in auth_schemes]), 

667 self._requested_auth_scheme, 

668 ) 

669 

670 if self._requested_auth_scheme == UNSIGNED: 

671 return 'none', {} 

672 

673 auth_schemes = [ 

674 {**scheme, 'name': self._strip_sig_prefix(scheme['name'])} 

675 for scheme in auth_schemes 

676 ] 

677 if self._requested_auth_scheme is not None: 

678 try: 

679 # Use the first scheme that matches the requested scheme, 

680 # after accounting for naming differences between botocore and 

681 # endpoint rulesets. Keep the requested name. 

682 name, scheme = next( 

683 (self._requested_auth_scheme, s) 

684 for s in auth_schemes 

685 if self._does_botocore_authname_match_ruleset_authname( 

686 self._requested_auth_scheme, s['name'] 

687 ) 

688 ) 

689 except StopIteration: 

690 # For legacy signers, no match will be found. Do not raise an 

691 # exception, instead default to the logic in botocore 

692 # customizations. 

693 return None, {} 

694 else: 

695 try: 

696 name, scheme = next( 

697 (s['name'], s) 

698 for s in auth_schemes 

699 if s['name'] in AUTH_TYPE_MAPS 

700 ) 

701 except StopIteration: 

702 # If no auth scheme was specifically requested and an 

703 # authSchemes list is present in the Endpoint object but none 

704 # of the entries are supported, raise an exception. 

705 fixable_with_crt = False 

706 auth_type_options = [s['name'] for s in auth_schemes] 

707 if not HAS_CRT: 

708 fixable_with_crt = any( 

709 scheme in CRT_SUPPORTED_AUTH_TYPES 

710 for scheme in auth_type_options 

711 ) 

712 

713 if fixable_with_crt: 

714 raise MissingDependencyException( 

715 msg='This operation requires an additional dependency.' 

716 ' Use pip install botocore[crt] before proceeding.' 

717 ) 

718 else: 

719 raise UnknownSignatureVersionError( 

720 signature_version=', '.join(auth_type_options) 

721 ) 

722 

723 signing_context = {} 

724 if 'signingRegion' in scheme: 

725 signing_context['region'] = scheme['signingRegion'] 

726 elif 'signingRegionSet' in scheme: 

727 if len(scheme['signingRegionSet']) > 0: 

728 signing_context['region'] = ','.join( 

729 scheme['signingRegionSet'] 

730 ) 

731 if 'signingName' in scheme: 

732 signing_context.update(signing_name=scheme['signingName']) 

733 if 'disableDoubleEncoding' in scheme: 

734 signing_context['disableDoubleEncoding'] = ensure_boolean( 

735 scheme['disableDoubleEncoding'] 

736 ) 

737 

738 LOG.debug( 

739 'Selected auth type "%s" as "%s" with signing context params: %s', 

740 scheme['name'], # original name without "sig" 

741 name, # chosen name can differ when `signature_version` is set 

742 signing_context, 

743 ) 

744 return name, signing_context 

745 

746 def _strip_sig_prefix(self, auth_name): 

747 """Normalize auth type names by removing any "sig" prefix""" 

748 return auth_name[3:] if auth_name.startswith('sig') else auth_name 

749 

750 def _does_botocore_authname_match_ruleset_authname(self, botoname, rsname): 

751 """ 

752 Whether a valid string provided as signature_version parameter for 

753 client construction refers to the same auth methods as a string 

754 returned by the endpoint ruleset provider. This accounts for: 

755 

756 * The ruleset prefixes auth names with "sig" 

757 * The s3 and s3control rulesets don't distinguish between v4[a] and 

758 s3v4[a] signers 

759 * The v2, v3, and HMAC v1 based signers (s3, s3-*) are botocore legacy 

760 features and do not exist in the rulesets 

761 * Only characters up to the first dash are considered 

762 

763 Example matches: 

764 * v4, sigv4 

765 * v4, v4 

766 * s3v4, sigv4 

767 * s3v7, sigv7 (hypothetical example) 

768 * s3v4a, sigv4a 

769 * s3v4-query, sigv4 

770 

771 Example mismatches: 

772 * v4a, sigv4 

773 * s3, sigv4 

774 * s3-presign-post, sigv4 

775 """ 

776 rsname = self._strip_sig_prefix(rsname) 

777 botoname = botoname.split('-')[0] 

778 if botoname != 's3' and botoname.startswith('s3'): 

779 botoname = botoname[2:] 

780 return rsname == botoname 

781 

782 def ruleset_error_to_botocore_exception(self, ruleset_exception, params): 

783 """Attempts to translate ruleset errors to pre-existing botocore 

784 exception types by string matching exception strings. 

785 """ 

786 msg = ruleset_exception.kwargs.get('msg') 

787 if msg is None: 

788 return 

789 

790 if msg.startswith('Invalid region in ARN: '): 

791 # Example message: 

792 # "Invalid region in ARN: `us-we$t-2` (invalid DNS name)" 

793 try: 

794 label = msg.split('`')[1] 

795 except IndexError: 

796 label = msg 

797 return InvalidHostLabelError(label=label) 

798 

799 service_name = self._service_model.service_name 

800 if service_name == 's3': 

801 if ( 

802 msg == 'S3 Object Lambda does not support S3 Accelerate' 

803 or msg == 'Accelerate cannot be used with FIPS' 

804 ): 

805 return UnsupportedS3ConfigurationError(msg=msg) 

806 if ( 

807 msg.startswith('S3 Outposts does not support') 

808 or msg.startswith('S3 MRAP does not support') 

809 or msg.startswith('S3 Object Lambda does not support') 

810 or msg.startswith('Access Points do not support') 

811 or msg.startswith('Invalid configuration:') 

812 or msg.startswith('Client was configured for partition') 

813 ): 

814 return UnsupportedS3AccesspointConfigurationError(msg=msg) 

815 if msg.lower().startswith('invalid arn:'): 

816 return ParamValidationError(report=msg) 

817 if service_name == 's3control': 

818 if msg.startswith('Invalid ARN:'): 

819 arn = params.get('Bucket') 

820 return UnsupportedS3ControlArnError(arn=arn, msg=msg) 

821 if msg.startswith('Invalid configuration:') or msg.startswith( 

822 'Client was configured for partition' 

823 ): 

824 return UnsupportedS3ControlConfigurationError(msg=msg) 

825 if msg == "AccountId is required but not set": 

826 return ParamValidationError(report=msg) 

827 if service_name == 'events': 

828 if msg.startswith( 

829 'Invalid Configuration: FIPS is not supported with ' 

830 'EventBridge multi-region endpoints.' 

831 ): 

832 return InvalidEndpointConfigurationError(msg=msg) 

833 if msg == 'EndpointId must be a valid host label.': 

834 return InvalidEndpointConfigurationError(msg=msg) 

835 return None