import os
import sys
import click
from azure.identity import ClientSecretCredential
from azure.keyvault.secrets import SecretClient
from azure.keyvault.secrets._shared._generated.v7_0.models import KeyVaultErrorException
__version__ = '2.0.1'
def print_version(ctx, param, value):
if not value or ctx.resilient_parsing:
return
click.echo(__version__)
ctx.exit()
[docs]class AzureSecrets:
"""
Azure secrets object that can be used for CLI and as a module.
"""
def __init__(self, vault_base_url: str = None, client_id: str = None, secret: str = None,
tenant: str = None):
self.vault_base_url = vault_base_url
self.client_id = client_id
self.secret = secret
self.tenant = tenant
try:
if self.vault_base_url is None:
self.vault_base_url = os.environ["AZURE_VAULT_BASE_URL"]
if self.client_id is None:
self.client_id = os.environ['AZURE_CLIENT_ID']
if self.secret is None:
self.secret = os.environ['AZURE_SECRET_KEY']
if self.tenant is None:
self.tenant = os.environ['AZURE_TENANT_ID']
except KeyError as e:
print("Did you forget to set the environment variable?", e)
sys.exit(1)
self.client = SecretClient(vault_url=self.vault_base_url, credential=ClientSecretCredential(
client_id=self.client_id,
client_secret=self.secret,
tenant_id=self.tenant,
))
[docs] def get_secret(self, secret_name: str, secret_version: str = None) -> str:
"""
Get the value for the secret key.
:param secret_name: Name of the secret key.
:type secret_name: str
:param secret_version: The version string of the secret key.
:type secret_version: str
:return: The secret value.
:rtype: str
>>> secrets = AzureSecrets('https://', 'client id', 'secret key', 'tenant id')
>>> print(secrets.get_secret('secret-name'))
secret-value
"""
if secret_version is None:
secret_version = None
key_bundle = self.client.get_secret(secret_name, secret_version)
return key_bundle.value
[docs] def get_secrets(self, env_names: list = None) -> dict:
"""
A dictionary of secret name and it's value.
:param env_names: A list of secret names.
:type env_names: list
:rtype: dict
:return: Dictionary of secrets.
>>> secrets = AzureSecrets('https://', 'client id', 'secret key', 'tenant id')
>>> print(secrets.get_secrets(['secret-name-1', 'secret-name-2']))
{
'secret-name-1' : 'secret-value-1',
'secret-name-2': 'secret-value-2'
}
"""
secrets = {}
try:
if env_names is None:
for secret_id in self.client.list_properties_of_secrets():
secrets.update({secret_id.name: self.get_secret(secret_name=secret_id.name)})
else:
for secret_name in env_names:
secrets.update({secret_name: self.get_secret(secret_name=secret_name)})
except KeyVaultErrorException as e:
print("Error:", e)
sys.exit(1)
return secrets
[docs] def env_powershell(self, secret_names: list = None, except_names: list = None):
"""
Prints environment variable for PowerShell
"""
for key, value in self.get_secrets(secret_names).items():
print('$Env:{0}="{1}"'.format(key.replace('-', "_"), value))
print("# Run this command to configure your shell:")
print("# & secrets env --shell powershell | Invoke-Expression")
[docs] def env_cmd(self, secret_names: list = None, except_names: list = None):
"""
Prints environment variable for CMD
"""
for key, value in self.get_secrets(secret_names).items():
print("SET {0}={1}".format(key.replace('-', "_"), value))
print("REM Run this command to configure your shell:")
print("REM @FOR /f \"tokens=*\" %i IN ('secrets env --shell cmd') DO @%i")
[docs] def env_bash(self, secret_names: list = None, except_names: list = None):
"""
Prints environment variable for Bash
"""
for key, value in self.get_secrets(secret_names).items():
print("export {0}='{1}'".format(key.replace('-', "_"), value))
print("# Run this command to configure your shell:")
print("# eval $(secrets env --shell bash)")
@click.group()
@click.option('--version', is_flag=True, callback=print_version, expose_value=False, is_eager=True,
help="Show version and exit.")
@click.option('--vault-base-url', help='Azure KeyVault base URL. Defaults to None.', default=None)
@click.option('--client-id', help='Azure KeyVault client ID.', default=None)
@click.option('--secret', help='Azure KeyVault secret.', default=None)
@click.option('--tenant', help='Azure tenant ID.', default=None)
@click.pass_context
def cli(ctx, vault_base_url, client_id, secret, tenant):
ctx.obj['vault_base_url'] = vault_base_url
ctx.obj['client_id'] = client_id
ctx.obj['secret'] = secret
ctx.obj['tenant'] = tenant
@cli.command(help="Environment configuration: [powershell, cmd or bash].")
@click.option('--shell', type=click.Choice(['powershell', 'cmd', 'bash']))
@click.option('--secret-names',
help="A comma separated names of the secret to use (without space): NAME-1,NAME-2",
default=None)
@click.option('--except', 'except_names',
help="A comma separated names of the secret to ignore (without space): NAME-1,NAME-2",
default=None)
@click.pass_context
def env(ctx, shell, secret_names, except_names):
if secret_names is not None:
secret_names = [name.strip() for name in secret_names.split(',')]
if except_names is not None:
except_names = [excepts.strip() for excepts in except_names.split(',')]
if shell == 'powershell':
AzureSecrets(ctx.obj['vault_base_url'], ctx.obj['client_id'], ctx.obj['secret'],
ctx.obj['tenant']).env_powershell(secret_names, except_names)
elif shell == 'cmd':
AzureSecrets(ctx.obj['vault_base_url'], ctx.obj['client_id'], ctx.obj['secret'],
ctx.obj['tenant']).env_cmd(secret_names, except_names)
elif shell == 'bash':
AzureSecrets(ctx.obj['vault_base_url'], ctx.obj['client_id'], ctx.obj['secret'],
ctx.obj['tenant']).env_bash(secret_names, except_names)
def main():
cli(obj={})
if __name__ == '__main__':
cli(obj={})