20
20
from botocore .docs .docstring import PaginatorDocstring
21
21
from botocore .exceptions import (
22
22
DataNotFoundError , OperationNotPageableError , UnknownSignatureVersionError ,
23
- InvalidEndpointDiscoveryConfigurationError , UnknownFIPSEndpointError ,
23
+ InvalidEndpointDiscoveryConfigurationError
24
24
)
25
25
from botocore .hooks import first_non_none_response
26
26
from botocore .model import ServiceModel
@@ -82,9 +82,12 @@ def create_client(self, service_name, region_name, is_secure=True,
82
82
service_name = first_non_none_response (responses , default = service_name )
83
83
service_model = self ._load_service_model (service_name , api_version )
84
84
cls = self ._create_client_class (service_name , service_model )
85
+ region_name , client_config = self ._normalize_fips_region (
86
+ region_name , client_config )
85
87
endpoint_bridge = ClientEndpointBridge (
86
88
self ._endpoint_resolver , scoped_config , client_config ,
87
- service_signing_name = service_model .metadata .get ('signingName' ))
89
+ service_signing_name = service_model .metadata .get ('signingName' ),
90
+ config_store = self ._config_store )
88
91
client_args = self ._get_client_args (
89
92
service_model , region_name , is_secure , endpoint_url ,
90
93
verify , credentials , scoped_config , client_config , endpoint_bridge )
@@ -99,7 +102,6 @@ def create_client(self, service_name, region_name, is_secure=True,
99
102
self ._register_endpoint_discovery (
100
103
service_client , endpoint_url , client_config
101
104
)
102
- self ._register_lazy_block_unknown_fips_pseudo_regions (service_client )
103
105
return service_client
104
106
105
107
def create_client_class (self , service_name , api_version = None ):
@@ -120,6 +122,30 @@ def _create_client_class(self, service_name, service_model):
120
122
cls = type (str (class_name ), tuple (bases ), class_attributes )
121
123
return cls
122
124
125
+ def _normalize_fips_region (self , region_name ,
126
+ client_config ):
127
+ if region_name is not None :
128
+ normalized_region_name = region_name .replace (
129
+ 'fips-' , '' ).replace ('-fips' , '' )
130
+ # If region has been transformed then set flag
131
+ if normalized_region_name != region_name :
132
+ config_use_fips_endpoint = Config (use_fips_endpoint = True )
133
+ if client_config :
134
+ # Keeping endpoint setting client specific
135
+ client_config = client_config .merge (
136
+ config_use_fips_endpoint )
137
+ else :
138
+ client_config = config_use_fips_endpoint
139
+ logger .warn (
140
+ 'transforming region from %s to %s and setting '
141
+ 'use_fips_endpoint to true. client should not '
142
+ 'be configured with a fips psuedo region.' % (
143
+ region_name , normalized_region_name
144
+ )
145
+ )
146
+ region_name = normalized_region_name
147
+ return region_name , client_config
148
+
123
149
def _load_service_model (self , service_name , api_version = None ):
124
150
json_model = self ._loader .load_service_model (service_name , 'service-2' ,
125
151
api_version = api_version )
@@ -235,45 +261,20 @@ def _requires_endpoint_discovery(self, client, enabled):
235
261
return client .meta .service_model .endpoint_discovery_required
236
262
return enabled
237
263
238
- def _register_lazy_block_unknown_fips_pseudo_regions (self , client ):
239
- # This function blocks usage of FIPS pseudo-regions when the endpoint
240
- # is not explicitly known to exist to the client to prevent accidental
241
- # usage of incorrect or non-FIPS endpoints. This is done lazily by
242
- # registering an exception on the before-sign event to allow for
243
- # general client usage such as can_paginate, exceptions, etc.
244
- region_name = client .meta .region_name
245
- if not region_name or 'fips' not in region_name .lower ():
246
- return
247
-
248
- partition = client .meta .partition
249
- endpoint_prefix = client .meta .service_model .endpoint_prefix
250
- known_regions = self ._endpoint_resolver .get_available_endpoints (
251
- endpoint_prefix ,
252
- partition ,
253
- allow_non_regional = True ,
254
- )
255
-
256
- if region_name not in known_regions :
257
- def _lazy_fips_exception (** kwargs ):
258
- service_name = client .meta .service_model .service_name
259
- raise UnknownFIPSEndpointError (
260
- region_name = region_name ,
261
- service_name = service_name ,
262
- )
263
- client .meta .events .register ('before-sign' , _lazy_fips_exception )
264
-
265
264
def _register_s3_events (self , client , endpoint_bridge , endpoint_url ,
266
265
client_config , scoped_config ):
267
266
if client .meta .service_model .service_name != 's3' :
268
267
return
269
268
S3RegionRedirector (endpoint_bridge , client ).register ()
270
269
S3ArnParamHandler ().register (client .meta .events )
270
+ use_fips_endpoint = client .meta .config .use_fips_endpoint
271
271
S3EndpointSetter (
272
272
endpoint_resolver = self ._endpoint_resolver ,
273
273
region = client .meta .region_name ,
274
274
s3_config = client .meta .config .s3 ,
275
275
endpoint_url = endpoint_url ,
276
- partition = client .meta .partition
276
+ partition = client .meta .partition ,
277
+ use_fips_endpoint = use_fips_endpoint
277
278
).register (client .meta .events )
278
279
self ._set_s3_presign_signature_version (
279
280
client .meta , client_config , scoped_config )
@@ -284,13 +285,15 @@ def _register_s3_control_events(
284
285
):
285
286
if client .meta .service_model .service_name != 's3control' :
286
287
return
288
+ use_fips_endpoint = client .meta .config .use_fips_endpoint
287
289
S3ControlArnParamHandler ().register (client .meta .events )
288
290
S3ControlEndpointSetter (
289
291
endpoint_resolver = self ._endpoint_resolver ,
290
292
region = client .meta .region_name ,
291
293
s3_config = client .meta .config .s3 ,
292
294
endpoint_url = endpoint_url ,
293
- partition = client .meta .partition
295
+ partition = client .meta .partition ,
296
+ use_fips_endpoint = use_fips_endpoint
294
297
).register (client .meta .events )
295
298
296
299
def _set_s3_presign_signature_version (self , client_meta ,
@@ -415,30 +418,42 @@ class ClientEndpointBridge(object):
415
418
utilize "us-east-1" by default if no region can be resolved."""
416
419
417
420
DEFAULT_ENDPOINT = '{service}.{region}.amazonaws.com'
418
- _DUALSTACK_ENABLED_SERVICES = ['s3' , 's3-control' ]
421
+ _DUALSTACK_CUSTOMIZED_SERVICES = ['s3' , 's3-control' ]
419
422
420
423
def __init__ (self , endpoint_resolver , scoped_config = None ,
421
424
client_config = None , default_endpoint = None ,
422
- service_signing_name = None ):
425
+ service_signing_name = None , config_store = None ):
423
426
self .service_signing_name = service_signing_name
424
427
self .endpoint_resolver = endpoint_resolver
425
428
self .scoped_config = scoped_config
426
429
self .client_config = client_config
427
430
self .default_endpoint = default_endpoint or self .DEFAULT_ENDPOINT
431
+ self .config_store = config_store
428
432
429
433
def resolve (self , service_name , region_name = None , endpoint_url = None ,
430
434
is_secure = True ):
431
435
region_name = self ._check_default_region (service_name , region_name )
436
+ use_dualstack_endpoint = self ._resolve_use_dualstack_endpoint (
437
+ service_name )
438
+ use_fips_endpoint = self ._resolve_endpoint_variant_config_var (
439
+ 'use_fips_endpoint'
440
+ )
432
441
resolved = self .endpoint_resolver .construct_endpoint (
433
- service_name , region_name )
442
+ service_name , region_name ,
443
+ use_dualstack_endpoint = use_dualstack_endpoint ,
444
+ use_fips_endpoint = use_fips_endpoint ,
445
+ )
434
446
435
447
# If we can't resolve the region, we'll attempt to get a global
436
448
# endpoint for non-regionalized services (iam, route53, etc)
437
449
if not resolved :
438
450
# TODO: fallback partition_name should be configurable in the
439
451
# future for users to define as needed.
440
452
resolved = self .endpoint_resolver .construct_endpoint (
441
- service_name , region_name , partition_name = 'aws' )
453
+ service_name , region_name , partition_name = 'aws' ,
454
+ use_dualstack_endpoint = use_dualstack_endpoint ,
455
+ use_fips_endpoint = use_fips_endpoint ,
456
+ )
442
457
443
458
if resolved :
444
459
return self ._create_endpoint (
@@ -456,20 +471,13 @@ def _check_default_region(self, service_name, region_name):
456
471
457
472
def _create_endpoint (self , resolved , service_name , region_name ,
458
473
endpoint_url , is_secure ):
459
- explicit_region = region_name is not None
460
474
region_name , signing_region = self ._pick_region_values (
461
475
resolved , region_name , endpoint_url )
462
476
if endpoint_url is None :
463
- if self ._is_s3_dualstack_mode (service_name ):
464
- endpoint_url = self ._create_dualstack_endpoint (
465
- service_name , region_name ,
466
- resolved ['dnsSuffix' ], is_secure , explicit_region )
467
- else :
468
- # Use the sslCommonName over the hostname for Python 2.6 compat.
469
- hostname = resolved .get ('sslCommonName' , resolved .get ('hostname' ))
470
- endpoint_url = self ._make_url (
471
- hostname , is_secure , resolved .get ('protocols' , [])
472
- )
477
+ # Use the sslCommonName over the hostname for Python 2.6 compat.
478
+ hostname = resolved .get ('sslCommonName' , resolved .get ('hostname' ))
479
+ endpoint_url = self ._make_url (hostname , is_secure ,
480
+ resolved .get ('protocols' , []))
473
481
signature_version = self ._resolve_signature_version (
474
482
service_name , resolved )
475
483
signing_name = self ._resolve_signing_name (service_name , resolved )
@@ -479,9 +487,28 @@ def _create_endpoint(self, resolved, service_name, region_name,
479
487
endpoint_url = endpoint_url , metadata = resolved ,
480
488
signature_version = signature_version )
481
489
490
+ def _resolve_endpoint_variant_config_var (self , config_var ):
491
+ client_config = self .client_config
492
+ config_val = False
493
+
494
+ # Client configuration arg has precedence
495
+ if client_config and getattr (client_config , config_var ) is not None :
496
+ return getattr (client_config , config_var )
497
+ elif self .config_store is not None :
498
+ # Check config store
499
+ config_val = self .config_store .get_config_variable (config_var )
500
+ return config_val
501
+
502
+ def _resolve_use_dualstack_endpoint (self , service_name ):
503
+ s3_dualstack_mode = self ._is_s3_dualstack_mode (service_name )
504
+ if s3_dualstack_mode is not None :
505
+ return s3_dualstack_mode
506
+ return self ._resolve_endpoint_variant_config_var (
507
+ 'use_dualstack_endpoint' )
508
+
482
509
def _is_s3_dualstack_mode (self , service_name ):
483
- if service_name not in self ._DUALSTACK_ENABLED_SERVICES :
484
- return False
510
+ if service_name not in self ._DUALSTACK_CUSTOMIZED_SERVICES :
511
+ return None
485
512
# TODO: This normalization logic is duplicated from the
486
513
# ClientArgsCreator class. Consolidate everything to
487
514
# ClientArgsCreator. _resolve_signature_version also has similarly
@@ -491,27 +518,11 @@ def _is_s3_dualstack_mode(self, service_name):
491
518
'use_dualstack_endpoint' in client_config .s3 :
492
519
# Client config trumps scoped config.
493
520
return client_config .s3 ['use_dualstack_endpoint' ]
494
- if self .scoped_config is None :
495
- return False
496
- enabled = self .scoped_config .get ('s3' , {}).get (
497
- 'use_dualstack_endpoint' , False )
498
- if enabled in [True , 'True' , 'true' ]:
499
- return True
500
- return False
501
-
502
- def _create_dualstack_endpoint (self , service_name , region_name ,
503
- dns_suffix , is_secure , explicit_region ):
504
- if not explicit_region and region_name == 'aws-global' :
505
- # If the region_name passed was not explicitly set, default to
506
- # us-east-1 instead of the modeled default aws-global. Dualstack
507
- # does not support aws-global
508
- region_name = 'us-east-1'
509
- hostname = '{service}.dualstack.{region}.{dns_suffix}' .format (
510
- service = service_name , region = region_name ,
511
- dns_suffix = dns_suffix )
512
- # Dualstack supports http and https so were hardcoding this value for
513
- # now. This can potentially move into the endpoints.json file.
514
- return self ._make_url (hostname , is_secure , ['http' , 'https' ])
521
+ if self .scoped_config is not None :
522
+ enabled = self .scoped_config .get ('s3' , {}).get (
523
+ 'use_dualstack_endpoint' )
524
+ if enabled in [True , 'True' , 'true' ]:
525
+ return True
515
526
516
527
def _assume_endpoint (self , service_name , region_name , endpoint_url ,
517
528
is_secure ):
0 commit comments