Outomatiese generering en invul van netwerktoestelkonfigurasie-elemente met behulp van Nornir

Outomatiese generering en invul van netwerktoestelkonfigurasie-elemente met behulp van Nornir

Haai Habr!

Onlangs het 'n artikel hier verskyn Mikrotik en Linux. Roetine en outomatisering waar 'n soortgelyke probleem met behulp van fossielmiddele opgelos is. En hoewel die taak heeltemal tipies is, is daar niks soortgelyks daaroor op Habré nie. Ek waag dit om my fiets aan die gerespekteerde IT-gemeenskap aan te bied.

Dit is nie die eerste fiets vir so 'n taak nie. Die eerste opsie is etlike jare gelede geïmplementeer ansible weergawe 1.x.x. Die fiets is min gebruik en het dus voortdurend geroes. In die sin dat die taak self nie so gereeld voorkom as weergawes opgedateer word nie ansible. En elke keer as jy moet ry, val die ketting af of die wiel val af. Die eerste deel, wat konfigurasies genereer, werk egter altyd baie duidelik, gelukkig jinja2 Die enjin is lank reeds gevestig. Maar die tweede deel, die uitrol van die konfigurasies, het gewoonlik verrassings gebring. En aangesien ek die konfigurasie op afstand moet uitrol na 'n halfhonderd toestelle, waarvan sommige duisende kilometers ver geleë is, was dit 'n bietjie vervelig om hierdie instrument te gebruik.

Hier moet ek erken dat my onsekerheid heel waarskynlik lê in my gebrek aan vertroudheid met ansibleas in sy tekortkominge. En dit is terloops 'n belangrike punt. ansible is 'n heeltemal aparte, sy eie area van kennis met sy eie DSL (Domain Specific Language), wat op 'n selfversekerde vlak gehandhaaf moet word. Wel, daardie oomblik daardie ansible Dit ontwikkel redelik vinnig, en sonder spesiale agting vir terugwaartse verenigbaarheid, voeg dit nie vertroue by nie.

Daarom is 'n tweede weergawe van die fiets nie so lank gelede geïmplementeer nie. Hierdie keer aan python, of eerder op 'n raamwerk geskryf in python en vir python genoem Nornir

So - Nornir is 'n mikroraamwerk geskryf in python en vir python en ontwerp vir outomatisering. Dieselfde as in die geval met ansible, om probleme hier op te los, word bekwame datavoorbereiding vereis, m.a.w. inventaris van gashere en hul parameters, maar skrifte word nie in 'n aparte DSL geskryf nie, maar in dieselfde nie baie ou nie, maar baie goeie p[i|i]ton.

Kom ons kyk wat dit is deur die volgende lewendige voorbeeld te gebruik.

Ek het 'n taknetwerk met 'n paar dosyn kantore regoor die land. Elke kantoor het 'n WAN-roeteerder wat verskeie kommunikasiekanale van verskillende operateurs beëindig. Die roeteringprotokol is BGP. WAN-routers kom in twee tipes voor: Cisco ISG of Juniper SRX.

Nou die taak: jy moet 'n toegewyde subnet vir videotoesig op 'n aparte poort op alle WAN-routers van die taknetwerk instel - adverteer hierdie subnet in BGP - stel die spoedgrens van die toegewyde poort in.

Eerstens moet ons 'n paar sjablone voorberei, op grond waarvan konfigurasies afsonderlik vir Cisco en Juniper gegenereer sal word. Dit is ook nodig om data vir elke punt en verbindingsparameters voor te berei, m.a.w. versamel dieselfde voorraad

Gereed sjabloon vir 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

Sjabloon vir 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

Sjablone kom natuurlik nie uit die lug nie. Dit is in wese verskille tussen die werkkonfigurasies wat was en was nadat die taak op twee spesifieke routers van verskillende modelle opgelos is.

Uit ons sjablone sien ons dat ons net twee parameters vir Juniper en 3 parameters vir Cisco nodig het om die probleem op te los. hier is hulle:

  • ifname
  • ips agtervoegsel
  • ASN

Nou moet ons hierdie parameters vir elke toestel stel, d.w.s. doen dieselfde ding voorraad.

Vir voorraad Ons sal die dokumentasie streng volg Inisialiseer Nornir

dit wil sê, kom ons skep dieselfde lêerskelet:

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

Die config.yaml lêer is die standaard nornir konfigurasie lêer

$ 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"

Ons sal die hoofparameters in die lêer aandui gashere.yaml, groep (in my geval is dit logins/wagwoorde) in groepe.yamlen in verstek.yaml Ons sal niks aandui nie, maar jy moet drie minusse daar invoer - wat aandui dat dit is yaml die lêer is egter leeg.

Dit is hoe hosts.yaml lyk:

---
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 hier is groups.yaml:

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

juniper:
    platform: junos
    username: admin2
    password: juniper2

Dit is wat gebeur het voorraad vir ons taak. Tydens inisialisering word parameters van voorraadlêers na die objekmodel gekarteer InventarisElement.

Onder die bederf is 'n diagram van die 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"
                }
            }
        }
    }
}

Hierdie model kan 'n bietjie verwarrend lyk, veral aan die begin. Om dit uit te vind, is die interaktiewe modus in luislang.

 $ 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 laastens, kom ons gaan aan na die draaiboek self. Ek het niks om hier besonder trots op te wees nie. Ek het net 'n klaargemaakte voorbeeld van geneem tutoriaal en het dit amper onveranderd gebruik. Dit is hoe die voltooide werkende skrif lyk:

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)

Gee aandag aan die parameter dry_run=Waar in lyn voorwerp inisialisering nr.
Hier dieselfde as in ansible 'n toetslopie is geïmplementeer waarin 'n verbinding met die router gemaak word, 'n nuwe gewysigde konfigurasie word voorberei, wat dan deur die toestel bekragtig word (maar dit is nie seker nie; dit hang af van die toestelondersteuning en die bestuurderimplementering in NAPALM) , maar die nuwe konfigurasie word nie direk toegepas nie. Vir gevegsgebruik moet u die parameter verwyder droogloop of verander die waarde daarvan na Vals.

Wanneer die skrip uitgevoer word, voer Nornir gedetailleerde logs na die konsole uit.

Onder die bederf is die uitset van 'n gevegslopie op twee toetsrouters:

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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Versteek wagwoorde in ansible_vault

Aan die begin van die artikel het ek bietjie oorboord gegaan ansible, maar dit is nie so erg nie. Ek hou baie van hulle kluis soos, wat ontwerp is om sensitiewe inligting buite sig te verberg. En waarskynlik het baie opgemerk dat ons al die aanmeldings/wagwoorde vir alle gevegsroeteerders in oop vorm in 'n lêer het. gorups.yaml. Dit is natuurlik nie mooi nie. Kom ons beskerm hierdie data met kluis.

Kom ons dra die parameters oor van groups.yaml na creds.yaml, en enkripteer dit met AES256 met 'n 20-syfer wagwoord:

$ 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

Dis so eenvoudig. Dit bly om ons te leer Nornir-script om hierdie data te herwin en toe te pas.
Om dit te doen, in ons skrif na die inisialisering lyn nr = InitNornir(config_file=... voeg die volgende kode by:

...
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
...

Natuurlik moet vault.passwd nie langs creds.yaml geleë wees soos in my voorbeeld nie. Maar dit is goed om te speel.

Dit is al vir nou. Daar is nog 'n paar artikels oor Cisco + Zabbix wat kom, maar dit gaan nie 'n bietjie oor outomatisering nie. En in die nabye toekoms beplan ek om oor RESTCONF in Cisco te skryf.

Bron: will.com

Voeg 'n opmerking