Lab brief & addressing
Topology Open full size ↗
The Mission
Every lab so far, you configured a device by typing at it. Automation flips that: the device becomes an API you call from a program. By the end, you will read and change a live router's configuration over HTTPS — no CLI — and prove a single PUT became real IOS config, then delete it with a DELETE. You are learning the machine-to-machine interface every controller, script, and CI pipeline uses under the hood.
You'll be able to
- Turn on RESTCONF and read the device's config and live state as structured JSON (
application/yang-data+json). - Map HTTP verbs to config actions —
GETreads,PUT/POST/PATCHwrite,DELETEremoves — and read the status code that proves each one. - Address any piece of config by a URL built from a YANG model, and read a RESTCONF error body when you get the URL wrong.
Before you start
- One IOS-XE router (this lab is captured on a Cisco CSR1000v, IOS-XE 17.3.6 — RESTCONF needs IOS-XE 16.x+, it is not on classic IOSv 15.x).
- A management host that can reach the router and run
curl(Pythonrequestsworks identically). In GNS3, connect the router'sGigabitEthernet1to a NAT cloud so your host can reach it. - Comfortable with E07 (you know why on-box automation matters). This is the off-box, model-driven counterpart.
Addressing plan
| Device / Interface | Address | Role |
|---|---|---|
| CSR1 Gi1 (management) | 192.168.122.10 / 24 | the device you drive by API |
| Management host (curl/Python) | 192.168.122.1 | the API client |
| RESTCONF endpoint | https://192.168.122.10/restconf | TCP 443 (HTTPS) |
| Credentials | cisco / cisco123 (priv 15) | HTTP Basic auth |
The one idea behind this lab
Inside every modern IOS-XE box the configuration is described by YANG data models — a strict tree of what can be configured and read. RESTCONF publishes that tree over HTTPS: each branch has a URL, and you act on it with ordinary HTTP verbs. GET a URL and you get that slice of config or live state back as JSON; PUT a JSON body to it and you set that config; DELETE it and it's gone. It is the exact same configuration you would type at the CLI — but now it is addressable and machine-readable, which is the whole reason a script (or Ansible, or a controller) can drive a thousand devices without a human touching a keyboard. The skill here is not curl; it is seeing config as data you can request and replace.
See & Do Turn on RESTCONF and read the box as JSON
Type the config on CSR1, then run the curl commands from your management host. Read each response before moving on.
Enable RESTCONF (and the auth it requires)
CSR1(config)# aaa new-model CSR1(config)# aaa authentication login default local CSR1(config)# aaa authorization exec default local CSR1(config)# username cisco privilege 15 secret cisco123 CSR1(config)# ip http secure-server ! RESTCONF rides on HTTPS (tcp/443) CSR1(config)# restconf CSR1(config)# netconf-yang ! brings up the shared data-management backend
aaa + username). It rides on the HTTPS server, so it needs ip http secure-server (that also auto-generates a self-signed cert). Confirm the backend processes are up before you call it:
CSR1# show platform software yang-management process
confd : Running
nesd : Running
ncsshd : Running
dmiauthd : Running
nginx : Running
ndbmand : Running
pubd : Running
nginx is the web front door; ndbmand/confd are the datastore behind it. If a call returns "currently unavailable," these are still starting — give it a minute.Discover the API root
host$ curl -k -u cisco:cisco123 https://192.168.122.10/.well-known/host-meta
<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>
<Link rel='restconf' href='/restconf'/>
</XRD>
host$ curl -k -u cisco:cisco123 https://192.168.122.10/restconf/ \
-H "Accept: application/yang-data+json"
{"ietf-restconf:restconf":{"data":{},"operations":{},"yang-library-version":"2016-06-21"}}
host-meta → /restconf), and the root tells you the three doorways: data (config & state you can read/write), operations (RPCs you can call), and the YANG library version. -k accepts the self-signed cert; -u is HTTP Basic auth.Your first read — the hostname, as data
host$ curl -k -u cisco:cisco123 -H "Accept: application/yang-data+json" \
https://192.168.122.10/restconf/data/Cisco-IOS-XE-native:native/hostname
{
"Cisco-IOS-XE-native:hostname": "CSR1"
}
/restconf/data/ is the configuration datastore; Cisco-IOS-XE-native:native/hostname is the path to the hostname leaf in Cisco's native YANG model. You just read one line of the running config as a JSON object — no CLI, no screen-scraping.Read an interface — and look at the HTTP itself
host$ curl -k -i -u cisco:cisco123 -H "Accept: application/yang-data+json" \
https://192.168.122.10/restconf/data/Cisco-IOS-XE-native:native/interface/GigabitEthernet=1
HTTP/1.1 200 OK
Content-Type: application/yang-data+json
Cache-Control: private, no-cache, must-revalidate, proxy-revalidate
Pragma: no-cache
{
"Cisco-IOS-XE-native:GigabitEthernet": {
"name": "1",
"ip": {
"address": {
"dhcp": {}
}
},
"mop": { "enabled": false, "sysid": false },
"Cisco-IOS-XE-ethernet:negotiation": { "auto": true }
}
}
HTTP/1.1 200 OK is the verdict; Content-Type: application/yang-data+json is the RESTCONF media type that tells your program "this is YANG-modeled JSON." The body says Gi1 gets its address by DHCP and auto-negotiates — the same facts as show run interface Gi1, but parseable in one line of code.Now You (guided) Change the router with a single HTTPS call
Reading is half the story. The payoff is writing: create a whole interface with one PUT, then prove it became real IOS config. You build the request; I'll give you the target and the shape.
PUTto the URL of the object you want to exist — a Loopback100 undernative/interface— with a JSON body describing it.PUTmeans "make this resource look exactly like my body" (create if absent, replace if present).- Give it a description and
10.100.100.1/32. - Then
GETthe same URL to confirm, and check on the device that it is real running config.
Target to hit: the PUT returns HTTP 201 Created; the read-back shows your loopback; and show ip interface brief on CSR1 lists Loopback100 up/up. A REST call and a CLI both wrote the same config — that is the point.
Stuck? Reveal the PUT
host$ curl -k -u cisco:cisco123 -X PUT \
-H "Content-Type: application/yang-data+json" \
https://192.168.122.10/restconf/data/Cisco-IOS-XE-native:native/interface/Loopback=100 \
-d '{
"Cisco-IOS-XE-native:Loopback": {
"name": 100,
"description": "Created via RESTCONF",
"ip": { "address": { "primary": {
"address": "10.100.100.1", "mask": "255.255.255.255" } } }
}
}'
What "correct" looks like
The PUT returns just a status line — 201 means the resource was created:
host$ curl ... -X PUT ... -o /dev/null -w "%{http_code}\n"
201
Read it back over REST — it's there:
host$ curl -k -u cisco:cisco123 -H "Accept: application/yang-data+json" \
https://192.168.122.10/restconf/data/Cisco-IOS-XE-native:native/interface/Loopback=100
{
"Cisco-IOS-XE-native:Loopback": {
"name": 100,
"description": "Created via RESTCONF",
"ip": { "address": { "primary": {
"address": "10.100.100.1", "mask": "255.255.255.255" } } }
}
}
Now the proof that matters — on the router's own CLI, the REST write is indistinguishable from something you typed:
CSR1# show ip interface brief Interface IP-Address OK? Method Status Protocol GigabitEthernet1 192.168.122.10 YES DHCP up up Loopback100 10.100.100.1 YES other up up CSR1# show running-config interface Loopback100 interface Loopback100 description Created via RESTCONF ip address 10.100.100.1 255.255.255.255 end
Solo Challenge No commands this time
Do this from memory
Run a complete CRUD life-cycle on a second object entirely over RESTCONF, and then delete the loopback you made in Now You. Work out, for each step, the verb, the URL, and the status code that proves success:
- Create a
Loopback200(any /32 you like) — which verb, and what code confirms it? - Update just its description without resending the whole object — which verb merges a change instead of replacing?
- Delete both loopbacks — which verb, and what "success, nothing to return" code comes back?
You pass when you can state all three and predict each status code:
PUT(orPOSTto the parent) creates → 201 Created.PATCHmerges a partial change → 204 No Content (success, nothing to send back).DELETEremoves → 204, and a follow-upGETthen returns 404 Not Found.
Check your reasoning (open after you've done it)
Deleting the Now-You loopback and confirming it's gone:
host$ curl -k -u cisco:cisco123 -X DELETE -o /dev/null -w "%{http_code}\n" \
https://192.168.122.10/restconf/data/Cisco-IOS-XE-native:native/interface/Loopback=100
204
host$ curl -k -u cisco:cisco123 -o /dev/null -w "%{http_code}\n" \
-H "Accept: application/yang-data+json" \
https://192.168.122.10/restconf/data/Cisco-IOS-XE-native:native/interface/Loopback=100
404
The verb is the intent: PUT = "make it exactly this," PATCH = "change just this part," POST = "add a child under here," DELETE = "remove it." 204 means the write worked and there's nothing to return; the follow-up 404 is your proof the object is truly gone. Same four verbs drive every RESTCONF-capable device on earth.
Break & Fix Read a RESTCONF error on purpose
Automation lives or dies on reading the error the machine sends back. Ask for a path that doesn't exist — here, an operational leaf under a wrong keypath:
host$ curl -k -u cisco:cisco123 -H "Accept: application/yang-data+json" \
https://192.168.122.10/restconf/data/Cisco-IOS-XE-interfaces-oper:interfaces-oper-data/interface=GigabitEthernet1/statistics
{
"errors": {
"error": [
{
"error-message": "uri keypath not found",
"error-tag": "invalid-value",
"error-type": "application"
}
]
}
}
error-tag (invalid-value) is the machine-readable reason, error-message is the human hint, error-type says which layer rejected it. The two errors you'll hit constantly:
- 401 Unauthorized — wrong/missing
-ucredentials (the call never reaches the datastore). - 400 / "malformed message" — you forgot
-H "Content-Type: application/yang-data+json"on a write, or the JSON key doesn't match the model.
Now fix it: the reliable way to learn a correct URL is to read down the tree — GET the parent container first (.../Cisco-IOS-XE-native:native/interface) and copy the exact model names and keys from the JSON it returns. The model is self-documenting; let the device tell you the path.
Done check
Close this lab when every one of these is true, driven entirely from your management host:
| Call | Proof you want |
|---|---|
GET /restconf/ | 200 + the ietf-restconf:restconf root JSON |
GET .../native/hostname | {"Cisco-IOS-XE-native:hostname":"CSR1"}, Content-Type: application/yang-data+json |
PUT .../Loopback=100 | 201 Created |
show ip interface brief on CSR1 | Loopback100 10.100.100.1 up up — the REST write is real IOS config |
DELETE .../Loopback=100 then GET | 204, then 404 — created and removed purely by API |