Managing Two-Tier PKI in a Lab Environment

If you’re crazy like me, you go all out when running a homelab. Multiple domain controllers, role-based access, separate servers per role (for the most part), etc. This also means utilizing a two-tier Public Key Infrastructure (PKI), with an offline root Certificate Authority (CA). However, when you have a bit of turnover of environments in your lab, and a lack of public IP address space, you need to get clever with how some things are configured.

Recently, I added a Cloud Management Gateway (CMG) to my ConfigMgr primary site. Which immediately introduced a problem: I would not be able to enable certificate revocation checking, as my CRLs were not available externally. Since my PKI was already built out, it made the most sense to re-do it from the ground up, after doing some thinking about how to resolve a few specific issues.

The overview of the solution I thought up is as follows:

  • Create a new offline root CA, and publish the CRL and certificate within my GitHub Pages site that utilizes my lab domain name (ajf8729.com)
  • Create a new online subordinate CA, and publish the CRL and certificate via Certificate Services Web Enrollment, with an internal name of pki.corp.ajf8729.com (corp.ajf8729.com being my current lab Active Directory domain)
  • Utilize Azure AD Application Proxy to make pki.corp.ajf8729.com publicly accessible
  • Utilize split DNS to make pki.corp.ajf8729.com resolvable via my external DNS (hosted via DigitalOcean)

I do not plan on going into detail in this post regarding actual CA setup/configuration. Instead, I will refer you to Chris Kibble‘s awesome nine-part series here: Standing up a Microsoft Certificate Authority. I made heavy use of this series when first implementing my two-tier PKI, especially parts 1 and 2.

Offline Root CA CRL/AIA Availability

When I built out my first two-tier lab PKI, I really didn’t know too much about it at the time. I had prior experience with a single-tier PKI, and theoretical knowledge about two-tier PKI, but up to that point, never realized what issues could crop up, depending on your design choices.

At this point, I had set up an internal name of pki.corp.ajf8729.com to host the root CRL and certificate, and configured the root CA CDP/AIA accordingly. Next, I set up the subordinate CA, and configured it to also store the CRL and certificate at the same location. Everything worked, life is groovy. Well, almost everything. Not for things outside of my lab.

The problem is I need to change where the CRLs are located. In order to do that, I’d need to modify the CDP/AIA on the root CA, and issue a new subordinate CA, so its certificate contains the updated locations. Might as well just build a new root at that point.

There was a secondary problem I discovered after thinking about things a bit further. I sometimes have multiple lab environments, all utilizing the same root CA. Since I initially chose to host the CRLs via the original subordinate CA, this always needs to be accessible, in order to provide the root CA CRL/AIA. I needed a permanent, external, location to store the offline root CA files. Sounds like a perfect use for my GitHub pages site hosted via my lab domain name (ajf8729.com).

Root CA CDP/AIA Configuration

Above is the CDP and AIA configuration for my new root CA. I chose to define two URLs within my GitHub Pages site where I would store the CRL and certificate files, and configured them to be included in issued certificates. This means that any subordinate CA chained from my root CA will include these URLs, which now sit externally from any host in my lab environment and are accessible from anywhere. Since it’s an offline root, the CRL update time is configured with a much longer timeframe than an online CA, so I just need to remember to boot the offline CA every 6 months to generate a new CRL, copy it off, and commit it to my site.

Online Subordinate CA CRL/AIA Availability

The next problem I had was that the CRL and certificate for the online subordinate CA also needs to be available both internally and externally. This however, cannot also be stored in GitHub Pages, at least not easily, since the delta CRL needs to be published much more often (weekly by default if I recall correctly). Not something I feel like doing manually once a week.

To solve this, I decided to make use of Azure AD Application Proxy to make my online subordinate CA available both internally and externally via HTTP. This server is also hosting the Certificate Authority Web Enrollment role, and has the hostname pki.corp.ajf8729.com added via netdom.exe in order to register a DNS record and an SPN. In a production environment, this role should be separated from the CA itself, but I decided to co-locate them here just to consolidate resources a bit.

Subordinate CA CDP/AIA Configuration

Above is the CDP and AIA configuration for my new online subordinate CA. The URL I selected is the pki.corp.ajf8729.com hostname I mentioned earlier, and the same one I was already using for the first CA. However, this time, I will make this URL available externally via Azure AD Application Proxy and split DNS.

After setting up a server to host the proxy connector and registering it, I created a new enterprise application in Azure AD as follows:

Azure AD Application Proxy Configuration

Next, I created a CNAME in my external DNS to make it accessible.

External DNS CNAME

And just like that, my subordinate CA is now accessible externally!

Subordinate CA CDP/AIA

For ease of use for myself, I decided to add a page to my GItHub Pages site to collect the links together: https://ajf8729.com/certs/certificate-authorities/.

Conclusion

Moving forward, if I stand up any additional environments, I now have the freedom to create another subordinate CA, and repeat the configuration with another Azure AD App Proxy, effectively isolating the environments, and maintaining public availability of the root CA files.

This was an interesting solution to craft, and was a fun project to work through. Next steps are going to include reconfiguring my ConfigMgr site to utilize the new certificate chain, reconfigure my CMG, and test end-to-end external access from a co-managed Azure AD joined client!