Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/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

356 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 

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 f'Client is configured with the deprecated endpoint: {endpoint_name}' 

365 ) 

366 

367 service_defaults = service_data.get('defaults', {}) 

368 partition_defaults = partition.get('defaults', {}) 

369 tags = self._create_tag_list(use_dualstack_endpoint, use_fips_endpoint) 

370 

371 if tags: 

372 result = self._resolve_variant( 

373 tags, endpoint_data, service_defaults, partition_defaults 

374 ) 

375 if result == {}: 

376 error_msg = ( 

377 f"Endpoint does not exist for {service_name} " 

378 f"in region {endpoint_name}" 

379 ) 

380 raise EndpointVariantError(tags=tags, error_msg=error_msg) 

381 self._merge_keys(endpoint_data, result) 

382 else: 

383 result = endpoint_data 

384 

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

386 if 'dnsSuffix' not in result: 

387 result['dnsSuffix'] = partition['dnsSuffix'] 

388 

389 result['partition'] = partition['partition'] 

390 result['endpointName'] = endpoint_name 

391 

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

393 self._merge_keys(service_defaults, result) 

394 self._merge_keys(partition_defaults, result) 

395 

396 result['hostname'] = self._expand_template( 

397 partition, 

398 result['hostname'], 

399 service_name, 

400 endpoint_name, 

401 result['dnsSuffix'], 

402 ) 

403 if 'sslCommonName' in result: 

404 result['sslCommonName'] = self._expand_template( 

405 partition, 

406 result['sslCommonName'], 

407 service_name, 

408 endpoint_name, 

409 result['dnsSuffix'], 

410 ) 

411 

412 return result 

413 

414 def _merge_keys(self, from_data, result): 

415 for key in from_data: 

416 if key not in result: 

417 result[key] = from_data[key] 

418 

419 def _expand_template( 

420 self, partition, template, service_name, endpoint_name, dnsSuffix 

421 ): 

422 return template.format( 

423 service=service_name, region=endpoint_name, dnsSuffix=dnsSuffix 

424 ) 

425 

426 

427class EndpointResolverBuiltins(str, Enum): 

428 # The AWS Region configured for the SDK client (str) 

429 AWS_REGION = "AWS::Region" 

430 # Whether the UseFIPSEndpoint configuration option has been enabled for 

431 # the SDK client (bool) 

432 AWS_USE_FIPS = "AWS::UseFIPS" 

433 # Whether the UseDualStackEndpoint configuration option has been enabled 

434 # for the SDK client (bool) 

435 AWS_USE_DUALSTACK = "AWS::UseDualStack" 

436 # Whether the global endpoint should be used with STS, rather than the 

437 # regional endpoint for us-east-1 (bool) 

438 AWS_STS_USE_GLOBAL_ENDPOINT = "AWS::STS::UseGlobalEndpoint" 

439 # Whether the global endpoint should be used with S3, rather than the 

440 # regional endpoint for us-east-1 (bool) 

441 AWS_S3_USE_GLOBAL_ENDPOINT = "AWS::S3::UseGlobalEndpoint" 

442 # Whether S3 Transfer Acceleration has been requested (bool) 

443 AWS_S3_ACCELERATE = "AWS::S3::Accelerate" 

444 # Whether S3 Force Path Style has been enabled (bool) 

445 AWS_S3_FORCE_PATH_STYLE = "AWS::S3::ForcePathStyle" 

446 # Whether to use the ARN region or raise an error when ARN and client 

447 # region differ (for s3 service only, bool) 

448 AWS_S3_USE_ARN_REGION = "AWS::S3::UseArnRegion" 

449 # Whether to use the ARN region or raise an error when ARN and client 

450 # region differ (for s3-control service only, bool) 

451 AWS_S3CONTROL_USE_ARN_REGION = 'AWS::S3Control::UseArnRegion' 

452 # Whether multi-region access points (MRAP) should be disabled (bool) 

453 AWS_S3_DISABLE_MRAP = "AWS::S3::DisableMultiRegionAccessPoints" 

454 # Whether a custom endpoint has been configured (str) 

455 SDK_ENDPOINT = "SDK::Endpoint" 

456 # An AWS account ID that can be optionally configured for the SDK client (str) 

457 ACCOUNT_ID = "AWS::Auth::AccountId" 

458 # Whether an endpoint should include an account ID (str) 

459 ACCOUNT_ID_ENDPOINT_MODE = "AWS::Auth::AccountIdEndpointMode" 

460 

461 

462class EndpointRulesetResolver: 

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

464 

465 def __init__( 

466 self, 

467 endpoint_ruleset_data, 

468 partition_data, 

469 service_model, 

470 builtins, 

471 client_context, 

472 event_emitter, 

473 use_ssl=True, 

474 requested_auth_scheme=None, 

475 ): 

476 self._provider = EndpointProvider( 

477 ruleset_data=endpoint_ruleset_data, 

478 partition_data=partition_data, 

479 ) 

480 self._param_definitions = self._provider.ruleset.parameters 

481 self._service_model = service_model 

482 self._builtins = builtins 

483 self._client_context = client_context 

484 self._event_emitter = event_emitter 

485 self._use_ssl = use_ssl 

486 self._requested_auth_scheme = requested_auth_scheme 

487 self._instance_cache = {} 

488 

489 def construct_endpoint( 

490 self, 

491 operation_model, 

492 call_args, 

493 request_context, 

494 ): 

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

496 if call_args is None: 

497 call_args = {} 

498 

499 if request_context is None: 

500 request_context = {} 

501 

502 provider_params = self._get_provider_params( 

503 operation_model, call_args, request_context 

504 ) 

505 LOG.debug( 

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

507 ) 

508 try: 

509 provider_result = self._provider.resolve_endpoint( 

510 **provider_params 

511 ) 

512 except EndpointProviderError as ex: 

513 botocore_exception = self.ruleset_error_to_botocore_exception( 

514 ex, provider_params 

515 ) 

516 if botocore_exception is None: 

517 raise 

518 else: 

519 raise botocore_exception from ex 

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

521 

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

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

524 provider_result = provider_result._replace( 

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

526 ) 

527 

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

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

530 # dropping any additionally entries. 

531 provider_result = provider_result._replace( 

532 headers={ 

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

534 } 

535 ) 

536 

537 return provider_result 

538 

539 def _get_provider_params( 

540 self, operation_model, call_args, request_context 

541 ): 

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

543 

544 The resolution order for parameter values is: 

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

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

547 3. Client-specific context parameters 

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

549 """ 

550 provider_params = {} 

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

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

553 customized_builtins = self._get_customized_builtins( 

554 operation_model, call_args, request_context 

555 ) 

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

557 param_val = self._resolve_param_from_context( 

558 param_name=param_name, 

559 operation_model=operation_model, 

560 call_args=call_args, 

561 ) 

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

563 param_val = self._resolve_param_as_builtin( 

564 builtin_name=param_def.builtin, 

565 builtins=customized_builtins, 

566 ) 

567 if param_val is not None: 

568 provider_params[param_name] = param_val 

569 self._register_endpoint_feature_ids(param_name, param_val) 

570 

571 return provider_params 

572 

573 def _resolve_param_from_context( 

574 self, param_name, operation_model, call_args 

575 ): 

576 static = self._resolve_param_as_static_context_param( 

577 param_name, operation_model 

578 ) 

579 if static is not None: 

580 return static 

581 dynamic = self._resolve_param_as_dynamic_context_param( 

582 param_name, operation_model, call_args 

583 ) 

584 if dynamic is not None: 

585 return dynamic 

586 operation_context_params = ( 

587 self._resolve_param_as_operation_context_param( 

588 param_name, operation_model, call_args 

589 ) 

590 ) 

591 if operation_context_params is not None: 

592 return operation_context_params 

593 return self._resolve_param_as_client_context_param(param_name) 

594 

595 def _resolve_param_as_static_context_param( 

596 self, param_name, operation_model 

597 ): 

598 static_ctx_params = self._get_static_context_params(operation_model) 

599 return static_ctx_params.get(param_name) 

600 

601 def _resolve_param_as_dynamic_context_param( 

602 self, param_name, operation_model, call_args 

603 ): 

604 dynamic_ctx_params = self._get_dynamic_context_params(operation_model) 

605 if param_name in dynamic_ctx_params: 

606 member_name = dynamic_ctx_params[param_name] 

607 return call_args.get(member_name) 

608 

609 def _resolve_param_as_client_context_param(self, param_name): 

610 client_ctx_params = self._get_client_context_params() 

611 if param_name in client_ctx_params: 

612 client_ctx_varname = client_ctx_params[param_name] 

613 return self._client_context.get(client_ctx_varname) 

614 

615 def _resolve_param_as_operation_context_param( 

616 self, param_name, operation_model, call_args 

617 ): 

618 operation_ctx_params = operation_model.operation_context_parameters 

619 if param_name in operation_ctx_params: 

620 path = operation_ctx_params[param_name]['path'] 

621 return jmespath.search(path, call_args) 

622 

623 def _resolve_param_as_builtin(self, builtin_name, builtins): 

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

625 raise UnknownEndpointResolutionBuiltInName(name=builtin_name) 

626 builtin = builtins.get(builtin_name) 

627 if callable(builtin): 

628 return builtin() 

629 return builtin 

630 

631 @instance_cache 

632 def _get_static_context_params(self, operation_model): 

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

634 return { 

635 param.name: param.value 

636 for param in operation_model.static_context_parameters 

637 } 

638 

639 @instance_cache 

640 def _get_dynamic_context_params(self, operation_model): 

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

642 return { 

643 param.name: param.member_name 

644 for param in operation_model.context_parameters 

645 } 

646 

647 @instance_cache 

648 def _get_client_context_params(self): 

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

650 return { 

651 param.name: xform_name(param.name) 

652 for param in self._service_model.client_context_parameters 

653 } 

654 

655 def _get_customized_builtins( 

656 self, operation_model, call_args, request_context 

657 ): 

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

659 customized_builtins = copy.copy(self._builtins) 

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

661 self._event_emitter.emit( 

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

663 builtins=customized_builtins, 

664 model=operation_model, 

665 params=call_args, 

666 context=request_context, 

667 ) 

668 return customized_builtins 

669 

670 def auth_schemes_to_signing_ctx(self, auth_schemes): 

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

672 

673 :type auth_schemes: list 

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

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

676 ``EndpointProvider``. 

677 

678 :rtype: str, dict 

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

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

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

682 """ 

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

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

685 

686 LOG.debug( 

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

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

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

690 self._requested_auth_scheme, 

691 ) 

692 

693 if self._requested_auth_scheme == UNSIGNED: 

694 return 'none', {} 

695 

696 auth_schemes = [ 

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

698 for scheme in auth_schemes 

699 ] 

700 if self._requested_auth_scheme is not None: 

701 try: 

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

703 # after accounting for naming differences between botocore and 

704 # endpoint rulesets. Keep the requested name. 

705 name, scheme = next( 

706 (self._requested_auth_scheme, s) 

707 for s in auth_schemes 

708 if self._does_botocore_authname_match_ruleset_authname( 

709 self._requested_auth_scheme, s['name'] 

710 ) 

711 ) 

712 except StopIteration: 

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

714 # exception, instead default to the logic in botocore 

715 # customizations. 

716 return None, {} 

717 else: 

718 try: 

719 name, scheme = next( 

720 (s['name'], s) 

721 for s in auth_schemes 

722 if s['name'] in AUTH_TYPE_MAPS 

723 ) 

724 except StopIteration: 

725 # If no auth scheme was specifically requested and an 

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

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

728 fixable_with_crt = False 

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

730 if not HAS_CRT: 

731 fixable_with_crt = any( 

732 scheme in CRT_SUPPORTED_AUTH_TYPES 

733 for scheme in auth_type_options 

734 ) 

735 

736 if fixable_with_crt: 

737 raise MissingDependencyException( 

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

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

740 ) 

741 else: 

742 raise UnknownSignatureVersionError( 

743 signature_version=', '.join(auth_type_options) 

744 ) 

745 

746 signing_context = {} 

747 if 'signingRegion' in scheme: 

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

749 elif 'signingRegionSet' in scheme: 

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

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

752 scheme['signingRegionSet'] 

753 ) 

754 if 'signingName' in scheme: 

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

756 if 'disableDoubleEncoding' in scheme: 

757 signing_context['disableDoubleEncoding'] = ensure_boolean( 

758 scheme['disableDoubleEncoding'] 

759 ) 

760 

761 LOG.debug( 

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

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

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

765 signing_context, 

766 ) 

767 return name, signing_context 

768 

769 def _strip_sig_prefix(self, auth_name): 

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

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

772 

773 def _does_botocore_authname_match_ruleset_authname(self, botoname, rsname): 

774 """ 

775 Whether a valid string provided as signature_version parameter for 

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

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

778 

779 * The ruleset prefixes auth names with "sig" 

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

781 s3v4[a] signers 

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

783 features and do not exist in the rulesets 

784 * Only characters up to the first dash are considered 

785 

786 Example matches: 

787 * v4, sigv4 

788 * v4, v4 

789 * s3v4, sigv4 

790 * s3v7, sigv7 (hypothetical example) 

791 * s3v4a, sigv4a 

792 * s3v4-query, sigv4 

793 

794 Example mismatches: 

795 * v4a, sigv4 

796 * s3, sigv4 

797 * s3-presign-post, sigv4 

798 """ 

799 rsname = self._strip_sig_prefix(rsname) 

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

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

802 botoname = botoname[2:] 

803 return rsname == botoname 

804 

805 def ruleset_error_to_botocore_exception(self, ruleset_exception, params): 

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

807 exception types by string matching exception strings. 

808 """ 

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

810 if msg is None: 

811 return 

812 

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

814 # Example message: 

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

816 try: 

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

818 except IndexError: 

819 label = msg 

820 return InvalidHostLabelError(label=label) 

821 

822 service_name = self._service_model.service_name 

823 if service_name == 's3': 

824 if ( 

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

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

827 ): 

828 return UnsupportedS3ConfigurationError(msg=msg) 

829 if ( 

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

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

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

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

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

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

836 ): 

837 return UnsupportedS3AccesspointConfigurationError(msg=msg) 

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

839 return ParamValidationError(report=msg) 

840 if service_name == 's3control': 

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

842 arn = params.get('Bucket') 

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

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

845 'Client was configured for partition' 

846 ): 

847 return UnsupportedS3ControlConfigurationError(msg=msg) 

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

849 return ParamValidationError(report=msg) 

850 if service_name == 'events': 

851 if msg.startswith( 

852 'Invalid Configuration: FIPS is not supported with ' 

853 'EventBridge multi-region endpoints.' 

854 ): 

855 return InvalidEndpointConfigurationError(msg=msg) 

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

857 return InvalidEndpointConfigurationError(msg=msg) 

858 return None 

859 

860 def _register_endpoint_feature_ids(self, param_name, param_val): 

861 if param_name == 'AccountIdEndpointMode': 

862 register_feature_id(f'ACCOUNT_ID_MODE_{param_val.upper()}') 

863 elif param_name == 'AccountId': 

864 register_feature_id('RESOLVED_ACCOUNT_ID')