def initial_token(vault_client, opt): """Generate our first token based on workstation configuration""" token_file = os.environ.get('VAULT_TOKEN_FILE', "%s/.vault-token" % os.environ['HOME']) app_file = os.environ.get('AOMI_APP_FILE', "%s/.aomi-app-token" % os.environ['HOME']) token_file = os.path.abspath(token_file) app_file = os.path.abspath(app_file) if 'VAULT_TOKEN' in os.environ and len(os.environ['VAULT_TOKEN']) > 0: log('Token derived from VAULT_TOKEN environment variable', opt) return os.environ['VAULT_TOKEN'].strip() elif 'VAULT_USER_ID' in os.environ and \ 'VAULT_APP_ID' in os.environ and \ len(os.environ['VAULT_USER_ID']) > 0 and \ len(os.environ['VAULT_APP_ID']) > 0: token = app_token(vault_client, os.environ['VAULT_APP_ID'].strip(), os.environ['VAULT_USER_ID'].strip()) log("Token derived from VAULT_APP_ID and VAULT_USER_ID", opt) return token elif os.path.exists(app_file): token = yaml.load(open(app_file).read().strip()) if 'app_id' in token and 'user_id' in token: token = app_token(vault_client, token['app_id'], token['user_id']) log("Token derived from %s" % app_file, opt) return token elif os.path.exists(token_file): log("Token derived from %s" % token_file, opt) return open(token_file, 'r').read().strip() else: problems('Unable to determine vault authentication method')
def files(client, secret, opt): """Seed files into Vault""" if 'mount' not in secret or 'path' not in secret: problems("Invalid files specification %s" % secret) obj = {} vault_path = "%s/%s" % (secret['mount'], secret['path']) if not is_tagged(secret.get('tags', []), opt.tags): log("Skipping %s as it does not have appropriate tags" % vault_path, opt) return for f in secret.get('files', []): if 'source' not in f or 'name' not in f: problems("Invalid file specification %s" % f) filename = hard_path(f['source'], opt.secrets) data = open(filename, 'r').read() obj[f['name']] = data log('writing file %s into %s/%s' % (filename, vault_path, f['name']), opt) ensure_mounted(client, 'generic', secret['mount']) client.write(vault_path, **obj)
def app_token(vault_client, app_id, user_id): """Returns a vault token based on the app and user id.""" resp = vault_client.auth_app_id(app_id, user_id) if 'auth' in resp and 'client_token' in resp['auth']: return resp['auth']['client_token'] else: problems('Unable to retrieve app token')
def ensure_mounted(client, backend, mount): """Will ensure a mountpoint exists, or bail with a polite error""" backends = client.list_secret_backends() if not is_mounted(mount, backends, backend): try: client.enable_secret_backend(backend, mount_point=mount) except hvac.exceptions.InvalidRequest as e: m = re.match('existing mount at (?P<path>.+)', str(e)) if m: problems("%s has a mountpoint conflict with %s" % (mount, m.group('path')))
def subdir_file(item, relative): """Returns a file path relative to another file.""" item_bits = item.split(os.sep) relative_bits = relative.split(os.sep) for i in range(0, len(item_bits)): if i == len(relative_bits) - 1: return os.sep.join(item_bits[i:]) else: if item_bits[i] != relative_bits[i]: problems("gitignore and secrets paths diverge!") return None
def raw_file(client, src, dest): """Write the contents of a vault path/key to a file""" path_bits = src.split('/') path = '/'.join(path_bits[0:len(path_bits) - 1]) key = path_bits[len(path_bits) - 1] resp = client.read(path) if not resp: problems("Unable to retrieve %s" % path) else: if 'data' in resp and key in resp['data']: secret = resp['data'][key] open(os.path.abspath(dest), 'w').write(secret) else: problems("Key %s not found in %s" % (key, path))
def template(client, src, dest, vault_path): """Writes a template using variables from a vault path""" template_src = Template(open(os.path.abspath(src), 'r').read()) secrets = client.read(vault_path) if not secrets: problems("Unable to retrieve %s" % vault_path) obj = {} for k, v in secrets['data'].items(): norm_path = [x for x in vault_path.split('/') if x] v_name = ("%s_%s" % ('_'.join(norm_path), k)).lower() obj[v_name] = v output = template_src.render(**obj) open(os.path.abspath(dest), 'w').write(output)
def seed(vault_client, opt): """Will provision vault based on the definition within a Secretfile""" config = yaml.load(open(os.path.abspath(opt.secretfile)).read()) for secret in config.get('secrets', []): if 'var_file' in secret: aomi.seed.var_file(vault_client, secret, opt) elif 'aws_file' in secret: aomi.seed.aws(vault_client, secret, opt) elif 'files' in secret: aomi.seed.files(vault_client, secret, opt) else: problems("Invalid secret element %s" % secret) for app in config.get('apps', []): if 'app_file' in app: aomi.seed.app(vault_client, app, opt) else: problems("Invalid app element %s" % app)
def aws(client, path, opt): """Renders a shell environment snippet with AWS information""" creds = client.read(path) if creds and 'data' in creds: print("AWS_ACCESS_KEY_ID=\"%s\"" % creds['data']['access_key']) print("AWS_SECRET_ACCESS_KEY=\"%s\"" % creds['data']['secret_key']) if 'security_token' in creds['data'] \ and creds['data']['security_token']: token = creds['data']['security_token'] print("AWS_SECURITY_TOKEN=\"%s\"" % token) else: problems("Unable to generate AWS credentials from %s" % path) if opt.export: print("export AWS_ACCESS_KEY_ID") print("export AWS_SECRET_ACCESS_KEY") if 'security_token' in creds['data'] \ and creds['data']['security_token']: print("export AWS_SECURITY_TOKEN")
def var_file(client, secret, opt): """Seed a var_file into Vault""" path = "%s/%s" % (secret['mount'], secret['path']) var_file_name = hard_path(secret['var_file'], opt.secrets) varz = yaml.load(open(var_file_name).read()) if 'var_file' not in secret \ or 'mount' not in secret \ or 'path' not in secret: problems("Invalid generic secret definition %s" % secret) if not is_tagged(secret.get('tags', []), opt.tags): log("Skipping %s as it does not have appropriate tags" % path, opt) return ensure_mounted(client, 'generic', secret['mount']) client.write(path, **varz) log( 'wrote var_file %s into %s/%s' % (var_file_name, secret['mount'], secret['path']), opt)
def aws(client, secret, opt): """Seed an aws_file into Vault""" if 'aws_file' not in secret or 'mount' not in secret: problems("Invalid aws secret definition" % secret) aws_file_path = hard_path(secret['aws_file'], opt.secrets) aws_obj = yaml.load(open(aws_file_path, 'r').read()) if 'access_key_id' not in aws_obj \ or 'secret_access_key' not in aws_obj \ or 'region' not in aws_obj \ or 'roles' not in aws_obj: problems("Invalid AWS secrets" % aws) aws_path = "%s/config/root" % secret['mount'] if not is_tagged(secret.get('tags', []), opt.tags): log("Skipping %s as it does not have appropriate tags" % aws_path, opt) return ensure_mounted(client, 'aws', secret['mount']) obj = { 'access_key': aws_obj['access_key_id'], 'secret_key': aws_obj['secret_access_key'], 'region': aws_obj['region'] } client.write(aws_path, **obj) log('wrote aws_file %s into %s' % (aws_file_path, aws_path), opt) ttl_obj = {} lease_msg = '' if 'lease' in aws_obj: ttl_obj['lease'] = aws_obj['lease'] lease_msg = "%s lease:%s" % (lease_msg, ttl_obj['lease']) if 'lease_max' in aws_obj: ttl_obj['lease_max'] = aws_obj['lease_max'] else: if 'lease' in ttl_obj: ttl_obj['lease_max'] = ttl_obj['lease'] if 'lease_max' in ttl_obj: lease_msg = "%s lease_max:%s" % (lease_msg, ttl_obj['lease_max']) if ttl_obj: client.write("%s/config/lease" % (secret['mount']), **ttl_obj) log("Updated lease for %s %s" % (secret['mount'], lease_msg), opt) for role in aws_obj['roles']: if 'policy' not in role or 'name' not in role: problems("Invalid role definition %s" % role) data = open(hard_path(role['policy'], opt.policies), 'r').read() role_path = "%s/roles/%s" % (secret['mount'], role['name']) client.write(role_path, policy=data)
def gitignore(opt): """Will check directories upwards from the Secretfile in order to ensure the gitignore file is set properly""" directory = os.path.dirname(abspath(opt.secretfile)) gitignore_file = find_file('.gitignore', directory) if gitignore_file: secrets_path = subdir_file(abspath(opt.secrets), gitignore_file) if not secrets_path: problems("Unable to determine relative location of secretfile") if not in_file(secrets_path, gitignore_file): problems("The path %s was not found in %s" % (secrets_path, gitignore_file)) else: problems("You should really have a .gitignore")
def app(client, app_obj, opt): """Seed an app file into Vault""" if 'app_file' not in app_obj: problems("Invalid app definition %s" % app_obj) name = None if 'name' in app_obj: name = app_obj['name'] else: name = os.path.splitext(os.path.basename(app_obj['app_file']))[0] if not is_tagged(app_obj.get('tags', []), opt.tags): log("Skipping %s as it does not have appropriate tags" % name, opt) return app_file = hard_path(app_obj['app_file'], opt.secrets) data = yaml.load(open(app_file).read()) if 'app_id' not in data \ or 'policy' not in data: problems("Invalid app file %s" % app_file) policy_name = None if 'policy_name' in data: policy_name = data['policy_name'] else: policy_name = name policy = open(hard_path(data['policy'], opt.policies), 'r').read() client.set_policy(name, policy) app_path = "auth/app-id/map/app-id/%s" % data['app_id'] app_obj = {'value': policy_name, 'display_name': name} client.write(app_path, **app_obj) users = data.get('users', []) for user in users: if 'id' not in user: problems("Invalid user definition %s" % user) user_path = "auth/app-id/map/user-id/%s" % user['id'] user_obj = {'value': data['app_id']} if 'cidr' in user: user_obj['cidr_block'] = user['cidr'] client.write(user_path, **user_obj) log('created %d users in application %s' % (len(users), name), opt)
def client(operation, opt): """Return a vault client""" if 'VAULT_ADDR' not in os.environ: problems('VAULT_ADDR must be defined') vault_host = os.environ['VAULT_ADDR'] ssl_verify = True if 'VAULT_SKIP_VERIFY' in os.environ: if os.environ['VAULT_SKIP_VERIFY'] == '1': log('Skipping SSL Validation!', opt) ssl_verify = False log("Connecting to %s" % vault_host, opt) vault_client = hvac.Client(vault_host, verify=ssl_verify) vault_client.token = initial_token(vault_client, opt) if not vault_client.is_authenticated(): problems("Unable to retrieve initial token") vault_client.token = operational_token(vault_client, operation, opt) if not vault_client.is_authenticated(): problems("Unable to retrieve operational token") return vault_client