def _resolve_authconfig_credstore(authconfig, registry, credstore_name): if not registry or registry == INDEX_NAME: # The ecosystem is a little schizophrenic with index.docker.io VS # docker.io - in that case, it seems the full URL is necessary. registry = 'https://index.docker.io/v1/' log.debug("Looking for auth entry for {0}".format(repr(registry))) store = dockerpycreds.Store(credstore_name) try: data = store.get(registry) res = { 'ServerAddress': registry, } if data['Username'] == TOKEN_USERNAME: res['IdentityToken'] = data['Secret'] else: res.update({ 'Username': data['Username'], 'Password': data['Secret'], }) return res except dockerpycreds.CredentialsNotFound as e: log.debug('No entry found') return None except dockerpycreds.StoreError as e: raise errors.DockerException('Credentials store error: {0}'.format( repr(e)))
def resolve_authconfig(authconfig, registry=None): """ Returns the authentication data from the given auth configuration for a specific registry. As with the Docker client, legacy entries in the config with full URLs are stripped down to hostnames before checking for a match. Returns None if no match was found. """ if 'credsStore' in authconfig: log.debug('Using credentials store "{0}"'.format( authconfig['credsStore'])) return _resolve_authconfig_credstore(authconfig, registry, authconfig['credsStore']) # Default to the public index server registry = resolve_index_name(registry) if registry else INDEX_NAME log.debug("Looking for auth entry for {0}".format(repr(registry))) if registry in authconfig: log.debug("Found {0}".format(repr(registry))) return authconfig[registry] for key, config in six.iteritems(authconfig): if resolve_index_name(key) == registry: log.debug("Found {0}".format(repr(key))) return config log.debug("No entry found") return None
def parse_auth(entries, raise_on_error=False): """ Parses authentication entries Args: entries: Dict of authentication entries. raise_on_error: If set to true, an invalid format will raise InvalidConfigFile Returns: Authentication registry. """ conf = {} for registry, entry in six.iteritems(entries): if not isinstance(entry, dict): log.debug( 'Config entry for key {0} is not auth config'.format(registry)) # We sometimes fall back to parsing the whole config as if it was # the auth config by itself, for legacy purposes. In that case, we # fail silently and return an empty conf if any of the keys is not # formatted properly. if raise_on_error: raise errors.InvalidConfigFile( 'Invalid configuration for registry {0}'.format(registry)) return {} if 'identitytoken' in entry: log.debug('Found an IdentityToken entry for registry {0}'.format( registry)) conf[registry] = {'IdentityToken': entry['identitytoken']} continue # Other values are irrelevant if we have a token, skip. if 'auth' not in entry: # Starting with engine v1.11 (API 1.23), an empty dictionary is # a valid value in the auths config. # https://github.com/docker/compose/issues/3265 log.debug('Auth data for {0} is absent. Client might be using a ' 'credentials store instead.') conf[registry] = {} continue username, password = decode_auth(entry['auth']) log.debug('Found entry (registry={0}, username={1})'.format( repr(registry), repr(username))) conf[registry] = { 'username': username, 'password': password, 'email': entry.get('email'), 'serveraddress': registry, } return conf
def get_config_header(client, registry): log.debug('Looking for auth config') if not client._auth_configs: log.debug("No auth config in memory - loading from filesystem") client._auth_configs = load_config() authcfg = resolve_authconfig(client._auth_configs, registry) # Do not fail here if no authentication exists for this # specific registry as we can have a readonly pull. Just # put the header if we can. if authcfg: log.debug('Found auth config') # auth_config needs to be a dict in the format used by # auth.py username , password, serveraddress, email return encode_header(authcfg) log.debug('No auth config found') return None
def find_config_file(config_path=None): paths = list( filter( None, [ config_path, # 1 config_path_from_environment(), # 2 os.path.join(home_dir(), DOCKER_CONFIG_FILENAME), # 3 os.path.join(home_dir(), LEGACY_DOCKER_CONFIG_FILENAME), # 4 ])) log.debug("Trying paths: {0}".format(repr(paths))) for path in paths: if os.path.exists(path): log.debug("Found file at path: {0}".format(path)) return path log.debug("No config file found") return None
def load_config(config_path=None): """ Loads authentication data from a Docker configuration file in the given root directory or if config_path is passed use given path. Lookup priority: explicit config_path parameter > DOCKER_CONFIG environment variable > ~/.docker/config.json > ~/.dockercfg """ config_file = find_config_file(config_path) if not config_file: return {} try: with open(config_file) as f: data = json.load(f) res = {} if data.get('auths'): log.debug("Found 'auths' section") res.update(parse_auth(data['auths'], raise_on_error=True)) if data.get('HttpHeaders'): log.debug("Found 'HttpHeaders' section") res.update({'HttpHeaders': data['HttpHeaders']}) if data.get('credsStore'): log.debug("Found 'credsStore' section") res.update({'credsStore': data['credsStore']}) if res: return res else: log.debug("Couldn't find 'auths' or 'HttpHeaders' sections") f.seek(0) return parse_auth(json.load(f)) except (IOError, KeyError, ValueError) as e: # Likely missing new Docker config file or it's in an # unknown format, continue to attempt to read old location # and format. log.debug(e) log.debug("Attempting to parse legacy auth file format") try: data = [] with open(config_file) as f: for line in f.readlines(): data.append(line.strip().split(' = ')[1]) if len(data) < 2: # Not enough data raise errors.InvalidConfigFile( 'Invalid or empty configuration file!') username, password = decode_auth(data[0]) return { INDEX_NAME: { 'username': username, 'password': password, 'email': data[1], 'serveraddress': INDEX_URL, } } except Exception as e: log.debug(e) pass log.debug("All parsing attempts failed - returning empty config") return {}