From 5d9cca2fc7f38bf2f96eb4ce00a5cdcb04126ffe Mon Sep 17 00:00:00 2001 From: Brent Douglas Date: Wed, 31 Oct 2018 14:37:10 +0900 Subject: [PATCH] Probe naked domain and protocols for credential When probing for a registries credential currently only the https prefixed domain is searched. Some credential services (e.g. docker-credential-secretservice) do not store the protocol and only return the credential for the naked domain. This probes the naked domain, https and http prefixed domain to see if any are stored and returns the first match. --- client/docker_creds_.py | 56 +++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/client/docker_creds_.py b/client/docker_creds_.py index fd610e2ab..8335125d8 100755 --- a/client/docker_creds_.py +++ b/client/docker_creds_.py @@ -129,6 +129,13 @@ def password(self): _MAGIC_NOT_FOUND_MESSAGE = 'credentials not found in native keychain' +_DOMAIN_FORMATS = [ + # Allow naked domains + '%s', + # Allow scheme-prefixed. + 'https://%s', + 'http://%s', +] class Helper(Basic): """This provider wraps a particularly named credential helper.""" @@ -151,29 +158,39 @@ def Get(self): bin_name = 'docker-credential-{name}'.format(name=self._name) logging.info('Invoking %r to obtain Docker credentials.', bin_name) - try: - p = subprocess.Popen( - [bin_name, 'get'], - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - stderr=subprocess.STDOUT) - except OSError as e: - if e.errno == errno.ENOENT: - raise Exception('executable not found: ' + bin_name) - raise + + def get_credential(registry): + try: + p = subprocess.Popen( + [bin_name, 'get'], + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.STDOUT) + except OSError as e: + if e.errno == errno.ENOENT: + raise Exception('executable not found: ' + bin_name) + raise + stdout = p.communicate(input=registry)[0] + if stdout.strip() == _MAGIC_NOT_FOUND_MESSAGE: + return _MAGIC_NOT_FOUND_MESSAGE + if p.returncode != 0: + raise Exception('Error fetching credential for %s, exit status: %d\n%s' % + (self._name, p.returncode, stdout)) + return stdout # Some keychains expect a scheme: # https://github.com/bazelbuild/rules_docker/issues/111 - stdout = p.communicate(input='https://' + self._registry)[0] - if stdout.strip() == _MAGIC_NOT_FOUND_MESSAGE: + stdout = _MAGIC_NOT_FOUND_MESSAGE + for form in _DOMAIN_FORMATS: + stdout = get_credential(form % self._registry) + if stdout != _MAGIC_NOT_FOUND_MESSAGE: + break + + if stdout == _MAGIC_NOT_FOUND_MESSAGE: # Use empty auth when no auth is found. logging.info('Credentials not found, falling back to anonymous auth.') return Anonymous().Get() - if p.returncode != 0: - raise Exception('Error fetching credential for %s, exit status: %d\n%s' % - (self._name, p.returncode, stdout)) - blob = json.loads(stdout) logging.info('Successfully obtained Docker credentials.') return Basic(blob['Username'], blob['Secret']).Get() @@ -196,12 +213,7 @@ def Resolve(self, name): # pytype: enable=bad-return-type -_FORMATS = [ - # Allow naked domains - '%s', - # Allow scheme-prefixed. - 'https://%s', - 'http://%s', +_FORMATS = _DOMAIN_FORMATS + [ # Allow scheme-prefixes with version in url path. 'https://%s/v1/', 'http://%s/v1/',