Automatyske generaasje en ynfoljen fan konfiguraasje-eleminten foar netwurkapparaat mei Nornir

Automatyske generaasje en ynfoljen fan konfiguraasje-eleminten foar netwurkapparaat mei Nornir

Hoi Habr!

Koartlyn ferskynde hjir in artikel Mikrotik en Linux. Routine en automatisearring dêr't in ferlykber probleem waard oplost mei help fan fossile middels. En hoewol't de taak folslein typysk is, is der op Habré neat fergelykber mei. Ik doar myn fyts oan te bieden oan de respektearre IT-mienskip.

Dit is net de earste fyts foar sa'n taak. De earste opsje waard ferskate jierren lyn ynfierd werom yn tawinske ferzje 1.x.x. De fyts waard mar min brûkt en ferroeste dêrtroch hieltyd. Yn 'e sin dat de taak sels net sa faak ûntstiet as ferzjes wurde bywurke tawinske. En elke kear as jo ride moatte, falt de ketting ôf of falt it tsjil ôf. It earste diel, it generearjen fan konfiguraasjes, wurket lykwols altyd heul dúdlik, gelokkich jinja2 De motor is lang fêststeld. Mar it twadde diel, it útroljen fan de konfiguraasjes, brocht meast ferrassingen. En om't ik de konfiguraasje op ôfstân moat útrolje nei healhûndert apparaten, wêrfan guon tûzenen kilometers fuort lizze, wie it brûken fan dit ark in bytsje saai.

Hjir moat ik tajaan dat myn ûnwissichheid nei alle gedachten leit yn myn gebrek oan bekendheid mei tawinskedan yn syn tekoarten. En dit is trouwens in wichtich punt. tawinske is in folslein apart, in eigen gebiet fan kennis mei in eigen DSL (Domain Specific Language), dat moat wurde ûnderhâlden op in selsbetrouwen nivo. No, dat momint dat tawinske It ûntwikkelet frij fluch, en sûnder spesjaal omtinken foar efterkompatibiliteit, foeget it gjin fertrouwen ta.

Dêrom is noch net sa lang lyn in twadde ferzje fan de fyts ynfierd. Dizze kear op python, of leaver op in ramt skreaun yn python en foar python ûnder de namme Nornir

Dus - Nornir is in mikroframework skreaun yn python en foar python en ûntwurpen foar automatisearring. Itselde as yn it gefal mei tawinske, om hjir problemen op te lossen, is foechhawwende gegevenstarieding nedich, d.w.s. ynventarisaasje fan hosts en harren parameters, mar skripts wurde skreaun net yn in aparte DSL, mar yn deselde net hiel âlde, mar hiel goede p[i|i]ton.

Litte wy sjen nei wat it is mei it folgjende live foarbyld.

Ik haw in filiaalnetwurk mei ferskate tsientallen kantoaren yn it hiele lân. Elk kantoar hat in WAN-router dy't ferskate kommunikaasjekanalen fan ferskate operators beëiniget. It routingprotokol is BGP. WAN routers komme yn twa soarten: Cisco ISG of Juniper SRX.

No de taak: jo moatte in tawijd subnet foar Video Surveillance konfigurearje op in aparte poarte op alle WAN-routers fan it filiaalnetwurk - advertearje dit subnet yn BGP - konfigurearje de snelheidslimyt fan 'e tawijde poarte.

Earst moatte wy in pear sjabloanen tariede, op basis wêrfan konfiguraasjes apart wurde generearre foar Cisco en Juniper. It is ek nedich om te tarieden gegevens foar elk punt en ferbining parameters, i.e. sammelje deselde ynventarisaasje

Klear sjabloan foar Cisco:

$ cat templates/ios/base.j2 
class-map match-all VIDEO_SURV
 match access-group 111

policy-map VIDEO_SURV
 class VIDEO_SURV
    police 1500000 conform-action transmit  exceed-action drop

interface {{ host.task_data.ifname }}
  description VIDEOSURV
  ip address 10.10.{{ host.task_data.ipsuffix }}.254 255.255.255.0
  service-policy input VIDEO_SURV

router bgp {{ host.task_data.asn }}
  network 10.40.{{ host.task_data.ipsuffix }}.0 mask 255.255.255.0

access-list 11 permit 10.10.{{ host.task_data.ipsuffix }}.0 0.0.0.255
access-list 111 permit ip 10.10.{{ host.task_data.ipsuffix }}.0 0.0.0.255 any

Sjabloan foar Juniper:

$ cat templates/junos/base.j2 
set interfaces {{ host.task_data.ifname }} unit 0 description "Video surveillance"
set interfaces {{ host.task_data.ifname }} unit 0 family inet filter input limit-in
set interfaces {{ host.task_data.ifname }} unit 0 family inet address 10.10.{{ host.task_data.ipsuffix }}.254/24
set policy-options policy-statement export2bgp term 1 from route-filter 10.10.{{ host.task_data.ipsuffix }}.0/24 exact
set security zones security-zone WAN interfaces {{ host.task_data.ifname }}
set firewall policer policer-1m if-exceeding bandwidth-limit 1m
set firewall policer policer-1m if-exceeding burst-size-limit 187k
set firewall policer policer-1m then discard
set firewall policer policer-1.5m if-exceeding bandwidth-limit 1500000
set firewall policer policer-1.5m if-exceeding burst-size-limit 280k
set firewall policer policer-1.5m then discard
set firewall filter limit-in term 1 then policer policer-1.5m
set firewall filter limit-in term 1 then count limiter

Templates komme fansels net út 'e loft. Dit binne yn wêzen ferskillen tusken de wurkjende konfiguraasjes dy't wiene en wiene nei it oplossen fan de taak op twa spesifike routers fan ferskate modellen.

Ut ús sjabloanen wy sjogge dat foar in losse it probleem, wy moatte mar twa parameters foar Juniper en 3 parameters foar Cisco. hjir binne se:

  • ifname
  • ipsuffix
  • asn

No moatte wy dizze parameters ynstelle foar elk apparaat, d.w.s. doch itselde ding ynventarisaasje.

foar ynventarisaasje Wy sille de dokumintaasje strikt folgje Inisjalisearjen fan Nornir

dat is, litte wy itselde bestânskelet oanmeitsje:

.
├── config.yaml
├── inventory
│   ├── defaults.yaml
│   ├── groups.yaml
│   └── hosts.yaml

De config.yaml triem is de standert nornir konfiguraasje triem

$ cat config.yaml 
---
core:
    num_workers: 10

inventory:
    plugin: nornir.plugins.inventory.simple.SimpleInventory
    options:
        host_file: "inventory/hosts.yaml"
        group_file: "inventory/groups.yaml"
        defaults_file: "inventory/defaults.yaml"

Wy sille de wichtichste parameters yn 'e triem oanjaan hosts.yaml, groep (yn myn gefal binne dit oanmeldingen/wachtwurden) yn groups.yaml, en yn defaults.yaml Wy sille neat oanjaan, mar jo moatte dêr trije minussen ynfiere - wat oanjout dat it is yaml de triem is lykwols leech.

Dit is hoe hosts.yaml derút sjocht:

---
srx-test:
    hostname: srx-test
    groups: 
        - juniper
    data:
        task_data:
            ifname: fe-0/0/2
            ipsuffix: 111

cisco-test:
    hostname: cisco-test
    groups: 
        - cisco
    data:
        task_data:
            ifname: GigabitEthernet0/1/1
            ipsuffix: 222
            asn: 65111

En hjir is groups.yaml:

---
cisco:
    platform: ios
    username: admin1
    password: cisco1

juniper:
    platform: junos
    username: admin2
    password: juniper2

Dit is wat der bard is ynventarisaasje foar ús taak. Tidens inisjalisaasje wurde parameters fan ynventarisaasjebestannen yn kaart brocht oan it objektmodel InventoryElement.

Under de spoiler is in diagram fan it InventoryElement-model

print(json.dumps(InventoryElement.schema(), indent=4))
{
    "title": "InventoryElement",
    "type": "object",
    "properties": {
        "hostname": {
            "title": "Hostname",
            "type": "string"
        },
        "port": {
            "title": "Port",
            "type": "integer"
        },
        "username": {
            "title": "Username",
            "type": "string"
        },
        "password": {
            "title": "Password",
            "type": "string"
        },
        "platform": {
            "title": "Platform",
            "type": "string"
        },
        "groups": {
            "title": "Groups",
            "default": [],
            "type": "array",
            "items": {
                "type": "string"
            }
        },
        "data": {
            "title": "Data",
            "default": {},
            "type": "object"
        },
        "connection_options": {
            "title": "Connection_Options",
            "default": {},
            "type": "object",
            "additionalProperties": {
                "$ref": "#/definitions/ConnectionOptions"
            }
        }
    },
    "definitions": {
        "ConnectionOptions": {
            "title": "ConnectionOptions",
            "type": "object",
            "properties": {
                "hostname": {
                    "title": "Hostname",
                    "type": "string"
                },
                "port": {
                    "title": "Port",
                    "type": "integer"
                },
                "username": {
                    "title": "Username",
                    "type": "string"
                },
                "password": {
                    "title": "Password",
                    "type": "string"
                },
                "platform": {
                    "title": "Platform",
                    "type": "string"
                },
                "extras": {
                    "title": "Extras",
                    "type": "object"
                }
            }
        }
    }
}

Dit model kin in bytsje betiizjend sjen, benammen op it earste. Om it út te finen, is de ynteraktive modus yn ipython.

 $ ipython3
Python 3.6.9 (default, Nov  7 2019, 10:44:02) 
Type 'copyright', 'credits' or 'license' for more information
IPython 7.1.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from nornir import InitNornir                                                                           

In [2]: nr = InitNornir(config_file="config.yaml", dry_run=True)                                                

In [3]: nr.inventory.hosts                                                                                      
Out[3]: 
{'srx-test': Host: srx-test, 'cisco-test': Host: cisco-test}

In [4]: nr.inventory.hosts['srx-test'].data                                                                                    
Out[4]: {'task_data': {'ifname': 'fe-0/0/2', 'ipsuffix': 111}}

In [5]: nr.inventory.hosts['srx-test']['task_data']                                                     
Out[5]: {'ifname': 'fe-0/0/2', 'ipsuffix': 111}

In [6]: nr.inventory.hosts['srx-test'].platform                                                                                
Out[6]: 'junos'

En as lêste, litte wy trochgean nei it skript sels. Ik haw hjir neat om benammen grutsk op te wêzen. Ik haw krekt in ready-made foarbyld fan tutorial en brûkte it hast net feroare. Dit is hoe't it ôfmakke wurkskript derút sjocht:

from nornir import InitNornir
from nornir.plugins.tasks import networking, text
from nornir.plugins.functions.text import print_title, print_result

def config_and_deploy(task):
    # Transform inventory data to configuration via a template file
    r = task.run(task=text.template_file,
                 name="Base Configuration",
                 template="base.j2",
                 path=f"templates/{task.host.platform}")

    # Save the compiled configuration into a host variable
    task.host["config"] = r.result

    # Save the compiled configuration into a file
    with open(f"configs/{task.host.hostname}", "w") as f:
        f.write(r.result)

    # Deploy that configuration to the device using NAPALM
    task.run(task=networking.napalm_configure,
             name="Loading Configuration on the device",
             replace=False,
             configuration=task.host["config"])

nr = InitNornir(config_file="config.yaml", dry_run=True) # set dry_run=False, cross your fingers and run again

# run tasks
result = nr.run(task=config_and_deploy)
print_result(result)

Soarch omtinken foar de parameter dry_run=Wier yn line foarwerp inisjalisaasje nr.
Hjir itselde as yn tawinske in test run is ymplementearre wêryn in ferbining mei de router wurdt makke, in nije wizige konfiguraasje wurdt taret, dy't dan wurdt falidearre troch it apparaat (mar dit is net wis; it hinget ôf fan de apparaatstipe en de bestjoerder ymplemintaasje yn NAPALM) , mar de nije konfiguraasje wurdt net direkt tapast. Foar fjochtsjen moatte jo de parameter fuortsmite dry_run of feroarje syn wearde oan falsk.

As it skript wurdt útfierd, útfiert Nornir detaillearre logs nei de konsole.

Under de spoiler is de útfier fan in gefjochtsrun op twa testrouters:

config_and_deploy***************************************************************
* cisco-test ** changed : True *******************************************
vvvv config_and_deploy ** changed : True vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Base Configuration ** changed : True ------------------------------------- INFO
class-map match-all VIDEO_SURV
 match access-group 111

policy-map VIDEO_SURV
 class VIDEO_SURV
    police 1500000 conform-action transmit  exceed-action drop

interface GigabitEthernet0/1/1
  description VIDEOSURV
  ip address 10.10.222.254 255.255.255.0
  service-policy input VIDEO_SURV

router bgp 65001
  network 10.10.222.0 mask 255.255.255.0

access-list 11 permit 10.10.222.0 0.0.0.255
access-list 111 permit ip 10.10.222.0 0.0.0.255 any
---- Loading Configuration on the device ** changed : True --------------------- INFO
+class-map match-all VIDEO_SURV
+ match access-group 111
+policy-map VIDEO_SURV
+ class VIDEO_SURV
+interface GigabitEthernet0/1/1
+  description VIDEOSURV
+  ip address 10.10.222.254 255.255.255.0
+  service-policy input VIDEO_SURV
+router bgp 65001
+  network 10.10.222.0 mask 255.255.255.0
+access-list 11 permit 10.10.222.0 0.0.0.255
+access-list 111 permit ip 10.10.222.0 0.0.0.255 any
^^^^ END config_and_deploy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* srx-test ** changed : True *******************************************
vvvv config_and_deploy ** changed : True vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- Base Configuration ** changed : True ------------------------------------- INFO
set interfaces fe-0/0/2 unit 0 description "Video surveillance"
set interfaces fe-0/0/2 unit 0 family inet filter input limit-in
set interfaces fe-0/0/2 unit 0 family inet address 10.10.111.254/24
set policy-options policy-statement export2bgp term 1 from route-filter 10.10.111.0/24 exact
set security zones security-zone WAN interfaces fe-0/0/2
set firewall policer policer-1m if-exceeding bandwidth-limit 1m
set firewall policer policer-1m if-exceeding burst-size-limit 187k
set firewall policer policer-1m then discard
set firewall policer policer-1.5m if-exceeding bandwidth-limit 1500000
set firewall policer policer-1.5m if-exceeding burst-size-limit 280k
set firewall policer policer-1.5m then discard
set firewall filter limit-in term 1 then policer policer-1.5m
set firewall filter limit-in term 1 then count limiter
---- Loading Configuration on the device ** changed : True --------------------- INFO
[edit interfaces]
+   fe-0/0/2 {
+       unit 0 {
+           description "Video surveillance";
+           family inet {
+               filter {
+                   input limit-in;
+               }
+               address 10.10.111.254/24;
+           }
+       }
+   }
[edit]
+  policy-options {
+      policy-statement export2bgp {
+          term 1 {
+              from {
+                  route-filter 10.10.111.0/24 exact;
+              }
+          }
+      }
+  }
[edit security zones]
     security-zone test-vpn { ... }
+    security-zone WAN {
+        interfaces {
+            fe-0/0/2.0;
+        }
+    }
[edit]
+  firewall {
+      policer policer-1m {
+          if-exceeding {
+              bandwidth-limit 1m;
+              burst-size-limit 187k;
+          }
+          then discard;
+      }
+      policer policer-1.5m {
+          if-exceeding {
+              bandwidth-limit 1500000;
+              burst-size-limit 280k;
+          }
+          then discard;
+      }
+      filter limit-in {
+          term 1 {
+              then {
+                  policer policer-1.5m;
+                  count limiter;
+              }
+          }
+      }
+  }
^^^^ END config_and_deploy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Wachtwurden ferbergje yn ansible_vault

Oan it begjin fan it artikel gie ik wat oerboard tawinske, mar it is net allegear sa slim. Ik fyn se echt leuk ferwulf lykas, dat is ûntwurpen om te ferbergjen gefoelige ynformaasje út it sicht. En wierskynlik hawwe in protte opfallen dat wy alle oanmeldingen / wachtwurden hawwe foar alle fjochtsrouters yn iepen foarm yn in bestân fonkeljend gorups.yaml. It is net moai, fansels. Lit ús beskermje dizze gegevens mei ferwulf.

Litte wy de parameters oerdrage fan groups.yaml nei creds.yaml, en fersiferje it mei AES256 mei in 20-sifers wachtwurd:

$ cd inventory
$ cat creds.yaml
---
cisco:
    username: admin1
    password: cisco1

juniper:
    username: admin2
    password: juniper2

$ pwgen 20 -N 1 > vault.passwd
ansible-vault encrypt creds.yaml --vault-password-file vault.passwd  
Encryption successful
$ cat creds.yaml 
$ANSIBLE_VAULT;1.1;AES256
39656463353437333337356361633737383464383231366233386636333965306662323534626131
3964396534396333363939373539393662623164373539620a346565373439646436356438653965
39643266333639356564663961303535353364383163633232366138643132313530346661316533
6236306435613132610a656163653065633866626639613537326233653765353661613337393839
62376662303061353963383330323164633162386336643832376263343634356230613562643533
30363436343465306638653932366166306562393061323636636163373164613630643965636361
34343936323066393763323633336366366566393236613737326530346234393735306261363239
35663430623934323632616161636330353134393435396632663530373932383532316161353963
31393434653165613432326636616636383665316465623036376631313162646435

Sa ienfâldich is it. It bliuwt te learen ús Nornir-skript om dizze gegevens op te heljen en ta te passen.
Om dit te dwaan, yn ús skript nei de initialisaasjeline nr = InitNornir(config_file=... foegje de folgjende koade ta:

...
nr = InitNornir(config_file="config.yaml", dry_run=True) # set dry_run=False, cross your fingers and run again

# enrich Inventory with the encrypted vault data
from ansible_vault import Vault
vault_password_file="inventory/vault.passwd"
vault_file="inventory/creds.yaml"
with open(vault_password_file, "r") as fp:
    password = fp.readline().strip()   
    vault = Vault(password)
    vaultdata = vault.load(open(vault_file).read())

for a in nr.inventory.hosts.keys():
    item = nr.inventory.hosts[a]
    item.username = vaultdata[item.groups[0]]['username']
    item.password = vaultdata[item.groups[0]]['password']
    #print("hostname={}, username={}, password={}n".format(item.hostname, item.username, item.password))

# run tasks
...

Fansels moat vault.passwd net lizze neist creds.yaml lykas yn myn foarbyld. Mar it is ok om te spyljen.

Dat is alles foar no. D'r komme noch in pear artikels oer Cisco + Zabbix, mar dit is net in bytsje oer automatisearring. En yn 'e heine takomst plan ik te skriuwen oer RESTCONF yn Cisco.

Boarne: www.habr.com

Add a comment