Created
February 5, 2019 07:13
-
-
Save siennathesane/04fab28610b44931b8c3cdc9f8bcbc89 to your computer and use it in GitHub Desktop.
Sync Credhub to Vault
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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