auth.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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. INDEX_URL = 'https://index.docker.io/v1/'
  18. DOCKER_CONFIG_FILENAME = '.dockercfg'
  19. def swap_protocol(url):
  20. if url.startswith('http://'):
  21. return url.replace('http://', 'https://', 1)
  22. if url.startswith('https://'):
  23. return url.replace('https://', 'http://', 1)
  24. return url
  25. def expand_registry_url(hostname):
  26. if hostname.startswith('http:') or hostname.startswith('https:'):
  27. if '/' not in hostname[9:]:
  28. hostname = hostname + '/v1/'
  29. return hostname
  30. if utils.ping('https://' + hostname + '/v1/_ping'):
  31. return 'https://' + hostname + '/v1/'
  32. return 'http://' + hostname + '/v1/'
  33. def resolve_repository_name(repo_name):
  34. if '://' in repo_name:
  35. raise ValueError('Repository name cannot contain a '
  36. 'scheme ({0})'.format(repo_name))
  37. parts = repo_name.split('/', 1)
  38. if not '.' in parts[0] and not ':' in parts[0] and parts[0] != 'localhost':
  39. # This is a docker index repo (ex: foo/bar or ubuntu)
  40. return INDEX_URL, repo_name
  41. if len(parts) < 2:
  42. raise ValueError('Invalid repository name ({0})'.format(repo_name))
  43. if 'index.docker.io' in parts[0]:
  44. raise ValueError('Invalid repository name,'
  45. 'try "{0}" instead'.format(parts[1]))
  46. return expand_registry_url(parts[0]), parts[1]
  47. def resolve_authconfig(authconfig, registry=None):
  48. """Return the authentication data from the given auth configuration for a
  49. specific registry. We'll do our best to infer the correct URL for the
  50. registry, trying both http and https schemes. Returns an empty dictionnary
  51. if no data exists."""
  52. # Default to the public index server
  53. registry = registry or INDEX_URL
  54. # Ff its not the index server there are three cases:
  55. #
  56. # 1. this is a full config url -> it should be used as is
  57. # 2. it could be a full url, but with the wrong protocol
  58. # 3. it can be the hostname optionally with a port
  59. #
  60. # as there is only one auth entry which is fully qualified we need to start
  61. # parsing and matching
  62. if '/' not in registry:
  63. registry = registry + '/v1/'
  64. if not registry.startswith('http:') and not registry.startswith('https:'):
  65. registry = 'https://' + registry
  66. if registry in authconfig:
  67. return authconfig[registry]
  68. return authconfig.get(swap_protocol(registry), None)
  69. def decode_auth(auth):
  70. if isinstance(auth, six.string_types):
  71. auth = auth.encode('ascii')
  72. s = base64.b64decode(auth)
  73. login, pwd = s.split(b':')
  74. return login.decode('ascii'), pwd.decode('ascii')
  75. def encode_header(auth):
  76. auth_json = json.dumps(auth).encode('ascii')
  77. return base64.b64encode(auth_json)
  78. def load_config(root=None):
  79. """Loads authentication data from a Docker configuration file in the given
  80. root directory."""
  81. conf = {}
  82. data = None
  83. config_file = os.path.join(root or os.environ.get('HOME', '.'),
  84. DOCKER_CONFIG_FILENAME)
  85. # First try as JSON
  86. try:
  87. with open(config_file) as f:
  88. conf = {}
  89. for registry, entry in six.iteritems(json.load(f)):
  90. username, password = decode_auth(entry['auth'])
  91. conf[registry] = {
  92. 'username': username,
  93. 'password': password,
  94. 'email': entry['email'],
  95. 'serveraddress': registry,
  96. }
  97. return conf
  98. except:
  99. pass
  100. # If that fails, we assume the configuration file contains a single
  101. # authentication token for the public registry in the following format:
  102. #
  103. # auth = AUTH_TOKEN
  104. # email = [email protected]
  105. try:
  106. data = []
  107. for line in fileinput.input(config_file):
  108. data.append(line.strip().split(' = ')[1])
  109. if len(data) < 2:
  110. # Not enough data
  111. raise Exception('Invalid or empty configuration file!')
  112. username, password = decode_auth(data[0])
  113. conf[INDEX_URL] = {
  114. 'username': username,
  115. 'password': password,
  116. 'email': data[1],
  117. 'serveraddress': INDEX_URL,
  118. }
  119. return conf
  120. except:
  121. pass
  122. # If all fails, return an empty config
  123. return {}