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 'Client is configured with the deprecated endpoint: %s', 

365 endpoint_name, 

366 ) 

367 

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

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

370 tags = self._create_tag_list(use_dualstack_endpoint, use_fips_endpoint) 

371 

372 if tags: 

373 result = self._resolve_variant( 

374 tags, endpoint_data, service_defaults, partition_defaults 

375 ) 

376 if result == {}: 

377 error_msg = ( 

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

379 f"in region {endpoint_name}" 

380 ) 

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

382 self._merge_keys(endpoint_data, result) 

383 else: 

384 result = endpoint_data 

385 

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

387 if 'dnsSuffix' not in result: 

388 result['dnsSuffix'] = partition['dnsSuffix'] 

389 

390 result['partition'] = partition['partition'] 

391 result['endpointName'] = endpoint_name 

392 

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

394 self._merge_keys(service_defaults, result) 

395 self._merge_keys(partition_defaults, result) 

396 

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

398 partition, 

399 result['hostname'], 

400 service_name, 

401 endpoint_name, 

402 result['dnsSuffix'], 

403 ) 

404 if 'sslCommonName' in result: 

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

406 partition, 

407 result['sslCommonName'], 

408 service_name, 

409 endpoint_name, 

410 result['dnsSuffix'], 

411 ) 

412 

413 return result 

414 

415 def _merge_keys(self, from_data, result): 

416 for key in from_data: 

417 if key not in result: 

418 result[key] = from_data[key] 

419 

420 def _expand_template( 

421 self, partition, template, service_name, endpoint_name, dnsSuffix 

422 ): 

423 return template.format( 

424 service=service_name, region=endpoint_name, dnsSuffix=dnsSuffix 

425 ) 

426 

427 

428class EndpointResolverBuiltins(str, Enum): 

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

430 AWS_REGION = "AWS::Region" 

431 # Whether the UseFIPSEndpoint configuration option has been enabled for 

432 # the SDK client (bool) 

433 AWS_USE_FIPS = "AWS::UseFIPS" 

434 # Whether the UseDualStackEndpoint configuration option has been enabled 

435 # for the SDK client (bool) 

436 AWS_USE_DUALSTACK = "AWS::UseDualStack" 

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

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

439 AWS_STS_USE_GLOBAL_ENDPOINT = "AWS::STS::UseGlobalEndpoint" 

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

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

442 AWS_S3_USE_GLOBAL_ENDPOINT = "AWS::S3::UseGlobalEndpoint" 

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

444 AWS_S3_ACCELERATE = "AWS::S3::Accelerate" 

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

446 AWS_S3_FORCE_PATH_STYLE = "AWS::S3::ForcePathStyle" 

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

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

449 AWS_S3_USE_ARN_REGION = "AWS::S3::UseArnRegion" 

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

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

452 AWS_S3CONTROL_USE_ARN_REGION = 'AWS::S3Control::UseArnRegion' 

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

454 AWS_S3_DISABLE_MRAP = "AWS::S3::DisableMultiRegionAccessPoints" 

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

456 SDK_ENDPOINT = "SDK::Endpoint" 

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

458 ACCOUNT_ID = "AWS::Auth::AccountId" 

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

460 ACCOUNT_ID_ENDPOINT_MODE = "AWS::Auth::AccountIdEndpointMode" 

461 

462 

463class EndpointRulesetResolver: 

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

465 

466 def __init__( 

467 self, 

468 endpoint_ruleset_data, 

469 partition_data, 

470 service_model, 

471 builtins, 

472 client_context, 

473 event_emitter, 

474 use_ssl=True, 

475 requested_auth_scheme=None, 

476 ): 

477 self._provider = EndpointProvider( 

478 ruleset_data=endpoint_ruleset_data, 

479 partition_data=partition_data, 

480 ) 

481 self._param_definitions = self._provider.ruleset.parameters 

482 self._service_model = service_model 

483 self._builtins = builtins 

484 self._client_context = client_context 

485 self._event_emitter = event_emitter 

486 self._use_ssl = use_ssl 

487 self._requested_auth_scheme = requested_auth_scheme 

488 self._instance_cache = {} 

489 

490 def construct_endpoint( 

491 self, 

492 operation_model, 

493 call_args, 

494 request_context, 

495 ): 

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

497 if call_args is None: 

498 call_args = {} 

499 

500 if request_context is None: 

501 request_context = {} 

502 

503 provider_params = self._get_provider_params( 

504 operation_model, call_args, request_context 

505 ) 

506 LOG.debug( 

507 'Calling endpoint provider with parameters: %s', provider_params 

508 ) 

509 try: 

510 provider_result = self._provider.resolve_endpoint( 

511 **provider_params 

512 ) 

513 except EndpointProviderError as ex: 

514 botocore_exception = self.ruleset_error_to_botocore_exception( 

515 ex, provider_params 

516 ) 

517 if botocore_exception is None: 

518 raise 

519 else: 

520 raise botocore_exception from ex 

521 LOG.debug('Endpoint provider result: %s', provider_result.url) 

522 

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

524 if ( 

525 not self._use_ssl 

526 and provider_result.url.startswith('https://') 

527 and 'Endpoint' not in provider_params 

528 ): 

529 provider_result = provider_result._replace( 

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

531 ) 

532 

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

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

535 # dropping any additionally entries. 

536 provider_result = provider_result._replace( 

537 headers={ 

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

539 } 

540 ) 

541 

542 return provider_result 

543 

544 def _get_provider_params( 

545 self, operation_model, call_args, request_context 

546 ): 

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

548 

549 The resolution order for parameter values is: 

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

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

552 3. Client-specific context parameters 

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

554 """ 

555 provider_params = {} 

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

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

558 customized_builtins = self._get_customized_builtins( 

559 operation_model, call_args, request_context 

560 ) 

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

562 param_val = self._resolve_param_from_context( 

563 param_name=param_name, 

564 operation_model=operation_model, 

565 call_args=call_args, 

566 ) 

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

568 param_val = self._resolve_param_as_builtin( 

569 builtin_name=param_def.builtin, 

570 builtins=customized_builtins, 

571 ) 

572 if param_val is not None: 

573 provider_params[param_name] = param_val 

574 self._register_endpoint_feature_ids(param_name, param_val) 

575 

576 return provider_params 

577 

578 def _resolve_param_from_context( 

579 self, param_name, operation_model, call_args 

580 ): 

581 static = self._resolve_param_as_static_context_param( 

582 param_name, operation_model 

583 ) 

584 if static is not None: 

585 return static 

586 dynamic = self._resolve_param_as_dynamic_context_param( 

587 param_name, operation_model, call_args 

588 ) 

589 if dynamic is not None: 

590 return dynamic 

591 operation_context_params = ( 

592 self._resolve_param_as_operation_context_param( 

593 param_name, operation_model, call_args 

594 ) 

595 ) 

596 if operation_context_params is not None: 

597 return operation_context_params 

598 return self._resolve_param_as_client_context_param(param_name) 

599 

600 def _resolve_param_as_static_context_param( 

601 self, param_name, operation_model 

602 ): 

603 static_ctx_params = self._get_static_context_params(operation_model) 

604 return static_ctx_params.get(param_name) 

605 

606 def _resolve_param_as_dynamic_context_param( 

607 self, param_name, operation_model, call_args 

608 ): 

609 dynamic_ctx_params = self._get_dynamic_context_params(operation_model) 

610 if param_name in dynamic_ctx_params: 

611 member_name = dynamic_ctx_params[param_name] 

612 return call_args.get(member_name) 

613 

614 def _resolve_param_as_client_context_param(self, param_name): 

615 client_ctx_params = self._get_client_context_params() 

616 if param_name in client_ctx_params: 

617 client_ctx_varname = client_ctx_params[param_name] 

618 return self._client_context.get(client_ctx_varname) 

619 

620 def _resolve_param_as_operation_context_param( 

621 self, param_name, operation_model, call_args 

622 ): 

623 operation_ctx_params = operation_model.operation_context_parameters 

624 if param_name in operation_ctx_params: 

625 path = operation_ctx_params[param_name]['path'] 

626 return jmespath.search(path, call_args) 

627 

628 def _resolve_param_as_builtin(self, builtin_name, builtins): 

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

630 raise UnknownEndpointResolutionBuiltInName(name=builtin_name) 

631 builtin = builtins.get(builtin_name) 

632 if callable(builtin): 

633 return builtin() 

634 return builtin 

635 

636 @instance_cache 

637 def _get_static_context_params(self, operation_model): 

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

639 return { 

640 param.name: param.value 

641 for param in operation_model.static_context_parameters 

642 } 

643 

644 @instance_cache 

645 def _get_dynamic_context_params(self, operation_model): 

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

647 return { 

648 param.name: param.member_name 

649 for param in operation_model.context_parameters 

650 } 

651 

652 @instance_cache 

653 def _get_client_context_params(self): 

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

655 return { 

656 param.name: xform_name(param.name) 

657 for param in self._service_model.client_context_parameters 

658 } 

659 

660 def _get_customized_builtins( 

661 self, operation_model, call_args, request_context 

662 ): 

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

664 customized_builtins = copy.copy(self._builtins) 

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

666 self._event_emitter.emit( 

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

668 builtins=customized_builtins, 

669 model=operation_model, 

670 params=call_args, 

671 context=request_context, 

672 ) 

673 return customized_builtins 

674 

675 def auth_schemes_to_signing_ctx(self, auth_schemes): 

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

677 

678 :type auth_schemes: list 

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

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

681 ``EndpointProvider``. 

682 

683 :rtype: str, dict 

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

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

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

687 """ 

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

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

690 

691 LOG.debug( 

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

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

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

695 self._requested_auth_scheme, 

696 ) 

697 

698 if self._requested_auth_scheme == UNSIGNED: 

699 return 'none', {} 

700 

701 auth_schemes = [ 

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

703 for scheme in auth_schemes 

704 ] 

705 if self._requested_auth_scheme is not None: 

706 try: 

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

708 # after accounting for naming differences between botocore and 

709 # endpoint rulesets. Keep the requested name. 

710 name, scheme = next( 

711 (self._requested_auth_scheme, s) 

712 for s in auth_schemes 

713 if self._does_botocore_authname_match_ruleset_authname( 

714 self._requested_auth_scheme, s['name'] 

715 ) 

716 ) 

717 except StopIteration: 

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

719 # exception, instead default to the logic in botocore 

720 # customizations. 

721 return None, {} 

722 else: 

723 try: 

724 name, scheme = next( 

725 (s['name'], s) 

726 for s in auth_schemes 

727 if s['name'] in AUTH_TYPE_MAPS 

728 ) 

729 except StopIteration: 

730 # If no auth scheme was specifically requested and an 

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

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

733 fixable_with_crt = False 

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

735 if not HAS_CRT: 

736 fixable_with_crt = any( 

737 scheme in CRT_SUPPORTED_AUTH_TYPES 

738 for scheme in auth_type_options 

739 ) 

740 

741 if fixable_with_crt: 

742 raise MissingDependencyException( 

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

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

745 ) 

746 else: 

747 raise UnknownSignatureVersionError( 

748 signature_version=', '.join(auth_type_options) 

749 ) 

750 

751 signing_context = {} 

752 if 'signingRegion' in scheme: 

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

754 elif 'signingRegionSet' in scheme: 

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

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

757 scheme['signingRegionSet'] 

758 ) 

759 if 'signingName' in scheme: 

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

761 if 'disableDoubleEncoding' in scheme: 

762 signing_context['disableDoubleEncoding'] = ensure_boolean( 

763 scheme['disableDoubleEncoding'] 

764 ) 

765 

766 LOG.debug( 

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

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

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

770 signing_context, 

771 ) 

772 return name, signing_context 

773 

774 def _strip_sig_prefix(self, auth_name): 

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

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

777 

778 def _does_botocore_authname_match_ruleset_authname(self, botoname, rsname): 

779 """ 

780 Whether a valid string provided as signature_version parameter for 

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

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

783 

784 * The ruleset prefixes auth names with "sig" 

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

786 s3v4[a] signers 

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

788 features and do not exist in the rulesets 

789 * Only characters up to the first dash are considered 

790 

791 Example matches: 

792 * v4, sigv4 

793 * v4, v4 

794 * s3v4, sigv4 

795 * s3v7, sigv7 (hypothetical example) 

796 * s3v4a, sigv4a 

797 * s3v4-query, sigv4 

798 

799 Example mismatches: 

800 * v4a, sigv4 

801 * s3, sigv4 

802 * s3-presign-post, sigv4 

803 """ 

804 rsname = self._strip_sig_prefix(rsname) 

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

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

807 botoname = botoname[2:] 

808 return rsname == botoname 

809 

810 def ruleset_error_to_botocore_exception(self, ruleset_exception, params): 

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

812 exception types by string matching exception strings. 

813 """ 

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

815 if msg is None: 

816 return 

817 

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

819 # Example message: 

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

821 try: 

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

823 except IndexError: 

824 label = msg 

825 return InvalidHostLabelError(label=label) 

826 

827 service_name = self._service_model.service_name 

828 if service_name == 's3': 

829 if ( 

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

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

832 ): 

833 return UnsupportedS3ConfigurationError(msg=msg) 

834 if ( 

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

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

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

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

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

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

841 ): 

842 return UnsupportedS3AccesspointConfigurationError(msg=msg) 

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

844 return ParamValidationError(report=msg) 

845 if service_name == 's3control': 

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

847 arn = params.get('Bucket') 

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

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

850 'Client was configured for partition' 

851 ): 

852 return UnsupportedS3ControlConfigurationError(msg=msg) 

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

854 return ParamValidationError(report=msg) 

855 if service_name == 'events': 

856 if msg.startswith( 

857 'Invalid Configuration: FIPS is not supported with ' 

858 'EventBridge multi-region endpoints.' 

859 ): 

860 return InvalidEndpointConfigurationError(msg=msg) 

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

862 return InvalidEndpointConfigurationError(msg=msg) 

863 return None 

864 

865 def _register_endpoint_feature_ids(self, param_name, param_val): 

866 if param_name == 'AccountIdEndpointMode': 

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

868 elif param_name == 'AccountId': 

869 register_feature_id('RESOLVED_ACCOUNT_ID')