diff --git a/Makefile b/Makefile index dcc2dcd..334d259 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ install: - pip install -e .[docs,test] + pip install -e .[docs,test,azure] test: py.test diff --git a/docs/conf.py b/docs/conf.py index 81b3966..59eaf2a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,7 +56,7 @@ # |version| and |release|, also used in various other places throughout the # built documents. # -version = '0.2.1' +version = '0.2.2' release = version # The language for content autogenerated by Sphinx. Refer to documentation @@ -139,7 +139,7 @@ # The name for this set of Sphinx documents. # " v documentation" by default. # -# html_title = u'python-param-store v0.2.1' +# html_title = u'python-param-store v0.2.2' # A shorter title for the navigation bar. Default is the same as html_title. # diff --git a/setup.cfg b/setup.cfg index fb4aadb..f97362e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.2.1 +current_version = 0.2.2 commit = true tag = true tag_name = {new_version} diff --git a/setup.py b/setup.py index e4b3198..35d3d21 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,10 @@ 'six>=1.1', ] +azure_require = [ + 'azure-keyvault', +] + docs_require = [ 'sphinx>=1.4.0', ] @@ -32,7 +36,7 @@ setup( name='param-store', - version='0.2.1', + version='0.2.2', description="Parameter store for secrets", long_description=long_description, url='https://github.com/labd/python-param-store', @@ -41,6 +45,7 @@ install_requires=install_requires, tests_require=tests_require, extras_require={ + 'azure': azure_require, 'docs': docs_require, 'test': tests_require, }, diff --git a/src/param_store/__init__.py b/src/param_store/__init__.py index 9f8c88c..21faa97 100644 --- a/src/param_store/__init__.py +++ b/src/param_store/__init__.py @@ -3,4 +3,4 @@ __all__ = ['Env', 'EC2ParameterStore'] -__version__ = '0.2.1' +__version__ = '0.2.2' diff --git a/src/param_store/stores.py b/src/param_store/stores.py index aaa927e..21ef17b 100644 --- a/src/param_store/stores.py +++ b/src/param_store/stores.py @@ -1,7 +1,8 @@ from itertools import islice __all__ = [ - 'EC2ParameterStore' + 'EC2ParameterStore', + 'AzureVaultParameterStore', ] @@ -44,3 +45,72 @@ def chunk(it, size): value = parameter['Value'] result[key] = value return result + + +class AzureVaultConfigurationException(Exception): + pass + + +class AzureVaultParameterStore(BaseStore): + + def __init__(self): + from azure.keyvault import KeyVaultClient, KeyVaultAuthentication + from os import getenv + + self.vault_id = getenv("AZURE_VAULT_ID", None) + self.tenant_id = getenv("AZURE_TENANT_ID", None) + + self.app_id = getenv("AZURE_APP_ID", None) + self.client_id = getenv("AZURE_CLIENT_ID", None) + self.secret = getenv("AZURE_SECRET", None) + + if self.app_id: + import adal + + # create an adal authentication context + auth_context = adal.AuthenticationContext( + 'https://login.microsoftonline.com/%s' % self.tenant_id) + + def auth_callback(server, resource, scope): + user_code_info = auth_context.acquire_user_code(resource, self.app_id) + + token = auth_context.acquire_token_with_device_code(resource=resource, + client_id=self.app_id, + user_code_info=user_code_info) + return token['token_type'], token['access_token'] + elif self.client_id and self.secret: + from azure.common.credentials import ServicePrincipalCredentials + + def auth_callback(server, resource, scope): + credentials = ServicePrincipalCredentials( + client_id=self.client_id, + secret=self.secret, + tenant=self.tenant_id, + resource="https://vault.azure.net" + ) + token = credentials.token + return token['token_type'], token['access_token'] + else: + raise AzureVaultConfigurationException("Either set AZURE_APP_ID or " + "(AZURE_CLIENT_ID and AZURE_SECRET)") + + self.client = KeyVaultClient(KeyVaultAuthentication(auth_callback)) + + def load_values(self, items): + from azure.keyvault.models import KeyVaultErrorException + from azure.keyvault import KeyVaultId + + if not items or not self.vault_id: + return {} + + result = {} + for key in items: + try: + data = self.client.get_secret("https://%s.vault.azure.net/" % self.vault_id, + key, KeyVaultId.version_none) + except KeyVaultErrorException: + continue + + result[key] = data.value + + return result