close up gray hoppe tubular key

Synchronising VMware SDDC Manager Credentials to Hashicorp Vault with Python

VMware’s SDDC has a great feature whereby it can manage credentials for all the applications and appliances within its scope, such as vCenter, NSX-T, WSA, vRealize suite etc. The best part of this management is the periodic rotation feature which keeps security bods happy and systems secure.

If you are familiar the SDDC manager, you have inevitably used the lookup_passwords function to extract the latest password for a particular appliance or application. It’s clunky and requires you to SSH onto the SDDC manager, which in itself can be a pain.

In our system we run Hashicorp Vault for secrets management, so the logical next step was to extract the credentials from SDDC manager and pass them into Vault so all secrets are accessible centrally.

The following script works very neatly and creates a logical secrets structure within Vault, based upon the various variables and constructs passed out by the SDDC manager.

We now set this script to run periodically to maintain parity between the SDDC manager and Vault.

Hopefully this helps or inspires someone who is bored of running lookup_passwords !!

import json
import os
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

debug = 0
# Grab relevant output environment variables from the gitlab-ci scripts.
access_token = os.environ['SDDC_TOKEN']
vault_pass = os.environ['VAULT_ROTATE_PWD']
sddc_base_url = os.environ['SDDC_MANAGER']
vault_base_url = os.environ['VAULT_SERVER_URL']
vault_role = os.environ['VAULT_AUTH_ROLE']
vault_namespace = os.environ['VAULT_NAMESPACE'] 

# Print generic varibales 
print("Starting script with the following variables. Role: " + vault_role + " | Namespace: " + vault_namespace)
if debug == 1: 
    print("Using Vault Token: " + vault_pass)
    print("Base URLs:")
    print(sddc_base_url)
    print(vault_base_url)

# Set Vault headers
vault_session = requests.Session()
vault_session.headers.update({ "X-Vault-Namespace" : vault_namespace }) #optional if using namespaces within Vault
vault_session.headers.update({ "Content-Type" : "application/json" })
vault_session.headers.update({ "Accept" : "application/json" })

# Obtain Vault Login Token
vault_login_url = vault_base_url + "/v1/auth/ldap/login/<service_account_name>"
data =  { "password":  vault_pass}
vault_json = json.dumps(data)
response = vault_session.post(vault_login_url,data = vault_json, verify = False, proxies = None)

if response.status_code == 200:
    json_response = json.loads(response.content)
    vault_token = json_response['auth']['client_token']
else:
    raise NameError("Failed to obtain Vault token")  

# Grab all the credentials from SDDC Manager
credentials_url = sddc_base_url + '/v1/credentials'

# Set Headers
session = requests.Session()
basic_auth_header = "Bearer " + access_token
session.headers.update({"authorization" : basic_auth_header})
session.headers.update({"accept" : "application/json"})
session.headers.update({"content-type" : "application/json"})

# Execute call and parse JSON
response = session.get(credentials_url,verify = False, proxies = None)
if response.status_code == 200:
    credentials_json = json.loads(response.content)
    print("Credential dump obtained from SDDC Manager")

    # Create Vault headers
    vault_session = requests.Session()
    vault_session.headers.update({ "X-Vault-Token" : vault_token })
    vault_session.headers.update({ "X-Vault-Namespace" : vault_namespace })
    vault_session.headers.update({ "Content-Type" : "application/json" })
    vault_session.headers.update({ "Accept" : "application/json" })

    # Iterate through all records and extract relevant vars.
    for credential in credentials_json['elements']:
        domain_name = credential['resource']['domainName'].lower()
        resource_type = credential['resource']['resourceType'].lower()
        resource_name = credential['resource']['resourceName'].lower()
        credential_type = credential['credentialType'].lower()
        account_type = credential['accountType'].lower()
        username = credential['username'].lower()
        modification_time = credential['modificationTimestamp']
        # Some SDDC credentials don't have password records so skip them...
        if 'password' in credential:
            password = credential['password']

            # Create Vault path and push record 
            path = '/v1/data/sddc/' + domain_name + '/' + resource_type + '/' + resource_name + '/' + credential_type + '/' + account_type + '/' + username
            vault_url = vault_base_url + path
            data =  { "options": { "max_versions": "12" }, "data": { "password": password , "last_modified" : modification_time }}
            json_data = json.dumps(data)
            response = vault_session.post(vault_url, data = json_data,verify = False, proxies = None)
            print("Attempting to upload record to Vault: " + vault_url)
            print(response.text)
            if response.status_code == 200:
                print("Successful Upload")
            else:
                raise NameError("Failed Upload") 

else:
    raise NameError("Failed to obtain credentials from SDDC manager")  

Leave a Reply