├── Ansible
├── ansible.cfg
├── hosts
├── playbook-apply_required_IND_IOS-configuration.yml
├── playbook-apply_required_IND_IOS-confiuration.yml
├── playbook-configure-vlan-int-netconf.yml
├── playbook-configure-vlan-int.yml
├── playbook-enable-netconf-restconf.yml
├── playbook-save-output-to-file.yml
├── playbook-save-run-to-file.yml
├── playbook-save-run-to-file_netconf.yml
└── playbook-simple-ping.yml
├── LICENSE.md
├── NETCONF
├── netconf-IR1101.py
├── netconf-get_allinterfaces.py
├── netconf-getting-started.py
├── netconf-remote-access-netconf-acls.py
└── netconf-webexbot.py
├── NOTICE.md
├── README.md
├── RESTCONF
├── restconf-get_allinterfaces.py
└── restconf-getting-started.py
├── images
├── automation_1.png
├── automation_2.png
├── industrial_netdevops_manufacturing.png
├── iot-hardware-overview.png
└── webex-bot.png
└── requirements.txt
/Ansible/ansible.cfg:
--------------------------------------------------------------------------------
1 | #Simple Ansible Configuration file for Devbox (CentOS 7 Linux)
2 |
3 | [defaults]
4 | inventory = /home/developer/ansible/hosts
--------------------------------------------------------------------------------
/Ansible/hosts:
--------------------------------------------------------------------------------
1 | #Example Ansible hosts File
2 | #ansible_user -> IOS username
3 | #[cell] -> name of the group
4 |
5 | [all:vars]
6 | ansible_user=developer
7 | ansible_network_os=ios
8 | ansible_connection=network_cli
9 |
10 | [cell]
11 | 10.10.20.81
--------------------------------------------------------------------------------
/Ansible/playbook-apply_required_IND_IOS-configuration.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # Required IOs Configuration for IND 1.8
4 | # https://www.cisco.com/c/en/us/td/docs/switches/ind/release_notes/rn-iot-ind-1-8.html#pgfId-315890
5 | # Please check the documentation for further modifications
6 | # You may also check out working with variables
7 |
8 |
9 | - name: IND required IOS-Configuration
10 | hosts: all
11 | gather_facts: no
12 |
13 | tasks:
14 | - ios_config:
15 | lines:
16 | - snmp-server community mycommunity ro
17 | - snmp-server group mygroup v3 noauth
18 | - snmp-server user myusername mygroup v3
19 | - username myusername privilege 15 password 0 mypassword
20 | - aaa new-model
21 | - aaa authentication login default local
22 | - aaa authorization exec default local
23 | - ip ssh version 2
24 | - ip http server
25 | - ip http secure-server
26 | - ip http authentication aaa login-authentication default
27 | - line vty 0 15
28 | - login authentication default
29 | - transport input all
30 | - transport output all
31 |
32 |
--------------------------------------------------------------------------------
/Ansible/playbook-apply_required_IND_IOS-confiuration.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # Required IOs Configuration for IND 1.8
4 | # https://www.cisco.com/c/en/us/td/docs/switches/ind/release_notes/rn-iot-ind-1-8.html#pgfId-315890
5 | # Please check the documentation for further modifications
6 | # You may also check out working with variables
7 |
8 |
9 | - name: IND required IOS-Configuration
10 | hosts: all
11 | gather_facts: no
12 |
13 | tasks:
14 | - ios_config:
15 | lines:
16 | - snmp-server community mycommunity ro
17 | - snmp-server group mygroup v3 noauth
18 | - snmp-server user myusername mygroup v3
19 | - username myusername privilege 15 password 0 mypassword
20 | - aaa new-model
21 | - aaa authentication login default local
22 | - aaa authorization exec default local
23 | - ip ssh version 2
24 | - ip http server
25 | - ip http secure-server
26 | - ip http authentication aaa login-authentication default
27 | - line vty 0 15
28 | - login authentication default
29 | - transport input all
30 | - transport output all
31 |
32 |
--------------------------------------------------------------------------------
/Ansible/playbook-configure-vlan-int-netconf.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Configure VLAN Interface with NETCONF
4 | hosts: all
5 | gather_facts: no
6 |
7 | tasks:
8 |
9 | - set_fact:
10 | ansible_connection: netconf
11 | ansible_network_os: default
12 |
13 | - name: Configure VLAN Interface with NETCONF
14 | netconf_config:
15 | hostkey_verify: False
16 | content: |
17 |
18 |
19 |
20 |
21 | 90
22 |
23 |
24 |
25 | 192.168.20.1
26 | 255.255.255.0
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Ansible/playbook-configure-vlan-int.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: configure Vlan interface
4 | hosts: all
5 | gather_facts: no
6 |
7 | tasks:
8 | - ios_config:
9 | lines:
10 | - description configured via ios_config module
11 | - ip address 192.168.100.1 255.255.255.0
12 | before: interface Vlan10
--------------------------------------------------------------------------------
/Ansible/playbook-enable-netconf-restconf.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: enable NETCONF and RESTCONF
4 | hosts: all
5 | gather_facts: no
6 |
7 | tasks:
8 | - ios_config:
9 | lines:
10 | - netconf-yang
11 | - restconf
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Ansible/playbook-save-output-to-file.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: saving CLI output to a file
4 | hosts: all
5 | gather_facts: no
6 |
7 | tasks:
8 | - ios_command:
9 | commands: "show ip route"
10 | register: output
11 | - copy: content="{{ output.stdout }}" dest=file.txt
--------------------------------------------------------------------------------
/Ansible/playbook-save-run-to-file.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Create a backup on your computer of your IOS running-configuration
4 | hosts: all
5 | gather_facts: no
6 |
7 | tasks:
8 | - ios_config:
9 | backup: yes
10 | # backup_options:
11 | # dir_path:
--------------------------------------------------------------------------------
/Ansible/playbook-save-run-to-file_netconf.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Save running configuration as a pretty XML to a file
4 | hosts: all
5 | gather_facts: no
6 |
7 | tasks:
8 |
9 | - set_fact:
10 | ansible_connection: netconf
11 | ansible_network_os: default
12 |
13 | - name: Backup the running configuration in XML
14 | netconf_config:
15 | hostkey_verify: no
16 | backup: yes
17 | backup_options:
18 | dir_path: backup/
19 | filename: backup_running-config_{{ inventory_hostname }}.txt
20 |
21 | - name: Make XML pretty
22 | xml:
23 | input_type: xml
24 | path: backup/backup_running-config_{{ inventory_hostname }}.txt
25 | pretty_print: yes
26 |
--------------------------------------------------------------------------------
/Ansible/playbook-simple-ping.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: simple ping
4 | hosts: all
5 | gather_facts: no
6 |
7 | tasks:
8 | - ping:
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2020 Cisco Systems
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/NETCONF/netconf-IR1101.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # NETCONF on Cisco IR1101
4 | # Flo Pachinger / flopach, Cisco Systems, Dec 2019
5 | # Apache License 2.0
6 | #
7 | from ncclient import manager
8 | import xmltodict
9 | import xml.dom.minidom
10 |
11 | #Input here the connection parameters for the IOS XE device
12 | #Do not forget to enable RESTCONF: device(config)#netconf-yang
13 | m = manager.connect(host="",
14 | port=830,
15 | username="",
16 | password="",
17 | hostkey_verify=False)
18 |
19 | # get capabilities / all supported YANG models of the device
20 | def get_capabilities():
21 | for c in m.server_capabilities:
22 | print(c)
23 |
24 | # get the running config in XML of the device
25 | def get_running_config():
26 | netconf_reply = m.get_config(source='running')
27 | netconf_data = xml.dom.minidom.parseString(netconf_reply.xml).toprettyxml()
28 | print(netconf_data)
29 |
30 | # Get the status of all interfaces of the device
31 | def get_allinterfaces():
32 | filter = '''
33 |
34 |
35 |
36 |
37 | '''
38 | netconf_reply = m.get_config(source='running', filter=filter)
39 | netconf_data = xmltodict.parse(netconf_reply.xml)
40 | for x in netconf_data["rpc-reply"]["data"]["interfaces"]["interface"]:
41 | print("Interface: {} - enabled:{}".format(x["name"], x["enabled"]))
42 |
43 | # Enable or disable the interface of FastEthernet 4 on the IR1101
44 | # depends on the user input
45 | def change_interface4(v):
46 | config = '''
47 |
48 |
49 |
50 | FastEthernet0/0/4
51 | false
52 |
53 |
54 |
55 | '''
56 | config_dict = xmltodict.parse(config)
57 |
58 | if v == 1:
59 | config_dict["config"]["interfaces"]["interface"]["enabled"] = "true"
60 | config = xmltodict.unparse(config_dict)
61 |
62 | netconf_reply = m.edit_config(target='running', config=config)
63 | print("Did it work? {}".format(netconf_reply.ok))
64 |
65 | # Get SCADA information from the IR1101
66 | def set_scada_config():
67 | config = '''
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | testchannel101
76 |
77 | Async0/2/0
78 |
79 |
80 |
81 | testsession101
82 |
83 |
84 | testsector101
85 |
86 |
87 |
88 |
89 |
90 |
91 | testchannel104
92 |
93 |
94 | testsession104
95 |
96 |
97 | testsector104
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | '''
106 | netconf_reply = m.edit_config(target='running', config=config)
107 | print("Did it work? {}".format(netconf_reply.ok))
108 |
109 | if __name__ == "__main__":
110 | while True:
111 | print("""Welcome to RESTCONF on IR1101! Here are your options
112 | 0: Quit
113 | 1: Get all the YANG models from the device
114 | 2: Get running config
115 | 3: Get status of all interfaces of the device
116 | 4e: Enable FastEthernet 4 on IR1101
117 | 4d: Disable FastEthernet 4 on IR1101
118 | 5: Set predefined SCADA config to IR1101\n
119 | """)
120 | var = input("Enter: ")
121 | if var == "0":
122 | exit()
123 | elif var == "1":
124 | get_capabilities()
125 | elif var == "2":
126 | get_running_config()
127 | elif var == "3":
128 | get_allinterfaces()
129 | elif var == "4e":
130 | change_interface4(1)
131 | elif var == "4d":
132 | change_interface4(0)
133 | elif var == "5":
134 | set_scada_config()
135 | else:
136 | print("Wrong input")
137 | print("Done.\n")
138 |
--------------------------------------------------------------------------------
/NETCONF/netconf-get_allinterfaces.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Get all interfaces NETCONF
4 | # Flo Pachinger / flopach, Cisco Systems, Dec 2019
5 | # Apache License 2.0
6 | #
7 | from ncclient import manager
8 | import xmltodict
9 | import xml.dom.minidom
10 |
11 | #Input here the connection parameters for the IOS XE device
12 | #Do not forget to enable RESTCONF: device(config)#netconf-yang
13 | m = manager.connect(host="",
14 | port=830,
15 | username="",
16 | password="",
17 | hostkey_verify=False)
18 |
19 | # Get the status of all interfaces of the device
20 | def get_allinterfaces():
21 | filter = '''
22 |
23 |
24 |
25 |
26 | '''
27 | netconf_reply = m.get_config(source='running', filter=filter)
28 | netconf_data = xmltodict.parse(netconf_reply.xml)
29 | for x in netconf_data["rpc-reply"]["data"]["interfaces"]["interface"]:
30 | print("Interface: {} - enabled:{}".format(x["name"], x["enabled"]))
31 |
32 | get_allinterfaces()
--------------------------------------------------------------------------------
/NETCONF/netconf-getting-started.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # NETCONF getting started
4 | # Flo Pachinger / flopach, Cisco Systems, Dec 2019
5 | # Apache License 2.0
6 | #
7 | from ncclient import manager
8 | import xmltodict
9 | import xml.dom.minidom
10 | import lxml.etree as ET
11 |
12 | #Input here the connection parameters for the IOS XE device
13 | #Do not forget to enable NETCONF: device(config)#netconf-yang
14 | m = manager.connect(host="10.10.20.48",
15 | port=830,
16 | username="developer",
17 | password="C1sco12345",
18 | hostkey_verify=False)
19 |
20 | print("Connected.")
21 |
22 | # get the running config in XML of the device
23 | def get_running_config():
24 | netconf_reply = m.get_config(source='running')
25 | netconf_data = xml.dom.minidom.parseString(netconf_reply.xml).toprettyxml()
26 | print(netconf_data)
27 |
28 | # get hostname and IOS version of the device - loading the whole config
29 | # this should take longer
30 | def get_hostname():
31 | netconf_reply = m.get_config(source='running')
32 | netconf_data = xmltodict.parse(netconf_reply.xml)
33 | print("IOS Version: {}".format(netconf_data["rpc-reply"]["data"]["native"]["version"]))
34 | print("Hostname: {}".format(netconf_data["rpc-reply"]["data"]["native"]["hostname"]))
35 |
36 | # get hostname and IOS version of the device - using a filter
37 | # this should be faster
38 | def get_hostname_filter():
39 | filter = '''
40 |
41 |
42 |
43 |
44 |
45 |
46 | '''
47 | netconf_reply = m.get_config(source='running', filter=filter)
48 | netconf_data = xmltodict.parse(netconf_reply.xml)
49 | print("IOS Version: {}".format(netconf_data["rpc-reply"]["data"]["native"]["version"]))
50 | print("Hostname: {}".format(netconf_data["rpc-reply"]["data"]["native"]["hostname"]))
51 |
52 | # get capabilities / all supported YANG models of the device
53 | def get_capabilities():
54 | for c in m.server_capabilities:
55 | print(c)
56 |
57 | # change interface GigabitEthernet3
58 | # 0 --> disable | 1 --> enable
59 | def change_interface(user_selection):
60 | config = '''
61 |
62 |
63 |
64 | GigabitEthernet3
65 | false
66 |
67 |
68 |
69 | '''
70 | config_dict = xmltodict.parse(config)
71 |
72 | if int(user_selection) == 1:
73 | config_dict["config"]["interfaces"]["interface"]["enabled"] = "true"
74 | config = xmltodict.unparse(config_dict)
75 |
76 | netconf_reply = m.edit_config(target='running', config=config)
77 | print("Did it work? {}".format(netconf_reply.ok))
78 |
79 | # copy run start
80 | def save_running_config():
81 | rpc_body = ''''''
82 | netconf_reply = m.dispatch(ET.fromstring(rpc_body)).xml
83 | print("Did it work? {}".format(netconf_reply))
84 |
85 | if __name__ == "__main__":
86 | while True:
87 | print("""Welcome to NETCONF on IOS XE devices! Here are your options
88 | 0: Quit
89 | 1: Get running config
90 | 2: Get hostname and IOS version (whole config)
91 | 3: Get hostname and IOS version (filter used)
92 | 4: Get all the YANG models from the device
93 | 5: Enable GigabitEthernet3
94 | 6: Disable GigabitEthernet3
95 | 7: Save the running-configuration
96 | """)
97 | var = input("Enter: ")
98 | if var == "0":
99 | exit()
100 | elif var == "1":
101 | get_running_config()
102 | elif var == "2":
103 | get_hostname()
104 | elif var == "3":
105 | get_hostname_filter()
106 | elif var == "4":
107 | get_capabilities()
108 | elif var == "5":
109 | change_interface(1)
110 | elif var == "6":
111 | change_interface(0)
112 | elif var == "7":
113 | save_running_config()
114 | else:
115 | print("Wrong input")
116 | print("Done.\n")
117 |
--------------------------------------------------------------------------------
/NETCONF/netconf-remote-access-netconf-acls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Remote Access with NETCONF + ACLs
4 | # Flo Pachinger / flopach, Cisco Systems, May 2020
5 | # Apache License 2.0
6 | #
7 | from ncclient import manager
8 | import xmltodict
9 | import xml.dom.minidom
10 |
11 | m = manager.connect(host="172.19.88.15",port=830,username="cisco",password="Cisco123!",hostkey_verify=False)
12 |
13 | # get the running config in XML of the device
14 | def get_running_config():
15 | netconf_reply = m.get_config(source='running')
16 | netconf_data = xml.dom.minidom.parseString(netconf_reply.xml).toprettyxml()
17 | print(netconf_data)
18 |
19 | # Create the ACL
20 | def create_remoteaccess():
21 | config = '''
22 |
23 |
24 |
25 |
26 |
27 | remote-access
28 |
29 | 10
30 |
31 | permit
32 | tcp
33 |
34 |
35 | 22
36 |
37 |
38 |
39 | 20
40 |
41 | permit
42 | tcp
43 |
44 |
45 | 830
46 |
47 |
48 |
49 | 30
50 |
51 | permit
52 | tcp
53 |
54 | 172.19.88.14
55 | www
56 |
57 |
58 |
59 | 40
60 |
61 | deny
62 | ip
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | '''
73 | config_dict = xmltodict.parse(config)
74 |
75 | netconf_reply = m.edit_config(target='running', config=config)
76 | print("Did it work? {}".format(netconf_reply.ok))
77 |
78 | # Add or delete the ACL group to the Northbound-Interface
79 | def change_remoteaccess():
80 | config = '''
81 |
82 |
83 |
84 |
85 | 1/3
86 |
87 |
88 |
89 |
90 | remote-access
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | '''
101 | config_dict = xmltodict.parse(config)
102 |
103 | if int(var) == 4:
104 | config_dict["config"]["native"]["interface"]["GigabitEthernet"]["ip"]["access-group"]["@operation"] = "delete"
105 | config = xmltodict.unparse(config_dict)
106 |
107 | netconf_reply = m.edit_config(target='running', config=config)
108 | print("Did it work? {}".format(netconf_reply.ok))
109 |
110 | if __name__ == "__main__":
111 | while True:
112 | print("""Welcome to RESTCONF on IOS XE devices! Here are your options
113 | 0: Quit
114 | 1: Get running config
115 | 2: Create Remote Access ACL
116 | 3: Enable Remote Access on Interface 1/3
117 | 4: Disable Remote Access on Interface 1/3
118 | """)
119 | var = input("Enter: ")
120 | if var == "0":
121 | exit()
122 | elif var == "1":
123 | get_running_config()
124 | elif var == "2":
125 | create_remoteaccess()
126 | elif var == "3":
127 | change_remoteaccess()
128 | elif var == "4":
129 | change_remoteaccess()
130 | else:
131 | print("Wrong input")
132 | print("Done.\n")
133 |
--------------------------------------------------------------------------------
/NETCONF/netconf-webexbot.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # NETCONF Webex Bot
4 | # Flo Pachinger / flopach, Cisco Systems, Dec 2019
5 | # Apache License 2.0
6 | #
7 | from webexteamsbot import TeamsBot
8 | import json
9 | from ncclient import manager
10 | import xmltodict
11 |
12 | # Retrieve required details from environment variables
13 | bot_email = "xxx@webex.bot"
14 | bot_token = ""
15 | bot_url = "" #try with ngrok.io
16 | bot_app_name = ""
17 |
18 | m = manager.connect(host="", port=830, username="", password="", hostkey_verify=False)
19 |
20 | # Create a Bot Object
21 | bot = TeamsBot(
22 | bot_app_name,
23 | teams_bot_token=bot_token,
24 | teams_bot_url=bot_url,
25 | teams_bot_email=bot_email,
26 | )
27 |
28 | # The bot returns the hostname and IOS version of the device
29 | def getinfo(incoming_msg):
30 | netconf_reply = m.get_config(source='running')
31 | netconf_data = xmltodict.parse(netconf_reply.xml)
32 | return "Hostname: {}, IOS Version: {}".format(netconf_data["rpc-reply"]["data"]["native"]["hostname"],netconf_data["rpc-reply"]["data"]["native"]["version"])
33 |
34 | # The bot returns all interfaces of the device
35 | def getallinterfaces(incoming_msg):
36 | filter = '''
37 |
38 |
39 |
40 | '''
41 | netconf_reply = m.get_config(source='running', filter=filter)
42 | netconf_data = xmltodict.parse(netconf_reply.xml)
43 | msg = ""
44 | for x in netconf_data["rpc-reply"]["data"]["interfaces"]["interface"]:
45 | msg = msg + "* Interface: {} - enabled: {} \n ".format(x["name"], x["enabled"])
46 | return msg
47 |
48 | # Enable or disable the interface of FastEthernet 4
49 | # depends on the user input
50 | def change_interface4(incoming_msg):
51 | config = '''
52 |
53 |
54 |
55 | FastEthernet0/0/4
56 | true
57 |
58 |
59 |
60 | '''
61 | config_dict = xmltodict.parse(config)
62 |
63 | # most basic differentiation of the user's command.
64 | if incoming_msg.text == "/changeint4 enable":
65 | netconf_reply = m.edit_config(target='running', config=config)
66 | return "Int4 was successfully enabled."
67 | elif incoming_msg.text == "/changeint4 disable":
68 | config_dict["config"]["interfaces"]["interface"]["enabled"] = "false"
69 | config = xmltodict.unparse(config_dict)
70 | netconf_reply = m.edit_config(target='running', config=config)
71 | return "Int4 was successfully disabled."
72 | else:
73 | return "Wrong command, please try again."
74 |
75 | if __name__ == "__main__":
76 | # Add new commands to the box.
77 | bot.add_command("/getinfo", "Get basic device information", getinfo)
78 | bot.add_command("/getallinterfaces", "Get information from all interfaces", getallinterfaces)
79 | bot.add_command("/changeint4", "disable or enable Interface 4: e.g. /changeint4 disable", change_interface4)
80 | bot.remove_command("/echo")
81 | bot.set_help_message("Welcome to the IoT Config Bot! You can use the following commands:\n")
82 |
83 | # Run Bot
84 | bot.run(host="0.0.0.0", port=5000)
85 |
--------------------------------------------------------------------------------
/NOTICE.md:
--------------------------------------------------------------------------------
1 | industrial-netdevops
2 |
3 | Copyright (c) 2020 Cisco Systems, Inc. and/or its affiliates
4 |
5 | This project includes software developed at Cisco Systems, Inc. and/or its affiliates.
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://developer.cisco.com/codeexchange/github/repo/CiscoDevNet/industrial-netdevops)
2 |
3 | # Industrial NetDevOps: Getting Started with Programmability & APIs on Cisco IoT Hardware
4 |
5 | > **Industrial NetDevOps is the combination of the tools and best practices from DevOps with network engineering and operations in industrial networks.**
6 |
7 | Instead of using SNMP and CLI, you configure, manage and monitor industrial network devices via **standardized network device APIs and software automation tools**. Industrial NetDevOps workflows use Open Source, standards and Python scripts alongside commercial devices and tools to deliver flexible and fast-responsive industrial networks.
8 |
9 | This is a collection of scripts which will get you started on how NETCONF & RESTCONF works especially on Cisco IoT hardware. Also, some Ansible playbooks are here to get you started with Ansible.
10 |
11 | * IOS XE on Cisco IR1101
12 | * IOS XE on Cisco IE3200, IE3300, IE3400
13 | * IOX XE on Cisco ESS3300
14 |
15 | 
16 |
17 | ## Script Overview
18 |
19 | > For more information on how to use these scripts, definitely check out the learning labs at [DevNet Learning Labs](https://developer.cisco.com/learning/tracks).
20 |
21 | 
22 |
23 | ### /NETCONF
24 |
25 | **netconf-getting-started.py**: Specifically created for the DevNet Learning Lab
26 |
27 | * YANG models what the device is supporting
28 | * Running Config in XML
29 | * Get one single value using whole config and a XML filter
30 | * Get the status of all interfaces
31 |
32 | **netconf-IR1101.py**
33 |
34 | Get the lastest YANG model here: https://github.com/YangModels/yang/tree/master/vendor/cisco/xe (Version 17.x)
35 |
36 | * Basic functions
37 | * Enable/disable Interface 4
38 | * Set a SCADA Config. This is possible since 17.1. You can find more information in the [software configuration guide](https://www.cisco.com/c/en/us/td/docs/routers/access/1101/software/configuration/guide/b_IR1101config/b_IR1101config_chapter_01111.html#con_1138532)
39 |
40 | **netconf-webexbot.py**
41 |
42 | You can also connect the configuration part with a Webex Teams bot to make it more interactive (**ChatOps**).
43 |
44 | 1. Create easily a Webex bot online at [developer.webex.com](https://developer.webex.com)
45 | 2. Add the credentials of your bot in the script. You can find information on how to setup your bot with ngrok.io here: https://developer.cisco.com/learning/modules/spark-apps/collab-spark-botl-ngrok/step/1
46 | 3. Ready to use
47 |
48 | 
49 |
50 | ### /RESTCONF
51 |
52 | **restconf-getting-started.py**: Specifically created for the DevNet Learning Lab
53 |
54 | * YANG models what the device is supporting
55 | * Running Config in JSON
56 | * Get one single value of the configuration
57 | * Get the status of all interfaces
58 |
59 | ### /Ansible
60 |
61 | * Ansible sample Playbooks and Hosts file to get you started
62 |
63 | Follow the [Introduction to Ansible for IOS XE Configuration Management Learning Labs](https://developer.cisco.com/learning/modules/intro-ansible-iosxe) on how to apply the playbooks.
64 |
65 | ## Use-cases
66 |
67 | ### Network Automation
68 |
69 | 
70 |
71 | ### Industrial NetDevOps in Manufacturing
72 |
73 | Here is an example architecture where your Industrial NetDevOps tools can be placed and used.
74 |
75 | 
76 |
77 | ## Getting your Hands dirty
78 |
79 | For more information on how to use these scripts, definitely check out the learning labs at [DevNet Learning Labs](https://developer.cisco.com/learning/tracks).
80 |
81 | ### A) Setup your virtual environment (optional)
82 |
83 | 1. Clone this repo in your project folder via git
84 |
85 | ```
86 | git clone https://github.com/flopach/industrial-netdevops
87 | ```
88 |
89 | 2. Create your python virtual environment
90 |
91 | Python 2
92 |
93 | ```
94 | pip install virtualenv
95 | python -m virtualenv venv_dir
96 | ```
97 |
98 | Python 3
99 |
100 | ```
101 | pip3 install virtualenv
102 | python3 -m venv venv_dir
103 | ```
104 |
105 | 3. Activate your virtual environment
106 |
107 | ```
108 | source venv_dir/bin/activate
109 | ```
110 |
111 | 4. Install the required python libraries (depending on your python version):
112 |
113 | ```
114 | pip install -r requirements.txt
115 | pip3 install -r requirements.txt
116 | ```
117 |
118 | 5. Now you can run your script.
119 |
120 | ### B) Configuration needed on the device
121 |
122 | Do not forget to enable NETCONF and/or RESTCONF on the device when using the APIs.
123 |
124 | ```
125 | device(config)#netconf-yang
126 | device(config)#restconf
127 | ```
128 |
129 | ## Built With
130 |
131 | * [ncclient](https://github.com/ncclient/ncclient) - a very useful python library for NETCONF
132 | * [Webexteamsbot](https://github.com/hpreston/webexteamsbot) - Easy python library to create Webex Bots
133 | * [Webexteamssdk](https://github.com/CiscoDevNet/webexteamssdk) - Webex Teams python library
134 | * [XMLtoDict](https://github.com/martinblech/xmltodict) - Python library to switch between XML and dicts
135 |
136 | ## Versioning
137 |
138 | **11/2020**: Updated NETCONF scripts and License
139 |
140 | **06/2020**: Added RESTCONF, Ansible and Industrial NetDevOps Story
141 |
142 | **12/2019**: Added 3 inital scripts
143 |
144 | ## License
145 |
146 | This project is licensed under the Apache License 2.0 - see the [LICENSE.md](LICENSE.md) file for details
147 |
148 | ## Further Links
149 |
150 | * [Cisco DevNet Website](https://developer.cisco.com)
151 |
--------------------------------------------------------------------------------
/RESTCONF/restconf-get_allinterfaces.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Get all interfaces RESTCONF
4 | # Flo Pachinger / flopach, Cisco Systems, Dec 2019
5 | # Apache License 2.0
6 | #
7 | import requests
8 | import json
9 |
10 | #Input here the connection parameters for the IOS XE device
11 | #Do not forget to enable RESTCONF: device(config)#restconf
12 | host = '172.19.88.15'
13 | port = 443
14 | username = 'cisco'
15 | password = 'Cisco123!'
16 |
17 | def get_allinterfaces():
18 | url = "https://{h}:{p}/restconf/data/ietf-interfaces:interfaces".format(h=host, p=port)
19 | headers = { "Accept" : "application/yang-data+json"}
20 | response = requests.get(url, auth=(username, password),headers=headers, verify=False)
21 | print(json.dumps(response.json(),indent=4))
22 |
23 | get_allinterfaces()
--------------------------------------------------------------------------------
/RESTCONF/restconf-getting-started.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # RESTCONF getting started
4 | # Flo Pachinger / flopach, Cisco Systems, Dec 2019
5 | # Apache License 2.0
6 | #
7 | import requests
8 | import json
9 |
10 | #Input here the connection parameters for the IOS XE device
11 | #Do not forget to enable RESTCONF: device(config)#restconf
12 | host = '10.10.20.48'
13 | port = 443
14 | username = 'developer'
15 | password = 'C1sco12345'
16 |
17 |
18 | # get the running config in XML of the device
19 | def get_running_config_json():
20 | url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native".format(h=host, p=port)
21 | headers = { "Accept" : "application/yang-data+json"}
22 | response = requests.get(url, auth=(username, password),headers=headers, verify=False)
23 | print(response.text)
24 |
25 | # get the running config in XML of the device
26 | def get_running_config_xml():
27 | url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native".format(h=host, p=port)
28 | headers = { "Accept" : "application/yang-data+xml"}
29 | response = requests.get(url, auth=(username, password),headers=headers, verify=False)
30 | print(response.text)
31 |
32 | # get the hostname of the device
33 | def get_hostname():
34 | url = "https://{h}:{p}/restconf/data/Cisco-IOS-XE-native:native/hostname".format(h=host, p=port)
35 | headers = { "Accept" : "application/yang-data+json"}
36 | response = requests.get(url, auth=(username, password),headers=headers, verify=False)
37 | print(response.text)
38 |
39 | # get all the YANG models
40 | def get_yangmodels():
41 | url = "https://{h}:{p}/restconf/data/netconf-state/capabilities".format(h=host, p=port)
42 | headers = { "Accept" : "application/yang-data+json"}
43 | response = requests.get(url, auth=(username, password),headers=headers, verify=False)
44 | print(json.dumps(response.json(),indent=4))
45 |
46 | # get all interfaces
47 | def get_allinterfaces():
48 | url = "https://{h}:{p}/restconf/data/ietf-interfaces:interfaces".format(h=host, p=port)
49 | headers = { "Accept" : "application/yang-data+json"}
50 | response = requests.get(url, auth=(username, password),headers=headers, verify=False)
51 | print(json.dumps(response.json(),indent=4))
52 |
53 | # get all interfaces and the enabled/disabled status
54 | def get_allinterfaces_status():
55 | url = "https://{h}:{p}/restconf/data/ietf-interfaces:interfaces".format(h=host, p=port)
56 | headers = { "Accept" : "application/yang-data+json"}
57 | response = requests.get(url, auth=(username, password),headers=headers, verify=False)
58 | jsondata = json.loads(response.content)
59 |
60 | #Request all information an cycle through each interface
61 | for item in jsondata["ietf-interfaces:interfaces"]["interface"]:
62 | if "name" in item:
63 | int_name = item["name"]
64 | if "enabled" in item:
65 | int_status = item["enabled"]
66 | print("{} - enabled: {}".format(int_name,int_status))
67 |
68 | # change interface GigabitEthernet3
69 | # 0 --> disable | 1 --> enable
70 | def change_interface(user_selection):
71 | if int(user_selection) == 1:
72 | int_status = "true"
73 | else:
74 | int_status = "false"
75 |
76 | url = "https://{h}:{p}/restconf/data/ietf-interfaces:interfaces/interface=GigabitEthernet3".format(h=host, p=port)
77 | headers = { "Content-Type" : "application/yang-data+json", "Accept" : "application/yang-data+json"}
78 | config = """
79 | {
80 | "ietf-interfaces:interface": {
81 | "name": "GigabitEthernet3",
82 | "type": "iana-if-type:ethernetCsmacd",
83 | "enabled": """+int_status+"""
84 | }
85 | }
86 | """
87 |
88 | response = requests.put(url, auth=(username, password),headers=headers, data=config, verify=False)
89 | print(response.status_code)
90 |
91 | # copy running-configuration startup-configuration
92 | def save_running_config():
93 | url = "https://{h}:{p}/restconf/operations/cisco-ia:save-config".format(h=host, p=port)
94 | headers = { "Accept" : "application/yang-data+json"}
95 | response = requests.post(url, auth=(username, password),headers=headers, verify=False)
96 | print(response.status_code)
97 |
98 | if __name__ == "__main__":
99 | while True:
100 | print("""Welcome to RESTCONF on IOS XE devices! Here are your options
101 | 0: Quit
102 | 1: Get running config (JSON)
103 | 2: Get running config (XML)
104 | 3: Get the hostname
105 | 4: Get all YANG models
106 | 5: Get all Interfaces
107 | 6: Get the status of all interfaces
108 | 7: Enable GigabitEthernet3
109 | 8: Disable GigabitEthernet3
110 | 9: Save the running-configuration
111 | """)
112 | var = input("Enter: ")
113 | if var == "0":
114 | exit()
115 | elif var == "1":
116 | get_running_config_json()
117 | elif var == "2":
118 | get_running_config_xml()
119 | elif var == "3":
120 | get_hostname()
121 | elif var == "4":
122 | get_yangmodels()
123 | elif var == "5":
124 | get_allinterfaces()
125 | elif var == "6":
126 | get_allinterfaces_status()
127 | elif var == "7":
128 | change_interface(1)
129 | elif var == "8":
130 | change_interface(0)
131 | elif var == "9":
132 | save_running_config()
133 | else:
134 | print("Wrong input")
135 | print("Done.\n")
--------------------------------------------------------------------------------
/images/automation_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/industrial-netdevops/e45a34ff1f58f2d98d93f695dfd8e6626f3e5ebb/images/automation_1.png
--------------------------------------------------------------------------------
/images/automation_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/industrial-netdevops/e45a34ff1f58f2d98d93f695dfd8e6626f3e5ebb/images/automation_2.png
--------------------------------------------------------------------------------
/images/industrial_netdevops_manufacturing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/industrial-netdevops/e45a34ff1f58f2d98d93f695dfd8e6626f3e5ebb/images/industrial_netdevops_manufacturing.png
--------------------------------------------------------------------------------
/images/iot-hardware-overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/industrial-netdevops/e45a34ff1f58f2d98d93f695dfd8e6626f3e5ebb/images/iot-hardware-overview.png
--------------------------------------------------------------------------------
/images/webex-bot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CiscoDevNet/industrial-netdevops/e45a34ff1f58f2d98d93f695dfd8e6626f3e5ebb/images/webex-bot.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | ncclient==0.6.9
2 | webexteamsbot==0.1.4.2
3 | webexteamssdk==1.6
4 | xmltodict==0.12.0
5 | requests==2.25.0
6 |
--------------------------------------------------------------------------------