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

推荐订阅源

Apple Machine Learning Research
Apple Machine Learning Research
M
MIT News - Artificial intelligence
罗磊的独立博客
博客园 - 【当耐特】
A
About on SuperTechFans
Last Week in AI
Last Week in AI
雷峰网
雷峰网
IT之家
IT之家
aimingoo的专栏
aimingoo的专栏
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园_首页
博客园 - 叶小钗
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - Franky
J
Java Code Geeks
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
Docker
Engineering at Meta
Engineering at Meta
B
Blog RSS Feed
The Cloudflare Blog
大猫的无限游戏
大猫的无限游戏
阮一峰的网络日志
阮一峰的网络日志
S
SegmentFault 最新的问题
Recent Announcements
Recent Announcements

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
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 :)