Skip to content

Instantly share code, notes, and snippets.

@siennathesane
Created February 5, 2019 07:13
Show Gist options
  • Save siennathesane/04fab28610b44931b8c3cdc9f8bcbc89 to your computer and use it in GitHub Desktop.
Save siennathesane/04fab28610b44931b8c3cdc9f8bcbc89 to your computer and use it in GitHub Desktop.
Sync Credhub to Vault
#!/usr/bin/env python3
import json
import logging
import os
import subprocess
import shlex
import sys
import hvac
class SyncerFailure(Exception):
"""Base class for exceptions in this module."""
pass
class Syncer(object):
def __init__(self, vault_mount_point="", **kwargs):
"""
Base module for syncing between Cloud Foundry's Credhub and Hashicorp's Vault.
:param str vault_mount_point: Base path for Vault's sync'd credentials. Defaults to 'bosh'
:param args:
:param kwargs:
"""
self.creds_to_download = []
self.creds = []
self.hvac_client = hvac.Client
if vault_mount_point != "":
self.vault_mount_point = vault_mount_point
else:
self.vault_mount_point = "bosh"
self.logger = logging.getLogger(__name__)
console_handler = logging.StreamHandler(sys.stdout)
self.logger.addHandler(console_handler)
self.logger.setLevel(logging.INFO)
if kwargs is not None:
if kwargs["logging_level"]:
self.logger.setLevel(kwargs["logging_level"])
self._check_env()
self._login()
def _check_env(self):
"""
Verify the needed environment variables exist.
:return:
"""
target_vars = ["CREDHUB_CERTIFICATE", "BOSH_CA", "CREDHUB_CLIENT", "CREDHUB_SECRET",
"CREDHUB_SERVER", "VAULT_ADDR", "VAULT_CLIENT_ID", "VAULT_CLIENT_SECRET"]
for v in target_vars:
try:
_ = os.environ[v] # just test for existence.
self.logger.debug("found {}".format(v))
except Exception:
self.logger.exception("missing env var: {0}".format(v))
raise SyncerFailure("missing env var: {0}".format(v))
credhub_cert = os.environ["CREDHUB_CERTIFICATE"]
bosh_ca = os.environ["BOSH_CA"]
os.environ["CREDHUB_CA_CERT"] = credhub_cert + "\n" + bosh_ca
def _login(self):
"""
Log into both Credhub and Vault.
:return:
"""
login_command = shlex.split("credhub login")
self.logger.debug("running: {}".format(login_command))
login_result = subprocess.run(login_command)
if login_result.returncode != 0:
self.logger.exception("non-zero return code on credhub login: {}".format(login_result.stderr))
raise SyncerFailure("non-zero return code on credhub login: {}".format(login_result.stderr))
self.hvac_client = hvac.Client(url=os.environ["VAULT_ADDR"])
self.hvac_client.auth_approle(role_id=os.environ["VAULT_CLIENT_ID"],
secret_id=os.environ["VAULT_CLIENT_SECRET"], mount_point="clients")
def sync(self):
"""
Syncs all credentials from Credhub and send them to Vault.
:return:
"""
list_command = shlex.split("credhub find --output-json")
self.logger.debug("running: {}".format(list_command))
raw_creds_list = subprocess.run(list_command, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
if raw_creds_list.returncode != 0:
self.logger.error("non-zero return code when listing credentials: {0}".format(raw_creds_list.stderr))
raise SyncerFailure("non-zero return code when listing credentials: {0}".format(raw_creds_list.stderr))
creds_to_load = json.loads(raw_creds_list.stdout)
for cred in creds_to_load["credentials"]:
self._fetch(cred["name"])
def _fetch(self, c: str):
"""
Retrieves a c from Credhub.
:param str c: Credential name. Ex: /bosh/concourse/worker_key
:return:
"""
get_command = shlex.split("credhub get -n {} --output-json".format(c))
self.logger.debug("running: {}".format(get_command))
local_credential = subprocess.run(get_command, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
if local_credential.returncode != 0:
self.logger.error("non-zero return code for getting {0}: {1}".format(c, local_credential.stderr))
raise SyncerFailure(
"non-zero return code for getting {0}: {1}".format(c, local_credential.stderr))
self._sync(json.loads(local_credential.stdout))
def _sync(self, cred: dict):
"""
Internal syncing mechanism.
:param dict cred: Credhub credential in JSON/Dict form.
:return:
"""
c = self._fix_cred_string(cred["name"])
if cred["type"] == "value":
self.logger.debug("synchronising value for {0}".format(c))
self.hvac_client.secrets.kv.v2.create_or_update_secret(
path=c,
secret=dict(value=cred["value"]),
mount_point=self.vault_mount_point
)
if cred["type"] == "password":
self.logger.debug("synchronising password for {0}".format(c))
self.hvac_client.secrets.kv.v2.create_or_update_secret(
path=c,
secret=dict(password=cred["value"]),
mount_point=self.vault_mount_point
)
if cred["type"] == "certificate":
self.logger.debug("synchronising certificate for {0}".format(c))
self.hvac_client.secrets.kv.v2.create_or_update_secret(
path=c,
secret=dict(
ca=cred["value"]["ca"],
certificate=cred["value"]["certificate"],
private_key=cred["value"]["private_key"]
),
mount_point=self.vault_mount_point
)
if cred["type"] == "ssh":
self.logger.debug("synchronising ssh key pair for {0}".format(c))
self.hvac_client.secrets.kv.v2.create_or_update_secret(
path=c,
secret=dict(
private_key=cred["value"]["private_key"],
public_key=cred["value"]["public_key"],
public_key_fingerprint=cred["value"]["public_key_fingerprint"]
),
mount_point=self.vault_mount_point
)
if cred["type"] == "rsa":
self.logger.debug("synchronising rsa key pair for {0}".format(c))
self.hvac_client.secrets.kv.v2.create_or_update_secret(
path=c,
secret=dict(
private_key=cred["value"]["private_key"],
public_key=cred["value"]["public_key"]
),
mount_point=self.vault_mount_point
)
@staticmethod
def _fix_cred_string(x: str) -> str:
"""
Cleans up pathing to remove baseline "/bosh" from Credhub credential name.
:param str x: Credhub credential name.
:return:
"""
spl = x.split("/")
if spl[1] == "bosh":
return "/".join(spl[2:])
else:
return x
if __name__ == "__main__":
syncer = Syncer(logging_level=logging.DEBUG)
syncer.sync()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment