Certificate Operations

Overview

Manage ISE certificates including system certs, trusted CAs, and Let’s Encrypt integration.

Commands

netapi ise cert [COMMAND]
Command Description

list

List system certificates on ISE node

list-trusted

List trusted CA certificates

import

Import system certificate from local files (Vault PKI, etc.)

add-trusted

Import trusted CA certificate

add-letsencrypt-ca

Add Let’s Encrypt CA chain to ISE

prepare-for-import

Prepare LE certs for manual import

import-from-certmgr

Import from certmgr-01 (Let’s Encrypt)

mtls-preflight

Run mTLS preflight checks

List System Certificates

netapi ise cert list [OPTIONS]
Option Default Description

--detail, -d

(off)

Show detailed info (expiry, issuer, key size, self-signed)

--usage, -u

(none)

Filter by usage (Admin, EAP, Portal, pxGrid, SAML)

--hostname, -n

ise-02

ISE node hostname

# Basic list
netapi ise cert list

# Detailed view with expiration dates
netapi ise cert list --detail

# Filter by usage
netapi ise cert list --usage Portal
netapi ise cert list --usage EAP
netapi ise cert list --usage pxGrid

# Different node (multi-node deployment)
netapi ise cert list --hostname ise-psn-01

Shows certificates installed on the ISE node for:

  • Admin portal

  • EAP authentication

  • pxGrid

  • Portal (Guest/Sponsor)

  • SAML

  • RADIUS DTLS

  • ISE Messaging Service

List Trusted CAs

netapi ise cert list-trusted [OPTIONS]
Option Default Description

--all, -a

(off)

Fetch ALL trusted certs (auto-paginate)

--search

(none)

Filter by name (case-insensitive)

--size, -s

100

Results per page (max 100)

--page, -p

1

Page number

# List all trusted certs (auto-paginate)
netapi ise cert list-trusted --all

# Search for Let's Encrypt certs
netapi ise cert list-trusted --all --search LetsEncrypt

# Search for AD CS certs
netapi ise cert list-trusted --all --search HOME-ROOT

# First page with 50 results
netapi ise cert list-trusted --size 50 --page 1

Naming convention: Prefix trusted CA names with the issuer (e.g., LetsEncrypt-E7, HOME-ROOT-CA) for easy searching with --search.

Add Let’s Encrypt CA

# Add LE root and intermediate CAs to ISE trust store
netapi ise cert add-letsencrypt-ca

Required for trusting Let’s Encrypt certificates used by:

  • External RADIUS clients

  • API clients with LE certs

  • pxGrid subscribers

Prepare Let’s Encrypt Certs

# Prepare LE certs for manual ISE import
netapi ise cert prepare-for-import

Creates certificate bundle in format ISE expects.

Import from Certmgr

# Import certificate from certmgr-01 server
netapi ise cert import-from-certmgr --help
# Load credentials
dsource d000 dev/network

# Import Let's Encrypt portal cert from certmgr-01
netapi ise cert import-from-certmgr --cert-dir public-portals --portal

Options

Option Default Description

--certmgr, -c

10.50.1.60

certmgr host

--certmgr-user

ansible

SSH user for certmgr

--domain, -D

guest.domusdigitalis.dev

Certificate domain name

--cert-dir

(domain)

Certbot directory name

--name, -n

LetsEncrypt-Portal

Certificate friendly name in ISE

--portal, -p

True

Use for Portal

--admin, -a

False

Use for Admin

--eap, -e

False

Use for EAP

Import System Certificate (Local Files)

Import a system certificate and private key directly from local files. This is the preferred method for Vault-issued certificates and other internal CA certificates.

ISE OpenAPI Requirement: The private key must be in PEM format. The API automatically handles base64 encoding. The field name allowWildCardCerts (not Certificates) is required for the payload to be accepted.

netapi ise cert import CERT_FILE KEY_FILE [OPTIONS]

Options

Option Default Description

--name, -n

(from filename)

Certificate friendly name in ISE

--hostname, -H

ise-02

ISE node hostname (for display only)

--admin, -a

False

Use for Admin interface

--eap, -e

False

Use for EAP authentication

--portal, -p

False

Use for Portal

--pxgrid

False

Use for pxGrid

--radius

False

Use for RADIUS DTLS

--saml

False

Use for SAML

--password

(empty)

Private key password (if encrypted)

--allow-replacement

True

Allow replacing existing cert with same usage

Import Vault-Issued EAP/Admin Certificate

For internal certificates issued by HashiCorp Vault PKI:

# Issue certificate from Vault
ssh certmgr-01 "vault write pki_int/issue/domus-server \
  common_name=ise-02.inside.domusdigitalis.dev \
  alt_names=ise-02.inside.domusdigitalis.dev \
  ip_sans=10.50.1.21 \
  ttl=8760h" > /tmp/ise-cert.json

# Extract cert and key
jq -r '.data.certificate' /tmp/ise-cert.json > /tmp/ise-eap.crt
jq -r '.data.private_key' /tmp/ise-cert.json > /tmp/ise-eap.key

# Import to ISE for Admin and EAP authentication
dsource d000 dev/network
netapi ise cert import /tmp/ise-eap.crt /tmp/ise-eap.key \
  --name ISE-VAULT-EAP --admin --eap

Import pxGrid Certificate

# Issue pxGrid certificate from Vault
vault write pki_int/issue/domus-pxgrid \
  common_name=netapi-pxgrid.inside.domusdigitalis.dev \
  ttl=8760h > /tmp/pxgrid.json

jq -r '.data.certificate' /tmp/pxgrid.json > /tmp/pxgrid.crt
jq -r '.data.private_key' /tmp/pxgrid.json > /tmp/pxgrid.key

netapi ise cert import /tmp/pxgrid.crt /tmp/pxgrid.key \
  --name ISE-PXGRID-VAULT --pxgrid

Verification

# Verify certificate was imported
netapi ise cert list --detail

# Check specific usage
netapi ise cert --format json list | \
  jq '.[] | select(.usedBy | contains("EAP")) | {name: .friendlyName, issuer: .issuedBy}'

Ansible Automation (certmgr-01)

Alternative to netapi for CI/CD pipelines or when running from certmgr-01 directly.

Prerequisites

# On certmgr-01
export ISE_API_USER="domus_ers_admin"
export ISE_API_PASS="<password>"

Run Playbook

cd ~/ansible
ansible-playbook playbooks/deploy-ise.yml \
  -i inventory/hosts.yml \
  -e "cert_domain=public-portals" \
  --limit ise-02

Playbook Details

Location: certmgr-01:~/ansible/playbooks/deploy-ise.yml

The playbook uses the uri module instead of cisco.ise.system_certificate_import due to a base64 encoding bug in the cisco.ise collection (v2.9.6). The ISE API expects base64-encoded cert data, but the Ansible module sends raw PEM.

Inventory

# ~/ansible/inventory/hosts.yml
ise_nodes:
  hosts:
    ise-02:
      ansible_host: 10.50.1.21
      cert_domain: guest.domusdigitalis.dev
  vars:
    ansible_connection: local

Certbot Auto-Renewal Hook

The certbot deploy hook at /etc/letsencrypt/renewal-hooks/deploy/deploy-certs.sh can trigger automatic deployment on renewal. Currently uses curl directly (not Ansible).

# Manual trigger
sudo /etc/letsencrypt/renewal-hooks/deploy/deploy-certs.sh

# Force renewal + deploy
sudo certbot renew --force-renewal --cert-name public-portals

mTLS Preflight Check

# Verify mTLS configuration before enabling
netapi ise cert mtls-preflight

Checks:

  • Client certificate validity

  • CA chain completeness

  • ISE trust store configuration

  • API endpoint accessibility

Use Cases

Audit Certificate Expiry

# List system certificates (table view)
netapi ise cert list

# List trusted CAs
netapi ise cert list-trusted

Check Expiration Dates (JSON + jq)

# Get expiration dates for all certs
netapi ise cert --format json list | jq '.[] | {name: .friendlyName, expires: .expirationDate, usedBy: .usedBy}'

# Find certs expiring within 30 days
netapi ise cert --format json list | jq '.[] | select(.expirationDate | contains("2026")) | {name: .friendlyName, expires: .expirationDate}'

# Check specific role (Portal, Admin, EAP, pxGrid)
netapi ise cert --format json list | jq '.[] | select(.usedBy | contains("Portal"))'

Quick Portal Cert Check

# Is the portal cert valid?
netapi ise cert --format json list | jq '.[] | select(.usedBy | contains("Portal")) | {name: .friendlyName, expires: .expirationDate}'

Verify Let’s Encrypt Chain

# Check if LE CA chain is complete in ISE trust store
netapi ise cert list-trusted --all --search LetsEncrypt

# Expected output (4 certs):
# - Letsencrypt-ISRG-Root-X1 (Root)
# - Letsencrypt-ISRG-Root-X2 (Root)
# - LetsEncrypt-E7 (Intermediate)
# - Letsencrypt-E8 (Intermediate)

# JSON output for scripting
netapi ise cert --format json list-trusted --all --search LetsEncrypt | jq '.[].friendlyName'

Let’s Encrypt Setup

#!/bin/bash
# Full LE setup
echo "Adding Let's Encrypt CA chain..."
netapi ise cert add-letsencrypt-ca

echo "Preparing certificates for import..."
netapi ise cert prepare-for-import

echo "Done. Import certs via ISE GUI:"
echo "  Administration > System > Certificates > System Certificates"

Count Trusted Certificates

# Total count of trusted CAs
netapi ise cert --format json list-trusted --all | jq 'length'

Find ISE Internal CA Certs

# ISE's internal Certificate Services CA chain
netapi ise cert list-trusted --all --search "Certificate Services"

# Just the root CAs
netapi ise cert --format json list-trusted --all | jq '.[] | select(.friendlyName | contains("Root CA"))'

Find Certs by Issuer

# All certs issued by DigiCert
netapi ise cert --format json list-trusted --all | jq '.[] | select(.issuedBy | contains("DigiCert"))'

# All certs issued by Cisco
netapi ise cert list-trusted --all --search Cisco

Find Self-Signed Certs

# Self-signed = subject matches issuer
netapi ise cert --format json list-trusted --all | jq '.[] | select(.friendlyName | test("self-signed"; "i"))'

System Cert Usage Report

# What's each system cert used for?
netapi ise cert --format json list | jq '.[] | {name: .friendlyName, usage: .usedBy, expires: .expirationDate}'

# Find the EAP cert
netapi ise cert --format json list | jq '.[] | select(.usedBy | contains("EAP"))'

# Find the Admin cert
netapi ise cert --format json list | jq '.[] | select(.usedBy | contains("Admin"))'

# Find the pxGrid cert
netapi ise cert --format json list | jq '.[] | select(.usedBy | contains("pxGrid"))'

Export Cert Inventory (CSV-ready)

# System certs as CSV
netapi ise cert --format json list | jq -r '.[] | [.friendlyName, .usedBy, .expirationDate] | @csv'

# Trusted CAs as CSV
netapi ise cert --format json list-trusted --all | jq -r '.[] | [.friendlyName, .issuedBy] | @csv'

# Full inventory to file
netapi ise cert --format json list > /tmp/system-certs.json
netapi ise cert --format json list-trusted --all > /tmp/trusted-certs.json

Expiration Monitoring

# All system cert expiration dates (sorted)
netapi ise cert --format json list | jq -r '.[] | "\(.expirationDate) - \(.friendlyName)"' | sort

# Find certs expiring in 2026
netapi ise cert --format json list | jq '.[] | select(.expirationDate | contains("2026"))'

# Portal cert expiry specifically
netapi ise cert --format json list | jq '.[] | select(.usedBy | contains("Portal")) | {name: .friendlyName, expires: .expirationDate}'

Quick Health Check

#!/bin/bash
# cert-health.sh - Quick ISE certificate health check

echo "=== System Certificates ==="
netapi ise cert list

echo -e "\n=== Trusted CA Count ==="
netapi ise cert --format json list-trusted --all | jq 'length'

echo -e "\n=== Let's Encrypt Chain ==="
netapi ise cert list-trusted --all --search LetsEncrypt

echo -e "\n=== Expiration Dates ==="
netapi ise cert --format json list | jq -r '.[] | "\(.expirationDate) - \(.friendlyName)"' | sort

Enterprise Use Cases

Certs Expiring Within 90 Days

#!/bin/bash
# Find certs expiring within N days
DAYS=90
CUTOFF=$(date -d "+${DAYS} days" +%s)

netapi ise cert --format json list | jq -r --argjson cutoff "$CUTOFF" '
  .[] |
  select(.expirationDate) |
  {name: .friendlyName, expires: .expirationDate, usage: .usedBy}
' | while read -r line; do
  echo "$line"
done

# Quick check - certs expiring in 2026
netapi ise cert --format json list | jq '.[] | select(.expirationDate | contains("2026")) | {name: .friendlyName, expires: .expirationDate, usage: .usedBy}'

Find Enterprise CA Certs (AD CS)

# Find your enterprise/AD CS root CAs
netapi ise cert list-trusted --all --search "ROOT-CA"
netapi ise cert list-trusted --all --search "Enterprise"
netapi ise cert list-trusted --all --search "AD-CS"

# Find by domain name
netapi ise cert --format json list-trusted --all | jq '.[] | select(.friendlyName | test("corp|enterprise|internal"; "i"))'

pxGrid Readiness Check

# Is pxGrid cert configured?
netapi ise cert --format json list | jq '.[] | select(.usedBy | contains("pxGrid")) | {name: .friendlyName, expires: .expirationDate}'

# Which CAs are trusted for pxGrid?
netapi ise cert --format json list-trusted --all | jq '[.[] | select(.trustForIseAuth == true)] | length'
# (Note: requires full cert details - check ISE GUI for trust settings)

EAP-TLS Readiness Check

# EAP cert configured?
netapi ise cert --format json list | jq '.[] | select(.usedBy | contains("EAP")) | {name: .friendlyName, expires: .expirationDate, issuer: .issuedBy}'

# Count CAs trusted for endpoint auth
netapi ise cert --format json list-trusted --all | jq 'length'

Compliance Audit Report

#!/bin/bash
# compliance-audit.sh - Certificate compliance report

DATE=$(date +%Y-%m-%d)
REPORT="/tmp/ise-cert-audit-${DATE}.txt"

{
  echo "ISE Certificate Compliance Audit - $DATE"
  echo "=========================================="

  echo -e "\n## System Certificates"
  netapi ise cert --format json list | jq -r '.[] | "- \(.friendlyName): \(.usedBy) (expires: \(.expirationDate))"'

  echo -e "\n## Trusted CA Count: $(netapi ise cert --format json list-trusted --all | jq 'length')"

  echo -e "\n## Certificate Usage Summary"
  echo "- Admin: $(netapi ise cert --format json list | jq '[.[] | select(.usedBy | contains("Admin"))] | length')"
  echo "- EAP: $(netapi ise cert --format json list | jq '[.[] | select(.usedBy | contains("EAP"))] | length')"
  echo "- Portal: $(netapi ise cert --format json list | jq '[.[] | select(.usedBy | contains("Portal"))] | length')"
  echo "- pxGrid: $(netapi ise cert --format json list | jq '[.[] | select(.usedBy | contains("pxGrid"))] | length')"
  echo "- SAML: $(netapi ise cert --format json list | jq '[.[] | select(.usedBy | contains("SAML"))] | length')"

  echo -e "\n## Expiration Timeline"
  netapi ise cert --format json list | jq -r '.[] | "\(.expirationDate) - \(.friendlyName)"' | sort

} > "$REPORT"

echo "Report saved to: $REPORT"
cat "$REPORT"

Before/After Change Comparison

# Before making cert changes - save baseline
netapi ise cert --format json list > /tmp/certs-before.json
netapi ise cert --format json list-trusted --all > /tmp/trusted-before.json

# After changes - compare
netapi ise cert --format json list > /tmp/certs-after.json
diff <(jq -S . /tmp/certs-before.json) <(jq -S . /tmp/certs-after.json)

# Quick count comparison
echo "Before: $(jq 'length' /tmp/trusted-before.json) trusted CAs"
echo "After: $(jq 'length' /tmp/trusted-after.json) trusted CAs"

Find Duplicate/Similar Certs

# Find certs with similar names (potential duplicates)
netapi ise cert --format json list-trusted --all | jq -r '.[].friendlyName' | sort | uniq -d

# Group by issuer
netapi ise cert --format json list-trusted --all | jq 'group_by(.issuedBy) | .[] | {issuer: .[0].issuedBy, count: length}'

Cert Documentation for DR/Runbook

#!/bin/bash
# Generate cert documentation for disaster recovery runbook

echo "# ISE Certificate Documentation"
echo "Generated: $(date)"
echo ""

echo "## System Certificates"
echo '```'
netapi ise cert list
echo '```'
echo ""

echo "## System Cert Details (JSON)"
echo '```json'
netapi ise cert --format json list | jq '.'
echo '```'
echo ""

echo "## Trusted CA Summary"
echo "Total CAs: $(netapi ise cert --format json list-trusted --all | jq 'length')"
echo ""
echo "### By Category:"
echo "- Let's Encrypt: $(netapi ise cert --format json list-trusted --all | jq '[.[] | select(.friendlyName | test("LetsEncrypt|ISRG"; "i"))] | length')"
echo "- Cisco: $(netapi ise cert --format json list-trusted --all | jq '[.[] | select(.friendlyName | test("Cisco"; "i"))] | length')"
echo "- DigiCert: $(netapi ise cert --format json list-trusted --all | jq '[.[] | select(.friendlyName | test("DigiCert"; "i"))] | length')"
echo "- ISE Internal: $(netapi ise cert --format json list-trusted --all | jq '[.[] | select(.friendlyName | test("Certificate Services"; "i"))] | length')"

Verify Cert Chain Completeness

# Check if your enterprise CA chain is complete
# Example: Verify HOME-ROOT-CA chain
netapi ise cert list-trusted --all --search "HOME"

# Check portal cert issuer is trusted
PORTAL_ISSUER=$(netapi ise cert --format json list | jq -r '.[] | select(.usedBy | contains("Portal")) | .issuedBy')
echo "Portal cert issued by: $PORTAL_ISSUER"
netapi ise cert --format json list-trusted --all | jq --arg issuer "$PORTAL_ISSUER" '.[] | select(.friendlyName | contains($issuer))'

Weekly Cert Check (Cron-ready)

#!/bin/bash
# weekly-cert-check.sh - Run via cron for proactive monitoring
# 0 8 * * 1 /path/to/weekly-cert-check.sh | mail -s "ISE Cert Report" admin@company.com

# Load credentials
source ~/.config/netapi/d000-dev-network.env  # or use dsource

echo "ISE Certificate Weekly Report - $(date)"
echo "========================================"

# Check for certs expiring within 60 days
echo -e "\nāš ļø  CERTS EXPIRING WITHIN 60 DAYS:"
SIXTY_DAYS=$(date -d "+60 days" "+%Y")
netapi ise cert --format json list | jq -r --arg year "$SIXTY_DAYS" '
  .[] | select(.expirationDate | contains($year)) |
  "  - \(.friendlyName): \(.expirationDate)"'

# Summary
echo -e "\nšŸ“Š SUMMARY:"
echo "  System certs: $(netapi ise cert --format json list | jq 'length')"
echo "  Trusted CAs: $(netapi ise cert --format json list-trusted --all | jq 'length')"

# Next expiration
echo -e "\nšŸ“… NEXT EXPIRATION:"
netapi ise cert --format json list | jq -r '.[] | "\(.expirationDate) - \(.friendlyName)"' | sort | head -1

Multi-Environment Comparison

#!/bin/bash
# Compare certs across environments (dev/test/prod)

for ENV in dev test prod; do
  echo "=== $ENV ==="
  dsource d000 $ENV/network 2>/dev/null
  echo "System certs: $(netapi ise cert --format json list 2>/dev/null | jq 'length')"
  echo "Trusted CAs: $(netapi ise cert --format json list-trusted --all 2>/dev/null | jq 'length')"
  echo ""
done