惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

AWS News Blog
AWS News Blog
Engineering at Meta
Engineering at Meta
T
Tailwind CSS Blog
博客园 - 【当耐特】
F
Fortinet All Blogs
博客园 - 司徒正美
Stack Overflow Blog
Stack Overflow Blog
罗磊的独立博客
Google DeepMind News
Google DeepMind News
博客园_首页
A
About on SuperTechFans
博客园 - 聂微东
N
Netflix TechBlog - Medium
MongoDB | Blog
MongoDB | Blog
H
Help Net Security
GbyAI
GbyAI
O
OpenAI News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Check Point Blog
S
Schneier on Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Apple Machine Learning Research
Apple Machine Learning Research
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Martin Fowler
Martin Fowler
J
Java Code Geeks
T
Tor Project blog
T
Threatpost
G
GRAHAM CLULEY
P
Privacy & Cybersecurity Law Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Y
Y Combinator Blog
The Register - Security
The Register - Security
阮一峰的网络日志
阮一峰的网络日志
D
Docker
Vercel News
Vercel News
I
Intezer
Microsoft Security Blog
Microsoft Security Blog
爱范儿
爱范儿
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Hacker News: Front Page
P
Privacy International News Feed
Cyberwarzone
Cyberwarzone
Spread Privacy
Spread Privacy
N
News and Events Feed by Topic
N
News | PayPal Newsroom
The GitHub Blog
The GitHub Blog
U
Unit 42
WordPress大学
WordPress大学
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Simon Willison's Weblog
Simon Willison's Weblog

Posts on Noah Bailey

How to turn anything into a router Deploy to Cloudfront from GitHub using OpenID Connect Backup Postgres databases with Kubernetes CronJobs The spelling error made 200 billion times a day Restarting Kubernetes pods using a CronJob You've just bought a new domain. Now what? Who Sawed My Motherboard??? Linux on the P8 Aliexpress Mini Laptop Recovering Mysql/Mariadb after a nasty crash Using EXIF data to pick my next lens Converting and developing RAW photos on Linux automatically Thank you, 2016 iPhone Don't Make It Work Self-hosted Surveillance with ZoneMinder Backups, Monitoring, and Security for small Mastodon servers Block web scanners with ipset & iptables Executing commands over SSH with GitHub Actions Debian Sid on encrypted ZFS Protect your dangerously insecure redis server Debian: the luxurious boring lifestyle Monitor radiation with a Raspberry Pi Simple Linux server alerts: Know your performance, errors, security, syslog, and security NUC crashes on debian 11 - How I fixed it Basic Linux server security with fail2ban, ossec, and firewall Windows 11 will create heaps of needless trash Domesticated Kubernetes Networking The Cursed Certificate Our mostly disposable and entirely stupid world Trying out OpenBSD (as a Linux geek) Making VoIP Calls with Antique Rotary Phones Monitoring WAN speed with speedtest-cli and ElasticSearch Monitoring WAN latency with InfluxDB The Zeroshell botnet returns Installing Gentoo on a vintage Thinkpad T60 Malware emails 2: Russian boogaloo TP-Link Device Weirdness ElasticSearch broke all my nice things (a story of cascading failure) A New Botnet is Targeting Network Infrastructure Malware on the Wire: Monitoring Network Traffic with Suricata and ClamAV Cloud Threat Protection with OSSEC and Suricata Malware Emails From Jerks Surviving the Apocalypse with an Offline Wikipedia Server Being Attacked by Bots Linux Router, Firewall and IDS Appliance You Probably Don't Need a VPN Fix an Oversharded Elasticsearch Cluster Automating KVM Virtualization Update all your linux servers as fast as possible Cleanup Systemd Journald Storage Stop Putting Your SSH Keys on Github! Clustering KVM with Ceph Storage Stealing Windows Sessions FreeRadius Active Directory Integration Retrieving WPA2 Keys on Windows Deploy MDT Litetouch on Linux with TFTPD and Syslinux Generating MSI transform files with Orca The Inflatable Dinghy Homebrew SAN Getting Cloudy
Generating Cisco IOS config files with Python
2018-09-18 · via Posts on Noah Bailey

Cisco IOS. It’s fun to configure, isn’t it? No?

In an effort to learn Python scripting, I decided to take a bit of the monotony of managing and updating IOS config files away and replace it with the monotony of managing and updating spreadsheets! The real goal with this project was to design a ‘gitops’ system for periodically checking configs against baselines and build a stripped down orchestration platform. This turned out to be a little ambitious, but I’m fairly happy with the results regardless.

This system involves three tables (formatted as CSV) and one .ini general config file that contains device specific information.

   switch-name +
               |- svi.csv
               |- vlan.csv
               |- switch.csv
               \- config.ini

   ioscfg.py /foo/bar/switch-name/

The different tables look like this:

svi.csv - Configures switch virtual interfaces with IP/IPv6 addresses, sets DHCP relay addresses, and configures basic failover with HSRP.

vlan_id comment ipv4_addr ipv4_subn dhcp_relay hsrp_ipv4 hsrp_primary
10 Finance-2Floor 10.123.10.2 255.255.255.0 10.123.10.10 10.123.10.1 TRUE
15 Marketing-3Floor 10.123.15.2 255.255.255.0 10.123.10.10 10.123.15.1 FALSE
20 Sales-4Floor 10.123.20.2 255.255.255.0 10.123.10.10 10.123.20.1 TRUE
25 Research-5Floor 10.123.25.2 255.255.255.0 10.123.10.10 10.123.25.1 FALSE

vlan.csv - Sets up the VLAN database on the switch:

vlan_id name state
10 Finance-2Floor active
15 Marketing-3Floor active
20 Sales-4Floor active
25 Research-5Floor active
80 Inactive suspend
99 Trunk active
101 IT active

switch.csv - Sets port roles on the switchports:

interface port_id comment ac_vlan tr_untag tr_tag
Gi 0/1 A-1 Uplink to R-1 99 “1,10,15,20,25,80-101”
Gi 0/2 A-2 Uplink to R-2 99 “1,10,15,20,25,80-101”
Gi 0/3 1 PC-1 10
Gi 0/4 2 PC-2 15
Gi 0/5 3 PC-3 20
Gi 0/6 4 PC-4 25
Gi 0/7 5 MGMT-PC 101
Gi 0/8 6 Access Switch 99 “1,10,15,20,25”
Gi 0/9 7 Unused port

config.ini - Configures system settings for the device, such as the roles, protocols, services, and passwords. (Again, NOT for production)

[DEFAULT]
hostname = xyu-n5w-ds01
vlan = True
switch = True
svi = True
routing = True
routing_eigrp = False
routing_ospf = True

[SYSTEM]
domain = lab.internal
domain_lookup = False

[AUTH]
admin_password = cisco123
password_encryption = True
logging_sync = True
constrain_ssh = True
cons_timeout = False
cons_login = True
banner_login = No unauthorized access!

[SERVICES]
ssh_enable = True
ssh_timeout = 60
ssh_retries = 5
rsa_modulus = 2048
http_server = False
https_server = True
ipv6_routing = False
ip_routing = True

Now that we have our config data in place, we can start bringing it into a script that will create our config files.

To start, let’s set up the arguments for the script. These will determine the role of the device and which parts of the config file will be generated.

import sys
import csv
import json
import configparser
import argparse
import datetime 

ar = argparse.ArgumentParser()
ar.add_argument("--config_path",
    help="Path to configuration files",
    default='./'
)
ar.add_argument('--switch',
    action='store_true',
    help="Configure this device as a switch"
)
ar.add_argument('--router',
    action='store_true',
    help="Configure the device as a router",
)
ar.add_argument('--multilayer',
    action='store_true',
    help="Configure the device as a multilayer switch",
)
args = ar.parse_args()

Now that we have the configuration parameters, we can bring in the different configuration files. This currently assumes that config files are in the same directory as the script, though this will likely change in a future version.

config_file = args.config_path + '/config.ini'
vlan_file = args.config_path + '/vlan.csv'
switch_file = args.config_path + '/switch.csv'
svi_file = args.config_path + '/svi.csv'

cfg = ConfigParser.ConfigParser() 
cfg.read(config_file)
def system_config(): 
    out = []
    out.append("\nhostname " + cfg.get('SYSTEM','hostname'))
    out.append("ip domain-name " + cfg.get('SYSTEM','domain'))

    if cfg.getboolean('SYSTEM','domain_lookup') == False:
        out.append("no ip domain lookup")
    return out

This function sets up the various authentication parameters for the cisco device. Depending on the settings in the config.ini file, SSH and Telnet may be enabled or disabled depending on the desired security of the system.

def auth_config():
    out = []

    out.append('\n\n'+
        '! -------------------- !\n' +
        '! -- Authentication -- !\n' +
        '! -------------------- !\n'
    )

    out.append("line vty 0 15\n  login local")
    if cfg.getboolean('AUTH','constrain_ssh') == True:
        out.append("  transport input ssh")
    else:
        out.append("  transport input all")

    # Console config:
    out.append("line con 0")
    if cfg.getboolean('AUTH','cons_login') == False:
        out.append("  no login")
    else:
        out.append("  login local")
    if cfg.getboolean('AUTH','logging_sync') == True :
        out.append("  logging sync")
    if cfg.getboolean('AUTH','cons_timeout')==False:
        out.append("  exec timeout 0 0")
    out.append("exit\n!")

    # Password encryption: 
    if cfg.getboolean('AUTH','password_encryption') == True:
        out.append("service password encryption")

    out.append("banner login # " + cfg.get('AUTH','banner_login') + "#")
    out.append('username admin priv 15 secret ' + cfg.get('AUTH','admin_password'))
    out.append('\n')

    return out

Next, the system services will be set up. Again, depending on the settings specified in the config.ini file, services like HTTP or HTTPS might be enabled or disabled. Note that some services might not be available on all platforms, for example ipv6 unicast-routing might not be available on devices that don’t have enough TCAM or may require an SDM modification. Your milage may vary.

def services_config():
    out = [] 

    out.append('\n' + 
        '! ---------------------------- !\n' +
        '! -- System services config -- !\n' +
        '! ---------------------------- !\n'
    )

    if cfg.getboolean('SERVICES','ssh_enable') == True:
        out.append("crypto key generate rsa general-keys modulus " + 
            cfg.get('SERVICES','rsa_modulus') + "\n!")
        out.append("ip ssh version 2\nip ssh auth retries 5\nip ssh time-out 30")

    if cfg.getboolean('SERVICES','http_server') == True:
        out.append("ip http server")
    if cfg.getboolean('SERVICES','http_server') == False:
        out.append("no ip http server")    

    if cfg.getboolean('SERVICES','https_server') == True:
        out.append("ip http secure-server")
    if cfg.getboolean('SERVICES','https_server') == False:
        out.append("no ip http secure-server")

    if cfg.getboolean('SERVICES','ipv6_routing') == True:
        out.append("ipv6 unicast-routing")
    
    if cfg.getboolean('SERVICES','ip_routing') == True:
        out.append("ip routing")

    return out

When switch mode is enabled, the script will use the contents of the vlan.csv to add VLANs to the local database. This also requires that VTP is not enabled on the switch (though we are assuming that we’re starting with a clean device, so that most likely won’t be the case).

def vlan_config():
    out = []

    out.append('\n\n' +
        '! -------------------------- !\n' +
        '! -- Vlan Database Config -- !\n' +
        '! -------------------------- !\n'
    )

    with open(vlan_file, 'r') as csv_file:
        reader = csv.DictReader(csv_file, delimiter=',')

        for i in reader:
            out.append(
                "vlan " + i['vlan_id'] + "\n" +
                "  name "  + i['name'] + "\n" +
                "  state " + i['state'] 
            )
    out.append("  exit\n")
    return out 
def switch_config(): 
    out = []

    out.append('\n' +
        '! ----------------------- !\n' +
        '! -- Switchport Config -- !\n' +
        '! ----------------------- !\n'
    )

    with open(switch_file, 'r') as csv_file:
        reader = csv.DictReader(csv_file, delimiter=',')

        for i in reader:
            out.append("interface " + i['interface'])
            out.append(
                "  description [" + i['port_id'] + "] :: " + i['comment'])
            if i['ac_vlan']:
                out.append(
                    "  switchport mode access\n  switchport access vlan " + i['ac_vlan'])
                out.append("  no shutdown\n!")
            elif i['tr_tag']:
                out.append(
                    "  switchport trunk encapsulation dot1q\n  switchport mode trunk")
                out.append(
                    "  switchport trunk native vlan " + i['tr_untag'])
                out.append(
                    "  switchport trunk allowed vlan " + i['tr_tag'])
                out.append("  no shutdown\n!")
            else:
                out.append("  switchport nonegotiate\n  shutdown\n!")
    out.append("exit\n")
    return out

When multilayer switching is enabled, SVIs (Switcch Virtual Interfaces) will be created and configured with IPv4 and IPv6 addresses.

This also supports basic high availability when using more than one device by configuring HSRP. Of course, STP topologies must match up in a way that a failed router will either trigger an STP recalculation, or the backup router will be reachable without a recalculation.

In a future version, I do intent to automate spanning-tree protocol as part of this script.

def svi_config():
    out = []

    out.append('\n' + 
        '! ------------------------ !\n' +
        '! -- Virtual Interfaces -- !\n' +
        '! ------------------------ !\n'
    )

    with open(svi_file, 'r') as csv_file:
        reader = csv.DictReader(csv_file, delimiter=',')

        for i in reader:
            out.append("interface vlan " + i['vlan_id'])
            out.append("  description " + i['comment'])
            out.append("  ip address " + i['ipv4_addr'] + ' ' + i['ipv4_subn'])
            
            if not i['ipv6_local']:
                out.append("  ipv6 address autoconfig")
            else: 
                out.append("  ipv6 address " + i['ipv6_local'] + ' link-local')
            out.append("  ipv6 address " + i['ipv6_global'])


            if i['hsrp_ipv4']:
                out.append("  standby " + i['vlan_id'] + " ip " + i['hsrp_ipv4'])
                if i['hsrp_primary']:
                    out.append("  standby " + i['vlan_id'] + " priority 50")
                    out.append("  standby " + i['vlan_id'] + " preempt")
                else: 
                    out.append("  standby " + i['vlan_id'] + "priority 150")
            if i['dhcp_relay']:
                out.append("  ip helper address " + i['dhcp_relay'])
            out.append("  no shutdown\n!")
    out.append('exit\n')
    return out

Finally, the main function will call out to the other ones depending on the config parameters used on the command line. This will also save the finished config file with the date and time as part of the file name.

def main(): 

    output_file = []
    output_file.append([
        '! -- Configuration script for device ' + cfg.get('SYSTEM','hostname'), 
        '! -- Generated on {:%Y/%m/%d-%H:%M}'.format(datetime.datetime.now()),
        '! -- https://nbailey.ca\n\n',
        'configure terminal'
    ])
    
    output_file.append(system_config())
    output_file.append(auth_config())
    output_file.append(services_config())

    # optional configs: 
    if args.switch or args.multilayer:
        output_file.append(vlan_config())
        output_file.append(switch_config())
    
    if args.multilayer:
        output_file.append(svi_config())

    if args.router or args.multilayer:
        output_file.append(routing_config())
        output_file.append(routing_config_eigrp())
        output_file.append(routing_config_ospf())
    
    output_file.append(['\ndo write-memory'])

    output_file_name = cfg.get('SYSTEM','hostname') + '{:-%Y%m%d-%H%M}.cfg'.format(datetime.datetime.now())
    with open (output_file_name, 'a') as output:
        for i in output_file: output.write("\n".join(i))
    
main()

And that’s it for now. In the future I want to make this script much more in depth and add much more functionality to make much more useful config files. As it is right now, it’s almost entirely for testing purposes. As always, don’t run code you downloaded from a stranger on the internet :)