Certificate Replacement on vCenter

I use a Let’s Encrypt certificate on my VCSA. It validates and give me a pretty padlock in my address bar. Generally I use a wildcard cert for all my services but vCenter wont take that. So I have a “regular” server certificate for vCenter. As with all Let’s Encrypt certificates it expires regularly. This is pretty annoying when the certificate expires and then vCenter becomes inaccessible. I could update the certificate through the HTML 5 client but that would require I remembered it was expiring. So of course what to do but script the replacement. With the new rest APIs available in vCenter 7 this becomes much easier than the old hack the cli utility to do it for you way. The documentation for the API around certificate management is located here: https://developer.vmware.com/docs/vsphere-automation/latest/vcenter/certificate_management/. They appear to be relatively straight forward. But as with most things that not the case.

Trusted Roots

In order to install a third party certificate you must first import the certificate chain of the root certificate. For Let’s Encrypt there are two certificates required. They are located on this page: https://letsencrypt.org/certificates/. As of this writing the direct links to the the PEM files are: https://letsencrypt.org/certs/isrgrootx1.pem and https://letsencrypt.org/certs/lets-encrypt-r3.pem. If you were to import these in the H5 client you can just save them and import them. For the API it is of course not that easy. The documentation is here: https://developer.vmware.com/docs/vsphere-automation/latest/vcenter/api/vcenter/certificate-management/vcenter/trusted-root-chains/post/. You will need to get a session ID first for any vCenter API call. The documentation for that is here: https://developer.vmware.com/docs/vsphere-automation/latest/cis/api/session/post/. For the trusted root call the body according to the docs looks like this:

{
    "cert_chain": {
        "cert_chain": [
            "string"
        ]
    }
}

Since Let’s Encrypt has two certificates in the chain this request looks more like this:

{
    "cert_chain": {
        "cert_chain": [
            "string",
            "string"      
        ]
    }
}

"string" gets replaced by the certificate data. Unfortunately you can’t just put the the file data we downloaded earlier into that field without doing some modification. All of the “\r\n”s need to be replaced with a literal “\n”. This is a little hard to do on the CLI. Python would solve this problem quickly but since I started my script in bash I kept going. Thanks to Stack Overflow I got a quick answer: https://stackoverflow.com/questions/38672680/replace-newlines-with-literal-n/38674872.

sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g'

The Script

The code is here if you want to jump straight to it: https://github.com/khensler/VMware_Scripts/blob/main/replace_vcenter_cert.sh

The first section are a couple of subs to generate the JSON format needed for the request. These formats come from the documentation and have variables created later to fill in the data.

gen_data(){
cat <<EOF
        {
                "cert" : "$CERTDATA",
                "key" : "$KEYDATA"
        }
EOF

}

gen_cert_chain(){
cat <<EOF
{"cert_chain":
        {
                "cert_chain": [
                        "$X1",
                        "$R3"
                        ]
        }
}
EOF
}

Next comes downloading the root certificates:

curl https://letsencrypt.org/certs/isrgrootx1.pem | \
     sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g' > X1.pem
X1=`cat X1.pem`
echo $X1
curl https://letsencrypt.org/certs/lets-encrypt-r3.pem | \
     sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g' > R3.pem
R3=`cat R3.pem`
echo $R3

Lots of ways to do this and I’m sure someone has a better way. This downloads and replaces all \r\n with literal “\n”s then saves the files and sets the variables for use in the subs above. Now the same for the vCenter certificates:

scp vcenter@<LB IP>:/conf/acme/vcenter.crt vcenter.crt
scp vcenter@<LB IP>:/conf/acme/vcenter.key vcenter.key

sed -i -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g' vcenter.crt
sed -i -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g' vcenter.key

CERTDATA=`cat vcenter.crt`
KEYDATA=`cat vcenter.key`

This pulls the vCenter certificate and private key from my router/firewall/load balancer. This is a pfSense box that runs the ACME package to create and renew certificates. After this you’ll need a session key:

skey=`curl --insecure -X POST https://<vcenter>/api/session \
     -H 'authorization: Basic <BASIC AUTH>'| tr -d \"`

Now generate the JSON files

echo "$(gen_cert_chain)" > X1.json
echo "$(gen_data)" > cert.json

Lastly POST/PUT the content to vCenter

curl --insecure -X POST https://<vcenter>/api/vcenter/certificate-management/vcenter/trusted-root-chains \
     -H "vmware-api-session-id: $skey" -H "Content-Type: application/json" -d @X1.json
curl --insecure -X PUT https://<vcenter>/api/vcenter/certificate-management/vcenter/tls \
     -H "vmware-api-session-id: $skey" -H "Content-Type: application/json" -d @cert.json

This posts the trusted root and then updates the TLS certificate. This still need some work for scheduling and checking to make sure the cert that gets installed is different than the last one but that’s for another post next time my certificate comes close to expiration.

One thought on “Certificate Replacement on vCenter

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: