gNMI (gRPC Network Management Interface) is a single gRPC service, specified by the OpenConfig project, that lets a client read a network device's state, change its configuration and subscribe to a continuous stream of telemetry, usually modelled in YANG. To run it safely, use TLS with certificate checks on both sides, give each collector its own identity and read-only rights, and keep the Set RPC away from monitoring systems.
The rest of this guide walks through the specification: the four RPCs, paths and encodings, subscription types and stream modes, dial-in versus dial-out, where the IETF's YANG-Push fits, and what the gNMI authentication document and the gNSI services ask of a device. It ends with a gnmic collector setup with TLS verification on, and the mistakes that turn telemetry into a quiet path into the network.
It builds on our explainers on NETCONF, RESTCONF and YANG and SNMP, and assumes you know a YANG model is a tree of containers, lists and leaves.
Key takeaways
- gNMI is defined by the OpenConfig gNMI specification (version 0.10.0, dated 25 May 2023) and the
gnmi.protofile in the openconfig/gnmi repository. It has four RPCs:Capabilities,Get,SetandSubscribe. Subscribehas three subscription types,ONCE,POLLandSTREAM. ASTREAMsubscription chooses one of three modes per path:SAMPLE,ON_CHANGEorTARGET_DEFINED. The default type isSTREAM.- Data is addressed by structured paths with an
origin. Whenoriginis empty it defaults toopenconfig, so the same tool can read vendor neutral OpenConfig models and a device's native models. - The specification says the session MUST use TLS and MUST NOT fall back to plaintext, and that connections are mutually authenticated with X.509 certificates. Authorization happens per RPC, using a username (and optionally a password) carried in gRPC metadata.
- Dial-in means the collector opens the connection to the device. Dial-out means the device opens it, which OpenConfig handles with a generic gRPC tunnel rather than a new gNMI RPC. YANG-Push (RFC 8639 and RFC 8641) is the IETF's separate answer to the same problem for NETCONF and RESTCONF.
- gNSI is the companion OpenConfig repository for security services on the device:
authz(which user can call which RPC),pathz(which user can read or write which gNMI path),certz(certificate and trust bundle rotation),credentialz(SSH and console credentials) andacctz(accounting records). - Most failures are operational: lab TLS shortcuts in production, one shared client certificate, write rights on a monitoring account, wildcard subscriptions that pull secrets, and unthrottled
ON_CHANGEstorms.
What is gNMI, who maintains it, and how does it relate to YANG, NETCONF and SNMP?
The gNMI specification opens by saying it "defines a gRPC-based protocol for the modification and retrieval of configuration from a target device, as well as the control and generation of telemetry streams." Its stated design goal is that one gRPC service covers both configuration and telemetry, so a device needs one implementation and a management system needs one client.
The document lives in the openconfig/reference repository on GitHub. The version current at the time of writing is 0.10.0, dated 25 May 2023, and it is compatible with gNMI service version 0.10.x. The message and service definitions are protocol buffers (proto3), kept in the separate openconfig/gnmi repository. On openconfig.net the project describes itself as an open source effort that "defines and implements a common, vendor-independent software layer for managing network devices," led by an Operator Working Group of network operators, with contributions from operators, equipment makers and the wider community. The reference, gnmi and gnmic repositories are all published under the Apache 2.0 licence.
In the specification's terms, the target is the device that owns the data (usually a router or switch) and the client is the system that reads, changes or collects it (usually a management system or telemetry collector).
How gNMI relates to YANG
gNMI does not require YANG. The specification says it is "assumed to carry payloads that contain data instances of OpenConfig YANG schemas," but it works for any data that fits a tree where nodes are identified by a path of names (plus list keys) and values can be serialised as scalars. In practice nearly every implementation exposes YANG modelled data, either the OpenConfig models or the device's own native models, and the Capabilities RPC returns the list of models and versions the target supports.
How gNMI relates to NETCONF and RESTCONF
NETCONF (RFC 6241) and RESTCONF (RFC 8040) are IETF protocols that also carry YANG modelled data: NETCONF over SSH or TLS with XML RPCs and explicit datastores, RESTCONF as HTTP methods over YANG data. gNMI is not an IETF standard; it uses protobuf, HTTP/2 multiplexing and a streaming first design. All three often coexist on one device, and an OpenConfig path you read with NETCONF is usually the same tree you subscribe to with gNMI.
How gNMI relates to SNMP
SNMP polls: a manager asks over UDP, the agent walks its MIB tables and replies, every time. gNMI inverts that for telemetry: the client describes once what it wants and how often, and the target pushes updates over a long lived HTTP/2 stream. SNMPv3 secures messages with its User-based Security Model (RFC 3414); gNMI relies on TLS and per RPC credentials. A comparison table across the four telemetry families comes later.
What are the four gNMI RPCs?
The service definition in gnmi.proto is short:
service gNMI {
rpc Capabilities(CapabilityRequest) returns (CapabilityResponse);
rpc Get(GetRequest) returns (GetResponse);
rpc Set(SetRequest) returns (SetResponse);
rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeResponse);
}
Only Subscribe is a bidirectional stream. The other three are unary: one request, one response. The specification also says a target SHOULD register with the gRPC server reflection service so clients can discover that gNMI is offered, and that implementations must not assume gNMI will remain the only service on the endpoint.
| RPC | Direction | What it does | Changes device state? | Typical caller |
|---|---|---|---|---|
Capabilities | unary | Returns the gNMI version, supported models with versions, and supported encodings | No | Any client, first call |
Get | unary | Returns a snapshot of data at one or more paths | No | Audits, one off reads, config backup |
Set | unary | Applies deletes, replaces and updates as one transaction | Yes | Configuration systems only |
Subscribe | bidirectional stream | Creates ONCE, POLL or STREAM subscriptions and carries the updates | No | Telemetry collectors |
Capabilities
Capabilities is the handshake. The target MUST reply with its gNMI service version, a list of ModelData entries (name, organisation and version for each model) and the encodings it supports. The specification says that if a client does not name the models it wants, the target should consider every model it supports, and if the client does not specify an encoding it MUST send JSON, which is the default.
Get
Get returns a snapshot. A GetRequest can ask for a data type (ALL, CONFIG, STATE or OPERATIONAL), useful when you want only configuration for a backup. The specification notes that for large subtrees a ONCE subscription can be cheaper for the target, since it need not assemble one in-memory view first.
Set
Set is where gNMI changes the device. A single SetRequest is a transaction: the specification says "either all modifications within the request are applied, or the target MUST rollback the state changes." The order of operations inside one request is fixed: delete first, then union_replace, then replace, then update. A replace on a list removes every entry not present in the new value; an update merges. A request that uses union_replace must not also carry delete, replace or update operations. Telemetry must not show a change until the whole transaction has been accepted, which is a useful property when a collector is watching the same tree that a configuration system is writing.
From a security point of view Set is the RPC you most want to keep away from telemetry systems. A collector never needs it.
Subscribe
Subscribe is the reason most people deploy gNMI. It gets its own section next.
How do gNMI subscriptions work: ONCE, POLL and STREAM?
A client creates a subscription by sending a SubscribeRequest that contains a SubscriptionList. The list holds one or more Subscription entries (each one a path plus options) and a mode for the list as a whole. Per the specification the paths in a subscription cannot be changed once it exists; to change them you cancel the RPC and create a new one.
The SubscriptionList fields worth knowing:
| Field | What it does | Default |
|---|---|---|
mode | ONCE, POLL or STREAM | STREAM |
prefix | A common path prefix applied to every subscription path in the list | none |
qos | DSCP marking for the response packets | device default for management traffic |
allow_aggregation | Lets the target bundle leaves that the schema marks as eligible into one update | false |
use_models | Restricts the subscription to named models | all supported models |
updates_only | Skip the initial dump of current state, send only changes | false |
ONCE
A ONCE subscription behaves like a single request and reply on a stream. The target sends updates for everything that matches, then a SubscribeResponse with sync_response set to true, then closes the RPC. It is a good fit for inventory jobs and for reading very large trees.
POLL
A POLL subscription stays open, but the target sends data only when asked. The client sends a SubscribeRequest containing an empty Poll message and the target MUST generate updates for all paths in the list. This gives you SNMP style timing (the collector decides when) with gNMI paths and encodings, and it avoids rebuilding the subscription each time.
STREAM and its three modes
A STREAM subscription is long lived. The target first sends the current value of every matching leaf, then sync_response: true to mark the end of the initial synchronisation, then updates according to the mode set on each Subscription entry:
| Stream mode | When the target sends | Key options | Good for |
|---|---|---|---|
SAMPLE | Once per sample_interval | sample_interval in nanoseconds; suppress_redundant; heartbeat_interval | Counters, gauges, anything that changes all the time |
ON_CHANGE | When the value changes, after an initial full send | heartbeat_interval (re-send even if unchanged) | Interface oper-status, BGP session state, config leaves |
TARGET_DEFINED | The target decides per leaf whether to sample or send on change | none; a sample_interval here should be rejected | Mixed subtrees where you do not want to pick |
Some details from the specification that change how you size a deployment:
- For
SAMPLE, the interval is a 64 bit unsigned integer in nanoseconds. A 10 second interval is10000000000. If the target cannot support the requested interval it MUST close the RPC withInvalidArgument (3). An interval of 0 tells the target to use the lowest interval it can. - With
suppress_redundanton aSAMPLEsubscription, the target should send only leaves whose value changed since the last update. Aheartbeat_intervalforces one update per heartbeat regardless, so the collector can tell a quiet leaf from a dead stream. - For
TARGET_DEFINED, the target chooses the sample interval and MAY change it over time. Do not assume a fixed rate when you plot those series. - Updates are not guaranteed to arrive in timestamp order, and a client MUST NOT assume they will. Collectors and time series stores need to handle out of order points.
- A subscription to a path that does not exist yet is legal. The target MUST NOT close the RPC; it keeps watching and sends data if the path appears later. This is helpful for interfaces that come up after the subscription starts and unhelpful when a typo silently produces no data.
- By default each update carries one leaf. Aggregation into larger objects happens only if both the schema marks the element as eligible and the client sets
allow_aggregation.
A worked sizing example
Assume one collector subscribes in SAMPLE mode to the counters of 48 interfaces on 200 switches, with 12 counter leaves per interface (in and out octets, unicast, multicast, broadcast packets, discards and errors). That is 48 × 12 = 576 leaves per switch, and 576 × 200 = 115,200 leaves in total.
- At a 10 second interval the collector receives 115,200 ÷ 10 = 11,520 leaf updates per second.
- At a 1 second interval the same subscription is 115,200 updates per second, ten times the load on the collector and on every device's telemetry process, for data most dashboards will downsample anyway.
- With
suppress_redundantand a 60 second heartbeat, an idle interface sends its unchanged counters once a minute instead of once every 10 seconds, so for idle ports that is one sixth of the updates.
Now the same arithmetic for ON_CHANGE on interface oper-status: 48 × 200 = 9,600 leaves, and in steady state almost no updates. During an incident where a line card flaps every interface once a second for a minute, that card alone can produce 48 × 60 = 2,880 updates in a minute, and a spanning tree or routing event can touch thousands of leaves at once. ON_CHANGE is cheap on average and spiky at the worst possible moment, which is why the common mistakes section returns to rate limits.
How do gNMI paths, origins and encodings work in practice?
Paths are structured, not strings
A gNMI Path is an ordered list of PathElem messages, each with a name and an optional key map for list keys. The human readable form that tools print, such as /interfaces/interface[name=Ethernet1]/state/counters, is a convenience defined in the separate gNMI path strings document. On the wire it is structured, which avoids the escaping problems of slash separated strings.
The gNMI path conventions document (version 0.4.0) adds rules that matter for security:
- Paths in
GetandSubscribeare recursive. A path selects the element and all of its descendants. Subscribing to/systemsubscribes to everything under it. - To match every entry in a list you may omit the keys or set the key value to
*. So/interfaces/interface[name=*]/state/oper-statusmatches the status of every interface. - The root path is an empty list of elements, and it is not the same as a single empty element.
Those two rules combine into the biggest data leakage risk in gNMI. A wildcard at a high level, or a subscription to /, returns every leaf the account is allowed to see, including configuration leaves that some implementations populate with key material, SNMP communities, RADIUS or TACACS shared secrets and local user hashes. The protocol does exactly what you asked. The fix is narrow paths in the collector and path level authorization on the device, which gNSI pathz exists to provide.
Origins: OpenConfig versus native models
Every Path has an origin field that names the schema the path belongs to. The specification says the tuple of origin and path uniquely identifies data, that an unset origin defaults to openconfig, and recommends setting it explicitly. A special case allows a CLI origin, in which case the path is ignored and the value is treated as CLI text in ASCII. One Set can carry several origins (with transaction rules described in the specification), but a prefix cannot be used when more than one origin appears in a message.
In practice that gives you three choices:
| Approach | What you get | What it costs |
|---|---|---|
OpenConfig models (origin openconfig) | The same paths across devices from different makers, which keeps collector configs and dashboards portable | Coverage gaps: not every feature has an OpenConfig model, and implementations differ in which leaves they populate |
| Native models (a device specific origin) | Full coverage of the platform's features, often closer to what the CLI shows | Paths and semantics differ per platform and per software release |
CLI origin in Set | Lets you push CLI text through gNMI's transport and authentication | No schema validation by gNMI; the format is agreed out of band. Treat it as a migration aid |
A common pattern is OpenConfig for the shared core (interfaces, BGP, LLDP, platform components) and native paths for the rest, with a record of which origin each dashboard depends on.
Encodings
The specification defines five encodings in the Encoding enum, and the client must infer which one the target used from the populated TypedValue field:
| Encoding | Enum value | TypedValue field | Notes |
|---|---|---|---|
JSON | 0 | json_val | JSON per RFC 7159. The default if the client does not choose |
BYTES | 1 | bytes_val | Opaque bytes |
PROTO | 2 | string_val, int_val, uint_val, bool_val, float_val and similar | Scalar protobuf types per leaf; compact and fast to parse |
ASCII | 3 | ascii_val | Device formatted text, for example CLI configuration |
JSON_IETF | 4 | json_ietf_val | JSON compatible with RFC 7951, the IETF JSON encoding of YANG data |
The difference between JSON and JSON_IETF is the RFC 7951 rules: member names are qualified with the YANG module name where needed ("openconfig-interfaces:interfaces"), 64 bit integers are encoded as JSON strings to avoid precision loss, and identities carry their module prefix. For streaming counters most collectors prefer PROTO because each update is a single typed leaf. For Get and Set of whole subtrees, JSON_IETF is common because it can be validated against the YANG modules.
Dial-in versus dial-out telemetry: who opens the connection?
The terms describe only which side opens the TCP connection. They say nothing about which side sends the data, which in both cases is the device.
| Dial-in | Dial-out | |
|---|---|---|
| Who connects | Collector connects to the device's gNMI server | Device (or an agent beside it) connects to a collector or tunnel server |
| Who is the gRPC server for gNMI | The device | Still the device, carried inside a tunnel |
| Firewall rule | Inbound to the device management address on the gNMI port | Outbound from the device to the collector or tunnel address |
| NAT | Collector must be able to reach each device address; awkward behind NAT or for devices with dynamic addresses | Works through NAT and from remote sites, since the device starts the session |
| Who decides what is collected | The collector, at subscription time | Usually configured on the device or tunnel registration, then the collector subscribes over the tunnel |
| Main security question | Who can reach the device's management plane? | Can the collector or tunnel server be impersonated, and is it now an inbound exposed service? |
How OpenConfig does dial-out
Native gNMI is dial-in: the specification defines the target as the gRPC server. OpenConfig's answer for dial-out is the grpctunnel project, described in the reference document "gNMI/gNOI/ssh dial-out via grpctunnel". A tunnel client dials a tunnel server and holds open a persistent bidirectional Register RPC. Targets are registered over that stream, and later sessions (gNMI, gNOI or even SSH) are carried inside it. The tunnel client can be a separate binary on the device or embedded in the gNMI server; the tunnel server can be a separate binary or embedded in the collector. The gnmic documentation describes a tunnel server mode for exactly this use.
The document is direct about security. Tunnel authentication is independent of authentication inside the tunnel. It says it is "tempting to run the tunnel without encryption", explains that doing so would remove authentication at the tunnel level, and recommends "strong authentication and encryption be used both at the tunnel level and any API carried over" it. It also notes that a gNMI server can support dial-in and dial-out at the same time. One line deserves attention: it says an implementation should support both TLS without certificate verification and mTLS. The first option exists for bring-up. Do not leave a production tunnel in that state.
Where YANG-Push fits (RFC 8639 and RFC 8641)
The IETF solved the same problem for NETCONF and RESTCONF with two Standards Track RFCs published in September 2019:
- RFC 8639, "Subscription to YANG Notifications", defines a YANG data model and mechanisms for subscriber specific subscriptions to a publisher's event streams. It distinguishes dynamic subscriptions, created by a subscriber over a session and torn down with it, from configured subscriptions, installed into a configuration datastore. For a configured subscription the RFC says the publisher "will attempt to connect with all receivers", which makes it the IETF form of dial-out.
- RFC 8641, "Subscription to YANG Notifications for Datastore Updates", known as YANG-Push, builds on RFC 8639 to stream updates from a YANG datastore. It defines periodic subscriptions (updates on an interval) and on-change subscriptions (updates when data changes), which map closely onto gNMI's
SAMPLEandON_CHANGE.
Transport bindings are separate documents: RFC 8640 for NETCONF and RFC 8650 for RESTCONF. If your devices and tools are built around NETCONF, YANG-Push keeps you in one protocol family. If they are built around gRPC and OpenConfig, gNMI keeps you in the other. Many networks run both, which means two sets of credentials and two attack surfaces to manage.
How is gNMI secured?
Security for gNMI comes from three documents read together: section 3.1 of the gNMI specification, the gNMI authentication document, and the gNSI repository.
What the gNMI specification requires
Section 3.1, "Session Security, Authentication and RPC Authorization", sets the baseline:
- The session "MUST be encrypted using TLS," and a target or client "MUST NOT fall back to unencrypted sessions." Both SHOULD implement TLS 1.2 or later.
- New connections are mutually authenticated: each side validates the other's X.509 certificate to make sure the peer is "both known, and authorized to connect."
- If the target is expected to authenticate an RPC, the client MUST supply a username and password in the RPC metadata, and the target MUST authenticate it using its local authentication.
- Authorization is per RPC. The client may include AAA metadata that must contain a username and may contain a password. With both, the target must authenticate and authorize; with only a username, it must authorize.
So gNMI security has two layers. TLS with client certificates answers "is this a machine I trust?" Per RPC metadata answers "which user is this, and what may they do?" Many deployments use only one layer. Use both.
What the gNMI authentication document adds
The separate "gNMI Authentication and Encryption" document (version 0.1.1, updated 26 July 2019) is older and briefer, but more operational. It asks the device to:
- allow installation of an X.509 certificate and private key for the server side of TLS;
- validate certificates on both sides, then authenticate either with the username and password in gRPC metadata or with the common name in the client certificate;
- authorise operations through the device's normal AAA (it names TACACS as the usual case), so a
Geton/interfacesby a user triggers the same command authorization that the CLI would; - use one gRPC endpoint, with each client opening its own channel and credentials;
- encrypt with TLS 1.2, fail if an encrypted channel cannot be negotiated, and prohibit fallback to plaintext;
- support an operator installed certificate bundle for validating clients, with periodic rotation.
On telemetry, the document says individual telemetry messages are not authenticated one by one, since that would be redundant over an already authenticated stream. The Subscribe RPC itself requires credentials in metadata, and Subscribe and Get should be allowed for read only users, while configuration changes require read write permission. That last point is the single most useful line in the document for a security team: a telemetry collector should hold an account that the device's AAA treats as read only.
How gRPC carries those credentials
gRPC's own authentication guide describes two kinds of credentials: channel credentials, attached to the connection (TLS, with or without a client certificate), and call credentials, attached to each RPC as metadata (tokens, or a username and password in the gNMI case). It also notes that "most gRPC language implementations will not let you send credentials on an unencrypted channel," which is one more reason not to run gNMI in plaintext: either the credentials do not get sent, or someone has worked around the guardrail that stops them leaking. Client tools expose this as flags. gnmic, for example, documents --username and --password, an --auth-scheme flag that turns them into an HTTP Authorization: Basic header, and a --token flag that adds a bearer token "applied only in the case of a secure gRPC connection".
What gNSI adds: authz, pathz, certz, credentialz and acctz
The gNMI documents say what a device must check. They do not say how an operator changes those checks at scale. That is the job of gNSI, the gRPC Network Security Interface, in the openconfig/gnsi repository. Its README lists authorization, accounting, certificate management, console access management, and SSH certificate and key management, with YANG models for monitoring the services themselves published in the OpenConfig public models repository. A gNSI server is expected to support a single gNSI release and to be pinned to a release tag.
| gNSI service | What it controls | How changes are made safe |
|---|---|---|
authz | Which user may call which gRPC method on the device (for example allow gNMI.Subscribe and gNMI.Get for the telemetry group, deny gNMI.Set) | Policy is a JSON document in the gRPC authorization policy format (gRPC proposal A43). Rotate() uploads it, Probe() tests whether a given user would be allowed a given RPC, and Finalize commits. Closing the stream without Finalize rolls back. Only one rotation can run at a time |
pathz | Which user or group may read or write which gNMI path, for example allow /interfaces/interface[name=*]/state/counters but not /system/aaa | The most specific match wins: the longest path, and a user match over a group match. Policies carry a version and creation timestamp so you can check which version is in force |
certz | Server certificates, trust bundles (CA chains), certificate revocation lists and authentication policies, grouped into SSL profiles assigned to gRPC services | Rotate() installs a candidate, you verify services still work, then Finalize. Without Finalize the candidate is discarded. A vendor supplied system_default_profile always exists and cannot be changed or deleted |
credentialz | SSH host keys and certificates, users' authorized keys and SSH certificates, and console passwords | RotateAccountCredentials and related RPCs with the same verify then finalize pattern. The README marks password based SSH as "strongly discouraged" |
acctz | Accounting records of what was done on the device | A collector calls RecordSubscribe() and receives a stream of records; devices should keep a configurable history so newly connected collectors can catch up |
Two points stand out. First, the default gRPC authorization policy in authz "must allow access to all RPCs." Until you upload a policy, any authenticated user can call Set. Second, the rotate, verify, finalize pattern in authz, certz and credentialz is designed so that a bad policy or certificate does not lock you out of the device you are trying to secure. Use it as intended: always probe before you finalize.
gNSI support varies by platform and release; where it is missing, use the device's own AAA command authorization.
SNMP polling versus gNMI streaming versus NETCONF notifications versus flow export
These four are often compared as if they were rivals. They answer different questions. SNMP and gNMI report device state; NETCONF notifications and YANG-Push report state and events in the IETF family; flow export reports traffic.
| SNMP polling | gNMI streaming | NETCONF notifications and YANG-Push | Flow export (NetFlow v9, IPFIX) | |
|---|---|---|---|---|
| Main standards | RFC 3411 to RFC 3418 (SNMPv3 framework) | OpenConfig gNMI specification and gnmi.proto | RFC 6241, RFC 5277, RFC 8639, RFC 8640, RFC 8641; RFC 8650 for RESTCONF | RFC 3954 (NetFlow v9, informational), RFC 7011 (IPFIX) |
| Transport | UDP (usually port 161; traps to 162) | gRPC over HTTP/2 over TCP (IANA port 9339 registered) | SSH or TLS for NETCONF; HTTPS for RESTCONF | UDP most often in practice; RFC 7011 makes SCTP mandatory to implement and also allows UDP and TCP |
| Who connects | Manager to agent; traps from agent to manager | Collector to device (dial-in) or device to collector via tunnel (dial-out) | Subscriber to publisher (dynamic) or publisher to receiver (configured) | Exporter to collector |
| Security built in | SNMPv3 USM: authentication and privacy per user; v1 and v2c use cleartext community strings | TLS required by the spec, mutual certificate authentication, per RPC credentials; gNSI for policy and rotation | SSH or TLS session security; NACM (RFC 8341) for data access control | Often none on UDP. IPFIX specifies DTLS or TLS but many deployments do not enable it |
| Data model | SMI MIBs | YANG (OpenConfig or native) | YANG | Templates of information elements (IANA IPFIX registry) |
| Push or pull | Pull, with traps and informs for events | Push (STREAM) or pull (ONCE, POLL, Get) | Push | Push |
| Load on the device | Repeated table walks on every poll; heavy for large tables at short intervals | One subscription, then incremental updates; heavy if intervals are short or ON_CHANGE storms | Similar to gNMI streaming | Depends on sampling rate and flow cache size |
| Typical use | Legacy monitoring, simple gauges, wide compatibility | High frequency counters, state changes, modern telemetry pipelines | IETF centred automation and model driven telemetry | Who talked to whom, how much, for capacity and security analytics |
Our NetFlow and IPFIX explainer covers flow records, and the syslog explainer covers event logs over TLS.
Step by step: run gnmic with TLS verification on
This walkthrough uses gnmic, the gNMI client and collector published under the OpenConfig GitHub organisation. Its documentation describes it as a single binary for Linux, macOS and Windows (through WSL), distributed through GitHub releases and as a container image. The latest release at the time of writing is v0.49.1, published on 21 October 2026. The commands below are from gnmic's own documentation; the addresses, certificate paths and user names are placeholders you replace with your own.
It assumes a device (or a lab container running a network operating system) that already has a gNMI server enabled with a certificate signed by your internal CA, and a read only AAA user for telemetry. How to enable the server differs per platform; follow your device's documentation.
Step 1: install gnmic from a release you can verify
The gnmic documentation offers a one line install script that pipes curl into bash. For a production collector, prefer downloading a pinned release and checking it against the published checksums file:
VER=0.49.1
curl -sSLO "https://github.com/openconfig/gnmic/releases/download/v${VER}/gnmic_${VER}_Linux_x86_64.tar.gz"
curl -sSLO "https://github.com/openconfig/gnmic/releases/download/v${VER}/checksums.txt"
grep "gnmic_${VER}_Linux_x86_64.tar.gz" checksums.txt | sha256sum -c -
tar -xzf "gnmic_${VER}_Linux_x86_64.tar.gz" gnmic
sudo install -m 0755 gnmic /usr/local/bin/gnmic
gnmic version
If you run the collector as a container, the documentation lists ghcr.io/openconfig/gnmic and gnmic/gnmic with tags that match release versions. Pin a version tag (or better, a digest) rather than latest.
Step 2: issue a client certificate per collector
Each collector should have its own key pair and certificate, so you can revoke one without touching the others. In a lab you can create a small CA with OpenSSL; in production use your internal PKI.
# Lab only: a throwaway CA. Use your internal PKI in production.
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
-keyout lab-ca.key -out lab-ca.pem -days 365 -subj "/CN=lab-telemetry-ca"
# One key and certificate per collector instance
openssl req -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
-keyout collector-01.key -out collector-01.csr -subj "/CN=collector-01"
openssl x509 -req -in collector-01.csr -CA lab-ca.pem -CAkey lab-ca.key \
-CAcreateserial -out collector-01.pem -days 90 \
-extfile <(printf "extendedKeyUsage=clientAuth")
chmod 600 collector-01.key
openssl x509 -in collector-01.pem -noout -subject -enddate -ext extendedKeyUsage
The device needs your CA in the trust bundle it uses to validate client certificates. Where the device supports gNSI certz, that is a Rotate() call with verification before Finalize; otherwise use the platform's own certificate commands.
Step 3: check the device's certificate and capabilities
First confirm what certificate the device presents and that its name matches what you will connect to:
openssl s_client -connect router1.example.net:57400 -servername router1.example.net \
-alpn h2 -CAfile ca.pem </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -ext subjectAltName
Then run Capabilities with server verification and a client certificate. Read the password from a prompt or a secrets manager instead of the command line so it does not land in shell history:
read -rs GNMI_PASS
gnmic -a router1.example.net:57400 \
--tls-ca ca.pem \
--tls-cert collector-01.pem --tls-key collector-01.key \
--tls-min-version 1.2 \
-u telemetry-ro -p "$GNMI_PASS" \
capabilities
The output lists the gNMI version, every supported model with its organisation and version, and the supported encodings. Save it. It is your record of what the device claims, and the encodings list tells you whether PROTO is available for the next step.
Note the flags you did not use: gnmic documents --insecure as a non-TLS connection and --skip-verify as skipping certificate verification, recommended "primarily for non-production environments". Neither belongs in production.
Step 4: a one off read with Get
gnmic -a router1.example.net:57400 \
--tls-ca ca.pem --tls-cert collector-01.pem --tls-key collector-01.key \
-u telemetry-ro -p "$GNMI_PASS" \
-e json_ietf \
get --path "/interfaces/interface[name=*]/state/oper-status"
If the device rejects the account, check AAA first. If it returns nothing, check whether the path exists on this platform with this origin.
Step 5: subscribe to interface counters
gnmic -a router1.example.net:57400 \
--tls-ca ca.pem --tls-cert collector-01.pem --tls-key collector-01.key \
-u telemetry-ro -p "$GNMI_PASS" \
-e proto \
subscribe --path "/interfaces/interface[name=*]/state/counters" \
--mode stream --stream-mode sample --sample-interval 10s
gnmic's subscribe reference documents --mode (ONCE, STREAM or POLL, default STREAM), --stream-mode (ON_CHANGE, SAMPLE or TARGET_DEFINED, default SAMPLE), --sample-interval (default 0s, which per the specification means the target's lowest interval), --heartbeat-interval, --suppress-redundant, --updates-only and --qos (DSCP marking, default 20 in gnmic). Always set --sample-interval explicitly so you do not get the device's fastest rate by accident.
For state changes, add a second subscription in ON_CHANGE mode on a narrow path:
gnmic -a router1.example.net:57400 \
--tls-ca ca.pem --tls-cert collector-01.pem --tls-key collector-01.key \
-u telemetry-ro -p "$GNMI_PASS" \
subscribe --path "/interfaces/interface[name=*]/state/oper-status" \
--stream-mode on-change --heartbeat-interval 5m
Step 6: move it into a config file and export to Prometheus
For a long running collector, put targets, subscriptions and outputs in a configuration file. gnmic reads .gnmic.yaml (or toml or json) from the current directory, home directory or XDG config directory, or a file given with --config. Per target settings override global ones, and the documentation recommends the targets form over a flat address list because it allows per target credentials and TLS.
# /etc/gnmic/gnmic.yaml
username: telemetry-ro
# password comes from the environment or a secrets manager, never this file
encoding: proto
tls-ca: /etc/gnmic/pki/ca.pem
tls-cert: /etc/gnmic/pki/collector-01.pem
tls-key: /etc/gnmic/pki/collector-01.key
tls-min-version: "1.2"
skip-verify: false
targets:
router1.example.net:57400: {}
router2.example.net:57400: {}
subscriptions:
if-counters:
paths:
- /interfaces/interface[name=*]/state/counters
mode: stream
stream-mode: sample
sample-interval: 10s
if-status:
paths:
- /interfaces/interface[name=*]/state/oper-status
mode: stream
stream-mode: on-change
heartbeat-interval: 5m
outputs:
prom:
type: prometheus
listen: 10.0.20.5:9804
path: /metrics
expiration: 60s
tls:
ca-file: /etc/gnmic/pki/scraper-ca.pem
cert-file: /etc/gnmic/pki/collector-01-https.pem
key-file: /etc/gnmic/pki/collector-01-https.key
client-auth: require-verify
Run it with:
gnmic --config /etc/gnmic/gnmic.yaml subscribe
The Prometheus output in gnmic exposes a scrape endpoint (the documented example listens on port 9804 at /metrics) and supports TLS with a client-auth setting. The documentation lists request, require, verify-if-given and require-verify, and says that when a CA file is set the default becomes require-verify. Use that, so only your Prometheus servers can scrape the collector. Bind the listener to a management address, not every interface.
On the Prometheus side, a matching scrape job uses HTTPS and a client certificate:
scrape_configs:
- job_name: gnmic
scheme: https
tls_config:
ca_file: /etc/prometheus/pki/collector-ca.pem
cert_file: /etc/prometheus/pki/prometheus.pem
key_file: /etc/prometheus/pki/prometheus.key
static_configs:
- targets: ["10.0.20.5:9804"]
gnmic also documents outputs such as Prometheus remote write, InfluxDB, Kafka, NATS and OpenTelemetry, plus processors (including drop, allow and rate limit) that run before data leaves the collector. Secure the transport out of the collector as well as into it.
Step 7: verify the security properties you think you have
A short checklist you can run after every change:
# 1. Plaintext must fail against the device
gnmic -a router1.example.net:57400 --insecure -u telemetry-ro -p "$GNMI_PASS" capabilities \
&& echo "FAIL: device accepted plaintext gRPC" || echo "ok: plaintext refused"
# 2. A connection without a client certificate must fail if the device requires mTLS
gnmic -a router1.example.net:57400 --tls-ca ca.pem -u telemetry-ro -p "$GNMI_PASS" capabilities \
&& echo "CHECK: device accepted a session without a client certificate" || echo "ok: client cert required"
# 3. The telemetry account must not be able to Set
# Run only against a lab device or a harmless leaf you own, and confirm with your change process first.
# 4. No skip-verify or insecure in deployed configs
grep -RInE "skip-verify: *true|insecure: *true|log-tls-secret" /etc/gnmic/ && echo "FAIL: unsafe TLS setting found"
For the third check, prefer gNSI authz Probe() where supported: it answers whether a user would be permitted gNMI.Set without making any change.
What can go wrong?
Set enabled for a read only collector
The gNMI authentication document says Subscribe and Get should be allowed for read only users and configuration changes need read write rights. If the collector's account maps to a role that can write, then anyone who steals the collector's credentials, or finds a bug in the collector, can change the network. Map the telemetry user to a read only AAA role, and where the platform supports it, deny gNMI.Set for that user explicitly with gNSI authz. Remember that the default authz policy allows every RPC until you upload one.
Skip-verify TLS left in configs
--skip-verify keeps the traffic encrypted but stops the client from checking who it is talking to. Anyone who can redirect traffic to the management address, through DNS, ARP on a management VLAN or a compromised jump host, can present any certificate and receive the collector's username and password in the RPC metadata. Search Ansible roles, Helm values and images for it.
Broad wildcard paths leaking configuration secrets
Because Get and Subscribe paths are recursive and list keys can be wildcarded, a subscription to / or /system can return configuration leaves holding shared secrets for AAA servers, SNMP communities, routing protocol authentication keys or user password hashes, depending on how the platform models and redacts them. Once in the time series store they are readable by everyone with dashboard access and retained for months. Subscribe to the state containers you need, and use gNSI pathz (or the platform's equivalent) to deny the telemetry user read access to /system/aaa and other sensitive subtrees.
A related debugging flag, gnmic's --log-tls-secret, writes each session's pre-master secret to a file so Wireshark can decrypt the traffic. Left on, it makes every session readable by anyone who can read that file.
The collector as a high value target
A collector holds credentials and network reachability for every device it watches. In dial-in mode it can open connections to every management address; in dial-out mode it accepts inbound sessions from every device. It also runs a lot of open source software: the collector itself, its dependencies, the container base image, and the time series database. Treat it like a jump host: its own management segment, minimal inbound exposure, short lived certificates, and a patched, inventoried software stack.
Common mistakes
- Plaintext gRPC in the lab carried into production. The specification forbids fallback to unencrypted sessions, and some platforms still allow a non TLS gNMI server for testing. Lab configs get copied. Make the production build of your collector config fail closed: no
insecure, noskip-verify, a CA file always set, and the step 7 checks in your deployment pipeline. - One shared client certificate for every collector. If every collector presents the same certificate, you cannot revoke one, cannot tell them apart in device logs, and a single leaked key from one container image gives access everywhere. Issue one certificate per collector instance, keep lifetimes short, and rotate with
certzor your PKI automation. - No rate limits on ON_CHANGE storms.
ON_CHANGEis quiet until a flap, then it floods. Narrow the paths, add heartbeats so you can tell quiet from dead, use the collector's rate limit or drop processors, and size the collector and message bus for the burst, not the average. - Sample intervals left at zero. A
sample_intervalof 0 means the device's fastest rate. Set it explicitly. - Credentials in the config file or on the command line. gnmic's flags accept a password, and the config file can hold one. Keep passwords out of version control and shell history; read them from a secrets store or environment injected at runtime, and prefer client certificates plus a read only user.
FAQ
What does gNMI stand for?
gRPC Network Management Interface. It is a gRPC service defined by the OpenConfig project for reading, changing and streaming the state of network devices.
Is gNMI an IETF standard?
No. The gNMI specification and its protobuf definitions are published by OpenConfig on GitHub under the Apache 2.0 licence. The IETF's equivalents for model driven telemetry are NETCONF, RESTCONF and the subscription RFCs 8639 and 8641 (YANG-Push).
What port does gNMI use?
The specification does not fix one. IANA's service name registry lists TCP port 9339 as gnmi-gnoi, "gRPC Network Mgmt/Operations Interface", registered on 9 May 2019. gnmic's documentation uses 57400 as its default when a target address omits the port, and many devices let you configure it. Check your platform's documentation and allow only the port in use, and only from your collectors.
What is the difference between SAMPLE and ON_CHANGE?
SAMPLE sends the value every sample_interval whether or not it changed (unless suppress_redundant is set). ON_CHANGE sends the current value once, then only when it changes, optionally with a heartbeat. Use SAMPLE for counters and ON_CHANGE for state.
What is the difference between gNMI dial-in and dial-out?
Dial-in means the collector connects to the device. Dial-out means the device connects out to a collector or tunnel server, which helps with NAT and remote sites. In OpenConfig's design, dial-out carries normal gNMI sessions inside a gRPC tunnel, so the device remains the gNMI server.
Does gNMI require mutual TLS?
The specification says the session must use TLS, must not fall back to plaintext, and that connections are mutually authenticated with X.509 certificates. Some implementations make client certificates optional. Require them anyway, and add per RPC username authorization.
What is gNSI?
The gRPC Network Security Interface, a set of OpenConfig services for managing a device's security configuration over gRPC: authz for RPC authorization, pathz for path based authorization, certz for certificates and trust bundles, credentialz for SSH and console credentials, and acctz for accounting records.
Should I use JSON_IETF or PROTO encoding?
PROTO for high rate streaming of single leaves, since it is compact and typed; JSON_IETF for Get and Set of subtrees, since it follows RFC 7951. Capabilities tells you what the device supports.
Why software supply chain hygiene still matters for gNMI
The protocol lives on the device, but the parts you deploy and patch yourself are software: the collector binary, its Go dependencies, the container image it ships in, the tunnel server, the message bus and the time series database, all pulled from open source projects and rebuilt every time you upgrade. Safeguard cannot secure a router's gNMI server or its firmware, but it can tell you what is inside the collector images and repositories you build and run, and help you get vulnerable components out of them.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.
3. Gold Registry: start from components that are already fixed
Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | Safeguard policies and gates in CI |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The first demo is a real Safeguard CLI session. It shows the assistant reading a vulnerability finding, editing a dependency, and running install and test commands, asking before any command that changes something and pushing nothing. The same flow applies to the repository where you build your collector image or your own gNMI tooling: point it at the repo, ask it to fix a vulnerable dependency, and review the change before it lands.
Click through it at your own pace, or open it full screen.
The second demo sets up the Safeguard runner on your own machine, approves it with a device code, and scans from the terminal. Telemetry teams can use the same flow to scan collector images and repositories on a build host they control.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and chooses which tools Claude may use. It is the same principle this post argues for on network devices: give each client only the operations it needs, and nothing that changes state unless you decide it should.
Click through it at your own pace, or open it full screen.
To try it on your own collector repositories and images, download the apps at safeguard.sh/download and read the CLI documentation.
Quick check
A collector sends a SAMPLE subscription with sample_interval set to 0. What does the target do?
It creates the subscription and sends data at the lowest interval it supports. That is usually far faster than a dashboard needs, so always set the interval explicitly.
Which gNSI service would you use to stop a telemetry user from calling gNMI.Set, and what is its default policy?
authz. Its default gRPC level authorization policy allows access to all RPCs, so until you rotate in and finalize a policy, any authenticated user can call Set.
Sources
- OpenConfig gNMI specification, version 0.10.0
- gNMI Authentication and Encryption, version 0.1.1
- gNMI Path Conventions, version 0.4.0
- gNMI Path Strings
- gnmi.proto service definition
- gNMI/gNOI/ssh dial-out via grpctunnel
- OpenConfig project site
- gNSI repository README
- gNSI authz
- gNSI pathz protobuf definition
- gNSI certz
- gNSI credentialz
- gNSI acctz
- gRPC authentication guide
- RFC 8639: Subscription to YANG Notifications
- RFC 8641: Subscription to YANG Notifications for Datastore Updates
- RFC 7951: JSON Encoding of Data Modeled with YANG
- gnmic documentation
- gnmic global flags
- gnmic subscribe command
- gnmic targets and session security
- gnmic Prometheus output
- gnmic installation
- gnmic subscriptions configuration
- gnmic releases
- IANA service name and port number registry
- RFC 8640: Dynamic Subscription to YANG Events and Datastores over NETCONF
- RFC 8650: Dynamic Subscription to YANG Events and Datastores over RESTCONF
- RFC 6241: NETCONF
- RFC 5277: NETCONF Event Notifications
- RFC 8040: RESTCONF
- RFC 8341: Network Configuration Access Control Model
- RFC 3411: SNMP management framework architecture
- RFC 3414: User-based Security Model for SNMPv3
- RFC 3954: NetFlow Services Export Version 9
- RFC 7011: IPFIX protocol specification
Last reviewed: 25 October 2026. The gNMI specification and gNSI services are revised on GitHub; check the linked repositories for the current versions.