Automatic certificate management in Cisco ISE

Did you know you can manage and replace certificates on ISE via the API/cli, and if you’re brave enough, automatically?

This was the topic of one of the sessions at Cisco Live US 2024 that peaked my interest:
DEVNET-2140 — Using ISE OpenAPI to automate certificate management.

Admittedly my previous experience with ISE & API was limited to the old ERS API.
Did a number of interesting things there, but I don’t recall certificate management being one of the things available.

During the DEVNET-2140 session the presenter, Steven McNutt (@densem0de) demonstrated the functionality with his lab setup.
So obviously I wonder, can I replicate what he did?
Follow a long and we’ll go through my tests.

Ok so I’ve cloned his github repo referenced in the slides.
I have a clean installation of Cisco ISE (ise1.escort.is – 172.31.30.87), and an Active Directory server (172.31.30.16) running Certificate Authority, with Network Device Enrollment Service (NDES) enabled for SCEP functionality.
The API services have been enabled on ISE, and I’ve created an admin user with API privileges.

Running the ise-c.py python script, and providing the credentials for ISE was straight forward enough, authentication works.
Selecting “1” (to list servers) or “2” to list server certificates gave expected results:

Enter a number to select a command: 1
Retrieving list of nodes in the deployment:

Found node: ise1
node id: 24ba0f90-28e5-11ef-9190-00505693a7a3

Enter a number to select a command: 2

retrieving certificates for ise1
ID: 1ceba895-e15c-4d28-bf37-87428132b925, Name: Default self-signed server certificate
ID: e593bae3-4664-4e22-87a3-ea1ef8450335, Name: Default self-signed saml server certificate - CN=SAML_ise1.escort.is
ID: 0d367886-2555-4433-a119-6d94c5ecf70d, Name: CN=ise1.escort.is, OU=ISE Messaging Service#Certificate Services Endpoint Sub CA - ise1#00001
ID: f418340b-8958-404f-940d-eccea4e76ad6, Name: CN=ise1.escort.is, OU=Certificate Services System Certificate#Certificate Services Endpoint Sub CA - ise1#00002

Doing actual stuff:

As expected with prototype code that’s created to demonstrate a functionality, not everything is super dynamic or fluid, so I start out by making these manual modifications:

  • I edit the data.py and add a new certificate dict used to create a csr.
  • I manually create a directory named “work” in the “python” directory.
    • natti@ns1:~/test/devnet-2140/python$ mkdir work
    • (the script otherwise errors out)
  • I edit the scep/sscep.conf file and replace the IP address for the URL value to represent my AD/CS server.

Using openssl I can verify that the CSR was successful:

natti@ns1:~/test/devnet-2140/python$ openssl req -text -noout -in work/csr.pem
<…>output omitted<…>

Next up would have been to use SCEP to get a new certificate from Active Directory, but I just got error after error, tried multiple debugs, without any actual success.

After speaking with Steven on Twitter/X, he pointed out that I needed to modify a registry on the CA to disable OTP for scep.

On the “todo” list to fix later. 😛
(yep, we know what that means…)


HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\MSCEP\EnforcePassword\EnforcePassword = 0

While adding the registry sounded promising, I was still plagued with errors.

I realized that in the “scep” directory, there were CA certificates from Steven’s LAB PKI, pod1-PKI1-CA-1, so I had to do two things:

  • Remove the ca.crt-0, ca.crt-1 and ca.crt-2 files:
    • rm scep/ca*
  • Fetch new ca files:
    • sscep getca -f scep/sscep.confsscep getca -f scep/sscep.conf

After doing this, I was able to fetch a new certificate from the AD, and (almost**) install to ISE.

**Because you see, I cheated on one thing with this screenshot.
Remember, prototype code, it did not include the functionality to install the CA certificate, which I just did manually through the ISE GUI this time.

Looking at the ISE swagger-ui, the functinality is there to easily implement later.

And while I totally forgot to rename the friendly name from McNutt’s script, I see the certificate in the Cisco ISE GUI:

As you can see on the imported, steve function test, certificate, it’s not used for any functionality like EAP or Admin, etc.
This was intentional while testing, because as we know that ISE may restart services when replacing those certificates, we’re not patient enough to do so.

Looking at the swagger UI, and the initial payload used in the import_cert function within the functions.py file, I can see I can simply change “eap” to True:

Original

Modified

payload = {
  "admin": False,
  "allowExtendedValidity": True,
  "allowOutOfDateCert": True,
  "allowPortalTagTransferForSameSubject": True,
  "allowReplacementOfCertificates": True,
  "allowReplacementOfPortalGroupTag": True,
  "allowRoleTransferForSameSubject": True,
  "allowSHA1Certificates": True,
  "allowWildCardCertificates": True,
  "eap": False,
  "ims": False,
  "name": "steve function test",
  "portal": False,
  "portalGroupTag": "",
  "pxgrid": False,
  "radius": False,
  "saml": False,
  "validateCertificateExtensions": False
}
payload = {
  "admin": False,
  "allowExtendedValidity": True,
  "allowOutOfDateCert": True,
  "allowPortalTagTransferForSameSubject": True,
  "allowReplacementOfCertificates": True,
  "allowReplacementOfPortalGroupTag": True,
  "allowRoleTransferForSameSubject": True,
  "allowSHA1Certificates": True,
  "allowWildCardCertificates": True,
  "eap": True,
  "ims": False,
  "name": "steve function test",
  "portal": False,
  "portalGroupTag": "",
  "pxgrid": False,
  "radius": False,
  "saml": False,
  "validateCertificateExtensions": False
}

And using the HTTP “PUT” function, I edit the certificate to be an EAP cert:

>>> response = requests.put(url, headers=headers, verify=False, auth=(ise_user, ise_password), json=payload)

Ok, so what other challenges did I run into….

  • I’ve never actually set up SCEP using Microsoft NDES services before, so it took some time to find out which Certificate Template I received via SCEP.
    Turns out it’s also a registry entries within HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\MSCEP that I need to modify to select which template is used.

Takeaways

  • Looking at, and testing the script and the swagger-ui, I realize I can, via the API, do the following:
    • retrieve certificate information from ISE
    • import/replace a certificate on the ISE
    • modify certificate usage, ie whether it’s used for admin, eap, and such.
  • After improving the security a bit regarding SCEP, I could manufacture a script that would automatically renew an ISE certificate before it expires.
  • I could fork & modify the script to be a “quick tool” for me to work with certificates on ISE, without having to utilize the ISE user interface.
    • Obviously figuring out the challenge authentication so we don’t have to disable the OTP password enforcement.
    • being able to create a CSR without editing the a python file.
    • automatically fetch the correct CA files for the sscep client
    • modify the certificate usage (eap/admin/portal/…)
    • upload the CA file to the trust store on ISE.
  • I still have a lot to learn regarding Microsoft CA and SCEP/NDES.