credentials.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. # Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  2. #
  3. # Modifications made by Cloudera are:
  4. # Copyright (c) 2016 Cloudera, Inc. All rights reserved.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License"). You
  7. # may not use this file except in compliance with the License. A copy of
  8. # the License is located at
  9. #
  10. # http://aws.amazon.com/apache2.0/
  11. #
  12. # or in the "license" file accompanying this file. This file is
  13. # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
  14. # ANY KIND, either express or implied. See the License for the specific
  15. # language governing permissions and limitations under the License.
  16. from collections import namedtuple
  17. import logging
  18. import os
  19. from ccscli import CCS_ACCESS_KEY_ID_KEY_NAME, CCS_PRIVATE_KEY_KEY_NAME
  20. import ccscli.compat
  21. from ccscli.compat import json
  22. from ccscli.configloader import raw_config_parse
  23. from ccscli.exceptions import ConfigNotFound
  24. from ccscli.exceptions import NoCredentialsError
  25. from ccscli.exceptions import PartialCredentialsError
  26. from ccscli.exceptions import UnknownCredentialError
  27. LOG = logging.getLogger('ccscli.credentials')
  28. ReadOnlyCredentials = namedtuple('ReadOnlyCredentials',
  29. ['access_key_id', 'private_key', 'method'])
  30. ACCESS_KEY_ID = 'access_key_id'
  31. PRIVATE_KEY = 'private_key'
  32. def create_credential_resolver(context):
  33. """Create a default credential resolver.
  34. This creates a pre-configured credential resolver
  35. that includes the default lookup chain for
  36. credentials.
  37. """
  38. profile_name = context.effective_profile
  39. auth_file = context.get_config_variable('auth_config')
  40. shared_credential_file = context.get_config_variable('credentials_file')
  41. env_provider = EnvProvider()
  42. providers = [
  43. env_provider,
  44. AuthConfigFile(auth_file),
  45. SharedCredentialProvider(
  46. creds_filename=shared_credential_file,
  47. profile_name=profile_name
  48. ),
  49. ]
  50. explicit_profile = context.get_config_variable('profile',
  51. methods=('instance',))
  52. if explicit_profile is not None:
  53. # An explicitly provided profile will negate an EnvProvider.
  54. # We will defer to providers that understand the "profile"
  55. # concept to retrieve credentials.
  56. # The one edge case is if all three values are provided via
  57. # env vars:
  58. # export CCS_ACCESS_KEY_ID=foo
  59. # export CCS_PRIVATE_KEY=bar
  60. # export CCS_PROFILE=baz
  61. # Then, just like our client() calls, the explicit credentials
  62. # will take precedence.
  63. #
  64. # This precedence is enforced by leaving the EnvProvider in the chain.
  65. # This means that the only way a "profile" would win is if the
  66. # EnvProvider does not return credentials, which is what we want
  67. # in this scenario.
  68. providers.remove(env_provider)
  69. LOG.debug('Skipping environment variable credential check because '
  70. 'profile name was explicitly set.')
  71. resolver = CredentialResolver(providers=providers)
  72. return resolver
  73. def get_credentials(context):
  74. resolver = create_credential_resolver(context)
  75. return resolver.load_credentials()
  76. class Credentials(object):
  77. """
  78. Holds the credentials needed to authenticate requests.
  79. """
  80. def __init__(self, access_key_id, private_key, method):
  81. self.access_key_id = access_key_id
  82. self.private_key = private_key
  83. self.method = method
  84. self._normalize()
  85. def _normalize(self):
  86. self.access_key_id = ccscli.compat.ensure_unicode(self.access_key_id)
  87. self.private_key = ccscli.compat.ensure_unicode(self.private_key)
  88. def get_frozen_credentials(self):
  89. return ReadOnlyCredentials(self.access_key_id,
  90. self.private_key,
  91. self.method)
  92. class CredentialProvider(object):
  93. # Implementations must provide a method.
  94. METHOD = None
  95. def load(self):
  96. return True
  97. def _extract_creds_from_mapping(self, mapping, *key_names):
  98. found = []
  99. for key_name in key_names:
  100. try:
  101. found.append(mapping[key_name])
  102. except KeyError:
  103. raise PartialCredentialsError(provider=self.METHOD,
  104. cred_var=key_name)
  105. return found
  106. class EnvProvider(CredentialProvider):
  107. METHOD = 'env'
  108. ACCESS_KEY_ID_ENV_VAR = 'CCS_ACCESS_KEY_ID'
  109. PRIVATE_KEY_ENV_VAR = 'CCS_PRIVATE_KEY'
  110. def __init__(self, environ=None, mapping=None):
  111. super(EnvProvider, self).__init__()
  112. if environ is None:
  113. environ = os.environ
  114. self.environ = environ
  115. self._mapping = self._build_mapping(mapping)
  116. def _build_mapping(self, mapping):
  117. # Mapping of variable name to env var name.
  118. var_mapping = {}
  119. if mapping is None:
  120. # Use the class var default.
  121. var_mapping[ACCESS_KEY_ID] = self.ACCESS_KEY_ID_ENV_VAR
  122. var_mapping[PRIVATE_KEY] = self.PRIVATE_KEY_ENV_VAR
  123. else:
  124. var_mapping[ACCESS_KEY_ID] = mapping.get(
  125. ACCESS_KEY_ID, self.ACCESS_KEY_ID_ENV_VAR)
  126. var_mapping[PRIVATE_KEY] = mapping.get(
  127. PRIVATE_KEY, self.PRIVATE_KEY_ENV_VAR)
  128. return var_mapping
  129. def load(self):
  130. """
  131. Search for credentials in explicit environment variables.
  132. """
  133. if self._mapping[ACCESS_KEY_ID] in self.environ:
  134. access_key_id, private_key = self._extract_creds_from_mapping(
  135. self.environ, self._mapping[ACCESS_KEY_ID],
  136. self._mapping[PRIVATE_KEY])
  137. LOG.info('Found credentials in environment variables.')
  138. if not os.path.isfile(private_key):
  139. LOG.debug("Private key at %s does not exist!" % private_key)
  140. raise NoCredentialsError()
  141. pem = open(private_key).read()
  142. return Credentials(access_key_id, pem, method=self.METHOD)
  143. else:
  144. return None
  145. class CredentialResolver(object):
  146. def __init__(self, providers):
  147. self.providers = providers
  148. def insert_before(self, name, credential_provider):
  149. """
  150. Inserts a new instance of ``CredentialProvider`` into the chain that will
  151. be tried before an existing one.
  152. """
  153. try:
  154. offset = [p.METHOD for p in self.providers].index(name)
  155. except ValueError:
  156. raise UnknownCredentialError(name=name)
  157. self.providers.insert(offset, credential_provider)
  158. def insert_after(self, name, credential_provider):
  159. """
  160. Inserts a new type of ``Credentials`` instance into the chain that will
  161. be tried after an existing one.
  162. """
  163. offset = self._get_provider_offset(name)
  164. self.providers.insert(offset + 1, credential_provider)
  165. def remove(self, name):
  166. """
  167. Removes a given ``Credentials`` instance from the chain.
  168. """
  169. available_methods = [p.METHOD for p in self.providers]
  170. if name not in available_methods:
  171. # It's not present. Fail silently.
  172. return
  173. offset = available_methods.index(name)
  174. self.providers.pop(offset)
  175. def get_provider(self, name):
  176. """
  177. Return a credential provider by name.
  178. """
  179. return self.providers[self._get_provider_offset(name)]
  180. def _get_provider_offset(self, name):
  181. try:
  182. return [p.METHOD for p in self.providers].index(name)
  183. except ValueError:
  184. raise UnknownCredentialError(name=name)
  185. def load_credentials(self):
  186. """
  187. Goes through the credentials chain, returning the first ``Credentials``
  188. that could be loaded.
  189. """
  190. # First provider to return a non-None response wins.
  191. for provider in self.providers:
  192. LOG.debug("Looking for credentials via: %s", provider.METHOD)
  193. creds = provider.load()
  194. if creds is not None:
  195. return creds
  196. raise NoCredentialsError()
  197. class AuthConfigFile(CredentialProvider):
  198. METHOD = 'auth_config_file'
  199. def __init__(self, conf):
  200. super(AuthConfigFile, self).__init__()
  201. self._conf = conf
  202. def load(self):
  203. """
  204. load the credential from the json configuration file.
  205. """
  206. if self._conf is None:
  207. return None
  208. if not os.path.isfile(self._conf):
  209. LOG.debug("Conf file at %s does not exist!" % self._conf)
  210. raise NoCredentialsError()
  211. try:
  212. conf = json.loads(open(self._conf).read())
  213. except Exception:
  214. LOG.debug("Could not read conf: %s", exc_info=True)
  215. return None
  216. if ACCESS_KEY_ID in conf:
  217. LOG.debug('Found credentials for key: %s in configuration file.',
  218. conf[ACCESS_KEY_ID])
  219. access_key_id, private_key = self._extract_creds_from_mapping(
  220. conf,
  221. ACCESS_KEY_ID,
  222. PRIVATE_KEY)
  223. return Credentials(access_key_id, private_key, self.METHOD)
  224. raise NoCredentialsError()
  225. class SharedCredentialProvider(CredentialProvider):
  226. METHOD = 'shared-credentials-file'
  227. def __init__(self, creds_filename, profile_name):
  228. self._creds_filename = creds_filename
  229. self._profile_name = profile_name
  230. def load(self):
  231. try:
  232. available_creds = raw_config_parse(self._creds_filename)
  233. except ConfigNotFound:
  234. LOG.debug("Credentials file at %s does not exist!" % self._creds_filename)
  235. return None
  236. if self._profile_name in available_creds:
  237. config = available_creds[self._profile_name]
  238. access_key_id, private_key = self._extract_creds_from_mapping(
  239. config, CCS_ACCESS_KEY_ID_KEY_NAME, CCS_PRIVATE_KEY_KEY_NAME)
  240. # We store the private key in the credentials file as a one-line
  241. # value in which the newlines in the PEM file are replaced with
  242. # '\n'. We need to replace them back as the RawConfigParser we use
  243. # does not do it for us. Note that if the value in the configuration
  244. # IS a PEM formatted value this is a no-op.
  245. private_key = private_key.replace('\\n', '\n')
  246. LOG.info("Found credentials in shared credentials file: %s",
  247. self._creds_filename)
  248. return Credentials(access_key_id, private_key, method=self.METHOD)