Experimenting with the CSR1000v REST API
This all started because we occasionally want to block traffic from an IP address or two for a short time. Our firewall is a pain to configure for this sort of thing: adding a drop for a single IP address literally takes 10 minutes. You have to open a fat client, create an object, add the object to a group, save the config, verify the config, push the config, etc.
I thought that SRTBH (Source-based Real-Time Black Hole) implemented by BGP would be the ticket: fast, easy, and theoretically easy to automate with the REST API in the Cisco Cloud Services Router 1000v. SRTBH is a simple and elegant way of dropping selected traffic on BGP speaking routers. In a nutshell:
In my case, I wanted to use the CSR1000v as the trigger router. (At this point, somebody's thinking "Why not use a Linux BGP daemon?") That's a valid approach; I just want to use the CSR1000v.
I followed the REST setup instructions, then wrote a simple CLI tool to make testing easier. It's easy enough to test the API with cURL:
$ curl -X POST https://10.1.1.1:55443/api/v1/auth/token-services -H "Accept:application/json" -u "testuser" -d "" --insecure -3
Enter host password for user 'testuser':
{"kind": "object#auth-token", "expiry-time": "Thu Feb 12 17:24:30 2015", "token-id": "xNOvrJh[...]rW3x0=", "link": "https://10.1.1.1:55443/api/v1/auth/token-services/2285580579"}
but because the API requires that you obtain a temporary authorization token (demonstrated above) before doing anything interesting, writing the Python tool made sense.
Then the problems started: the REST API on the CSR1000v doesn't support tags on static routes. You can't add them, or even retrieve them. Here's what happens normally if you retrieve static routes:
>python rest.py --device 10.1.1.1 --user testuser --resource /routing-svc/static-routes
Password:
{u'items': [{u'admin-distance': 1,
u'destination-network': u'10.0.0.0/8',
u'kind': u'object#static-route',
u'next-hop-router': u'10.30.254.152',
u'outgoing-interface': u''},
u'kind': u'collection#static-route'}
If one of the routes has a tag, it throws an error:
{u'detail': u' ',
u'error-code': -1,
u'error-message': u"invalid literal for int() with base 10: 'tag'"}
You can't set tags either. Furthermore, the same thing holds true for static routes with the "name" argument. (Incidentally, the error message seems to indicate that the IOS XE API is written in Python.)
The problem with this is that without tags, there's no convenient way to filter the RTBH black hole trigger routes at the time of redistribution. You can't set route-maps on BGP network statements via the API either, so no joy there.
The best option to me seems to be to redistribute all static routes, but filter on prefix-length rather than tag:
route-map SRTBH permit 10
description BGP Black Hole
match ip address prefix-list HOST_ROUTES_ONLY
set origin igp
set community no-export
set ip next-hop 192.0.2.1
But then you have to make sure that you never have any /32s that aren't trigger routes... a strategy which is prone to configuration mistakes.
Because SRTBH seems like a natural use-case for API automation, I hope Cisco fixes this soon.
[added]
Other interesting things I've found: the static route configuration API doesn't allow you to add equal-cost static routes. For example, if I already have a 1.1.1.5/32 route and I add another one, I get a 404:
python rest.py --device 10.1.1.1 --user testuser --password foo --resource /routing-svc/static-routes --method post --json '{"destination-network":"1.1.1.5/32","outgoing-interface":"null0"}' --verbose
[snip]
DEBUG:root:got status code: 404
DEBUG:root:reponse content: Static route already exists
got status code: 404
The same thing goes for adding other items: I tried adding a name-server twice, and got a similar error. I would think that the API would be idempotent where possible (such as when adding a duplicate nameserver), and require clarifying attributes otherwise. With equal-cost static routes for example, it could throw a 40x unless an extra attribute like {"allow-equal-cost":true} is present in the POST request.
I thought that SRTBH (Source-based Real-Time Black Hole) implemented by BGP would be the ticket: fast, easy, and theoretically easy to automate with the REST API in the Cisco Cloud Services Router 1000v. SRTBH is a simple and elegant way of dropping selected traffic on BGP speaking routers. In a nutshell:
- You configure a "trigger router" that speaks iBGP with the rest of your BGP-speaking routers (usually your Internet edge or transit routers), but doesn't participate in traffic forwarding.
- On each edge/transit router you configure a static route to null0 for an unused /32, usually 192.0.2.1: ip route 192.0.2.1 255.255.255.255 null0
- On each edge/transit router you configure loose-mode unicast RPF filtering on your outside interfaces: ip verify source reachable-via any. This has a special property: packets from any source for which the uRPF next-hop resolves to Null0 will be dropped.
- When you want to drop traffic from a particular host (e.g. 1.1.1.2), you configure a static host route on the trigger router and redistribute it into BGP by matching its tag: ip route 1.1.1.2 255.255.255.255 null0 tag 666.
- At redistribution, you set its next-hop to 192.0.2.1:
route-map SRTBH permit 10
description BGP Black Hole
match tag 666
set origin igp
set community no-export
set ip next-hop 192.0.2.1
In my case, I wanted to use the CSR1000v as the trigger router. (At this point, somebody's thinking "Why not use a Linux BGP daemon?") That's a valid approach; I just want to use the CSR1000v.
I followed the REST setup instructions, then wrote a simple CLI tool to make testing easier. It's easy enough to test the API with cURL:
$ curl -X POST https://10.1.1.1:55443/api/v1/auth/token-services -H "Accept:application/json" -u "testuser" -d "" --insecure -3
Enter host password for user 'testuser':
{"kind": "object#auth-token", "expiry-time": "Thu Feb 12 17:24:30 2015", "token-id": "xNOvrJh[...]rW3x0=", "link": "https://10.1.1.1:55443/api/v1/auth/token-services/2285580579"}
but because the API requires that you obtain a temporary authorization token (demonstrated above) before doing anything interesting, writing the Python tool made sense.
Then the problems started: the REST API on the CSR1000v doesn't support tags on static routes. You can't add them, or even retrieve them. Here's what happens normally if you retrieve static routes:
>python rest.py --device 10.1.1.1 --user testuser --resource /routing-svc/static-routes
Password:
{u'items': [{u'admin-distance': 1,
u'destination-network': u'10.0.0.0/8',
u'kind': u'object#static-route',
u'next-hop-router': u'10.30.254.152',
u'outgoing-interface': u''},
u'kind': u'collection#static-route'}
If one of the routes has a tag, it throws an error:
{u'detail': u' ',
u'error-code': -1,
u'error-message': u"invalid literal for int() with base 10: 'tag'"}
You can't set tags either. Furthermore, the same thing holds true for static routes with the "name" argument. (Incidentally, the error message seems to indicate that the IOS XE API is written in Python.)
The problem with this is that without tags, there's no convenient way to filter the RTBH black hole trigger routes at the time of redistribution. You can't set route-maps on BGP network statements via the API either, so no joy there.
The best option to me seems to be to redistribute all static routes, but filter on prefix-length rather than tag:
route-map SRTBH permit 10
description BGP Black Hole
match ip address prefix-list HOST_ROUTES_ONLY
set origin igp
set community no-export
set ip next-hop 192.0.2.1
But then you have to make sure that you never have any /32s that aren't trigger routes... a strategy which is prone to configuration mistakes.
Because SRTBH seems like a natural use-case for API automation, I hope Cisco fixes this soon.
[added]
Other interesting things I've found: the static route configuration API doesn't allow you to add equal-cost static routes. For example, if I already have a 1.1.1.5/32 route and I add another one, I get a 404:
python rest.py --device 10.1.1.1 --user testuser --password foo --resource /routing-svc/static-routes --method post --json '{"destination-network":"1.1.1.5/32","outgoing-interface":"null0"}' --verbose
[snip]
DEBUG:root:got status code: 404
DEBUG:root:reponse content: Static route already exists
got status code: 404
The same thing goes for adding other items: I tried adding a name-server twice, and got a similar error. I would think that the API would be idempotent where possible (such as when adding a duplicate nameserver), and require clarifying attributes otherwise. With equal-cost static routes for example, it could throw a 40x unless an extra attribute like {"allow-equal-cost":true} is present in the POST request.