Phase 5: Certificate Issuance

Phase 5: Certificate Issuance (DOMUS PKI)

Issue Admin/EAP Certificate from Workstation

dsource d000 dev/vault

vault write pki_int/issue/domus-server \
  common_name="ise-01.inside.domusdigitalis.dev" \
  ip_sans="10.50.1.20" \
  ttl="8760h" \
  -format=json > /dev/shm/ise-01-cert.json

Extract Components

jq -r '.data.certificate' /dev/shm/ise-01-cert.json > /dev/shm/ise-01.crt
jq -r '.data.private_key' /dev/shm/ise-01-cert.json > /dev/shm/ise-01.key
jq -r '.data.ca_chain[]' /dev/shm/ise-01-cert.json > /dev/shm/ise-01-chain.pem

Split CA Chain

cd /dev/shm
awk '/-----BEGIN CERTIFICATE-----/{n++} n==1' ise-01-chain.pem > DOMUS-ISSUING-CA.pem
awk '/-----BEGIN CERTIFICATE-----/{n++} n==2' ise-01-chain.pem > DOMUS-ROOT-CA.pem

Verify Before Import

openssl x509 -in /dev/shm/ise-01.crt -noout -subject -issuer -dates
openssl x509 -in DOMUS-ISSUING-CA.pem -noout -subject
openssl x509 -in DOMUS-ROOT-CA.pem -noout -subject
Expected
subject=CN=ise-01.inside.domusdigitalis.dev
issuer=CN=DOMUS-ISSUING-CA
subject=CN=DOMUS-ISSUING-CA
subject=C=US, O=Domus Digitalis, OU=Enterprise PKI, CN=DOMUS-ROOT-CA

Import to ISE GUI (10.50.1.20/admin)

Import Trusted CAs BEFORE the System Certificate. ISE validates the chain during import.

Step 1: Trusted Certificates (Administration → System → Certificates → Trusted Certificates)

  1. Import DOMUS-ROOT-CA.pem

    • Friendly Name: DOMUS-ROOT-CA

    • Trust for: authentication within ISE, client authentication, Cisco Services

  2. Import DOMUS-ISSUING-CA.pem

    • Friendly Name: DOMUS-ISSUING-CA

    • Same trust options

Step 2: System Certificate (Administration → System → Certificates → System Certificates)

  1. Import:

    • Certificate File: ise-01.crt

    • Private Key File: ise-01.key

    • Password: (empty — unencrypted key)

    • Friendly Name: DOMUS_ISE_ADMIN_EAP

  2. Enable for: Admin + EAP Authentication

ISE will restart Admin services — wait 2-3 minutes.

Issue pxGrid Certificate

Future enhancement: Create a dedicated Vault role domus-pxgrid that embeds purpose in the cert itself (OU, EKU). This is the product-grade pattern:

vault write pki_int/roles/domus-pxgrid \
  allowed_domains="inside.domusdigitalis.dev" \
  allow_subdomains=true \
  organization="Domus Digitalis" \
  ou="pxGrid Services" \
  server_flag=true \
  client_flag=true \
  ttl="8760h"

Then issue with: vault write pki_int/issue/domus-pxgrid …​

For now, using domus-server role — purpose is declared by ISE’s usage binding and Friendly Name.

vault write pki_int/issue/domus-server \
  common_name="ise-01.inside.domusdigitalis.dev" \
  ip_sans="10.50.1.20" \
  ttl="8760h" \
  -format=json > /dev/shm/ise-01-pxgrid.json

jq -r '.data.certificate' /dev/shm/ise-01-pxgrid.json > /dev/shm/ise-01-pxgrid.crt
jq -r '.data.private_key' /dev/shm/ise-01-pxgrid.json > /dev/shm/ise-01-pxgrid.key

Verify pxGrid Cert Before Import

openssl x509 -in /dev/shm/ise-01-pxgrid.crt -noout -subject -issuer -serial -dates
Expected
subject=CN=ise-01.inside.domusdigitalis.dev
issuer=CN=DOMUS-ISSUING-CA
serial=<serial from Vault>
notBefore=<today>
notAfter=<today + 8760h>

Import to ISE GUI (Administration → System → Certificates → System Certificates → Import):

  • Certificate File: ise-01-pxgrid.crt

  • Private Key File: ise-01-pxgrid.key

  • Friendly Name: DOMUS_ISE_PXGRID

  • Enable for: pxGrid only

Hyprland/Wayland file picker hang: The ISE cert import dialog uses the system file picker which delegates to xdg-desktop-portal-hyprland. It hangs after selecting the file — even after closing Firefox. Fix:

pkill -f xdg-desktop-portal

Then refresh the browser. The portal respawns on next file dialog. This may occur on each file import — kill and refresh each time.

Alternative: launch Firefox without portal delegation:

GTK_USE_PORTAL=0 firefox https://ise-01.inside.domusdigitalis.dev/admin &

Verify pxGrid Service Cert (Post-Import)

openssl s_client -connect ise-01.inside.domusdigitalis.dev:8910 </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -serial -dates
Expected
subject=CN=ise-01.inside.domusdigitalis.dev
issuer=CN=DOMUS-ISSUING-CA
serial=<new serial from Vault>
notBefore=<today>
notAfter=<today + 8760h>

Secure Cleanup

shred -vfz -n 3 /dev/shm/ise-01* /dev/shm/DOMUS-*.pem
rm -f /dev/shm/ise-01* /dev/shm/DOMUS-*.pem

Seal Vault

dsource d000 dev/vault loads the token into your local shell. SSH runs a remote shell that doesn’t inherit local variables. You must pass the token explicitly — double quotes on the outer SSH command allow $VAULT_ROOT_TOKEN to expand locally before the command is sent to the remote host.

# Load vault credentials locally
dsource d000 dev/vault

# Seal — token expands locally, executes remotely
ssh vault-01 "VAULT_ADDR='https://127.0.0.1:8200' VAULT_SKIP_VERIFY=1 VAULT_TOKEN='$VAULT_ROOT_TOKEN' vault operator seal"
  • VAULT_ADDR must be https:// (not http://) — Vault listens on TLS

  • VAULT_SKIP_VERIFY=1 bypasses cert verification for localhost seal operation

  • VAULT_TOKEN must be passed — seal is a privileged operation

  • The export && pattern is unnecessary — inline env vars before the command work

Verify Certificate Installation

Pre-Import: Confirm Restored Cert (CN mismatch expected)

After restore, ise-01 serves ise-02’s certificate. Verify the mismatch before replacing:

openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -serial -dates
Expected (before new cert import)
subject=CN=ise-02.inside.domusdigitalis.dev    <-- mismatch, from restore
issuer=CN=DOMUS-ISSUING-CA
serial=6420C923AA4E3CC19B431306A6195EA6BEAD2E40
notBefore=Mar  5 21:57:05 2026 GMT
notAfter=Mar  5 21:57:35 2027 GMT

Post-Import: Confirm Correct Cert

After importing the new cert, the same command should show the correct CN:

openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -serial -dates
Expected (after new cert import)
subject=CN=ise-01.inside.domusdigitalis.dev    <-- correct
issuer=CN=DOMUS-ISSUING-CA
serial=<new serial from Vault>
notBefore=<today>
notAfter=<today + 8760h>

Full Chain Verification

openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 -showcerts </dev/null 2>/dev/null \
  | grep -E "^(depth|Certificate chain| [0-9]+ s:)"
Expected
Certificate chain
 0 s:CN=ise-01.inside.domusdigitalis.dev
 1 s:CN=DOMUS-ISSUING-CA
 2 s:C=US, O=Domus Digitalis, OU=Enterprise PKI, CN=DOMUS-ROOT-CA

Verify Against Local CA

openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 \
  -CAfile /etc/ssl/certs/DOMUS-ROOT-CA.pem </dev/null 2>&1 \
  | grep "Verify return code"
Expected
Verify return code: 0 (ok)

The openssl s_client pipeline is a universal TLS diagnostic — works against any service on any port:

# ISE admin
openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -serial -dates

# Vault
openssl s_client -connect vault-01.inside.domusdigitalis.dev:8200 </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -serial -dates

# Keycloak
openssl s_client -connect keycloak-01.inside.domusdigitalis.dev:8443 </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -serial -dates

Use it to audit any TLS endpoint — before and after certificate changes — to confirm the correct cert is being served.