cd /news/ai-safety/automating-post-quantum-ipsec-on-cis… · home › topics › ai-safety › article
[ARTICLE · art-141170] src=blogs.cisco.com ↗ pub= topic=ai-safety verified=true sentiment=· neutral

Automating Post-Quantum IPsec on Cisco Routers – IPsec Series, Part 12

Cisco IOS XE 26.2 exposes post-quantum IPsec configuration as real YANG models, but the Cisco-IOS-XE-crypto.yang module contains zero rpc statements, so actions like 'crypto key generate mldsa', 'crypto pki enroll', and 'clear crypto ikev2 sa' must still run over CLI, according to a DevNet-focused blog post by Julio Gomez. The post details four Ansible-over-NETCONF playbooks for a three-router hub-and-spoke lab that push ML-KEM-768, optional or required, and ML-DSA-65 authentication, with playbooks published on GitHub. The author argues automation matters because a single missed 'pqc mlkem768 optional' line leaves one spoke tunnel classical while the others go hybrid, and because changing a proposal does not renegotiate an already-established SA.

by read10 min views3 publishedSep 28, 2026
Automating Post-Quantum IPsec on Cisco Routers – IPsec Series, Part 12
Image: Blogs (auto-discovered)

I know, I know… I said I was done with the IPsec series, BUT what would this series say about me if I did not automate everything??

In the last few blog posts we reviewed the CLI story: classical baseline, PPK, ML-KEM hybrid, ML-DSA certificates, the atomic cutover. That’s how you LEARN a platform. But that is not how you RUN a fabric.

And if you’ve been hanging around DevNet for a while, you already know the next move. That was the point the whole time: enable the community on programmability so you can stop typing at boxes and start driving them through APIs. YANG, NETCONF, RESTCONF, Ansible: years of that work. API-driven automation was one of the main goals. Let’s now use those habits to configure Post-quantum IPsec!

Type the same IKEv2 profile on three boxes, change two addresses, and you’ll get two routers agreeing and one that doesn’t. You’ll find out a week later. The post-quantum posture is a decision: ML-KEM-768, optional or required, ML-DSA-65. You want to state it once, push it, and prove the live SA actually used it. DONE.

This post is that layer. Same lab, same RFCs, same two pillars. The interface is Ansible over NETCONF instead of three consoles.

All docs and Ansible playbooks are available in my GitHub for your own testing, but if you don’t have three routers in your lab you can still follow. You won’t be typing along unless you’ve got the boxes. What you’ll see is the playbooks we ran, the XML that actually landed, and the show output that came back.

Why this is worth automating #

Go back to Part 9’s hub-and-spoke. The hub needs one IKEv2 profile per spoke. Each spoke needs one toward the hub. The proposal, the transform set, the tunnel, the fragmentation MTU: same lines, two addresses flipped.

Then add PQC and the cost of a typo goes up. Forget “pqc mlkem768 optional” on one spoke and that tunnel stays classical while the others go hybrid. Forget fragmentation and the handshake never completes. Flip authentication on one end of a pair and, as Part 11 showed, the tunnel dies until the other end follows.

Automation is not about saving keystrokes. It’s about stating the posture in one place and refusing to move on until the box agrees.

Config is modelled. Actions are not. #

Here’s the load-bearing fact about IOS XE 26.2, and it shapes everything below.

The post-quantum configuration is real YANG. Not CLI strings shipped over a fancier wire. Named leaves, typed, validated at the edit. If you’ve consumed a Cisco IOS XE model before, this is the same muscle. The leaf names are new (e.g. mlkem768 or mldsa-sig). The pattern is not: read the model, send structured data, check what came back.

Intent Where it lives
ML-KEM group /native/crypto/ikev2/proposal[name]/pqc/mlkem768
PPK (RFC 8784) /native/crypto/ikev2/keyring[name]/peer[name]/ppk/manual/...
ML-DSA auth trustpoint mldsakeypair +authentication/{local,remote}/mldsa-sig

What YANG will not do is ask the box to do something. Cisco-IOS-XE-crypto.yang has zero rpc statements. So these still go over CLI:

  • crypto key generate mldsa
  • crypto pki enroll
  • clear crypto ikev2 sa

“This proposal should offer ML-KEM-768” is expressible. “Renegotiate now” is not. And you need both, because changing a proposal does not touch an SA that is already up. Push ML-KEM, read the operational data, see no PQC, and you’ll conclude the feature is broken when it simply hasn’t renegotiated.

That’s the whole design in one paragraph. Desired state over NETCONF. The handful of actions over CLI, marked as escape hatches, not buried.

Four playbooks, same progression #

Three routers in hub-and-spoke. One playbook per unit. Each one tears itself down with -e state=absent. Every apply ends by reporting the resulting posture.

                    ┌─ PPK ────────────┐
baseline ───────────┼─ ML-KEM ─────────┼─────────► ML-DSA
                    └─ neither ────────┘
                         ▲
                     pick one
Playbook What it is Maps to
ipsec-baseline.yml classical Diffie-Hellman Part 9, the IKEv2 baseline
ipsec-pq-ppk.yml RFC 8784 PPK Part 9, the PPK section
ipsec-pq-mlkem.yml native ML-KEM-768 hybrid Part 9, native ML-KEM
ipsec-pq-mldsa.yml authentication swaps to ML-DSA certificates Parts 10 and 11

PPK and ML-KEM are alternatives, not steps. Same rule as Part 9: we never stack them. ML-DSA is orthogonal. It swaps how identity is proved and does not care whether you chose PPK, ML-KEM, or neither for keying.

The posture itself lives in one file, “pqc.yml”. Two lines decide the IPsec half of the lab:

pq_key_exchange: mlkem768
pq_key_exchange_optional: true
pq_signature_param_set: 65

Change a value, re-run, every proposal on the fabric moves. You should never have to open an XML template.

Before any of that, “bootstrap.yml” turns NETCONF on. That one still runs over SSH CLI, because you can’t configure the transport over the transport. 🙂 First run waits for the subsystem (on these boxes, a bit over two minutes). Second run diffs against the running config, pushes nothing, and reports changed=false.

ML-KEM is two leaves #

“ipsec-pq-mlkem.yml” adds two YANG leaves to the existing proposal and nothing else. The payload that lands on the box looks like this:

<ikev2 xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-crypto">
  <proposal>
    <name>LAB-PROPOSAL</name>
    <pqc>
      <mlkem768/>
      <optional/>
    </pqc>
  </proposal>
</ikev2>

The parameter set is the element name, not a value. mlkem512, mlkem768 and mlkem1024 are sibling leaves of type empty. That’s why pq_key_exchange has to be spelled the way the schema spells it.

On the hub CLI, the whole unit is just one line:

 crypto ikev2 proposal LAB-PROPOSAL
+ pqc mlkem768 optional
  encryption aes-cbc-256
  integrity sha512
  group 20

Then the role clears the IKEv2 SAs (that’s the CLI escape hatch) so the next negotiation actually uses the new proposal.

PLAY RECAP from the real apply, three routers in parallel:

r1                         : ok=33   changed=2    unreachable=0    failed=0
r2                         : ok=33   changed=2    unreachable=0    failed=0
r3                         : ok=28   changed=2    unreachable=0    failed=0

And the proof, off R2 (the hub), both tunnels:

R2# show crypto ikev2 sa detailed
Tunnel-id Local                 Remote                fvrf/ivrf            Status
2         10.0.12.2/500         10.0.12.1/500         none/none            READY
      Encr: AES-CBC, keysize: 256, PRF: SHA512, Hash: SHA512, DH Grp:20, Auth sign: PSK, Auth verify: PSK
      PQC Key Exchange: ML-KEM-768
      ...
      Quantum-safe Encryption using PQC: ML-KEM-768

Tunnel-id Local                 Remote                fvrf/ivrf            Status
1         10.0.23.1/500         10.0.23.2/500         none/none            READY
      Encr: AES-CBC, keysize: 256, PRF: SHA512, Hash: SHA512, DH Grp:20, Auth sign: PSK, Auth verify: PSK
      PQC Key Exchange: ML-KEM-768

That’s not “the edit returned <ok/>”. That’s the live SA telling you it negotiated ML-KEM-768. The operational YANG even has a leaf for it: “pqc-group = pqc-gt-mlkem768”.

Cannot stack PPK on ML-KEM #

Part 9 configured PPK, then removed PPK, then enabled ML-KEM. The automation encodes that as a hard stop.

Run “ipsec-pq-mlkem.yml” while a PPK is still configured and nothing is touched:

TASK [ipsec_pq_mlkem : Abort if a PPK is still configured]
fatal: [r1]: FAILED! => {
  "msg": "A post-quantum pre-shared key is still configured on r1.
          PPK and ML-KEM are alternative paths in this lab and are
          never stacked. Tear the PPK down first, then come back:
          \"ansible-playbook ipsec-pq-ppk.yml -e state=absent\".
          Nothing has been changed."
}

Same the other way: ipsec-pq-ppk.yml probes for ML-KEM and aborts if it finds it. Neither role auto-removes the other.

ML-DSA one router at a time #

Swapping authentication replaces the method rather than adding to it. Between one end switching and the other following, the peers disagree and the tunnel is legitimately down. A parallel run would leave the fabric half-migrated. Rolling r2, then r1, then r3 keeps that window to a single peer pair.

That’s why the first apply takes around ten minutes, not because Ansible is slow. Each router generates its own ML-DSA key on-box, builds a CSR, the control node signs it, the certificate comes back, then the profile flips. While the hub has switched and a spoke has not, overlay pings fail and retry. Those red FAILED - RETRYING lines are intentional and non-fatal. failed=0 in PLAY RECAP is what matters.

Clean build from zero, three routers:

r1                         : ok=75   changed=10   unreachable=0    failed=0
r2                         : ok=75   changed=10   unreachable=0    failed=0
r3                         : ok=70   changed=10   unreachable=0    failed=0

A second run is much faster: the CA and the issued certificates already exist, so most tasks report changed=0.

One difference from Part 10 is worth being explicit about. The hand-driven CLI walkthrough imported a PKCS#12 bundle built entirely on the workstation (private key included). The playbook does it the other way around: the router generates the key, only the CSR leaves the box, only the certificate comes back. Same external OpenSSL 3.5+ CA, same ML-DSA-65 chain. The private identity key never sits on the laptop.

After the loop has been all the way round:

R2# show crypto ikev2 sa | include Auth sign
      Encr: ... DH Grp:20, Auth sign: MLDSA, Auth verify: MLDSA
      Encr: ... DH Grp:20, Auth sign: MLDSA, Auth verify: MLDSA

Hub, two tunnels, both ML-DSA. The operational YANG cannot report it: typedef crypto-auth-method has no ML-DSA value. So this is the one legitimate screen-scrape on the IPsec read path. Everything else post-quantum comes back as a leaf.

And we check Auth sign on every SA row, on every router, not Auth verify. Part 11 already showed why: mid-migration, the initiator’s Auth verify field is not trustworthy. A spoke can print Auth verify: MLDSA while the hub is still signing PSK. Each box asserts only the direction it is authoritative for.

An <ok/> is not proof #

That’s the sentence to take away from this whole layer.

A successful NETCONF edit means the payload parsed. It does not mean the feature works. It does not mean the SA renegotiated. It does not mean the certificate the router just imported is the one the profile is using.

So every unit asserts against the box after configuring it:

Question What we check
Did this SA use ML-KEM, and which parameter set? pqc-group per IKEv2 SA (YANG)
Is PPK working? quantum-resistance counters (YANG + CLI)
Did this SA authenticate with ML-DSA? Auth sign: on every row (CLI)

You can re-check later without re-applying:

ansible-playbook verify.yml -e verify_ipsec_only=true -e verify_require_mlkem=true
ansible-playbook verify.yml -e verify_ipsec_only=true -e verify_require_mldsa=true

Pass both flags once the full stack is up, and you have a CI check: fail the job unless the live SAs are hybrid and ML-DSA.

What we’ve done #

Same three boxes, no more CLI typing. The posture lives in one file. PPK and ML-KEM refuse to stack. ML-DSA rolls one peer at a time because that’s what the protocol requires. And the check that matters is not “did NETCONF say <ok/>”, it’s PQC Key Exchange: ML-KEM-768 and Auth sign: MLDSA on a live SA.

You started Part 1 with a question: is your VPN ready for the quantum era? You can answer it now, on a laptop and on a Cisco router, by hand and as code. DevNet’s job was to enable this community on programmability, with API-driven automation as one of the main goals. Here’s that work, running against a live quantum-safe tunnel.

From “docker compose up” to “show crypto ikev2 sa” to “ansible-playbook ipsec-pq-mlkem.yml”. The headlines about quantum computers breaking the internet stop being scary once you’ve seen a tunnel come up, and they stay that way once you can bring the next hundred up the same way.

Go automate your PQC deployments!

── more in #ai-safety 4 stories · sorted by recency
── more on @cisco 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/automating-post-quan…] indexed:0 read:10min 2026-09-28 · —