auth.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. # Copyright 2013 dotCloud inc.
  2. # Licensed under the Apache License, Version 2.0 (the "License");
  3. # you may not use this file except in compliance with the License.
  4. # You may obtain a copy of the License at
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. # Unless required by applicable law or agreed to in writing, software
  7. # distributed under the License is distributed on an "AS IS" BASIS,
  8. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. # See the License for the specific language governing permissions and
  10. # limitations under the License.
  11. import base64
  12. import fileinput
  13. import json
  14. import os
  15. from fig.packages import six
  16. from ..utils import utils
  17. from .. import errors
  18. INDEX_URL = 'https://index.docker.io/v1/'
  19. DOCKER_CONFIG_FILENAME = '.dockercfg'
  20. def swap_protocol(url):
  21. if url.startswith('http://'):
  22. return url.replace('http://', 'https://', 1)
  23. if url.startswith('https://'):
  24. return url.replace('https://', 'http://', 1)
  25. return url
  26. def expand_registry_url(hostname):
  27. if hostname.startswith('http:') or hostname.startswith('https:'):
  28. if '/' not in hostname[9:]:
  29. hostname = hostname + '/v1/'
  30. return hostname
  31. if utils.ping('https://' + hostname + '/v1/_ping'):
  32. return 'https://' + hostname + '/v1/'
  33. return 'http://' + hostname + '/v1/'
  34. def resolve_repository_name(repo_name):
  35. if '://' in repo_name:
  36. raise errors.InvalidRepository(
  37. 'Repository name cannot contain a scheme ({0})'.format(repo_name))
  38. parts = repo_name.split('/', 1)
  39. if '.' not in parts[0] and ':' not in parts[0] and parts[0] != 'localhost':
  40. # This is a docker index repo (ex: foo/bar or ubuntu)
  41. return INDEX_URL, repo_name
  42. if len(parts) < 2:
  43. raise errors.InvalidRepository(
  44. 'Invalid repository name ({0})'.format(repo_name))
  45. if 'index.docker.io' in parts[0]:
  46. raise errors.InvalidRepository(
  47. 'Invalid repository name, try "{0}" instead'.format(parts[1]))
  48. return expand_registry_url(parts[0]), parts[1]
  49. def resolve_authconfig(authconfig, registry=None):
  50. """Return the authentication data from the given auth configuration for a
  51. specific registry. We'll do our best to infer the correct URL for the
  52. registry, trying both http and https schemes. Returns an empty dictionnary
  53. if no data exists."""
  54. # Default to the public index server
  55. registry = registry or INDEX_URL
  56. # Ff its not the index server there are three cases:
  57. #
  58. # 1. this is a full config url -> it should be used as is
  59. # 2. it could be a full url, but with the wrong protocol
  60. # 3. it can be the hostname optionally with a port
  61. #
  62. # as there is only one auth entry which is fully qualified we need to start
  63. # parsing and matching
  64. if '/' not in registry:
  65. registry = registry + '/v1/'
  66. if not registry.startswith('http:') and not registry.startswith('https:'):
  67. registry = 'https://' + registry
  68. if registry in authconfig:
  69. return authconfig[registry]
  70. return authconfig.get(swap_protocol(registry), None)
  71. def encode_auth(auth_info):
  72. return base64.b64encode(auth_info.get('username', '') + b':' +
  73. auth_info.get('password', ''))
  74. def decode_auth(auth):
  75. if isinstance(auth, six.string_types):
  76. auth = auth.encode('ascii')
  77. s = base64.b64decode(auth)
  78. login, pwd = s.split(b':')
  79. return login.decode('ascii'), pwd.decode('ascii')
  80. def encode_header(auth):
  81. auth_json = json.dumps(auth).encode('ascii')
  82. return base64.b64encode(auth_json)
  83. def encode_full_header(auth):
  84. """ Returns the given auth block encoded for the X-Registry-Config header.
  85. """
  86. return encode_header({'configs': auth})
  87. def load_config(root=None):
  88. """Loads authentication data from a Docker configuration file in the given
  89. root directory."""
  90. conf = {}
  91. data = None
  92. config_file = os.path.join(root or os.environ.get('HOME', '.'),
  93. DOCKER_CONFIG_FILENAME)
  94. # First try as JSON
  95. try:
  96. with open(config_file) as f:
  97. conf = {}
  98. for registry, entry in six.iteritems(json.load(f)):
  99. username, password = decode_auth(entry['auth'])
  100. conf[registry] = {
  101. 'username': username,
  102. 'password': password,
  103. 'email': entry['email'],
  104. 'serveraddress': registry,
  105. }
  106. return conf
  107. except:
  108. pass
  109. # If that fails, we assume the configuration file contains a single
  110. # authentication token for the public registry in the following format:
  111. #
  112. # auth = AUTH_TOKEN
  113. # email = [email protected]
  114. try:
  115. data = []
  116. for line in fileinput.input(config_file):
  117. data.append(line.strip().split(' = ')[1])
  118. if len(data) < 2:
  119. # Not enough data
  120. raise errors.InvalidConfigFile(
  121. 'Invalid or empty configuration file!')
  122. username, password = decode_auth(data[0])
  123. conf[INDEX_URL] = {
  124. 'username': username,
  125. 'password': password,
  126. 'email': data[1],
  127. 'serveraddress': INDEX_URL,
  128. }
  129. return conf
  130. except:
  131. pass
  132. # If all fails, return an empty config
  133. return {}