diff options
author | Matt Houglum <houglum@google.com> | 2018-09-16 18:10:07 -0700 |
---|---|---|
committer | Matt Houglum <houglum@google.com> | 2018-09-16 19:20:53 -0700 |
commit | aa0f1a5137e0062afc34e347d4a9c88e4f94a507 (patch) | |
tree | 0601b09dcef17fa8d542d9f2de85bfad63f93b73 | |
parent | 8fac1878734c5ac085b781f619c70ea4b6e913c3 (diff) | |
download | boto-aa0f1a5137e0062afc34e347d4a9c88e4f94a507.tar.gz |
Allows anon auth'n to S3 via a config option.
See https://stackoverflow.com/questions/52308440/fetch-content-in-aws-s3-public-bucket-from-gcp-data-storage/52322861#52322861
for a valid use case for this option.
This option was named after the "--no-sign-request" option in the AWS
CLI.
The additional changes to provider.py and auth.py are to prevent Boto
from checking and failing to find credentials on the metadata server
(and thus spewing error-level logs to the console), and to allow
pickling to work for the AnonAuthHandler and HmacKeys classes.
-rw-r--r-- | boto/auth.py | 26 | ||||
-rw-r--r-- | boto/connection.py | 5 | ||||
-rw-r--r-- | boto/provider.py | 9 | ||||
-rw-r--r-- | boto/s3/connection.py | 25 | ||||
-rw-r--r-- | tests/unit/s3/test_connection.py | 35 |
5 files changed, 81 insertions, 19 deletions
diff --git a/boto/auth.py b/boto/auth.py index b479d126..4374f73b 100644 --- a/boto/auth.py +++ b/boto/auth.py @@ -98,21 +98,26 @@ SIGV4_DETECT = [ class HmacKeys(object): """Key based Auth handler helper.""" - def __init__(self, host, config, provider): + def __init__(self, host, config, provider, anon=False): if provider.access_key is None or provider.secret_key is None: - raise boto.auth_handler.NotReadyToAuthenticate() + if not anon: + raise boto.auth_handler.NotReadyToAuthenticate() + else: + self._hmac = None + self._hmac_256 = None self.host = host self.update_provider(provider) def update_provider(self, provider): self._provider = provider - self._hmac = hmac.new(self._provider.secret_key.encode('utf-8'), - digestmod=sha) - if sha256: - self._hmac_256 = hmac.new(self._provider.secret_key.encode('utf-8'), - digestmod=sha256) - else: - self._hmac_256 = None + if self._provider.secret_key: # Anonymous handler has no key. + self._hmac = hmac.new(self._provider.secret_key.encode('utf-8'), + digestmod=sha) + if sha256: + self._hmac_256 = hmac.new( + self._provider.secret_key.encode('utf-8'), digestmod=sha256) + else: + self._hmac_256 = None def algorithm(self): if self._hmac_256: @@ -152,7 +157,8 @@ class AnonAuthHandler(AuthHandler, HmacKeys): capability = ['anon'] def __init__(self, host, config, provider): - super(AnonAuthHandler, self).__init__(host, config, provider) + AuthHandler.__init__(self, host, config, provider) + HmacKeys.__init__(self, host, config, provider, anon=True) def add_auth(self, http_request, **kwargs): pass diff --git a/boto/connection.py b/boto/connection.py index 2fef4487..34b428f1 100644 --- a/boto/connection.py +++ b/boto/connection.py @@ -543,6 +543,8 @@ class AWSAuthConnection(object): self.http_connection_kwargs['timeout'] = config.getint( 'Boto', 'http_socket_timeout', 70) + is_anonymous_connection = getattr(self, 'anon', False) + if isinstance(provider, Provider): # Allow overriding Provider self.provider = provider @@ -552,7 +554,8 @@ class AWSAuthConnection(object): aws_access_key_id, aws_secret_access_key, security_token, - profile_name) + profile_name, + anon=is_anonymous_connection) # Allow config file to override default host, port, and host header. if self.provider.host: diff --git a/boto/provider.py b/boto/provider.py index e08afa30..494f3f7d 100644 --- a/boto/provider.py +++ b/boto/provider.py @@ -179,7 +179,7 @@ class Provider(object): } def __init__(self, name, access_key=None, secret_key=None, - security_token=None, profile_name=None): + security_token=None, profile_name=None, anon=False): self.host = None self.port = None self.host_header = None @@ -187,6 +187,7 @@ class Provider(object): self.secret_key = secret_key self.security_token = security_token self.profile_name = profile_name + self.anon = anon self.name = name self.acl_class = self.AclClassMap[self.name] self.canned_acls = self.CannedAclsMap[self.name] @@ -244,6 +245,9 @@ class Provider(object): security_token = property(get_security_token, set_security_token) def _credentials_need_refresh(self): + if self.anon: + return False + if self._credential_expiry_time is None: return False else: @@ -264,6 +268,9 @@ class Provider(object): def get_credentials(self, access_key=None, secret_key=None, security_token=None, profile_name=None): + if self.anon: + return + access_key_name, secret_key_name, security_token_name, \ profile_name_name = self.CredentialMap[self.name] diff --git a/boto/s3/connection.py b/boto/s3/connection.py index fa3fbd72..f364a6b0 100644 --- a/boto/s3/connection.py +++ b/boto/s3/connection.py @@ -171,20 +171,31 @@ class S3Connection(AWSAuthConnection): host=NoHostProvided, debug=0, https_connection_factory=None, calling_format=DefaultCallingFormat, path='/', provider='aws', bucket_class=Bucket, security_token=None, - suppress_consec_slashes=True, anon=False, + suppress_consec_slashes=True, anon=None, validate_certs=None, profile_name=None): + self.bucket_class = bucket_class + + if isinstance(calling_format, six.string_types): + calling_format=boto.utils.find_class(calling_format)() + self.calling_format = calling_format + + # Fetching config options at init time, instead of using a class-level + # default (set at class declaration time) as the default arg value, + # allows our tests to ensure that the config file options are + # respected. + if anon is None: + # Only fetch from the config option if a non-default arg value was + # provided. + anon = boto.config.getbool('s3', 'no_sign_request', False) + self.anon = anon + no_host_provided = False - # Try falling back to the boto config file's value, if present. if host is NoHostProvided: host = boto.config.get('s3', 'host') if host is None: host = self.DefaultHost no_host_provided = True - if isinstance(calling_format, six.string_types): - calling_format=boto.utils.find_class(calling_format)() - self.calling_format = calling_format - self.bucket_class = bucket_class - self.anon = anon + super(S3Connection, self).__init__(host, aws_access_key_id, aws_secret_access_key, is_secure, port, proxy, proxy_port, proxy_user, proxy_pass, diff --git a/tests/unit/s3/test_connection.py b/tests/unit/s3/test_connection.py index 05c561b4..8fd33a0a 100644 --- a/tests/unit/s3/test_connection.py +++ b/tests/unit/s3/test_connection.py @@ -59,6 +59,41 @@ class TestAnon(MockServiceWithConfigTestCase): url = conn.generate_url(0, 'GET', bucket='examplebucket', key='test.txt') self.assertNotIn('Signature=', url) + def test_anon_default_taken_from_config_opt(self): + self.config = { + 's3': { + # Value must be a string for `config.getbool` to not crash. + 'no_sign_request': 'True', + } + } + + conn = self.connection_class( + aws_access_key_id='less', + aws_secret_access_key='more', + host='s3.amazonaws.com', + ) + url = conn.generate_url( + 0, 'GET', bucket='examplebucket', key='test.txt') + self.assertNotIn('Signature=', url) + + def test_explicit_anon_arg_overrides_config_value(self): + self.config = { + 's3': { + # Value must be a string for `config.getbool` to not crash. + 'no_sign_request': 'True', + } + } + + conn = self.connection_class( + aws_access_key_id='less', + aws_secret_access_key='more', + host='s3.amazonaws.com', + anon=False + ) + url = conn.generate_url( + 0, 'GET', bucket='examplebucket', key='test.txt') + self.assertIn('Signature=', url) + class TestPresigned(MockServiceWithConfigTestCase): connection_class = S3Connection |