{"id":253,"date":"2026-04-27T00:00:00","date_gmt":"2026-04-26T20:30:00","guid":{"rendered":"https:\/\/www.anynet.ir\/blog\/%da%af%d9%88%d8%a7%d9%87%db%8c%d9%87%d8%a7%db%8c-gnsi-%da%86%d8%b1%d8%ae%d9%87-%d8%ad%db%8c%d8%a7%d8%aa-%da%af%d9%88%d8%a7%d9%87%db%8c-tls-%d8%af%d8%b1-cisco-ios-xr-en-2\/"},"modified":"2026-04-27T00:00:00","modified_gmt":"2026-04-26T20:30:00","slug":"%da%af%d9%88%d8%a7%d9%87%db%8c%d9%87%d8%a7%db%8c-gnsi-%da%86%d8%b1%d8%ae%d9%87-%d8%ad%db%8c%d8%a7%d8%aa-%da%af%d9%88%d8%a7%d9%87%db%8c-tls-%d8%af%d8%b1-cisco-ios-xr-en-2","status":"publish","type":"post","link":"https:\/\/www.anynet.ir\/blog\/en\/%da%af%d9%88%d8%a7%d9%87%db%8c%d9%87%d8%a7%db%8c-gnsi-%da%86%d8%b1%d8%ae%d9%87-%d8%ad%db%8c%d8%a7%d8%aa-%da%af%d9%88%d8%a7%d9%87%db%8c-tls-%d8%af%d8%b1-cisco-ios-xr-en-2\/","title":{"rendered":"gNSI Certz \u2014 TLS Certificate Lifecycle on Cisco IOS XR"},"content":{"rendered":"<div><\/div>\n<div>\n<div><\/div>\n<\/div>\n<div>\n<div>\n<div>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">The Problem<\/h2>\n<p><a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/grpc-authorization-with-authz\" style=\"color: rgb(0, 102, 204)\">Authz<\/a> and <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/securing-gnmi-with-pathz\" style=\"color: rgb(0, 102, 204)\">Pathz<\/a> control what clients can do over gRPC and gNMI, but all of that depends on TLS being set up correctly. Certificates expire. Trust Bundles go stale. CRLs need updating. And every one of those changes has to happen without dropping the gRPC server. Traditionally, managing TLS material on IOS XR meant copying PEM files to specific paths and hoping the right files ended up in the right place. There was no standard API for rotating Certificates, no Rollback if something went wrong, and no clean way to manage multiple TLS Profiles on the same box.<\/p>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">What is Certz<\/h2>\n<p><a href=\"https:\/\/github.com\/openconfig\/gnsi\/tree\/main\/certz\" style=\"color: rgb(0, 102, 204)\">gNSI Certz<\/a> solves this by grouping everything TLS needs into <strong style=\"color: rgb(26, 26, 26)\">SSL Profiles<\/strong>. Each Profile bundles:<\/p>\n<ul>\n<li><strong style=\"color: rgb(26, 26, 26)\">Certificate and Private Key<\/strong> \u2014 the server Authentication material<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Trust Bundle<\/strong> \u2014 the CA chain clients validate against<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">CRL Bundle<\/strong> \u2014 Certificate Revocation Lists<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Authentication Policy<\/strong> \u2014 for mutual TLS and similar patterns<\/li>\n<\/ul>\n<p>Instead of copying files, a Controller stages new material through a <strong style=\"color: rgb(26, 26, 26)\">Rotate<\/strong> RPC, validates it, and finalizes \u2014 or drops the stream to roll back. On IOS XR, <strong style=\"color: rgb(26, 26, 26)\">EMSd<\/strong> implements Certz and stores Profiles under <code>\/misc\/config\/grpc\/gnsi\/certz\/ssl_profiles\/<\/code>.<\/p>\n<p>This article covers SSL Profiles, the six RPCs, two end-to-end Rotation flows (router-generated CSR vs workstation-generated key), and <strong style=\"color: rgb(26, 26, 26)\">grpcurl<\/strong> examples. Service-level Authorization is handled by <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/grpc-authorization-with-authz\" style=\"color: rgb(0, 102, 204)\">Authz<\/a>; path-level Authorization by <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/securing-gnmi-with-pathz\" style=\"color: rgb(0, 102, 204)\">Pathz<\/a>. Certz keeps the transport underneath trustworthy.<\/p>\n<p>Within AAA, Certz is the <strong style=\"color: rgb(26, 26, 26)\">Authentication<\/strong> plane for the gRPC management plane \u2014 it manages the TLS material that proves <em>who<\/em> a client is before any Authz\/Pathz decision runs. (Its sibling <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/gnsi-credentialz-ssh-credential-rotation-on-cisco-ios-xr\" style=\"color: rgb(0, 102, 204)\">Credentialz<\/a> plays the equivalent role for the SSH plane.)<\/p>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Certz compared with gNOI cert<\/h2>\n<p>IOS XR has long supported gNOI <a href=\"https:\/\/github.com\/openconfig\/gnoi\/tree\/master\/cert\" style=\"color: rgb(0, 102, 204)\"><code>cert.proto<\/code><\/a> for Certificate operations. Certz is the OpenConfig gNSI evolution and is where new feature work lands. The table below highlights why operators should move from the older interface to Profiles plus Rotate:<\/p>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Topic<\/th>\n<th>gNOI <code>cert.proto<\/code> (legacy)<\/th>\n<th>gNSI <code>certz.proto<\/code><\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Organization<\/td>\n<td>Certificate ID per leaf; CA Bundle updates overwrite a single Bundle<\/td>\n<td>All PKI material grouped under a named <strong style=\"color: rgb(26, 26, 26)\">SSL Profile<\/strong><\/td>\n<\/tr>\n<tr>\n<td>CSR<\/td>\n<td>No SAN in CSR params on some stacks<\/td>\n<td><strong style=\"color: rgb(26, 26, 26)\">SAN<\/strong> is part of <strong style=\"color: rgb(26, 26, 26)\"><code>CSRParams<\/code><\/strong><\/td>\n<\/tr>\n<tr>\n<td>RPC surface<\/td>\n<td>Install \/ load CA \/ revoke as <strong style=\"color: rgb(26, 26, 26)\">separate<\/strong> RPCs<\/td>\n<td><strong style=\"color: rgb(26, 26, 26)\">One streaming <code>Rotate<\/code><\/strong> carries Certificate, Key, Trust Bundle, CRL in <strong style=\"color: rgb(26, 26, 26)\"><code>UploadRequest<\/code><\/strong><\/td>\n<\/tr>\n<tr>\n<td>Algorithms<\/td>\n<td>RSA-oriented history<\/td>\n<td><strong style=\"color: rgb(26, 26, 26)\">RSA, ECDSA, Ed25519<\/strong> supported<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Industry direction treats gNOI <code>cert.proto<\/code> as deprecated. New deployments should use Certz.<\/p>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">SSL Profiles on IOS XR<\/h2>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">What is an SSL Profile?<\/h3>\n<p>An SSL Profile is a single place the router looks for &#8220;everything TLS needs&#8221; when it brings up gRPC:<\/p>\n<ul>\n<li><strong style=\"color: rgb(26, 26, 26)\">Authentication Material<\/strong> \u2014 the leaf Certificate and its Private Key<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Trust Anchors<\/strong> \u2014 the CA chain clients validate against<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Revocation<\/strong> \u2014 CRL Bundles<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Policy<\/strong> \u2014 optional hooks for mutual TLS (some fields remain platform-specific; check Release Notes)<\/li>\n<\/ul>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Default Profile: <code>gNxI<\/code><\/h3>\n<p>The gRPC server on IOS XR uses Profile ID <code>gNxI<\/code> unless configured otherwise. In <code>RotateCertificateRequest<\/code>, leaving <code>ssl_profile_id<\/code> empty typically resolves to <code>gNxI<\/code>.<\/p>\n<p>The Profile bound to the active gRPC server cannot be deleted \u2014 <code>DeleteProfile<\/code> returns an error.<\/p>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">On-disk Persistence<\/h3>\n<p>IOS XR stores Certz SSL Profiles under:<\/p>\n<pre><code><span>\/misc\/config\/grpc\/gnsi\/certz\/ssl_profiles\/&lt;ssl_profile_id&gt;\/\n<\/span><\/code><\/pre>\n<p>Metadata like <code>version<\/code> and <code>created_on<\/code> is stored in JSON next to the PEM files. Standby RP synchronization for everything under <code>\/misc\/config\/grpc\/gnsi\/<\/code> works through RDSFS. Failover and EMSd restarts depend on that directory tree staying consistent.<\/p>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Certz RPCs<\/h2>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>RPC<\/th>\n<th>Type<\/th>\n<th>What it does<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>GetProfileList<\/code><\/td>\n<td>Unary<\/td>\n<td>List every <code>ssl_profile_id<\/code> on the device<\/td>\n<\/tr>\n<tr>\n<td><code>AddProfile<\/code><\/td>\n<td>Unary<\/td>\n<td>Create an empty Profile shell; material arrives via <code>Rotate<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>CanGenerateCSR<\/code><\/td>\n<td>Unary<\/td>\n<td>Check whether the supplied <code>CSRParams<\/code> are supported<\/td>\n<\/tr>\n<tr>\n<td><code>Rotate<\/code><\/td>\n<td>Bidirectional stream<\/td>\n<td>Optional CSR generation, <code>UploadRequest<\/code> payloads, then <code>finalize_rotation<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>DeleteProfile<\/code><\/td>\n<td>Unary<\/td>\n<td>Remove a Profile that is not bound to the live gRPC server<\/td>\n<\/tr>\n<tr>\n<td><code>GetIntegrityManifest<\/code><\/td>\n<td>Unary<\/td>\n<td>Integrity \/ RIM manifest (often <code>Unimplemented<\/code> on current IOS XR builds)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<blockquote>\n<p><strong style=\"color: rgb(26, 26, 26)\">Note:<\/strong> The OpenConfig service comment says only one Certz RPC should run at a time. Serialize access the same way as with <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/grpc-authorization-with-authz\" style=\"color: rgb(0, 102, 204)\">Authz<\/a> Rotate or <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/securing-gnmi-with-pathz\" style=\"color: rgb(0, 102, 204)\">Pathz<\/a> Rotate.<\/p>\n<\/blockquote>\n<blockquote>\n<p><strong style=\"color: rgb(26, 26, 26)\">On 25.3.1:<\/strong> <code>grpcurl \u2026 list gnsi.certz.v1.Certz<\/code> shows <code>GetIntegrityManifest<\/code>, but a real call returns <code>Code: Unimplemented<\/code>. Reflection advertises the descriptor; it doesn&#8217;t guarantee EMSd implemented the RPC. Always verify with a real call.<\/p>\n<\/blockquote>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">How the Rotate RPC Works<\/h2>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Transaction Model<\/h3>\n<p>Rotate is a streamed Transaction for one <code>ssl_profile_id<\/code>:<\/p>\n<ol>\n<li>Open the stream for a single Profile.<\/li>\n<li>Send optional <code>generate_csr<\/code>, then one or more <code>certificates<\/code> messages (<code>UploadRequest<\/code> with repeated <code>Entity<\/code> values), then <code>finalize_rotation<\/code>.<\/li>\n<li>Until finalize arrives, new material stays as a Candidate. Drop the stream without finalize to trigger Rollback.<\/li>\n<li>Use <code>force_overwrite: true<\/code> to replace an existing <code>version<\/code> string. Without it, reusing a version is rejected \u2014 the proto names this <code>AlreadyExists<\/code>, but IOS XR 24.4.x surfaces it as <code>InvalidArgument: certificate: same version sent<\/code> (full example below).<\/li>\n<\/ol>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">IOS XR Behavior (24.4.x)<\/h3>\n<p>On recent lab images, <strong style=\"color: rgb(26, 26, 26)\">every<\/strong> <code>RotateCertificateRequest<\/code> on the stream \u2014 including the finalize message \u2014 needs the same <code>sslProfileId<\/code>. Omitting it on finalize produces:<\/p>\n<pre><code><span>InvalidArgument: Not expecting rotate RPC for multiple SSL profiles in single stream\n<\/span><\/code><\/pre>\n<p>Always follow this pattern:<\/p>\n<pre><code><span><span>{<\/span><span>\"sslProfileId\"<\/span><span>:<\/span><span>\"lab-profile-1\"<\/span><span>,<\/span><span>\"generateCsr\"<\/span><span>:<\/span><span>{<\/span>...<span>}<\/span><span>}<\/span>\n<\/span><span><span>{<\/span><span>\"sslProfileId\"<\/span><span>:<\/span><span>\"lab-profile-1\"<\/span><span>,<\/span><span>\"certificates\"<\/span><span>:<\/span><span>{<\/span>...<span>}<\/span><span>}<\/span>\n<\/span><span><span>{<\/span><span>\"sslProfileId\"<\/span><span>:<\/span><span>\"lab-profile-1\"<\/span><span>,<\/span><span>\"finalizeRotation\"<\/span><span>:<\/span><span>{<\/span><span>}<\/span><span>}<\/span>\n<\/span><\/code><\/pre>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Streaming with grpcurl<\/h3>\n<p>grpcurl accepts multiple JSON objects on stdin \u2014 one <code>RotateCertificateRequest<\/code> per line \u2014 for Rotate:<\/p>\n<pre><code><span>grpcurl \u2026 <span>-d<\/span> <span>'@'<\/span> \u2026 gnsi.certz.v1.Certz.Rotate <span>&lt;&lt;<\/span><span>'EOF'\n<\/span><\/span><span><span>{... first request...}\n<\/span><\/span><span><span>{... second request...}\n<\/span><\/span><span><span>EOF<\/span>\n<\/span><\/code><\/pre>\n<p>Use <code>-d '@'<\/code> with single quotes so grpcurl reads from stdin.<\/p>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">What <code>force_overwrite<\/code> actually does<\/h3>\n<p>The proto comment on <code>force_overwrite<\/code> says reusing a <code>version<\/code> without it returns <code>AlreadyExists<\/code>. On IOS XR 24.4.x the same condition surfaces as:<\/p>\n<pre><code><span>ERROR:\n<\/span><span> Code: InvalidArgument\n<\/span><span> Message: certificate: same version sent\n<\/span><\/code><\/pre>\n<p>Setting <code>\"forceOverwrite\": true<\/code> on every non-finalize message lets the same <code>version<\/code> be replayed without changing the value \u2014 handy when retrying after a transient error during a lab walk-through.<\/p>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">IOS XR Support<\/h2>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Release<\/th>\n<th>What changed<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong style=\"color: rgb(26, 26, 26)\">7.8.1<\/strong><\/td>\n<td>Certz SSL Profile infrastructure introduced.<\/td>\n<\/tr>\n<tr>\n<td><strong style=\"color: rgb(26, 26, 26)\">24.1.1<\/strong><\/td>\n<td>Rotate RPC, <code>grpc gnsi service certz ssl-profile-id<\/code> CLI, configurable CN for dial-in.<\/td>\n<\/tr>\n<tr>\n<td><strong style=\"color: rgb(26, 26, 26)\">24.4.1<\/strong><\/td>\n<td>TLS 1.3 support.<\/td>\n<\/tr>\n<tr>\n<td><strong style=\"color: rgb(26, 26, 26)\">25.4.1<\/strong><\/td>\n<td>Legacy <code>\/misc\/config\/grpc<\/code> PEM paths deprecated \u2014 SSL Profiles are the path forward.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Platforms: Cisco 8000, NCS 5500, ASR 9000 families. Confirm exact SKU and PI\/SMU in Release Notes.<\/p>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">IOS XR CLI Reference<\/h2>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Configuration command<\/th>\n<th>What it does<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>grpc gnsi service certz ssl-profile-id &lt;name&gt;<\/code><\/td>\n<td>Bind the gRPC server to a Certz SSL Profile<\/td>\n<\/tr>\n<tr>\n<td><code>no grpc gnsi service certz ssl-profile-id<\/code><\/td>\n<td>Unbind Certz; fall back to gNOI <code>cert.proto<\/code> or legacy trustpoint PEM paths<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Show \/ exec command<\/th>\n<th>What it shows<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>show grpc certificate<\/code><\/td>\n<td>Active Certificate subject, issuer, validity dates, and chain<\/td>\n<\/tr>\n<tr>\n<td><code>show gnsi state<\/code><\/td>\n<td>gNSI subsystem status (Certz, Acctz threads, RDSFS state)<\/td>\n<\/tr>\n<tr>\n<td><code>show tech-support gnsi<\/code><\/td>\n<td>Full gNSI diagnostic bundle (all services)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Hands-on Setup<\/h2>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Prerequisites<\/h3>\n<ul>\n<li>IOS XR router with gRPC enabled (24.1.1 or later for Certz)<\/li>\n<li><a href=\"https:\/\/github.com\/fullstorydev\/grpcurl\" style=\"color: rgb(0, 102, 204)\">grpcurl<\/a>, <a href=\"https:\/\/jqlang.github.io\/jq\/\" style=\"color: rgb(0, 102, 204)\">jq<\/a>, and <a href=\"https:\/\/www.openssl.org\/\" style=\"color: rgb(0, 102, 204)\">OpenSSL<\/a> installed<\/li>\n<li>OpenConfig gNSI protos cloned and laid out so that <code>$GNSI_IMPORT\/certz\/certz.proto<\/code> resolves:<\/li>\n<\/ul>\n<pre><code><span><span>git<\/span> clone https:\/\/github.com\/openconfig\/gnsi.git \/tmp\/gnsi\n<\/span><span><span>mkdir<\/span> <span>-p<\/span>.\/certz-import\/certz\n<\/span><span><span>cp<\/span> \/tmp\/gnsi\/certz\/certz.proto.\/certz-import\/certz\/certz.proto\n<\/span><span><span>export<\/span> <span>GNSI_IMPORT<\/span><span>=<\/span><span>\"<span>$PWD<\/span>\/certz-import\"<\/span>\n<\/span><\/code><\/pre>\n<p><code>certz.proto<\/code> only imports <code>google\/protobuf\/any.proto<\/code>, which <code>grpcurl<\/code> ships internally \u2014 no other files need to be staged.<\/p>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Environment<\/h3>\n<pre><code><span><span>export<\/span> <span>GNSI_IMPORT<\/span><span>=<\/span><span>\"<span>${GNSI_IMPORT<span>:-<\/span>$PWD<span>\/<\/span>certz-import}<\/span>\"<\/span>\n<\/span><span><span>export<\/span> <span>XR<\/span><span>=<\/span><span>\"<span>${XR<span>:-<\/span>198.51.100.1<span>:<\/span>57400}<\/span>\"<\/span>\n<\/span><span><span>export<\/span> <span>XR_USER<\/span><span>=<\/span><span>\"<span>${XR_USER<span>:-<\/span>cisco}<\/span>\"<\/span>\n<\/span><span><span>export<\/span> <span>XR_PASS<\/span><span>=<\/span><span>\"<span>${XR_PASS<span>:?<\/span>set XR_PASS for the IOS XR account}<\/span>\"<\/span>\n<\/span><span><span>GRPC_MD<\/span><span>=<\/span><span>(<\/span>-H <span>\"username:<span>${XR_USER}<\/span>\"<\/span> <span>-H<\/span> <span>\"password:<span>${XR_PASS}<\/span>\"<\/span><span>)<\/span>\n<\/span><span><span>PROTO_CERTZ<\/span><span>=<\/span><span>\"certz\/certz.proto\"<\/span>\n<\/span><\/code><\/pre>\n<p><code>${XR_PASS:?\u2026}<\/code> aborts the shell with a clear error if <code>XR_PASS<\/code> is not exported, so the reader doesn&#8217;t accidentally send an empty password to the device.<\/p>\n<blockquote>\n<p><strong style=\"color: rgb(26, 26, 26)\">Lab only:<\/strong> The examples below use <code>-plaintext<\/code> because they target a gRPC server that has no Certz Profile bound yet. Once a Profile is active and bound to the gRPC server (<code>grpc gnsi service certz ssl-profile-id &lt;name&gt;<\/code>), swap <code>-plaintext<\/code> for <code>-cacert<\/code>, and add <code>-cert<\/code>\/<code>-key<\/code> if mutual TLS is enabled.<\/p>\n<\/blockquote>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">What this hands-on covers (and what it does not)<\/h3>\n<p>Each row below maps a section of this hands-on to whether it completes with <code>grpcurl<\/code> alone, or needs a richer client \/ out-of-band step:<\/p>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Section<\/th>\n<th>Works end-to-end with grpcurl?<\/th>\n<th>Notes<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Reflection list<\/td>\n<td>Yes<\/td>\n<td>All six RPCs returned<\/td>\n<\/tr>\n<tr>\n<td><code>GetProfileList<\/code>, <code>AddProfile<\/code>, <code>DeleteProfile<\/code><\/td>\n<td>Yes<\/td>\n<td>Empty \/ populated responses both shown<\/td>\n<\/tr>\n<tr>\n<td><code>CanGenerateCSR<\/code><\/td>\n<td>Yes<\/td>\n<td>Success and <code>missing CN<\/code> failure both shown<\/td>\n<\/tr>\n<tr>\n<td><code>GetIntegrityManifest<\/code><\/td>\n<td>No<\/td>\n<td>Server returns <code>Unimplemented<\/code> on IOS XR 24.4.x \u2014 documented<\/td>\n<\/tr>\n<tr>\n<td>Lab CA + signed leaf (OpenSSL)<\/td>\n<td>Yes<\/td>\n<td>Two-tier CA, leaf signed locally<\/td>\n<\/tr>\n<tr>\n<td><strong style=\"color: rgb(26, 26, 26)\">Flow B<\/strong> \u2014 Client-generated key + nested chain <code>Rotate<\/code><\/td>\n<td><strong style=\"color: rgb(26, 26, 26)\">Yes<\/strong><\/td>\n<td>Full chain + finalize, plus optional Trust Bundle entity<\/td>\n<\/tr>\n<tr>\n<td><strong style=\"color: rgb(26, 26, 26)\">Flow A<\/strong> \u2014 Target-generated CSR<\/td>\n<td><strong style=\"color: rgb(26, 26, 26)\">CSR step yes, full Rotate no<\/strong><\/td>\n<td>Demonstrates a documented <code>grpcurl<\/code> limitation; production use needs a Python\/Go client<\/td>\n<\/tr>\n<tr>\n<td>Rollback (close stream pre-finalize)<\/td>\n<td>Yes<\/td>\n<td><code>Code: Unknown \/ EOF<\/code> is the rollback signal<\/td>\n<\/tr>\n<tr>\n<td><code>forceOverwrite<\/code> semantics<\/td>\n<td>Yes<\/td>\n<td>Same <code>version<\/code> retried cleanly<\/td>\n<\/tr>\n<tr>\n<td>gNMI verification (post-binding)<\/td>\n<td>Partial<\/td>\n<td>Authz counters always populated; Certz state subtree only after CLI binding<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">PEM, Base64, and CSR Vocabulary<\/h3>\n<p>PEM is the text envelope (<code>-----BEGIN \u2026-----<\/code>) around a DER blob. In <code>certz.proto<\/code>, Certificate and CSR fields are <code>bytes<\/code>; grpcurl represents <code>bytes<\/code> as standard Base64 over the PEM file bytes.<\/p>\n<p>Two CSR patterns appear throughout:<\/p>\n<ul>\n<li><strong style=\"color: rgb(26, 26, 26)\">Target-generated CSR<\/strong> \u2014 the router creates the Key Pair and CSR. Sign the CSR with a CA, then upload only the signed Certificate. The Private Key stays on the router.<\/li>\n<li><strong style=\"color: rgb(26, 26, 26)\">Client-generated CSR<\/strong> \u2014 create the Key and CSR on a workstation, sign with a CA, then upload both the signed Certificate and Private Key via <code>UploadRequest<\/code>.<\/li>\n<\/ul>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Hands-on: List the RPCs<\/h2>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">List exported Services (Reflection)<\/h3>\n<pre><code><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> list gnsi.certz.v1.Certz\n<\/span><\/code><\/pre>\n<p>Expected output:<\/p>\n<pre><code><span>gnsi.certz.v1.Certz.AddProfile\n<\/span><span>gnsi.certz.v1.Certz.CanGenerateCSR\n<\/span><span>gnsi.certz.v1.Certz.DeleteProfile\n<\/span><span>gnsi.certz.v1.Certz.GetIntegrityManifest\n<\/span><span>gnsi.certz.v1.Certz.GetProfileList\n<\/span><span>gnsi.certz.v1.Certz.Rotate\n<\/span><\/code><\/pre>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Unary RPCs: Profiles, CSR Checks, and Cleanup<\/h2>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">GetProfileList<\/h3>\n<p>Returns every Profile ID that exists on the device:<\/p>\n<pre><code><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-d<\/span> <span>'{}'<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> gnsi.certz.v1.Certz.GetProfileList\n<\/span><\/code><\/pre>\n<p>When no Profiles have been created yet, the response is an empty object. On some builds this call fails until the <code>ssl_profiles<\/code> tree exists on disk \u2014 if an internal error about a missing path appears, run <code>AddProfile<\/code> first and try again.<\/p>\n<p>Router response (no Profiles yet):<\/p>\n<pre><code><span><span>{<\/span><span>}<\/span>\n<\/span><\/code><\/pre>\n<p>After a few <code>AddProfile<\/code> calls (see below), the same RPC returns the populated list:<\/p>\n<pre><code><span><span>{<\/span>\n<\/span><span> <span>\"sslProfileIds\"<\/span><span>:<\/span> <span>[<\/span>\n<\/span><span> <span>\"lab-profile-1\"<\/span><span>,<\/span>\n<\/span><span> <span>\"lab-temp\"<\/span>\n<\/span><span> <span>]<\/span>\n<\/span><span><span>}<\/span>\n<\/span><\/code><\/pre>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">AddProfile<\/h3>\n<p>Creates an empty Profile shell. Pick any administrator-friendly ID:<\/p>\n<pre><code><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-d<\/span> <span>'{\"sslProfileId\":\"lab-profile-1\"}'<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> gnsi.certz.v1.Certz.AddProfile\n<\/span><\/code><\/pre>\n<p>Router response (success returns an empty JSON object):<\/p>\n<pre><code><span><span>{<\/span><span>}<\/span>\n<\/span><\/code><\/pre>\n<p>Re-running <code>AddProfile<\/code> for the same ID returns:<\/p>\n<pre><code><span>ERROR:\n<\/span><span> Code: InvalidArgument\n<\/span><span> Message: Profile:lab-profile-1 already exists\n<\/span><\/code><\/pre>\n<p>Most lab scripts wrap <code>AddProfile<\/code> with <code>|| true<\/code> so the duplicate error doesn&#8217;t abort the rest of the workflow.<\/p>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">CanGenerateCSR<\/h3>\n<p>Check before relying on the router to mint a CSR inside Rotate. Cheaper than opening a stream only to learn the suite is unsupported. Note: <code>commonName<\/code> is mandatory \u2014 omitting it returns <code>InvalidArgument<\/code>.<\/p>\n<pre><code><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-d<\/span> <span>'{\n<\/span><\/span><span><span> \"params\": {\n<\/span><\/span><span><span> \"csrSuite\": \"CSRSUITE_X509_KEY_TYPE_ECDSA_PRIME256V1_SIGNATURE_ALGORITHM_SHA_2_256\",\n<\/span><\/span><span><span> \"commonName\": \"router.example.com\",\n<\/span><\/span><span><span> \"organization\": \"Lab\",\n<\/span><\/span><span><span> \"country\": \"US\"\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}'<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> gnsi.certz.v1.Certz.CanGenerateCSR\n<\/span><\/code><\/pre>\n<p>Router response:<\/p>\n<pre><code><span><span>{<\/span>\n<\/span><span> <span>\"canGenerate\"<\/span><span>:<\/span> <span>true<\/span>\n<\/span><span><span>}<\/span>\n<\/span><\/code><\/pre>\n<p><code>RSA-2048 \/ SHA-256<\/code> returns the same <code>canGenerate: true<\/code> on IOS XR 24.4.x. Omitting <code>commonName<\/code> returns:<\/p>\n<pre><code><span>ERROR:\n<\/span><span> Code: InvalidArgument\n<\/span><span> Message: CSR parameters does not have common name\n<\/span><\/code><\/pre>\n<p>When <code>canGenerate<\/code> is <code>false<\/code>, use the client-generated path: build the Key and CSR on a workstation and upload through <code>Rotate<\/code>.<\/p>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">GetIntegrityManifest<\/h3>\n<pre><code><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-d<\/span> <span>'{}'<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> gnsi.certz.v1.Certz.GetIntegrityManifest\n<\/span><\/code><\/pre>\n<p>Several IOS XR releases return <code>Unimplemented<\/code> here even though Reflection lists the RPC. On 24.4.x, the exact response is:<\/p>\n<pre><code><span>ERROR:\n<\/span><span> Code: Unimplemented\n<\/span><span> Message: unknown method GetIntegrityManifest for service gnsi.certz.v1.Certz\n<\/span><\/code><\/pre>\n<p>That is a server-side gap, not <code>grpcurl<\/code> misconfiguration.<\/p>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">DeleteProfile<\/h3>\n<p>Removes a Profile that is <strong style=\"color: rgb(26, 26, 26)\">not<\/strong> bound to the active gRPC server:<\/p>\n<pre><code><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-d<\/span> <span>'{\"sslProfileId\":\"lab-temp\"}'<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> gnsi.certz.v1.Certz.DeleteProfile\n<\/span><\/code><\/pre>\n<p>Router response on success:<\/p>\n<pre><code><span><span>{<\/span><span>}<\/span>\n<\/span><\/code><\/pre>\n<p>Common error responses on IOS XR 24.4.x:<\/p>\n<pre><code><span>ERROR:\n<\/span><span> Code: InvalidArgument\n<\/span><span> Message: Profile: nonexistent-xyz does not exist: &lt;nil&gt;\n<\/span><\/code><\/pre>\n<p>The default Profile ID <code>gNxI<\/code> only resolves once Certz has actually been bound to the gRPC server \u2014 until then, deleting it returns the same <code>does not exist<\/code> error rather than the more descriptive &#8220;bound to active server&#8221; message.<\/p>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Hands-on: Rotate End to End<\/h2>\n<p>Continue from the <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/gnsi-certz-tls-certificate-lifecycle-on-cisco-ios-xr#hands-on-setup\" style=\"color: rgb(0, 102, 204)\">Hands-on Setup<\/a> environment above. The sections below walk through real Certificate material. Start with a small lab PKI so CSRs can be signed without involving a production CA, then try both Rotation styles.<\/p>\n<blockquote>\n<p><strong style=\"color: rgb(26, 26, 26)\">Which flow to start with:<\/strong> Most operators generate keys on a workstation and push them through Certz \u2014 that is <strong style=\"color: rgb(26, 26, 26)\">Flow B<\/strong> below. <strong style=\"color: rgb(26, 26, 26)\">Flow A<\/strong> (target-generated CSR) is useful when the Private Key must never leave the router, but <code>grpcurl<\/code> cannot complete the full multi-step stream interactively \u2014 a programmatic client (Python, Go) is needed for production use of that flow.<\/p>\n<\/blockquote>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Rotate Message Flow<\/h3>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Step<\/th>\n<th>Message field (<code>oneof rotate_request<\/code>)<\/th>\n<th>What it does<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>1 (optional)<\/td>\n<td><strong style=\"color: rgb(26, 26, 26)\"><code>generate_csr<\/code><\/strong><\/td>\n<td>Target generates <strong style=\"color: rgb(26, 26, 26)\">Key + CSR<\/strong>; PEM CSR returns on the response stream<\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td><strong style=\"color: rgb(26, 26, 26)\"><code>certificates<\/code><\/strong><\/td>\n<td><strong style=\"color: rgb(26, 26, 26)\"><code>UploadRequest<\/code><\/strong>: one or more <strong style=\"color: rgb(26, 26, 26)\"><code>Entity<\/code><\/strong> values (leaf Certificate, Trust Bundle, CRL, \u2026)<\/td>\n<\/tr>\n<tr>\n<td>last<\/td>\n<td><strong style=\"color: rgb(26, 26, 26)\"><code>finalize_rotation<\/code><\/strong><\/td>\n<td>Commit. Omitting \/ aborting triggers Rollback<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Each <code>Entity<\/code> carries:<\/p>\n<ul>\n<li><code>version<\/code> \u2014 opaque string surfaced in Telemetry<\/li>\n<li><code>created_on<\/code> \u2014 Unix epoch seconds<\/li>\n<li>Exactly one arm of the entity <code>oneof<\/code> (<code>certificateChain<\/code>, <code>trustBundle<\/code>, etc.)<\/li>\n<\/ul>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Build a Two-tier Lab CA (one-time OpenSSL)<\/h3>\n<p>Enterprise networks almost never sign router Certificates directly with a long-lived Root. They issue an Intermediate, keep the Root offline, and let the Intermediate sign day-to-day CSRs. This lab mirrors that pattern.<\/p>\n<p><strong style=\"color: rgb(26, 26, 26)\">1 \u2014 Root CA (Trust Anchor, self-signed)<\/strong><\/p>\n<pre><code><span><span>mkdir<\/span> <span>-p<\/span> lab-pki\/root lab-pki\/intermediate\n<\/span><span>openssl genrsa <span>-out<\/span> lab-pki\/root\/root.key <span>4096<\/span>\n<\/span><span>openssl req <span>-new<\/span> <span>-x509<\/span> <span>-key<\/span> lab-pki\/root\/root.key <span>-sha256<\/span> <span>-days<\/span> <span>3650<\/span> <span>\\<\/span>\n<\/span><span> <span>-out<\/span> lab-pki\/root\/root.crt <span>\\<\/span>\n<\/span><span> <span>-subj<\/span> <span>\"\/CN=Lab Root CA\/O=Lab\/C=US\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-addext<\/span> <span>\"basicConstraints=critical,CA:TRUE\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-addext<\/span> <span>\"keyUsage=critical,keyCertSign,cRLSign\"<\/span>\n<\/span><\/code><\/pre>\n<p><strong style=\"color: rgb(26, 26, 26)\">2 \u2014 Intermediate CA (signed by Root)<\/strong><\/p>\n<pre><code><span>openssl genrsa <span>-out<\/span> lab-pki\/intermediate\/intermediate.key <span>4096<\/span>\n<\/span><span>openssl req <span>-new<\/span> <span>-key<\/span> lab-pki\/intermediate\/intermediate.key <span>\\<\/span>\n<\/span><span> <span>-out<\/span> lab-pki\/intermediate\/intermediate.csr <span>\\<\/span>\n<\/span><span> <span>-subj<\/span> <span>\"\/CN=Lab Issuing CA\/O=Lab\/C=US\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-addext<\/span> <span>\"basicConstraints=critical,CA:TRUE,pathlen:0\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-addext<\/span> <span>\"keyUsage=critical,keyCertSign,cRLSign\"<\/span>\n<\/span><span>openssl x509 <span>-req<\/span> <span>-in<\/span> lab-pki\/intermediate\/intermediate.csr <span>\\<\/span>\n<\/span><span> <span>-CA<\/span> lab-pki\/root\/root.crt <span>-CAkey<\/span> lab-pki\/root\/root.key <span>-CAcreateserial<\/span> <span>\\<\/span>\n<\/span><span> <span>-out<\/span> lab-pki\/intermediate\/intermediate.crt <span>-days<\/span> <span>1825<\/span> <span>-sha256<\/span> <span>\\<\/span>\n<\/span><span> <span>-copy_extensions<\/span> copy\n<\/span><\/code><\/pre>\n<p><strong style=\"color: rgb(26, 26, 26)\">3 \u2014 Verify the Intermediate chains to the Root<\/strong><\/p>\n<pre><code><span>openssl verify <span>-CAfile<\/span> lab-pki\/root\/root.crt lab-pki\/intermediate\/intermediate.crt\n<\/span><\/code><\/pre>\n<ul>\n<li><code>lab-pki\/root\/root.key<\/code> is as sensitive as any Root signing Key \u2014 treat it accordingly.<\/li>\n<li><code>lab-pki\/intermediate\/intermediate.key<\/code> is the &#8220;online&#8221; signer; still guard it closely.<\/li>\n<li><code>lab-pki\/root\/root.crt<\/code> is the Trust Anchor passed to <code>openssl verify<\/code>.<\/li>\n<\/ul>\n<hr>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Flow A \u2014 Target-generated CSR (demonstrates a <code>grpcurl<\/code> limitation)<\/h3>\n<blockquote>\n<p><strong style=\"color: rgb(26, 26, 26)\">What this section actually shows:<\/strong> Steps 1-3 work cleanly \u2014 <code>grpcurl<\/code> can drive the router into producing a CSR, and OpenSSL can sign it. Step 5 then fails with a specific, expected error because <code>grpcurl<\/code> cannot keep a Rotate stream open across the external signing pause that this flow needs. The complete walk-through is included so operators considering Flow A see exactly <strong style=\"color: rgb(26, 26, 26)\">what works<\/strong>, <strong style=\"color: rgb(26, 26, 26)\">where it fails<\/strong>, and <strong style=\"color: rgb(26, 26, 26)\">why<\/strong>. Readers who only want a working Rotation should jump to <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/gnsi-certz-tls-certificate-lifecycle-on-cisco-ios-xr#flow-b--client-generated-key-and-csr-full-chain-upload\" style=\"color: rgb(0, 102, 204)\">Flow B \u2014 Client-generated Key and CSR<\/a>.<\/p>\n<\/blockquote>\n<p>The router owns the Private Key. Ask it for a CSR, sign the CSR with the Intermediate CA, upload only the signed leaf (no Private Key in <code>UploadRequest<\/code>), and finalize.<\/p>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 1 \u2014 Ensure Profile exists<\/h4>\n<pre><code><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-d<\/span> <span>'{\"sslProfileId\":\"lab-profile-1\"}'<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> gnsi.certz.v1.Certz.AddProfile <span>||<\/span> <span>true<\/span>\n<\/span><\/code><\/pre>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 2 \u2014 Request CSR and capture PEM<\/h4>\n<blockquote>\n<p><strong style=\"color: rgb(26, 26, 26)\">grpcurl limitation:<\/strong> In a production workflow, Steps 2\u20135 happen inside a single Rotate stream \u2014 the client sends <code>generateCsr<\/code>, receives the CSR, signs it, uploads the signed leaf, and finalizes without closing the connection. <code>grpcurl<\/code> cannot pause mid-stream for external signing, so the stream closes after the CSR is returned. The commands below work around this by running two separate Rotate calls. A programmatic client (Python, Go) handles the full flow in one stream.<\/p>\n<\/blockquote>\n<pre><code><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-d<\/span> <span>'@'<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> gnsi.certz.v1.Certz.Rotate <span>&lt;&lt;<\/span><span>'EOF'\n<\/span><\/span><span><span>{\n<\/span><\/span><span><span> \"sslProfileId\": \"lab-profile-1\",\n<\/span><\/span><span><span> \"forceOverwrite\": true,\n<\/span><\/span><span><span> \"generateCsr\": {\n<\/span><\/span><span><span> \"params\": {\n<\/span><\/span><span><span> \"csrSuite\": \"CSRSUITE_X509_KEY_TYPE_ECDSA_PRIME256V1_SIGNATURE_ALGORITHM_SHA_2_256\",\n<\/span><\/span><span><span> \"commonName\": \"router.example.com\",\n<\/span><\/span><span><span> \"san\": { \"dns\": [\"router.example.com\"]}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>{\"sslProfileId\": \"lab-profile-1\", \"finalizeRotation\": {}}\n<\/span><\/span><span><span>EOF<\/span>\n<\/span><\/code><\/pre>\n<p>Router response \u2014 the CSR comes back as Base64-encoded PEM inside <code>generatedCsr<\/code>. Copy the <code>certificateSigningRequest<\/code> value into <code>CSR_B64<\/code>:<\/p>\n<pre><code><span><span>{<\/span>\n<\/span><span> <span>\"generatedCsr\"<\/span><span>:<\/span> <span>{<\/span>\n<\/span><span> <span>\"certificateSigningRequest\"<\/span><span>:<\/span> <span>{<\/span>\n<\/span><span> <span>\"type\"<\/span><span>:<\/span> <span>\"CERTIFICATE_TYPE_X509\"<\/span><span>,<\/span>\n<\/span><span> <span>\"encoding\"<\/span><span>:<\/span> <span>\"CERTIFICATE_ENCODING_PEM\"<\/span><span>,<\/span>\n<\/span><span> <span>\"certificateSigningRequest\"<\/span><span>:<\/span> <span>\"LS0tLS1CRUdJTi...\"<\/span>\n<\/span><span> <span>}<\/span>\n<\/span><span> <span>}<\/span>\n<\/span><span><span>}<\/span>\n<\/span><\/code><\/pre>\n<p>Decode and inspect:<\/p>\n<pre><code><span><span>printf<\/span> <span>'%s'<\/span> <span>\"<span>$CSR_B64<\/span>\"<\/span> <span>|<\/span> base64 <span>-d<\/span> <span>&gt;<\/span> router.csr.pem\n<\/span><span>openssl req <span>-in<\/span> router.csr.pem <span>-noout<\/span> <span>-text<\/span>\n<\/span><\/code><\/pre>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 3 \u2014 Sign the CSR with the Intermediate CA<\/h4>\n<pre><code><span>openssl x509 <span>-req<\/span> <span>-in<\/span> router.csr.pem <span>\\<\/span>\n<\/span><span> <span>-CA<\/span> lab-pki\/intermediate\/intermediate.crt <span>\\<\/span>\n<\/span><span> <span>-CAkey<\/span> lab-pki\/intermediate\/intermediate.key <span>\\<\/span>\n<\/span><span> <span>-CAcreateserial<\/span> <span>\\<\/span>\n<\/span><span> <span>-out<\/span> router.crt <span>-days<\/span> <span>825<\/span> <span>-sha256<\/span>\n<\/span><\/code><\/pre>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 4 \u2014 Base64 the signed leaf PEM<\/h4>\n<pre><code><span><span>LEAF_B64<\/span><span>=<\/span><span><span>$(<\/span>base64 <span>&lt;<\/span> router.crt <span>|<\/span> <span>tr<\/span> <span>-d<\/span> <span>'\\n'<\/span><span>)<\/span><\/span>\n<\/span><\/code><\/pre>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 5 \u2014 Rotate: upload the signed leaf, then finalize<\/h4>\n<p>In a single-stream client, the leaf upload would land on the same connection that just produced the CSR \u2014 the router still has the candidate Key in memory and accepts a leaf-only <code>certificateChain<\/code>. Because <code>grpcurl<\/code> had to close the stream after Step 2, the candidate Key was discarded along with the rolled-back transaction. Re-uploading the signed leaf in a second <code>grpcurl<\/code> invocation therefore fails:<\/p>\n<pre><code><span><span>VER<\/span><span>=<\/span><span>\"lab-rotate-<span><span>$(<\/span><span>date<\/span> +%s<span>)<\/span><\/span>\"<\/span>\n<\/span><span><span>CREATED<\/span><span>=<\/span><span>\"<span><span>$(<\/span><span>date<\/span> +%s<span>)<\/span><\/span>\"<\/span>\n<\/span><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-d<\/span> <span>'@'<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> gnsi.certz.v1.Certz.Rotate <span>&lt;&lt;<\/span><span>EOF\n<\/span><\/span><span><span>{\n<\/span><\/span><span><span> \"sslProfileId\": \"lab-profile-1\",\n<\/span><\/span><span><span> \"forceOverwrite\": true,\n<\/span><\/span><span><span> \"certificates\": {\n<\/span><\/span><span><span> \"entities\": [{\n<\/span><\/span><span><span> \"version\": \"<span>${VER}<\/span>\",\n<\/span><\/span><span><span> \"createdOn\": <span>${CREATED}<\/span>,\n<\/span><\/span><span><span> \"certificateChain\": {\n<\/span><\/span><span><span> \"certificate\": {\n<\/span><\/span><span><span> \"type\": \"CERTIFICATE_TYPE_X509\",\n<\/span><\/span><span><span> \"encoding\": \"CERTIFICATE_ENCODING_PEM\",\n<\/span><\/span><span><span> \"rawCertificate\": \"<span>${LEAF_B64}<\/span>\"\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}]\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>{\"sslProfileId\": \"lab-profile-1\", \"finalizeRotation\": {}}\n<\/span><\/span><span><span>EOF<\/span>\n<\/span><\/code><\/pre>\n<p>Router response on IOS XR 24.4.x:<\/p>\n<pre><code><span>ERROR:\n<\/span><span> Code: InvalidArgument\n<\/span><span> Message: No private key found for leaf cert\n<\/span><\/code><\/pre>\n<p>Adding <code>\"keySource\": \"KEY_SOURCE_GENERATED\"<\/code> to the leaf <code>certificate<\/code> does <strong style=\"color: rgb(26, 26, 26)\">not<\/strong> rescue the flow \u2014 the candidate Key is gone, so the router still rejects the upload with the same error. This is the practical reason most operators run the <strong style=\"color: rgb(26, 26, 26)\">client-generated<\/strong> flow (Flow B below) when working through <code>grpcurl<\/code>. Flow A only completes end-to-end from a programmatic client (Python, Go) that keeps a single <code>Rotate<\/code> stream open across the CSR \/ sign \/ upload \/ finalize steps.<\/p>\n<p>For the Trust Bundle pattern (mutual TLS), see <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/gnsi-certz-tls-certificate-lifecycle-on-cisco-ios-xr#step-4b--optional-add-a-trust-bundle-entity\" style=\"color: rgb(0, 102, 204)\">Step 4b in Flow B<\/a> \u2014 the same nested layout works end-to-end from <code>grpcurl<\/code>.<\/p>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 6 \u2014 Where to go from here<\/h4>\n<ul>\n<li>For a working <code>grpcurl<\/code> walk-through, switch to <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/gnsi-certz-tls-certificate-lifecycle-on-cisco-ios-xr#flow-b--client-generated-key-and-csr-full-chain-upload\" style=\"color: rgb(0, 102, 204)\">Flow B<\/a> below.<\/li>\n<li>For a production Flow A implementation, use a programmatic client. The OpenConfig gNSI repo includes a <a href=\"https:\/\/github.com\/openconfig\/gnsi\/tree\/main\/certz\/client\" style=\"color: rgb(0, 102, 204)\">Go reference client<\/a> that keeps the Rotate stream open across CSR retrieval, external signing, leaf upload, and finalize.<\/li>\n<\/ul>\n<hr>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Flow B \u2014 Client-generated Key and CSR (full chain upload)<\/h3>\n<p>This flow matches what many operators already do with OpenSSL: create <code>server.key<\/code>, build a CSR, sign it with the Intermediate CA, then push leaf, Key, and issuer chain through Certz in one nested <code>certificateChain<\/code>. After finalize, bind the Profile with IOS XR CLI and confirm with <code>show grpc certificate<\/code>.<\/p>\n<p>In <code>certz.proto<\/code>, <code>certificateChain.certificate<\/code> holds the leaf PEM plus <code>rawPrivateKey<\/code> for the server Authentication material. Each issuer sits under <code>parent<\/code>, all the way to the Root, which omits <code>parent<\/code>. Optional <code>trustBundle<\/code> Entities reuse the same nesting for publishing CA material to validate connecting clients (mutual TLS).<\/p>\n<blockquote>\n<p><strong style=\"color: rgb(26, 26, 26)\">Note:<\/strong> In production the Root Key stays offline; only the Intermediate should sign day-to-day leaf Certificates.<\/p>\n<\/blockquote>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 0 \u2014 Set the Profile name<\/h4>\n<p>If the <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/gnsi-certz-tls-certificate-lifecycle-on-cisco-ios-xr#environment\" style=\"color: rgb(0, 102, 204)\">Environment<\/a> block is already exported, add only the Profile name:<\/p>\n<pre><code><span><span>export<\/span> <span>SSL_PROFILE_ID<\/span><span>=<\/span><span>\"<span>${SSL_PROFILE_ID<span>:-<\/span>lab-profile-1}<\/span>\"<\/span>\n<\/span><\/code><\/pre>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 1 \u2014 Create or reuse the SSL Profile<\/h4>\n<pre><code><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-d<\/span> <span>\"{<span title=\"\\&quot;\">\\\"<\/span>sslProfileId<span title=\"\\&quot;\">\\\"<\/span>:<span title=\"\\&quot;\">\\\"<\/span><span>${SSL_PROFILE_ID}<\/span><span title=\"\\&quot;\">\\\"<\/span>}\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> gnsi.certz.v1.Certz.AddProfile <span>||<\/span> <span>true<\/span>\n<\/span><\/code><\/pre>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 2 \u2014 Key, CSR, and signed leaf on the workstation<\/h4>\n<p>Finish the lab PKI section first so <code>lab-pki\/intermediate\/<\/code> and <code>lab-pki\/root\/<\/code> exist:<\/p>\n<pre><code><span>openssl genrsa <span>-out<\/span> server.key <span>2048<\/span>\n<\/span><span>openssl req <span>-new<\/span> <span>-key<\/span> server.key <span>-out<\/span> server.csr <span>\\<\/span>\n<\/span><span> <span>-subj<\/span> <span>\"\/CN=router.example.com\/O=Lab\/C=US\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-addext<\/span> <span>\"subjectAltName=DNS:router.example.com\"<\/span>\n<\/span><span>openssl x509 <span>-req<\/span> <span>-in<\/span> server.csr <span>\\<\/span>\n<\/span><span> <span>-CA<\/span> lab-pki\/intermediate\/intermediate.crt <span>\\<\/span>\n<\/span><span> <span>-CAkey<\/span> lab-pki\/intermediate\/intermediate.key <span>\\<\/span>\n<\/span><span> <span>-CAcreateserial<\/span> <span>\\<\/span>\n<\/span><span> <span>-out<\/span> server.crt <span>-days<\/span> <span>825<\/span> <span>-sha256<\/span> <span>-copy_extensions<\/span> copy\n<\/span><\/code><\/pre>\n<p>Verify the leaf against the chain:<\/p>\n<pre><code><span>openssl verify <span>-CAfile<\/span> lab-pki\/root\/root.crt <span>\\<\/span>\n<\/span><span> <span>-untrusted<\/span> lab-pki\/intermediate\/intermediate.crt <span>\\<\/span>\n<\/span><span> server.crt\n<\/span><\/code><\/pre>\n<p>If <code>-copy_extensions copy<\/code> is unavailable on an older OpenSSL build, use <code>-extfile<\/code> with a SAN snippet.<\/p>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 3 \u2014 Base64 the PEM files for JSON<\/h4>\n<pre><code><span><span>CERT_B64<\/span><span>=<\/span><span><span>$(<\/span>base64 <span>&lt;<\/span> server.crt <span>|<\/span> <span>tr<\/span> <span>-d<\/span> <span>'\\n'<\/span><span>)<\/span><\/span>\n<\/span><span><span>KEY_B64<\/span><span>=<\/span><span><span>$(<\/span>base64 <span>&lt;<\/span> server.key <span>|<\/span> <span>tr<\/span> <span>-d<\/span> <span>'\\n'<\/span><span>)<\/span><\/span>\n<\/span><span><span>INT_B64<\/span><span>=<\/span><span><span>$(<\/span>base64 <span>&lt;<\/span> lab-pki\/intermediate\/intermediate.crt <span>|<\/span> <span>tr<\/span> <span>-d<\/span> <span>'\\n'<\/span><span>)<\/span><\/span>\n<\/span><span><span>ROOT_B64<\/span><span>=<\/span><span><span>$(<\/span>base64 <span>&lt;<\/span> lab-pki\/root\/root.crt <span>|<\/span> <span>tr<\/span> <span>-d<\/span> <span>'\\n'<\/span><span>)<\/span><\/span>\n<\/span><span><span>VER<\/span><span>=<\/span><span>\"lab-chain-<span><span>$(<\/span><span>date<\/span> +%s<span>)<\/span><\/span>\"<\/span>\n<\/span><span><span>CREATED<\/span><span>=<\/span><span>\"<span><span>$(<\/span><span>date<\/span> +%s<span>)<\/span><\/span>\"<\/span>\n<\/span><span><span>VER_TRUST<\/span><span>=<\/span><span>\"<span>${VER}<\/span>-trust\"<\/span>\n<\/span><\/code><\/pre>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 4 \u2014 Rotate: upload the nested chain, then finalize<\/h4>\n<p>Two JSON objects through grpcurl with <code>-d '@'<\/code>: the first carries <code>certificates<\/code> with a nested <code>certificateChain<\/code>, the second finalizes. Repeat <code>sslProfileId<\/code> on both objects for IOS XR builds that enforce it.<\/p>\n<p>The Intermediate is the first <code>parent<\/code> under the leaf; the Root is the <code>parent<\/code> of that Intermediate. Only the leaf object includes <code>rawPrivateKey<\/code>.<\/p>\n<pre><code><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-format<\/span> json <span>-d<\/span> <span>'@'<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> gnsi.certz.v1.Certz.Rotate <span>&lt;&lt;<\/span><span>EOF\n<\/span><\/span><span><span>{\n<\/span><\/span><span><span> \"sslProfileId\": \"<span>${SSL_PROFILE_ID}<\/span>\",\n<\/span><\/span><span><span> \"forceOverwrite\": true,\n<\/span><\/span><span><span> \"certificates\": {\n<\/span><\/span><span><span> \"entities\": [{\n<\/span><\/span><span><span> \"version\": \"<span>${VER}<\/span>\",\n<\/span><\/span><span><span> \"createdOn\": <span>${CREATED}<\/span>,\n<\/span><\/span><span><span> \"certificateChain\": {\n<\/span><\/span><span><span> \"certificate\": {\n<\/span><\/span><span><span> \"type\": \"CERTIFICATE_TYPE_X509\",\n<\/span><\/span><span><span> \"encoding\": \"CERTIFICATE_ENCODING_PEM\",\n<\/span><\/span><span><span> \"rawCertificate\": \"<span>${CERT_B64}<\/span>\",\n<\/span><\/span><span><span> \"rawPrivateKey\": \"<span>${KEY_B64}<\/span>\"\n<\/span><\/span><span><span>},\n<\/span><\/span><span><span> \"parent\": {\n<\/span><\/span><span><span> \"certificate\": {\n<\/span><\/span><span><span> \"type\": \"CERTIFICATE_TYPE_X509\",\n<\/span><\/span><span><span> \"encoding\": \"CERTIFICATE_ENCODING_PEM\",\n<\/span><\/span><span><span> \"rawCertificate\": \"<span>${INT_B64}<\/span>\"\n<\/span><\/span><span><span>},\n<\/span><\/span><span><span> \"parent\": {\n<\/span><\/span><span><span> \"certificate\": {\n<\/span><\/span><span><span> \"type\": \"CERTIFICATE_TYPE_X509\",\n<\/span><\/span><span><span> \"encoding\": \"CERTIFICATE_ENCODING_PEM\",\n<\/span><\/span><span><span> \"rawCertificate\": \"<span>${ROOT_B64}<\/span>\"\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}]\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>{\"sslProfileId\": \"<span>${SSL_PROFILE_ID}<\/span>\", \"finalizeRotation\": {}}\n<\/span><\/span><span><span>EOF<\/span>\n<\/span><\/code><\/pre>\n<p>Router response:<\/p>\n<pre><code><span><span>{<\/span>\n<\/span><span> <span>\"certificates\"<\/span><span>:<\/span> <span>{<\/span><span>}<\/span>\n<\/span><span><span>}<\/span>\n<\/span><span><span>{<\/span><span>}<\/span>\n<\/span><\/code><\/pre>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 4b \u2014 Optional: add a Trust Bundle Entity<\/h4>\n<p>Add this when the Profile must also publish CA material for validating connecting clients. <code>VER_TRUST<\/code> was prepared in Step 3. The <code>entities<\/code> array becomes two elements:<\/p>\n<pre><code><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-format<\/span> json <span>-d<\/span> <span>'@'<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> gnsi.certz.v1.Certz.Rotate <span>&lt;&lt;<\/span><span>EOF\n<\/span><\/span><span><span>{\n<\/span><\/span><span><span> \"sslProfileId\": \"<span>${SSL_PROFILE_ID}<\/span>\",\n<\/span><\/span><span><span> \"forceOverwrite\": true,\n<\/span><\/span><span><span> \"certificates\": {\n<\/span><\/span><span><span> \"entities\": [\n<\/span><\/span><span><span> {\n<\/span><\/span><span><span> \"version\": \"<span>${VER}<\/span>\",\n<\/span><\/span><span><span> \"createdOn\": <span>${CREATED}<\/span>,\n<\/span><\/span><span><span> \"certificateChain\": {\n<\/span><\/span><span><span> \"certificate\": {\n<\/span><\/span><span><span> \"type\": \"CERTIFICATE_TYPE_X509\",\n<\/span><\/span><span><span> \"encoding\": \"CERTIFICATE_ENCODING_PEM\",\n<\/span><\/span><span><span> \"rawCertificate\": \"<span>${CERT_B64}<\/span>\",\n<\/span><\/span><span><span> \"rawPrivateKey\": \"<span>${KEY_B64}<\/span>\"\n<\/span><\/span><span><span>},\n<\/span><\/span><span><span> \"parent\": {\n<\/span><\/span><span><span> \"certificate\": {\n<\/span><\/span><span><span> \"type\": \"CERTIFICATE_TYPE_X509\",\n<\/span><\/span><span><span> \"encoding\": \"CERTIFICATE_ENCODING_PEM\",\n<\/span><\/span><span><span> \"rawCertificate\": \"<span>${INT_B64}<\/span>\"\n<\/span><\/span><span><span>},\n<\/span><\/span><span><span> \"parent\": {\n<\/span><\/span><span><span> \"certificate\": {\n<\/span><\/span><span><span> \"type\": \"CERTIFICATE_TYPE_X509\",\n<\/span><\/span><span><span> \"encoding\": \"CERTIFICATE_ENCODING_PEM\",\n<\/span><\/span><span><span> \"rawCertificate\": \"<span>${ROOT_B64}<\/span>\"\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>},\n<\/span><\/span><span><span> {\n<\/span><\/span><span><span> \"version\": \"<span>${VER_TRUST}<\/span>\",\n<\/span><\/span><span><span> \"createdOn\": <span>${CREATED}<\/span>,\n<\/span><\/span><span><span> \"trustBundle\": {\n<\/span><\/span><span><span> \"certificate\": {\n<\/span><\/span><span><span> \"type\": \"CERTIFICATE_TYPE_X509\",\n<\/span><\/span><span><span> \"encoding\": \"CERTIFICATE_ENCODING_PEM\",\n<\/span><\/span><span><span> \"rawCertificate\": \"<span>${INT_B64}<\/span>\"\n<\/span><\/span><span><span>},\n<\/span><\/span><span><span> \"parent\": {\n<\/span><\/span><span><span> \"certificate\": {\n<\/span><\/span><span><span> \"type\": \"CERTIFICATE_TYPE_X509\",\n<\/span><\/span><span><span> \"encoding\": \"CERTIFICATE_ENCODING_PEM\",\n<\/span><\/span><span><span> \"rawCertificate\": \"<span>${ROOT_B64}<\/span>\"\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>]\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>}\n<\/span><\/span><span><span>{\"sslProfileId\": \"<span>${SSL_PROFILE_ID}<\/span>\", \"finalizeRotation\": {}}\n<\/span><\/span><span><span>EOF<\/span>\n<\/span><\/code><\/pre>\n<p>Router response:<\/p>\n<pre><code><span><span>{<\/span>\n<\/span><span> <span>\"certificates\"<\/span><span>:<\/span> <span>{<\/span><span>}<\/span>\n<\/span><span><span>}<\/span>\n<\/span><span><span>{<\/span><span>}<\/span>\n<\/span><\/code><\/pre>\n<p>If nesting errors persist, run <code>grpcurl -import-path \"$GNSI_IMPORT\" -proto \"$PROTO_CERTZ\" -msg-template describe gnsi.certz.v1.CertificateChain<\/code> and mirror the field names. The template prints the proto&#8217;s deprecated names (<code>certificate<\/code>, <code>privateKey<\/code>); the wire-compatible names used above (<code>rawCertificate<\/code>, <code>rawPrivateKey<\/code>) belong to the modern oneof variants and are what every recent IOS XR build accepts.<\/p>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 5 \u2014 Activate the Profile (IOS XR CLI)<\/h4>\n<pre><code><span>configure\n<\/span><span> grpc gnsi service certz ssl-profile-id lab-profile-1\n<\/span><span> commit\n<\/span><span>end\n<\/span><\/code><\/pre>\n<p>Swap <code>lab-profile-1<\/code> for <code>${SSL_PROFILE_ID}<\/code> if a different name was exported.<\/p>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 6 \u2014 Verify chain and Authentication material<\/h4>\n<pre><code><span>show grpc certificate\n<\/span><\/code><\/pre>\n<p>Confirm subject, issuer, validity dates, and that the chain shows the Intermediate as the leaf issuer and the Root at the Trust Anchor. Representative output:<\/p>\n<pre><code><span>RP\/0\/RP0\/CPU0:router# show grpc certificate\n<\/span><span>Tue Apr 22 18:40:11.218 UTC\n<\/span><span>SSL Profile: lab-profile-1\n<\/span><span> Certificate:\n<\/span><span> Subject: CN=router.example.com, O=Lab, C=US\n<\/span><span> Issuer: CN=Lab Issuing CA, O=Lab, C=US\n<\/span><span> Validity:\n<\/span><span> Not Before: Apr 22 18:32:01 2026 GMT\n<\/span><span> Not After: Jul 26 18:32:01 2028 GMT\n<\/span><span> CA chain:\n<\/span><span> Subject: CN=Lab Issuing CA, O=Lab, C=US\n<\/span><span> Issuer: CN=Lab Root CA, O=Lab, C=US\n<\/span><span> Subject: CN=Lab Root CA, O=Lab, C=US\n<\/span><span> Issuer: CN=Lab Root CA, O=Lab, C=US\n<\/span><\/code><\/pre>\n<p>Once the Profile is bound, the gNMI state subtree also populates:<\/p>\n<pre><code><span>gnmic <span>-a<\/span> <span>\"<span>${XR}<\/span>\"<\/span> <span>--insecure<\/span> <span>-u<\/span> <span>\"<span>${XR_USER}<\/span>\"<\/span> <span>-p<\/span> <span>\"<span>${XR_PASS}<\/span>\"<\/span> <span>-e<\/span> json_ietf <span>\\<\/span>\n<\/span><span> get <span>--path<\/span> <span>\"\/system\/grpc-servers\/grpc-server[name=DEFAULT]\/state\/ssl-profile-id\"<\/span> <span>\\<\/span>\n<\/span><span> <span>--path<\/span> <span>\"\/system\/grpc-servers\/grpc-server[name=DEFAULT]\/state\/certificate-version\"<\/span>\n<\/span><\/code><\/pre>\n<p>The reply now returns the bound Profile ID and the <code>version<\/code> string from the most recent successful Rotate.<\/p>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Step 7 \u2014 Optional cleanup with DeleteProfile<\/h4>\n<pre><code><span>grpcurl <span>-plaintext<\/span> -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${GRPC_MD<span>[<\/span>@<span>]<\/span>}<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> <span>-d<\/span> <span>\"{<span title=\"\\&quot;\">\\\"<\/span>sslProfileId<span title=\"\\&quot;\">\\\"<\/span>:<span title=\"\\&quot;\">\\\"<\/span><span>${SSL_PROFILE_ID}<\/span><span title=\"\\&quot;\">\\\"<\/span>}\"<\/span> <span>\\<\/span>\n<\/span><span> <span>\"<span>${XR}<\/span>\"<\/span> gnsi.certz.v1.Certz.DeleteProfile\n<\/span><\/code><\/pre>\n<p>Only run cleanup when the Profile is not bound to the live gRPC server.<\/p>\n<h4 style=\"color: rgb(26, 26, 26); font-weight: 700\">Minimal variant (leaf + Key only) \u2014 not validated in this lab<\/h4>\n<p>If every peer already trusts the issuers out of band, uploading only the leaf and Key inside <code>certificateChain<\/code> with no <code>parent<\/code> nodes is permitted by the proto. This article was validated end-to-end with the nested <code>parent<\/code> layout from Step 4; if the minimal variant is needed in production, confirm the exact behavior on the target IOS XR release before relying on it.<\/p>\n<hr>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">grpcurl Debugging Aids<\/h3>\n<p>The <code>-msg-template<\/code> flag combined with <code>describe<\/code> prints the canonical JSON shape for any message \u2014 handy when chasing nesting errors:<\/p>\n<pre><code><span>grpcurl -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> -msg-template describe gnsi.certz.v1.RotateCertificateRequest\n<\/span><span>grpcurl -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> -msg-template describe gnsi.certz.v1.UploadRequest\n<\/span><span>grpcurl -import-path <span>\"<span>$GNSI_IMPORT<\/span>\"<\/span> <span>-proto<\/span> <span>\"<span>$PROTO_CERTZ<\/span>\"<\/span> <span>\\<\/span>\n<\/span><span> -msg-template describe gnsi.certz.v1.CertificateChain\n<\/span><\/code><\/pre>\n<p>The template uses the proto&#8217;s deprecated field names (<code>certificate<\/code>, <code>privateKey<\/code>); the modern oneof arms <code>rawCertificate<\/code> and <code>rawPrivateKey<\/code> are what live IOS XR builds accept on the wire \u2014 that is the form used throughout this article.<\/p>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Pointing gRPC at a Certz Profile (IOS XR CLI)<\/h2>\n<p>Starting in IOS XR 24.1.1, bind the gRPC server to a Certz Profile:<\/p>\n<pre><code><span>grpc gnsi service certz ssl-profile-id &lt;ssl-profile-name&gt;\n<\/span><\/code><\/pre>\n<p>Precedence matters: when Certz is not active, IOS XR falls back to gNOI <code>cert.proto<\/code>; when neither is configured, legacy TLS trustpoint data applies. Full details are in the <a href=\"https:\/\/www.cisco.com\/c\/en\/us\/td\/docs\/iosxr\/cisco8000\/programmability\/b-programmability-configuration-guide-cisco8000\/m-grpc-authentication.html#concept_C66C0E5371BF46E8937AB14F7D25EE11\" style=\"color: rgb(0, 102, 204)\">gRPC Authentication guide \u2014 Certz chapter<\/a>.<\/p>\n<pre><code><span>show grpc certificate\n<\/span><\/code><\/pre>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Monitoring with YANG Telemetry<\/h2>\n<p>Once an SSL Profile is deployed, the OpenConfig YANG model <strong style=\"color: rgb(26, 26, 26)\"><code>openconfig-gnsi-certz<\/code><\/strong> (v0.5.0) exposes Credential metadata and TLS handshake Counters that Telemetry can stream in real time.<\/p>\n<p>All YANG paths below are relative to <code>\/system\/grpc-servers\/grpc-server[name=&lt;server&gt;]<\/code>.<\/p>\n<p><strong style=\"color: rgb(26, 26, 26)\">Credential metadata:<\/strong><\/p>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>YANG path (relative to base)<\/th>\n<th>What it tells<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>...\/state\/certificate-version<\/code><\/td>\n<td>Version string of the active Certificate<\/td>\n<\/tr>\n<tr>\n<td><code>...\/state\/certificate-created-on<\/code><\/td>\n<td>When the active Certificate was installed (nanoseconds since epoch)<\/td>\n<\/tr>\n<tr>\n<td><code>...\/state\/ca-trust-bundle-version<\/code><\/td>\n<td>Version string of the active Trust Bundle<\/td>\n<\/tr>\n<tr>\n<td><code>...\/state\/ca-trust-bundle-created-on<\/code><\/td>\n<td>When the active Trust Bundle was installed<\/td>\n<\/tr>\n<tr>\n<td><code>...\/state\/certificate-revocation-list-bundle-version<\/code><\/td>\n<td>Version string of the active CRL Bundle<\/td>\n<\/tr>\n<tr>\n<td><code>...\/state\/certificate-revocation-list-bundle-created-on<\/code><\/td>\n<td>When the active CRL Bundle was installed<\/td>\n<\/tr>\n<tr>\n<td><code>...\/state\/authentication-policy-version<\/code><\/td>\n<td>Version of the Authentication Policy (mutual TLS)<\/td>\n<\/tr>\n<tr>\n<td><code>...\/state\/authentication-policy-created-on<\/code><\/td>\n<td>When the Authentication Policy was installed<\/td>\n<\/tr>\n<tr>\n<td><code>...\/state\/ssl-profile-id<\/code><\/td>\n<td>Which SSL Profile ID this gRPC server is using<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><strong style=\"color: rgb(26, 26, 26)\">TLS handshake Counters:<\/strong><\/p>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>YANG path (relative to base)<\/th>\n<th>What it tells<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>...\/state\/certz-counters\/access-accepts<\/code><\/td>\n<td>Total successful TLS handshakes<\/td>\n<\/tr>\n<tr>\n<td><code>...\/state\/certz-counters\/last-access-accept<\/code><\/td>\n<td>Timestamp of the most recent successful handshake<\/td>\n<\/tr>\n<tr>\n<td><code>...\/state\/certz-counters\/access-rejects<\/code><\/td>\n<td>Total failed TLS handshakes (client denied)<\/td>\n<\/tr>\n<tr>\n<td><code>...\/state\/certz-counters\/last-access-reject<\/code><\/td>\n<td>Timestamp of the most recent TLS rejection<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Subscribe to <code>access-rejects<\/code> and <code>last-access-reject<\/code> to detect Authentication failures in production. A sudden spike in rejects after a Rotate usually means the new Certificate or Trust Bundle has an issue \u2014 check <code>certificate-version<\/code> and <code>ca-trust-bundle-version<\/code> to correlate with the material that was just pushed.<\/p>\n<p>The <code>*-version<\/code> and <code>*-created-on<\/code> leaves mirror the exact strings passed in each <code>Entity<\/code> during the Rotate RPC. This means Telemetry pipelines can correlate gRPC behavior with the same identifiers used over the wire \u2014 no need to open PEM files on disk.<\/p>\n<h3 style=\"color: rgb(26, 26, 26); font-weight: 700\">Quick Telemetry check with gnmic<\/h3>\n<blockquote>\n<p><strong style=\"color: rgb(26, 26, 26)\">On 25.3.1:<\/strong> the Certz state subtree only populates after the gRPC server is bound to a profile (<code>grpc gnsi service certz ssl-profile-id &lt;name&gt;<\/code>). Until that CLI binding is committed, <code>state\/ssl-profile-id<\/code> and <code>state\/certz-counters<\/code> return <code>NotFound<\/code>, and the active gRPC server keeps using the legacy\/default TLS material even after a successful <code>Rotate<\/code>.<\/p>\n<\/blockquote>\n<p>Verify the active Certificate version and TLS handshake Counters with a gNMI Get (<a href=\"https:\/\/gnmic.openconfig.net\/\" style=\"color: rgb(0, 102, 204)\">gnmic<\/a>):<\/p>\n<pre><code><span>gnmic <span>-a<\/span> <span>\"<span>${XR}<\/span>\"<\/span> <span>--insecure<\/span> <span>-u<\/span> <span>\"<span>${XR_USER}<\/span>\"<\/span> <span>-p<\/span> <span>\"<span>${XR_PASS}<\/span>\"<\/span> <span>-e<\/span> json_ietf <span>\\<\/span>\n<\/span><span> get <span>--path<\/span> <span>\"\/system\/grpc-servers\/grpc-server\/state\/certificate-version\"<\/span> <span>\\<\/span>\n<\/span><span> <span>--path<\/span> <span>\"\/system\/grpc-servers\/grpc-server\/state\/ca-trust-bundle-version\"<\/span> <span>\\<\/span>\n<\/span><span> <span>--path<\/span> <span>\"\/system\/grpc-servers\/grpc-server\/state\/certz-counters\/access-rejects\"<\/span>\n<\/span><\/code><\/pre>\n<p>Per-RPC AAA counters from the <code>openconfig-gnsi-authz<\/code> model are populated regardless and are a quick way to confirm Certz traffic is even reaching the server:<\/p>\n<pre><code><span>gnmic <span>-a<\/span> <span>\"<span>${XR}<\/span>\"<\/span> <span>--insecure<\/span> <span>-u<\/span> <span>\"<span>${XR_USER}<\/span>\"<\/span> <span>-p<\/span> <span>\"<span>${XR_PASS}<\/span>\"<\/span> <span>-e<\/span> json_ietf <span>\\<\/span>\n<\/span><span> get <span>--path<\/span> <span>'\/system\/grpc-servers\/grpc-server[name=DEFAULT]'<\/span>\n<\/span><\/code><\/pre>\n<p>Filter the response with <code>jq<\/code> for just the Certz RPCs:<\/p>\n<pre><code><span>jq <span>'[.[0].updates[0].values.\"system\/grpc-servers\/grpc-server\"\n<\/span><\/span><span><span>.\"openconfig-gnsi-authz:authz-policy-counters\".rpcs.rpc[]\n<\/span><\/span><span><span> | select(.name | startswith(\"\/gnsi.certz.v1.Certz\/\"))]'<\/span>\n<\/span><\/code><\/pre>\n<p>Expected output (numbers reflect the lab walk-through):<\/p>\n<pre><code><span><span>[<\/span>\n<\/span><span> <span>{<\/span> <span>\"name\"<\/span><span>:<\/span> <span>\"\/gnsi.certz.v1.Certz\/AddProfile\"<\/span><span>,<\/span> <span>\"state\"<\/span><span>:<\/span> <span>{<\/span> <span>\"access-accepts\"<\/span><span>:<\/span> <span>\"4\"<\/span><span>,<\/span>... <span>}<\/span> <span>}<\/span><span>,<\/span>\n<\/span><span> <span>{<\/span> <span>\"name\"<\/span><span>:<\/span> <span>\"\/gnsi.certz.v1.Certz\/CanGenerateCSR\"<\/span><span>,<\/span> <span>\"state\"<\/span><span>:<\/span> <span>{<\/span> <span>\"access-accepts\"<\/span><span>:<\/span> <span>\"3\"<\/span><span>,<\/span>... <span>}<\/span> <span>}<\/span><span>,<\/span>\n<\/span><span> <span>{<\/span> <span>\"name\"<\/span><span>:<\/span> <span>\"\/gnsi.certz.v1.Certz\/DeleteProfile\"<\/span><span>,<\/span> <span>\"state\"<\/span><span>:<\/span> <span>{<\/span> <span>\"access-accepts\"<\/span><span>:<\/span> <span>\"3\"<\/span><span>,<\/span>... <span>}<\/span> <span>}<\/span><span>,<\/span>\n<\/span><span> <span>{<\/span> <span>\"name\"<\/span><span>:<\/span> <span>\"\/gnsi.certz.v1.Certz\/GetProfileList\"<\/span><span>,<\/span> <span>\"state\"<\/span><span>:<\/span> <span>{<\/span> <span>\"access-accepts\"<\/span><span>:<\/span> <span>\"4\"<\/span><span>,<\/span>... <span>}<\/span> <span>}<\/span><span>,<\/span>\n<\/span><span> <span>{<\/span> <span>\"name\"<\/span><span>:<\/span> <span>\"\/gnsi.certz.v1.Certz\/Rotate\"<\/span><span>,<\/span> <span>\"state\"<\/span><span>:<\/span> <span>{<\/span> <span>\"access-accepts\"<\/span><span>:<\/span> <span>\"11\"<\/span><span>,<\/span>... <span>}<\/span> <span>}<\/span>\n<\/span><span><span>]<\/span>\n<\/span><\/code><\/pre>\n<p><code>GetIntegrityManifest<\/code> is missing from the list \u2014 IOS XR returns <code>Unimplemented<\/code> before the AAA counter increments, so the RPC simply never appears in the gNMI tree.<\/p>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Things to watch out for<\/h2>\n<ul>\n<li>On IOS XR 24.4.x, repeat <code>sslProfileId<\/code> on <strong style=\"color: rgb(26, 26, 26)\">every<\/strong> stream message \u2014 including finalize \u2014 or the server rejects the RPC with <code>Not expecting rotate RPC for multiple SSL profiles in single stream<\/code>.<\/li>\n<li>The Profile bound to the <strong style=\"color: rgb(26, 26, 26)\">active gRPC server<\/strong> cannot be deleted. <code>DeleteProfile<\/code> returns an error for it.<\/li>\n<li><code>GetIntegrityManifest<\/code> may return <code>Unimplemented<\/code> on some images. Always test with a live call, not just Reflection.<\/li>\n<li>After Rotate, the new Certificate takes effect for <strong style=\"color: rgb(26, 26, 26)\">new<\/strong> connections. Existing sessions are not interrupted.<\/li>\n<li>Always verify with <code>show grpc certificate<\/code> after binding a Profile \u2014 a successful Rotate does not guarantee the Profile is active on the gRPC server.<\/li>\n<li>Flow A (target-generated CSR) <strong style=\"color: rgb(26, 26, 26)\">cannot complete<\/strong> end-to-end in <code>grpcurl<\/code> \u2014 closing the stream after the CSR returns discards the candidate Key, and the follow-up upload fails with <code>No private key found for leaf cert<\/code>. Use a programmatic client for that flow, or stick with Flow B.<\/li>\n<li>IOS XR returns <code>InvalidArgument: certificate: same version sent<\/code> (not the proto-documented <code>AlreadyExists<\/code>) when reusing a <code>version<\/code> without <code>forceOverwrite<\/code>. Set <code>\"forceOverwrite\": true<\/code> on every non-finalize message during retries.<\/li>\n<li>An unfinalized Rotate stream closes with <code>ERROR: Code: Unknown \/ Message: EOF<\/code> from the server&#8217;s perspective. That is the rollback signal, not a failure to investigate.<\/li>\n<li>The <code>openconfig-gnsi-certz<\/code> state subtree (<code>state\/ssl-profile-id<\/code>, <code>state\/certz-counters<\/code>) only populates after the gRPC server is bound to a Certz Profile. Until then, gNMI Gets on those leaves return <code>NotFound<\/code>.<\/li>\n<\/ul>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Conclusion<\/h2>\n<p>Adopting gNSI Certz on IOS XR aligns gRPC TLS Rotation with the same stage-validate-finalize model as the other gNSI services. The Protobuf contract is small on purpose, but the nested Certificate upload format benefits from <code>grpcurl -msg-template<\/code> before automating at scale. Combined with <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/grpc-authorization-with-authz\" style=\"color: rgb(0, 102, 204)\">Authz<\/a> and <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/securing-gnmi-with-pathz\" style=\"color: rgb(0, 102, 204)\">Pathz<\/a>, the coverage spans secure, programmable Management sessions end to end.<\/p>\n<ul>\n<li>Certz groups TLS Authentication material, Trust Anchors, CRLs, and optional Policy into named <strong style=\"color: rgb(26, 26, 26)\">SSL Profiles<\/strong> on IOS XR, backed by EMSd under <code>\/misc\/config\/grpc\/gnsi\/certz\/ssl_profiles\/<\/code>.<\/li>\n<li>Unary RPCs cover discovery (<code>GetProfileList<\/code>), Profile creation (<code>AddProfile<\/code>), CSR capability checks (<code>CanGenerateCSR<\/code>), and teardown (<code>DeleteProfile<\/code>).<\/li>\n<li><code>Rotate<\/code> is a streamed Transaction: optional <code>generate_csr<\/code>, one or more <code>certificates<\/code> uploads, then <code>finalize_rotation<\/code>. Drop the stream early to trigger Rollback.<\/li>\n<li>Two practical flows: let the router generate the CSR when <code>canGenerate<\/code> is true, or generate Key plus CSR on a workstation and upload the signed chain with <code>rawPrivateKey<\/code> on the leaf.<\/li>\n<li>After finalize, bind the Profile with <code>grpc gnsi service certz ssl-profile-id<\/code> and confirm with <code>show grpc certificate<\/code>.<\/li>\n<li>YANG Telemetry via <code>openconfig-gnsi-certz<\/code> streams Certificate versions, Trust Bundle freshness, and TLS handshake Counters \u2014 subscribe to <code>access-rejects<\/code> to catch Authentication failures early.<\/li>\n<div title=\"\u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0644 \u0627\u06cc\u0646 \u0628\u062e\u0634\" style=\"display: flex\"><svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><\/svg><\/div>\n<\/ul>\n<p><strong style=\"color: rgb(26, 26, 26)\">What to do next:<\/strong> To rotate SSH Credentials alongside gRPC TLS, continue with <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/gnsi-credentialz-ssh-credential-rotation-on-cisco-ios-xr\" style=\"color: rgb(0, 102, 204)\">Credentialz<\/a>. To add gRPC Service-level Authorization, see <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/grpc-authorization-with-authz\" style=\"color: rgb(0, 102, 204)\">Authz<\/a>.<\/p>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">Reverting \/ Undo<\/h2>\n<table class=\"english-numbers\">\n<thead>\n<tr>\n<th>Situation<\/th>\n<th>How to revert<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><strong style=\"color: rgb(26, 26, 26)\">Cancel a Rotate mid-stream<\/strong><\/td>\n<td>Send a <code>certificates<\/code> message but no <code>finalizeRotation<\/code>, then close the grpcurl stream. On IOS XR 24.4.x the server prints <code>ERROR: Code: Unknown \/ Message: EOF<\/code>, the Candidate is discarded, and the previous material stays active. The proof: re-running the same Rotate (same <code>version<\/code>, <strong style=\"color: rgb(26, 26, 26)\">without<\/strong> <code>forceOverwrite<\/code>) succeeds \u2014 which would not be possible if the Candidate had been kept.<\/td>\n<\/tr>\n<tr>\n<td><strong style=\"color: rgb(26, 26, 26)\">Revert to a different Profile<\/strong><\/td>\n<td>Re-run <code>grpc gnsi service certz ssl-profile-id &lt;old-profile&gt;<\/code> in IOS XR CLI to point the gRPC server back.<\/td>\n<\/tr>\n<tr>\n<td><strong style=\"color: rgb(26, 26, 26)\">Revert to legacy (non-Certz) TLS<\/strong><\/td>\n<td>Remove the <code>grpc gnsi service certz<\/code> configuration line entirely. The gRPC server falls back to gNOI <code>cert.proto<\/code> material, then to legacy trustpoint PEM paths.<\/td>\n<\/tr>\n<tr>\n<td><strong style=\"color: rgb(26, 26, 26)\">Delete a test Profile<\/strong><\/td>\n<td><code>DeleteProfile<\/code> RPC \u2014 only works if the Profile is not bound to the active gRPC server.<\/td>\n<\/tr>\n<tr>\n<td><strong style=\"color: rgb(26, 26, 26)\">Replace bad Certificate material<\/strong><\/td>\n<td>Run another <code>Rotate<\/code> with <code>forceOverwrite: true<\/code> and the corrected material. No need to delete first.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr>\n<h2 style=\"color: rgb(26, 26, 26); font-weight: 700\">References<\/h2>\n<ol>\n<li>OpenConfig \u2014 <a href=\"https:\/\/github.com\/openconfig\/gnsi\/blob\/main\/certz\/certz.proto\" style=\"color: rgb(0, 102, 204)\"><code>certz.proto<\/code><\/a>, <a href=\"https:\/\/github.com\/openconfig\/gnsi\/blob\/main\/certz\/README.md\" style=\"color: rgb(0, 102, 204)\"><code>certz\/README.md<\/code><\/a><\/li>\n<li>OpenConfig \u2014 <a href=\"https:\/\/www.netconfcentral.org\/modules\/openconfig-gnsi-certz\/2024-02-13\/source\/raw\/\" style=\"color: rgb(0, 102, 204)\"><code>openconfig-gnsi-certz<\/code> YANG model v0.5.0<\/a><\/li>\n<li>Cisco IOS XR \u2014 <a href=\"https:\/\/www.cisco.com\/c\/en\/us\/td\/docs\/iosxr\/cisco8000\/programmability\/b-programmability-configuration-guide-cisco8000\/m-grpc-authentication.html#concept_C66C0E5371BF46E8937AB14F7D25EE11\" style=\"color: rgb(0, 102, 204)\">Programmability Configuration Guide: gRPC Authentication \u2014 Manage Certificates using Certz.proto<\/a><\/li>\n<li>Cisco IOS XR \u2014 <a href=\"https:\/\/www.cisco.com\/c\/en\/us\/td\/docs\/iosxr\/cisco8000\/programmability\/b-programmability-cr-cisco8000\/grpc-commands.html\" style=\"color: rgb(0, 102, 204)\">Programmability Command Reference: gRPC commands<\/a><\/li>\n<li>XRdocs \u2014 <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/Introduction-to-gnsi\" style=\"color: rgb(0, 102, 204)\">Introduction to gNSI (Authz)<\/a>, <a href=\"https:\/\/xrdocs.io\/programmability\/blogs\/PathZ\" style=\"color: rgb(0, 102, 204)\">PathZ<\/a><\/li>\n<\/ol>\n<hr>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>The Problem Authz and Pathz control what clients can do over gRPC and gNMI, but all of that depends on TLS being set up\u2026<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[111,238,240,69,243,37],"tags":[],"class_list":["post-253","post","type-post","status-publish","format-standard","hentry","category-automation-en","category-gnsi-en","category-grpc-en","category-iosxr-en-en","category-programmability-en","category-security-en"],"_links":{"self":[{"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/posts\/253","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/comments?post=253"}],"version-history":[{"count":0,"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/posts\/253\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/media?parent=253"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/categories?post=253"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.anynet.ir\/blog\/wp-json\/wp\/v2\/tags?post=253"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}