Automatiseerimine ja seadmete vÔrgukonfiguratsioonide elemendid Norniri abil

Automatiseerimine ja seadmete vÔrgukonfiguratsioonide elemendid Norniri abil

Tere, Habr!

Hiljuti ilmus siin artikkel Mikrotik ja Linux. Rutiin ja automatiseerimine kus sarnast probleemi lahendati vanade vahenditega. Ja kuigi probleem on tÀiesti tavaline, ei leia selle kohta Habras mingit sarnast teavet. Julgen pakkuda austatud IT-kogukonnale oma jalgratast.

See ei ole esimene jalgratas sarnase ĂŒlesande jaoks. Esimene variant realiseeriti mitu aastat tagasi veel versioonil ansible 1.x.x. Jalgratast kasutati harva ja seetĂ”ttu see pidevalt roostetama hakkas. Just selle tĂ”ttu, et probleem ei esine nii tihti kui versioonid uuenevad ansible. Ja iga kord, kui tuleb minna, siis kas kett kukub maha vĂ”i ratta kelk laguneb. Siiski teadmiseks, et esimene osa, konfiguratsioonide genereerimine, töötab alati vĂ€ga tĂ€pselt, Ă”nneks jinja2 mootor on ammu tuntud. Ent teine osa — konfiguratsioonide rakendamine, tĂ”i tavaliselt ĂŒllatusi. Ja kuna pean neid konfiguratsioone rakendama kaugjuhtimisega viiekĂŒmnele seadmele, millest mĂ”ned asuvad tuhandete kilomeetrite kaugusel, siis oli selle tööriista kasutamine pisut hirmutav.

Siin tuleb tunnistada, et minu ebakindlus peitub pigem minu vĂ€heses tutvuses ansible, kui tema puudustes. Ja see on, muide, oluline punkt. ansible — see on tĂ€iesti eraldi, omaette teadmiste valdkond oma DSL-iga (spetsiaalne keel), mida tuleb hoida kindlal tasemel. Ja see, et ansible areneb ĂŒsna kiiresti, lisaks veel ilma eriliste tagasipöördumise arvestamiseta, ei lisa kindlust.

SeetÔttu realiseeriti hiljuti teine jalgratas. Sel korral python, tÀpsemalt raamistik, mis on kirjutatud python ja python nimega Nornir

Nii et — Nornir see on mikrorakendus, mis on kirjutatud python ja python ja mĂ”eldud automatiseerimiseks. Samamoodi nagu ka ansible, nĂ”uab siinsete ĂŒlesannete lahendamine korralikku andmete ettevalmistamist, st hostide ja nende parameetrite inventeerimist, samas kui stsenaariumid kirjutatakse mitte eraldi DSL-is, vaid kĂ”ik samas mitte eriti vanas, kuid ĂŒsna heas p[ĂŒ|i]thonis.

Vaatame, mis see on jÀrgmise elava nÀite pÔhjal.

Mul on filiaalide vĂ”rk, kus on mitu tosinat kontorit ĂŒle kogu riigi. Igas kontoris on WAN-ruuter, mis termineerib mitu sidekanalit erinevatelt operaatoritelt. Marsruutimisprotokoll — BGP. WAN-ruuterid on kahte tĂŒĂŒpi: Cisco ISG vĂ”i Juniper SRX.

NĂŒĂŒd on ĂŒlesanne: konfigureerida kĂ”ikides filiaalivĂ”rgu WAN marsruuterites eraldatud alamvĂ”rk videosalvestuse jaoks eraldi pordis — teatada sellest alamvĂ”rgust BGP-s — seadistada eraldi pordi kiiruspiirang.

Esiteks peame valmistama ette paar mallide, mille alusel genereeritakse konfigureerimised eraldi Cisco ja Juniper jaoks. Samuti on vaja ette valmistada andmed iga punkti kohta ja ĂŒhendusparameetrid, st koguda vajalik inventuur.

Valmis mall Cisco jaoks:

$ 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

Mall Juniper jaoks:

$ 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

Mallid ei tule muidugi iseenesest. Need on tegelikult diff’id töökonfiguratsioonide vahel, mis olid ja said pĂ€rast ĂŒlesande lahendamist kahe konkreetse erineva mudeliga marsruuteri puhul.

Meie mallidest nĂ€eme, et ĂŒlesande lahendamiseks on Juniper jaoks vaja kahte parameetrit ja Cisco jaoks kolme parameetrit. Need on:

  • ifname
  • ipsuffix
  • asn

NĂŒĂŒd peame need parameetrid mÀÀrama iga seadme jaoks, st tegema selle vajalikku. inventory.

Tooge inventory jÀrgime kindlalt dokumentatsiooni Initializing Nornir

st loome sarnase failistruktuuri:

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

Fail config.yaml — standardne norniri konfiguratsioonifail

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

Peamised parameetrid mÀÀrame failis hosts.yaml, rĂŒhmad (minu puhul on need sisselogimised/paroolid) sisse groups.yamltoob kaasa selle, et "saadetamise" hetkel server ei ole veel kĂ€ivitatud ja kĂ”ik lĂ€heb katki. defaults.yaml ei hakka midagi tĂ€psustama, kuid sinna tuleb kindlasti kirjutada kolm miinust — mis nĂ€itavad, et see on yaml fail on kĂŒll tĂŒhi.

Nii nÀeb vÀlja hosts.yaml:

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

Ja nii nÀeb vÀlja groups.yaml:

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

juniper:
    platform: junos
    username: admin2
    password: juniper2

Selline asi sai valmis inventory meie ĂŒlesande jaoks. Algatamisel mappitakse parameetrid inventory-failidest objektimudelile InventoryElement.

Peidetud ala all on InventoryElementi mudeli skeem

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

See mudel vĂ”ib esialgu tunduda natuke segane. Et paremini aru saada, aitab vĂ€ga interaktiivne reĆŸiim 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'

LÔpuks liigume skripti juurde. Siin ei ole mul liiga palju uhkust tunda. Ma vÔtsin lihtsalt valmis nÀite Ôpikust ja kasutasin seda peaaegu muutmata. Valmis töötav skript nÀeb vÀlja selline:

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):
    # Muuda inventari andmed konfiguratsiooniks malandi faili kaudu
    r = task.run(task=text.template_file,
                 name="PÔhi konfiguratsioon",
                 template="base.j2",
                 path=f"templates/{task.host.platform}")

    # Salvesta kompileeritud konfiguratsioon hosti muutujasse
    task.host["config"] = r.result

    # Salvesta kompileeritud konfiguratsioon faili
    with open(f"configs/{task.host.hostname}", "w") as f:
        f.write(r.result)

    # Deploi see konfiguratsioon seadmesse kasutades NAPALM-i
    task.run(task=networking.napalm_configure,
             name="Laadib konfiguratsiooni seadmesse",
             replace=False,
             configuration=task.host["config"])

nr = InitNornir(config_file="config.yaml", dry_run=True) # pane dry_run=False, hoia pöialt ja proovi uuesti

# kĂ€i ĂŒlesanded lĂ€bi
result = nr.run(task=config_and_deploy)
print_result(result)

Pange tÀhele parameetrit dry_run=True rea vitamise objekti initsialiseerimine nr.
Siin on samuti nagu ansible teostatud testkĂ€ik, mille kĂ€igus toimub ĂŒhendus marsruuteriga, valmistatakse ette uus muudetud konfiguratsioon, mis seejĂ€rel seadme poolt valideeritakse (aga see ei pruugi kehtida; sĂ”ltub seadme toest ja NAPALMi draiveri teostusest), kuid uue konfiguratsiooni rakendamist endiselt ei toimu. Tootmises kasutamiseks tuleb parameeter dry_run kas eemaldada vĂ”i selle vÀÀrtust muuta Vale.

Nornir skripti kĂ€ivitamisel kuvab konsool ĂŒksikasjalikke logisid.

Spoileri all on tootmisproovie teated kahel testmarsruuteril:

config_and_deploy***************************************************************
* cisco-test ** muutunud : True *******************************************
vvvv config_and_deploy ** muutunud : True vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- PÔhikonfiguratsioon ** muutunud : 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
---- Seadmise laadimine seadmesse ** muutunud : 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
^^^^ LÕPP config_and_deploy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* srx-test ** muutunud : True *******************************************
vvvv config_and_deploy ** muutunud : True vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
---- PÔhikonfiguratsioon ** muutunud : True ------------------------------------- INFO
set interfaces fe-0/0/2 unit 0 description "Videovalve"
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
---- Seadmise laadimine seadmesse ** muutunud : True --------------------- INFO
[edit interfaces]
+   fe-0/0/2 {
+       unit 0 {
+           description "Videovalve";
+           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;
+              }
+          }
+      }
+  }
^^^^ LÕPP config_and_deploy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Peidame paroolid ansible_vaults

Artikli alguses ĂŒtlesin natuke kriitiliselt ansible, aga seal ei ole kĂ”ik nii halb. Mulle vĂ€ga meeldib nende vault meeldib, mis on mĂ”eldud tundlike andmete silmadest varjamiseks. Ja ilmselt on paljud mĂ€rganud, et kĂ”ik meie kasutajanimed/paroolid kĂ”igi tootmisruuterite jaoks on avatud kujul failis. gorups.yaml. See pole kindlasti ilus. Kaitseme neid andmeid selle abil. vault.

Kandke parameetrid groups.yaml failist creds.yaml faili ja krĂŒpteerige see AES256-ga 20-mĂ€rgise parooliga:

$ 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  
KrĂŒptimine Ă”nnestus
$ cat creds.yaml 
$ANSIBLE_VAULT;1.1;AES256
39656463353437333337356361633737383464383231366233386636333965306662323534626131
3964396534396333363939373539393662623164373539620a346565373439646436356438653965
39643266333639356564663961303535353364383163633232366138643132313530346661316533
6236306435613132610a656163653065633866626639613537326233653765353661613337393839
62376662303061353963383330323164633162386336643832376263343634356230613562643533
30363436343465306638653932366166306562393061323636636163373164613630643965636361
34343936323066393763323633336366366566393236613737326530346234393735306261363239
35663430623934323632616161636330353134393435396632663530373932383532316161353963
31393434653165613432326636616636383665316465623036376631313162646435

Nii lihtsalt. JÀÀnud on Ôpetada meie Nornir-skripti andmeid tooma ja rakendama.
Selle jaoks lisame oma skripti pÀrast initsialiseerimisrida nr = InitNornir(config_file=
 jÀrgnev kood:

...
nr = InitNornir(config_file="config.yaml", dry_run=True) # seadke dry_run=False, hoidke pöidlad ristis ja kÀivitage uuesti

# rikastage inventaari krĂŒpteeritud rauaga
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))

# kĂ€ivitage ĂŒlesanded
...

Muidugi ei tohiks vault.passwd asuda naastudega koos creds.yaml failiga, nagu minu nÀites. Kuid harjuda vÔib sellega.

Sellega praeguseks kÔik. Tulekul on veel paar artiklit Cisco + Zabbixist, kuid see pole just automatiseerimise kohta. Ja peagi plaanin kirjutada RESTCONF-ist Cisco-s.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster