Cisco NSO – Create Service

In NSO, service is defined in YANG model. And once YANG model is defined and compiled, it will then be encoded to XML. There are few variations to define encode, such as “template only” and “python and template”. As name suggests, template is the most basic pattern, and it directly map the YANG model to XML. While with python some arbitrary operation can be configured based on YANG model before passing any values for XML encode.


Continue reading “Cisco NSO – Create Service”

What license is required to use Cisco API?

Basically you don’t need license to access APIs on the network devices directly, because it’s on base license.

However, if you search “Cisco API” online, or when you follow Cisco Devnet contents, it is confusing because a lot of contents are made around ACI/APIC/DNA. And DNA subscription is mandatory for some devices(e.g. Catalyst9k) now, and it sometimes misleads the customer that DNA subscription is required to use any kind of APIs on Cisco boxes.

Continue reading “What license is required to use Cisco API?”

Cisco Umbrella for Home Use – Good baseline

Is you PC protected at home? Where is your most valuable information stored?

In my case, my most valuable information is stored in my PC at home, or the cloud storage which only my PC can access to. It’s obvious that I need to protect my home network more than anything. It’s really scary for anyone getting your PC hacked like the one in BlackMirror.

Continue reading “Cisco Umbrella for Home Use – Good baseline”

k8s ex04: Security daemon – Cisco Stealthwatch

Network security used to be only deployed to investigate the traffic between north and south(in other word external and internal), but as the cloud and virtualization progress, it is now required to have east to west (intra-site) security investigation. For this purpose, ISFW(inter-segment firewall) is deployed on-premise, but it’s quite difficult if the servers are in the cloud.

And with Kubernetes, it’s even more difficult. Because each pod can be connected each other over some kind of tunnel(e.g. overlay network) as I mentioned in the previous post. So all the communications are somewhat hidden and simple security rule/policy cannot be used to restrict the communication. We use network policy or tools like Istio to restrict these unexpected traffic. But similar to the legacy network, these restrictions are still rely on manual work, we need to make policy by ourselves and it needs to be updated every time new service are procured. This is very difficult, the developer wants to deploy the service as smooth as possible, but the security needs to be guaranteed even though some services are dealt by another team…

Cisco Stealthwatch can be used in these cases as a turnkey security monitor.  As the stealthwatch doesn’t require anything to be changed on kubernetes config, actually it deploys a special host network pod in each worker node as a daemonset. It visualizes the inter-node/external communication dynamically and can send an alert in case unexpected communication happens. Please note that I use Stealthwatch Cloud for this trial.

 

Deploy Stealthwatch in K8s


1. Access Stealthwatch portal, and navigate to Integration > Kubernetes. You can find manifest file for stealthwatch daemonset along with service-key, which identifies your account.

2. Apply manifest files.

root@controller-1:~/work# {
> echo -n "YOUR_SECRET_KEY" > obsrvbl-service-key.txt
> kubectl create secret generic obsrvbl --from-file=service_key=obsrvbl-service-key.txt
> rm obsrvbl-service-key.txt
> }
secret "obsrvbl" created
root@controller-1:~/work# {
> kubectl create serviceaccount --generator=serviceaccount/v1 obsrvbl
> kubectl create clusterrolebinding "obsrvbl" --clusterrole="view" --serviceaccount="default:obsrvbl"
> }
serviceaccount "obsrvbl" created
clusterrolebinding.rbac.authorization.k8s.io "obsrvbl" created
root@controller-1:~/work# cat << EOF > obsrvbl-daemonset.yaml
> apiVersion: apps/v1
> kind: DaemonSet
> metadata:
>   name: obsrvbl-ona
> spec:
>   selector:
>     matchLabels:
>       name: obsrvbl-ona
>   template:
>     metadata:
>       labels:
>         name: obsrvbl-ona
>     spec:
>       serviceAccountName: obsrvbl
>       tolerations:
>         - key: node-role.kubernetes.io/master
>           effect: NoSchedule
>       hostNetwork: true
>       containers:
>         - name: ona
>           image: obsrvbl/ona:3.1
>           env:
>             - name: OBSRVBL_SERVICE_KEY
>               valueFrom:
>                 secretKeyRef:
>                   name: obsrvbl
>                   key: service_key
>             - name: OBSRVBL_KUBERNETES_WATCHER
>               value: "true"
>             - name: OBSRVBL_HOSTNAME_RESOLVER
>               value: "false"
>             - name: OBSRVBL_NOTIFICATION_PUBLISHER
>               value: "false"
> EOF
root@controller-1:~/work# kubectl apply -f obsrvbl-daemonset.yaml 
daemonset.apps "obsrvbl-ona" created
root@controller-1:~/work# kubectl get ds
NAME          DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
obsrvbl-ona   2         2         2         2            2           <none>          1m
root@controller-1:~/work# kubectl get pods -o wide
NAME                       READY     STATUS    RESTARTS   AGE       IP            NODE
busybox-5ccc978d8d-2nzs4   1/1       Running   4          2d        10.220.0.12   worker-1
nginx-65899c769f-txgtm     1/1       Running   3          2d        10.220.1.10   worker-2
obsrvbl-ona-dm9gb          1/1       Running   0          1m        10.240.0.21   worker-1
obsrvbl-ona-fkjnz          1/1       Running   0          1m        10.240.0.22   worker-2
root@controller-1:~/work#

 

3. After a few minutes, nodes with stealthwatch pod comes up as a sensor.

That’s all we need to do. With this setup, inter-pod/external communication are monitored and statistics information are sent to Stealthwatch cloud, where all the statistics data is processed.

 

Watch it works


1. After a few data being sent to the stealthwatch cloud, the portal starts to populate the valuable information. Dashboard by default shows the endpoint(in our case it includes pods on each nodes) and total in/out traffic.

2. And it shows any observations and alert as well. These are based on either static pattern(e.g. blacklisted), or behavioural basis(e.g. the traffic pattern is unusual compared to last 36 days).

Because this is my initial deployment it observes communication between controller-1(10.240.0.11) and other worker node are rather high. But this will be considered to be normal after some time.

And of course it can send an alert if there is any alert triggered.

3. You can explore network communication more if you would like to.

The good thing about this deployment is you don’t really have to care much of the things. Because the pod can see the traffic and it can also interact with the API server, it integrates all the real-time information with the historical data, and it nicely summarizes and show the outcome on the portal. And you can use them as a baseline to create security policies.

And it can be extended to GCP(with flow logs), AWS(flow logs), On-Premise(with onsite VM and sensor – e.g. Cat9300) to consolidate all the behavioural monitoring. Maybe I will introduce them in another post.

 

 

Python 100 project #36: Cisco CSR Reverse Shell

Sometimes there are a few occasions that edge network devices are not accessible from remote.  The reason varies from security reasons to environmental reasons(no global ip address is assigned and no access to the ISP router etc). In my previous project, I used code from Python Blackhat to show how Windows PC becomes a victim to expose its command access. In this time, I use this similar code for good.

This can be used to deploy periodically or can be triggered by syslog messages via EEM.

 

Output Example:

[ In the Server ]

# python3 cisco_revshell_server.py $(hostname -I) 2022
[+] Listening for connection ...
[+] Got a connection!
[+] Authenticated!
connected to us-east-1_e-rtr-01 
us-east-1_e-rtr-01(guestshell)# ls -l /bootflash     
total 406928
drwxr-xr-x  2      65534      65534      4096 May 28 06:20 CRDU
drwxr-xr-x  3      65534      65534      4096 May 28 06:19 core
-rw-r-----  1      65534      65534 377189336 Jan 17 19:31 csr1000v-mono-universalk9.16.07.01a.SPA.pkg
-rw-r--r--  1      65534      65534  38986404 Jan 17 19:31 csr1000v-rpboot.16.07.01a.SPA.pkg
-rw-r--r--  1      65534      65534       157 Jun  3 20:51 csrlxc-cfg.log
-rw-r--r--  1      65534      65534         0 Jun  3 20:51 cvac.log
drwxrwxr-x  2 guestshell guestshell      4096 Jun  3 22:57 gs_scripts
-rw-r--r--  1      65534      65534       174 Jun  3 20:50 iid_check.log
drwxr-xr-x 18      65534      65534      4096 Jun  3 20:51 iox
drwx------  2      65534      65534     16384 Jan 17 19:30 lost+found
-rw-r--r--  1      65534      65534        16 May 28 06:20 ovf-env.xml.md5
-rw-r--r--  1      65534      65534      1967 Jan 17 19:31 packages.conf
drwxr-xr-x  2      65534      65534      4096 Jun  3 22:30 syslog
-rw-r--r--  1      65534      65534        30 Jun  3 20

us-east-1_e-rtr-01(guestshell)# python -c "import cli;cli.executep('show ip route')"
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area 
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
       a - application route
       + - replicated route, % - next hop override, p - overrides from PfR
Gateway of last resort is 172.31.1.1 to network 0.0.0.0
S*    0.0.0.0/0 [1/0] via 172.31.1.1, GigabitEthernet1
      1.0.0.0/32 is subnetted, 1 subnets
C        1.1.1.1 is directly connected, Loopback0
      10.0.0.0/8 is variably subnetted, 4 subnets, 2 masks
C        10.0.0.0/24 is directly connected, VirtualPortGroup0
L        10.0.0.254/32 is directly connected, VirtualPortGroup0
C        10.10.10.0/24 is dir
us-east-1_e-rtr-01(guestshell)# exit
exiting
[-] Caught exception: exit

 

Here is the code:

[ In Cisco Box ] cisco_revshell_client.py

import sys

import paramiko
import subprocess

def ssh_command(ip, user, passwd, port, command):
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    client.connect(ip, username=user, password=passwd, port=port)
    ssh_session = client.get_transport().open_session()
    if ssh_session.active:
        ssh_session.send(command)
        print(ssh_session.recv(1024) + b'%s' % ip)
        while True:
            command = ssh_session.recv(1024)
            try:
                cmd_output = subprocess.check_output(command, shell=True)
                ssh_session.send(cmd_output)
            except Exception as e:
                ssh_session.send(str(e))
        client.close()
    return

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print "Usage: %s 'hostname'" % sys.argv[0]
        sys.exit(0)
    ssh_command('XX.XX.XX.XX', 'cisco-rtr', 'passw0rd', 2022, sys.argv[1])

 

[ In the Server ] cisco_revshell_server.py

import socket
import threading
import paramiko
import sys

class Server(paramiko.ServerInterface):
    def __init__(self):
        self.event = threading.Event()

    def check_channel_request(self, kind, chanid):
        if kind == 'session':
            return paramiko.OPEN_SUCCEEDED

    def check_auth_password(self, username, password):
        if (username == 'cisco-rtr') and (password == 'passw0rd'):
            return paramiko.AUTH_SUCCESSFUL
        return paramiko.AUTH_FAILED


server = sys.argv[1]
ssh_port = int(sys.argv[2])

try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((server, ssh_port))
    sock.listen(100)
    print("[+] Listening for connection ...")
    client, addr = sock.accept()

except Exception as e:
    print(f"[-] Listen failed: {str(e)}")
    sys.exit(1)

print("[+] Got a connection!")

try:
    bhSession = paramiko.Transport(client)
    bhSession.add_server_key(host_key)
    server = Server()
    try:
        bhSession.start_server(server=server)
    except paramiko.SSHException as x:
        print("[-] SSH negotiation failed.")
    chan = bhSession.accept(20)
    print("[+] Authenticated!")
    hostname = str(chan.recv(1024), 'utf-8')
    print(f"connected to {hostname}.")
    chan.send('remote connection is created to ')
    while True:
        try:
            command = input(f"{hostname}# ")
            if command != 'exit':
                chan.send(command)
                print(str(chan.recv(1024),'utf-8'))
            else:
                chan.send('exit')
                print('exiting')
                bhSession.close()
                raise Exception('exit')
        except KeyboardInterrupt:
            bhSession.close()
except Exception as e:
    print(f"[-] Caught exception: {str(e)}")
    try:
        bhSession.close()
    except:
        pass
    sys.exit(1)

 

Python 100 project #31: Cisco IOS Login notification to Slack with EEM

It is best practice and most of the router console is only accessible from certain ip address. Usually it is both from the service provider and internal ip address. But time to time, some of internal staff tries to access the router. It’s still just a tedious problem if there is centralised management system(or any syslog actually) to check those activities. But it will be a problem if the deployment is distributed and not centralised. In this project, I used cisco EEM along with python script so that it posts into the channel if there is any login activities.

 

Output Example:

 

Here is the code:

this is relevant eem config in cisco router-

us-east-1_e-rtr-01#sh run | sec event manager
event manager applet login_success_post
 event syslog pattern "SEC_LOGIN-5-LOGIN_SUCCESS"
 action 0.0 cli command "en"
 action 1.0 regexp "([0-9]+\.[0-9]+\.[0-9]+\.[0-9])" "$_syslog_msg" match source_ip
 action 2.0 cli command "guestshell run python /bootflash/gs_scripts/login_post.py $_info_routername success $source_ip"

this is relevant script which is saved as /bootflash/gs_scripts/login_post.py in the router-

import sys

import requests

# from cred import token
token = ***

def post_slack(msg, channel, hostname):

    headers = {
        "Content-type": "application/json"
    }

    params = {
        "token": token,
        "text": msg,
        "channel": channel,
        "as_user": False,
        "username": hostname,
        "icon_emoji": ":cisco-rtr:"
    }

    url = "https://slack.com/api/chat.postMessage"

    resp = requests.post(url, params=params, headers=headers)

    return resp.status_code

if __name__ == "__main__":

    if len(sys.argv) != 4:
        print "usage: %s 'hostname' 'fail|success' 'source_ip_address'"
        sys.exit(0)

    post_slack('Login %s from %s' % (sys.argv[2], sys.argv[3]), "security_logs", sys.argv[1])

 

IOx on Cisco: Launch Python

It’s been a long time since Cisco announced Fog computing, and implemented somewhat of programmability on their network devices. However I had never really looked at those functionalities until very recently.

I’m working in telecom industry and there has been no single day I don’t see Cisco devices, but most (if not all) of deployment is legacy, which utilizes only network function of those devices. It’s good as it is what those devices are meant to do, but I took a look on these new capabilities and found it very useful, and fun!

As Cisco mentioned in their article, these function should be used for non-networking functions. But these will be new weapons for those who knows networking very well to add-on new features as they wish, and on the other hand these will also be a good entry point for those who didn’t take care network(i.e developer, application engineer) until now. One of the most reliable network vendor provides small app platform on their small routers and switches.

 

Enable IOX service


It’s not enabled on boot, so you need to enable services on IOS-XE. As this test is done on CSR1000v on AWS, it uses LXC.

usrt01#conf t
Enter configuration commands, one per line.  End with CNTL/Z.
usrt01(config)#iox
usrt01(config)#do sh iox-service
Virtual Service Global State and Virtualization Limits:

Infrastructure version : 1.7
Total virtual services installed : 0
Total virtual services activated : 0

Machine types supported   : LXC
Machine types disabled    : KVM

Maximum VCPUs per virtual service : 0
Resource virtualization limits:
Name                         Quota     Committed     Available
--------------------------------------------------------------
system CPU (%)                  75             0            75
memory (MB)                   1024             0          1024
bootflash (MB)               20000             0          6751


IOx Infrastructure Summary:
---------------------------
IOx service (CAF)    : Running
IOx service (HA)     : Not Running
IOx service (IOxman) : Running
Libvirtd             : Running

 

The environment is almost ready.

  1. Create connection … VirtualPortGroup, which is the connection point between the IOS and the guest system.
  2. Allocate resource for application
  3. Start application
usrt01(config)#
usrt01(config)#int virtualportGroup 0
usrt01(config-if)#ip addre 10.0.0.254 255.255.255.0
usrt01(config-if)#exit
usrt01(config)#
usrt01(config)#app-hosting appid guestshell
usrt01(config-app-hosting)#vnic gateway1 virtualportgroup 0 guest-interface 0 guest-ipaddress 10.0.0.1 netmask 255.255.255.0 gateway 10.0.0.254 name-server 8.8.8.8 default
usrt01(config-app-hosting)#resource profile custom cpu 1500 memory 512
usrt01(config-app-hosting)#end
usrt01#guestshell enable
Interface will be selected if configured in app-hosting
Please wait for completion
guestshell installed successfully
Current state is: DEPLOYED
guestshell activated successfully
Current state is: ACTIVATED
guestshell started successfully
Current state is: RUNNING
Guestshell enabled successfully

usrt01#sh app-hosting list
App id                           State
------------------------------------------------------
guestshell                       RUNNING

usrt01#usrt01#guestshell

[guestshell@guestshell ~]$
[guestshell@guestshell ~]$ hostname -I
10.0.0.1

[guestshell@guestshell ~]$ ping 10.0.0.254
PING 10.0.0.254 (10.0.0.254) 56(84) bytes of data.
64 bytes from 10.0.0.254: icmp_seq=2 ttl=255 time=0.525 ms
64 bytes from 10.0.0.254: icmp_seq=3 ttl=255 time=0.550 ms
64 bytes from 10.0.0.254: icmp_seq=4 ttl=255 time=0.580 ms
^C
--- 10.0.0.254 ping statistics ---
4 packets transmitted, 3 received, 25% packet loss, time 2999ms
rtt min/avg/max/mdev = 0.525/0.551/0.580/0.035 ms

[guestshell@guestshell ~]$
[guestshell@guestshell ~]$ cat /flash/work/i_am_running.py
#!/bin/bash/python

import cli

# you can execute IOS command from guestshell
print "Hello, I\'m running on {}".format(cli.execute("show ver | inc Cisco IOS XE Software"))

[guestshell@guestshell ~]$ python /flash/work/i_am_running.py
Hello, I'm running on Cisco IOS XE Software, Version 16.07.01a

[guestshell@guestshell ~]$

 

Install NAPALM

Using vendor proprietary API is usually a good option if you have only a few vendor to deal with. But It’s very time consuming, and some vendors don’t have those in-built API.

I have numbers of customers to take care, and there are their own demands. There is no one-fit-all network devices, hence I need to take care several vendors. Definitely I cannot be the master of all the network devices, but it’s a key how I can decrease the time taken by routine tasks(e.g monitoring interface stats) so that I can use other time to concentrate customer specific requirement.

Install NAPALM


NAPALM is registered in PyPI, so all you need to do is just use pip to install along with all the dependencies.

(venv) shogokobayashi napalm $ pip install napalm
Collecting napalm
  Downloading napalm-2.3.0.tar.gz (147kB)
    100% |████████████████████████████████| 153kB 964kB/s 
Collecting future (from napalm)
  Downloading future-0.16.0.tar.gz (824kB)
    100% |████████████████████████████████| 829kB 1.3MB/s 
Collecting jtextfsm (from napalm)
  Downloading jtextfsm-0.3.1-py3-none-any.whl
Collecting jinja2 (from napalm)
  Downloading Jinja2-2.10-py2.py3-none-any.whl (126kB)
    100% |████████████████████████████████| 133kB 1.4MB/s 
Collecting netaddr (from napalm)
  Downloading netaddr-0.7.19-py2.py3-none-any.whl (1.6MB)
    100% |████████████████████████████████| 1.6MB 1.7MB/s 
Collecting pyYAML (from napalm)
  Downloading PyYAML-3.12.tar.gz (253kB)
    100% |████████████████████████████████| 256kB 2.1MB/s 
Collecting pyeapi (from napalm)
  Downloading pyeapi-0.8.2.tar.gz (133kB)
    100% |████████████████████████████████| 143kB 1.5MB/s 
Collecting netmiko>=1.4.3 (from napalm)
  Downloading netmiko-2.1.0.tar.gz (70kB)
    100% |████████████████████████████████| 71kB 2.1MB/s 
Collecting pyIOSXR>=0.51 (from napalm)
  Downloading pyIOSXR-0.52.tar.gz
Collecting junos-eznc>=2.1.5 (from napalm)
  Downloading junos_eznc-2.1.7-py2.py3-none-any.whl (150kB)
    100% |████████████████████████████████| 153kB 2.9MB/s 
Collecting pynxos (from napalm)
  Downloading pynxos-0.0.3.tar.gz
Collecting scp (from napalm)
  Downloading scp-0.10.2-py2.py3-none-any.whl
Collecting MarkupSafe>=0.23 (from jinja2->napalm)
  Downloading MarkupSafe-1.0.tar.gz
Collecting paramiko>=2.0.0 (from netmiko>=1.4.3->napalm)
  Downloading paramiko-2.4.1-py2.py3-none-any.whl (194kB)
    100% |████████████████████████████████| 194kB 2.5MB/s 
Collecting pyserial (from netmiko>=1.4.3->napalm)
  Downloading pyserial-3.4-py2.py3-none-any.whl (193kB)
    100% |████████████████████████████████| 194kB 2.4MB/s 
Collecting textfsm (from netmiko>=1.4.3->napalm)
  Downloading textfsm-0.3.2.tar.gz
Collecting lxml>=3.2.4 (from pyIOSXR>=0.51->napalm)
  Downloading lxml-4.2.0-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl (8.7MB)
    100% |████████████████████████████████| 8.7MB 3.4MB/s 
Collecting six (from junos-eznc>=2.1.5->napalm)
  Downloading six-1.11.0-py2.py3-none-any.whl
Collecting ncclient>=0.5.3 (from junos-eznc>=2.1.5->napalm)
  Downloading ncclient-0.5.3.tar.gz (63kB)
    100% |████████████████████████████████| 71kB 3.3MB/s 
Collecting requests>=2.7.0 (from pynxos->napalm)
  Downloading requests-2.18.4-py2.py3-none-any.whl (88kB)
    100% |████████████████████████████████| 92kB 2.2MB/s 
Collecting pyasn1>=0.1.7 (from paramiko>=2.0.0->netmiko>=1.4.3->napalm)
  Downloading pyasn1-0.4.2-py2.py3-none-any.whl (71kB)
    100% |████████████████████████████████| 71kB 2.5MB/s 
Collecting cryptography>=1.5 (from paramiko>=2.0.0->netmiko>=1.4.3->napalm)
  Downloading cryptography-2.2-cp34-abi3-macosx_10_6_intel.whl (1.5MB)
    100% |████████████████████████████████| 1.5MB 3.7MB/s 
Collecting pynacl>=1.0.1 (from paramiko>=2.0.0->netmiko>=1.4.3->napalm)
  Downloading PyNaCl-1.2.1-cp36-cp36m-macosx_10_6_intel.whl (243kB)
    100% |████████████████████████████████| 245kB 2.4MB/s 
Collecting bcrypt>=3.1.3 (from paramiko>=2.0.0->netmiko>=1.4.3->napalm)
  Downloading bcrypt-3.1.4-cp36-cp36m-macosx_10_6_intel.whl (51kB)
    100% |████████████████████████████████| 61kB 7.0MB/s 
Requirement already satisfied: setuptools>0.6 in ./venv/lib/python3.6/site-packages (from ncclient>=0.5.3->junos-eznc>=2.1.5->napalm)
Collecting certifi>=2017.4.17 (from requests>=2.7.0->pynxos->napalm)
  Downloading certifi-2018.1.18-py2.py3-none-any.whl (151kB)
    100% |████████████████████████████████| 153kB 4.3MB/s 
Collecting urllib3<1.23,>=1.21.1 (from requests>=2.7.0->pynxos->napalm)
  Downloading urllib3-1.22-py2.py3-none-any.whl (132kB)
    100% |████████████████████████████████| 133kB 2.7MB/s 
Collecting idna<2.7,>=2.5 (from requests>=2.7.0->pynxos->napalm)
  Downloading idna-2.6-py2.py3-none-any.whl (56kB)
    100% |████████████████████████████████| 61kB 3.7MB/s 
Collecting chardet<3.1.0,>=3.0.2 (from requests>=2.7.0->pynxos->napalm)
  Downloading chardet-3.0.4-py2.py3-none-any.whl (133kB)
    100% |████████████████████████████████| 143kB 3.7MB/s 
Collecting cffi>=1.7; platform_python_implementation != "PyPy" (from cryptography>=1.5->paramiko>=2.0.0->netmiko>=1.4.3->napalm)
  Downloading cffi-1.11.5-cp36-cp36m-macosx_10_6_intel.whl (241kB)
    100% |████████████████████████████████| 245kB 2.8MB/s 
Collecting asn1crypto>=0.21.0 (from cryptography>=1.5->paramiko>=2.0.0->netmiko>=1.4.3->napalm)
  Downloading asn1crypto-0.24.0-py2.py3-none-any.whl (101kB)
    100% |████████████████████████████████| 102kB 4.1MB/s 
Collecting pycparser (from cffi>=1.7; platform_python_implementation != "PyPy"->cryptography>=1.5->paramiko>=2.0.0->netmiko>=1.4.3->napalm)
  Downloading pycparser-2.18.tar.gz (245kB)
    100% |████████████████████████████████| 256kB 3.0MB/s 
Installing collected packages: future, jtextfsm, MarkupSafe, jinja2, netaddr, pyYAML, pyeapi, pyasn1, pycparser, cffi, asn1crypto, idna, six, cryptography, pynacl, bcrypt, paramiko, scp, pyserial, textfsm, netmiko, lxml, pyIOSXR, ncclient, junos-eznc, certifi, urllib3, chardet, requests, pynxos, napalm
  Running setup.py install for future ... done
  Running setup.py install for MarkupSafe ... done
  Running setup.py install for pyYAML ... done
  Running setup.py install for pyeapi ... done
  Running setup.py install for pycparser ... done
  Running setup.py install for textfsm ... done
  Running setup.py install for netmiko ... done
  Running setup.py install for pyIOSXR ... done
  Running setup.py install for ncclient ... done
  Running setup.py install for pynxos ... done
  Running setup.py install for napalm ... done
Successfully installed MarkupSafe-1.0 asn1crypto-0.24.0 bcrypt-3.1.4 certifi-2018.1.18 cffi-1.11.5 chardet-3.0.4 cryptography-2.2 future-0.16.0 idna-2.6 jinja2-2.10 jtextfsm-0.3.1 junos-eznc-2.1.7 lxml-4.2.0 napalm-2.3.0 ncclient-0.5.3 netaddr-0.7.19 netmiko-2.1.0 paramiko-2.4.1 pyIOSXR-0.52 pyYAML-3.12 pyasn1-0.4.2 pycparser-2.18 pyeapi-0.8.2 pynacl-1.2.1 pynxos-0.0.3 pyserial-3.4 requests-2.18.4 scp-0.10.2 six-1.11.0 textfsm-0.3.2 urllib3-1.22
You are using pip version 9.0.1, however version 9.0.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
(venv) shogokobayashi napalm $ 

Documentation can be found here.

And you can get the interfaces’ ip address with small script as follows:

from pprint import pprint


def get_interfaces_ip(ip):

    import napalm

    driver = napalm.get_network_driver('ios')
    device = driver(hostname=ip, username='api-user', password='*******' )

    device.open()
    interfaces = device.get_interfaces_ip()
    device.close()

    return interfaces


if __name__ == "__main__":
    pprint(get_interfaces_ip("<ip_address or hostname>"))

And output is as follows:

{'GigabitEthernet1': {'ipv4': {'10.1.145.191': {'prefix_length': 24}}},
 'GigabitEthernet2': {'ipv4': {'10.1.17.32': {'prefix_length': 24}}},
 'VirtualPortGroup0': {'ipv4': {'10.0.0.254': {'prefix_length': 24}}}}

The beauty of this script is:

  • It doesn’t require IOS device to be configured with restconf nor netconf, just needs to be accessible via ssh.
  • It can retrieve other vendors device information if you change ‘ios’ to other platform, such as ‘fortios’.

API on Cisco: IOS-XE 使ってみる (RESTCONF)

NETCONFに続き、RESTCONFのエントリー

 

このエントリーはRESTCONFの概要と、ルータから情報を引きだせるところまで。

ビデオで概要を知る


参考モジュール: Network Device APIs

  1. Learn to CRUD with GET, POST and DELETE using RESTCONF … RESTCONFの使い方概要

 

ラボ環境を作る


AWSに立てたCSR1000Vでテストを行う

    1. CSR1000Vを立ち上げる
    2. sshでCSR1000Vにアクセス
    3. RESTCONFでアクセスできる設定
ip-172-31-2-170#
ip-172-31-2-170#sh ver | inc Cisco IOS
Cisco IOS XE Software, Version 16.07.01a
Cisco IOS Software [Fuji], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.7.1a, RELEASE SOFTWARE (fc4)
Cisco IOS-XE software, Copyright (c) 2005-2017 by cisco Systems, Inc.
All rights reserved.  Certain components of Cisco IOS-XE software are
ip-172-31-2-170#
ip-172-31-2-170#conf t
Enter configuration commands, one per line.  End with CNTL/Z.
ip-172-31-2-170(config)#restconf
ip-172-31-2-170(config)#
ip-172-31-2-170(config)#ip http secure-server
ip-172-31-2-170(config)#exit
ip-172-31-2-170#

 

RESTCONFを使ってみる


GigabitEthernet1の状態を確認するスクリプト

import requests
import pprint

def get_json(interface):

    source = 'https://xxxxxx.compute-x.amazonaws.com/restconf/data/'
    module = 'ietf-interfaces:'
    container = 'interfaces'
    leaf = '/interface=' + interface
    options = ''

    url = source + module + container + leaf + options
    headers = {'Content-type': 'application/yang-data+json', 'Accept': 'application/yang-data+json'}

    r = requests.get(url, auth=('api-user', 'xxxxxx'), headers=headers, verify=False)

    return r.json()

if __name__ == '__main__':

    interface = 'GigabitEthernet1'

    pprint.pprint(get_json(interface))

CSR1000Vに対して実行したところ。インタフェースの情報が取得できた。

$ python3 get_one_interface.py
{'ietf-interfaces:interface': {'enabled': True,
                               'ietf-ip:ipv4': {'address': [{'ip': '172.31.2.170',
                                                             'netmask': '255.255.255.0'}]},
                               'ietf-ip:ipv6': {},
                               'name': 'GigabitEthernet1',
                               'type': 'iana-if-type:ethernetCsmacd'}}
$

GigabitEthernet1の設定を変更するスクリプト

import requests
import json

import get_one_interface

def put_json(interface, data):

    source = 'https://xxxxxx.compute-x.amazonaws.com/restconf/data/'
    module = 'ietf-interfaces:'
    container = 'interfaces'
    leaf = '/interface=' + interface
    options = ''

    url = source + module + container + leaf + options
    headers = {'Content-type': 'application/yang-data+json', 'Accept': 'application/yang-data+json'}

    r = requests.put(url, data=data, auth=('api-user', 'xxxxxx'), headers=headers, verify=False)

    return r.status_code


if __name__ == '__main__':
    # retrieve current config of the interface
    interface = 'GigabitEthernet1'
    interface_config = get_one_interface.get_json(interface)

    # add 'description' into the interface
    interface_config['ietf-interfaces:interface']['description'] = 'Configured By RESTCONF'

    print(put_json(interface, json.dumps(interface_config)))

実行した結果がこちら。ステータスコードは204なので成功。

$ python3 put_one_interface.py 
204
$

もう一度インターフェース情報を取得すると、descriptionが追加されているのが確認できる

$ python3 get_one_interface.py 
{'ietf-interfaces:interface': {'description': 'Configured By RESTCONF',
                               'enabled': True,
                               'ietf-ip:ipv4': {'address': [{'ip': '172.31.2.170',
                                                             'netmask': '255.255.255.0'}]},
                               'ietf-ip:ipv6': {},
                               'name': 'GigabitEthernet1',
                               'type': 'iana-if-type:ethernetCsmacd'}}
$