From a023e2a77682bc4a853a12589d25cd250035a460 Mon Sep 17 00:00:00 2001 From: Alex Schultz Date: Tue, 1 Sep 2020 14:17:12 -0600 Subject: [PATCH] Support basic auth for image registry If you stand up a docker-distribution registry, basic auth can be configured with tls to lock down the registry. Currently the only auth method that is supported is the bearer tokens used by the public registries. This change checks the www-authentication header to see if we should try basic auth or the bearer token auth. Change-Id: I57599ab3cd8773ae3312930145b9e84244940f41 Closes-Bug: #1893826 (cherry picked from commit 0c73e4f3eb310a90cc37ba8d0aba9c27ca50989b) --- tripleo_common/image/image_uploader.py | 45 +++++++++++++------ .../tests/image/test_image_uploader.py | 25 +++++++++++ 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/tripleo_common/image/image_uploader.py b/tripleo_common/image/image_uploader.py index 8d73a5a32..28b419780 100644 --- a/tripleo_common/image/image_uploader.py +++ b/tripleo_common/image/image_uploader.py @@ -667,31 +667,48 @@ class BaseImageUploader(object): raise ImageUploaderException( 'Unknown authentication method for headers: %s' % r.headers) + auth = None www_auth = r.headers['www-authenticate'] - if not www_auth.startswith('Bearer '): - raise ImageUploaderException( - 'Unknown www-authenticate value: %s' % www_auth) token_param = {} - realm = re.search('realm="(.*?)"', www_auth).group(1) - if 'service=' in www_auth: - token_param['service'] = re.search( - 'service="(.*?)"', www_auth).group(1) - token_param['scope'] = 'repository:%s:pull' % image[1:] + if www_auth.startswith('Bearer '): + LOG.debug('Using bearer token auth') + realm = re.search('realm="(.*?)"', www_auth).group(1) + if 'service=' in www_auth: + token_param['service'] = re.search( + 'service="(.*?)"', www_auth).group(1) + token_param['scope'] = 'repository:%s:pull' % image[1:] - auth = None - if username: + if username: + auth = requests_auth.HTTPBasicAuth(username, password) + LOG.debug('Token parameters: params {}'.format(token_param)) + rauth = session.get(realm, params=token_param, auth=auth, + timeout=30) + rauth.raise_for_status() + auth_header = 'Bearer %s' % rauth.json()['token'] + elif www_auth.startswith('Basic '): + LOG.debug('Using basic auth') + if not username or not password: + raise Exception('Authentication credentials required for ' + 'basic auth: %s' % url) auth = requests_auth.HTTPBasicAuth(username, password) - LOG.debug('Token parameters: params {}'.format(token_param)) - rauth = session.get(realm, params=token_param, auth=auth, timeout=30) - rauth.raise_for_status() - session.headers['Authorization'] = 'Bearer %s' % rauth.json()['token'] + rauth = session.get(url, params=token_param, auth=auth, timeout=30) + rauth.raise_for_status() + auth_header = ( + 'Basic %s' % base64.b64encode( + six.b(username + ':' + password)).decode('ascii') + ) + else: + raise ImageUploaderException( + 'Unknown www-authenticate value: %s' % www_auth) hash_request_id = hashlib.sha1(str(rauth.url).encode()) LOG.debug( 'Session authenticated: id {}'.format( hash_request_id.hexdigest() ) ) + session.headers['Authorization'] = auth_header + setattr(session, 'reauthenticate', self.authenticate) setattr( session, diff --git a/tripleo_common/tests/image/test_image_uploader.py b/tripleo_common/tests/image/test_image_uploader.py index a38b21c00..8e89c8e2f 100644 --- a/tripleo_common/tests/image/test_image_uploader.py +++ b/tripleo_common/tests/image/test_image_uploader.py @@ -800,6 +800,31 @@ class TestBaseImageUploader(base.TestCase): auth(url1).headers['Authorization'] ) + def test_authenticate_basic_auth(self): + req = self.requests + auth = self.uploader.authenticate + url1 = urlparse('docker://myrepo.com/t/nova-api:latest') + + # successful auth requests + headers = { + 'www-authenticate': 'Basic realm="Some Realm"' + } + + def req_match(request): + resp = requests.Response() + resp.headers = headers + resp.status_code = 401 + # if we got sent an user/password, return 200 + if 'Authorization' in request.headers: + resp.status_code = 200 + return resp + + req.add_matcher(req_match) + self.assertEqual( + 'Basic Zm9vOmJhcg==', + auth(url1, username='foo', password='bar').headers['Authorization'] + ) + def test_authenticate_with_no_service(self): req = self.requests auth = self.uploader.authenticate