Сценарий
- Выдаем /24 из 10.0.0.0/16
- В третьем октете имеем CLIENT_ID = 1...253 (не очень много у нас будет клиентов...)
- Для нечетных клиентов R1 будет VRRP мастером, для четных - R2
- Номер VRF/VFI и всего прочего в конфигурации соответсвует номеру клиента.
- Клиенту можно дать доступ в Интернет, а можно и не дать
Включаем API
[root@localhost ~]# for _RID in 11 12 21 22; do curl -k -X POST -F data='{"op": "showConfig", "path": ["system","host-name"]}' -F key="SECRET_ONE" https://192.168.0.$_RID/retrieve | python2 -m json.tool; done
{
"data": {
"host-name": "F1"
},
"error": null,
"success": true
}
{
"data": {
"host-name": "F2"
},
"error": null,
"success": true
}
{
"data": {
"host-name": "R1"
},
"error": null,
"success": true
}
{
"data": {
"host-name": "R2"
},
"error": null,
"success": true
}
- По сути мы имеем stateless HTTP API, который поддерживает только метод POST.
- Имеем следующие endpoint'ы
- retrieve - получаем конфигурации для пути
- image - для работы с образами
- show - надо разобраться чем отличается от retrieve
- generate - довольно специфичная штука для генерирования например ключей
- configure - для собственно настройки оборудования с помощью (set, delete и comment)
- config-file - для сохранения конфигурации (copy run start)
Пример работы
А теперь по порядку
[root@localhost vyos_api_example]# cat inventory.yaml
R1:
address: 192.168.0.21
R2:
address: 192.168.0.22
F1:
address: 192.168.0.11
F2:
address: 192.168.0.12
[root@localhost vyos_api_example]# cat constants.py
# This file should be in .gitignore
API_KEY = "SECRET_ONE"
{% if 'R1' in ROUTER_ID -%}
{% set LOCAL_RID = 1 -%}
{% set REMOTE_RID = 2 -%}
{% if CLIENT_ID | int % 2 == 0 -%}
{% set PRIORITY = 100 -%}
{% else -%}
{% set PRIORITY = 200 -%}
{% endif -%}
{% elif 'R2' in ROUTER_ID -%}
{% set LOCAL_RID = 2 -%}
{% set REMOTE_RID = 1 -%}
{% if CLIENT_ID | int % 2 == 0 -%}
{% set PRIORITY = 200 -%}
{% else -%}
{% set PRIORITY = 100 -%}
{% endif -%}
{% endif -%}
[{"op": "set", "path": ["vrf", "name", "VL{{CID}}", "table", "1{{CID}}"]},
{"op": "set", "path": ["interfaces", "ethernet", "eth0", "vif", "{{CID}}", "address", "172.16.{{CID}}.0/31"]},
{"op": "set", "path": ["interfaces", "ethernet", "eth0", "vif", "{{CID}}", "vrf", "VL{{CID}}"]},
{"op": "set", "path": ["interfaces", "ethernet", "{{CLIENT_INT}}", "address", "10.0.{{CID}}.{{LOCAL_RID}}/24"]},
{"op": "set", "path": ["interfaces", "ethernet", "{{CLIENT_INT}}", "description", "VLAN{{CID}}"]},
{"op": "set", "path": ["interfaces", "ethernet", "{{CLIENT_INT}}", "vrf", "VL{{CID}}"]},
{"op": "set", "path": ["protocols", "vrf", "VL{{CID}}", "static", "route", "0.0.0.0/0", "next-hop", "172.16.{{CID}}.1"]},
{% if PRIORITY == 200 -%}
{"op": "set", "path": ["high-availability", "vrrp", "group", "VLAN{{CID}}", "health-check", "failure-count", "3"]},
{"op": "set", "path": ["high-availability", "vrrp", "group", "VLAN{{CID}}", "health-check", "interval", "2"]},
{"op": "set", "path": ["high-availability", "vrrp", "group", "VLAN{{CID}}", "health-check", "script", "/config/we-are-isolated.sh"]},
{"op": "set", "path": ["high-availability", "vrrp", "group", "VLAN{{CID}}", "transition-script", "backup", "/config/vrrp-goes-backup.sh"]},
{"op": "set", "path": ["high-availability", "vrrp", "group", "VLAN{{CID}}", "transition-script", "master", "/config/vrrp-goes-master.sh"]},
{% endif -%}
{"op": "set", "path": ["high-availability", "vrrp", "group", "VLAN{{CID}}", "hello-source-address", "10.0.{{CID}}.{{LOCAL_RID}}"]},
{"op": "set", "path": ["high-availability", "vrrp", "group", "VLAN{{CID}}", "interface", "{{CLIENT_INT}}"]},
{"op": "set", "path": ["high-availability", "vrrp", "group", "VLAN{{CID}}", "peer-address", "10.0.{{CID}}.{{REMOTE_RID}}"]},
{"op": "set", "path": ["high-availability", "vrrp", "group", "VLAN{{CID}}", "priority", "{{PRIORITY}}"]},
{"op": "set", "path": ["high-availability", "vrrp", "group", "VLAN{{CID}}", "virtual-address", "10.0.{{CID}}.254/24"]},
{"op": "set", "path": ["high-availability", "vrrp", "group", "VLAN{{CID}}", "vrid", "{{CID}}"]}]
data:
ROUTER_ID: R1
CLIENT_ID: 13
CLIENT_INT: eth5
[{"op": "set", "path": ["interfaces", "ethernet", "eth1", "vif", "{{CID}}", "address", "172.16.{{CID}}.1/31"]},
{"op": "set", "path": ["protocols", "static", "route", "10.0.{{CID}}.0/24", "next-hop", "172.16.{{CID}}.0"]},
{%- if INTERNET == True -%}
{"op": "set", "path": ["firewall", "name", "WORLD-TO-VL{{CID}}","default-action", "drop"]},
{"op": "set", "path": ["firewall", "name", "WORLD-TO-VL{{CID}}","rule", "1", "action", "accept"]},
{"op": "set", "path": ["firewall", "name", "WORLD-TO-VL{{CID}}","rule", "1", "state", "established", "enable"]},
{"op": "set", "path": ["firewall", "name", "WORLD-TO-VL{{CID}}","rule", "1", "state", "related", "enable"]},
{"op": "set", "path": ["firewall", "name", "WORLD-TO-VL{{CID}}","rule", "2", "action", "drop"]},
{"op": "set", "path": ["firewall", "name", "WORLD-TO-VL{{CID}}","rule", "2", "state", "invalid", "enable"]},
{"op": "set", "path": ["zone-policy", "zone", "VL{{CID}}", "from", "WORLD", "firewall", "name", "WORLD-TO-VL{{CID}}"]},
{"op": "set", "path": ["zone-policy", "zone", "WORLD", "from", "VL{{CID}}", "firewall", "name", "TO-WORLD"]},
{%- endif %}
{"op": "set", "path": ["zone-policy", "zone", "VL{{CID}}", "default-action", "drop"]},
{"op": "set", "path": ["zone-policy", "zone", "VL{{CID}}", "description", "VL{{CID}}"]},
{"op": "set", "path": ["zone-policy", "zone", "VL{{CID}}", "interface", "eth1.{{CID}}"]}]
data:
CLIENT_ID: 13
INTERNET: True
[root@localhost vyos_api_example]# cat deployments/deploy_new_client.yaml
---
R1:
template: new_client_rtr.j2
data:
ROUTER_ID: R1
CLIENT_ID: 13
CLIENT_INT: eth5
R2:
template: new_client_rtr.j2
data:
ROUTER_ID: R2
CLIENT_ID: 13
CLIENT_INT: eth5
F1:
template: new_client_fw.j2
data:
CLIENT_ID: 13
INTERNET: True
F2:
template: new_client_fw.j2
data:
CLIENT_ID: 13
INTERNET: True
def prep_config(generated_config, api_key):
'''
Creating a dictionary which can be accepted by VyOS
{'data':(None,DATA),{'key':(None,KEY)}
'''
to_push = {}
to_push['data'] = (None, generated_config.replace('\n',''))
to_push['key'] = (None, api_key)
return(to_push)
def post_config(target, to_push):
'''
Posting data to the target
'''
url = 'https://' + target + '/configure'
r = requests.post(url, files=to_push, verify=False)
return(r)
Запуск
[root@localhost vyos_api_example]# python3 vyos_deploy.py -i inventory.yaml -d deploy_new_client.yaml -a API_KEY
API key gathered
Inventory file "inventory.yaml" parsed.
Deployment file "deploy_new_client.yaml" parsed.
Config for "R1" generated.
Config prepared.
Pushing config to "R1"
Returned result is "{"success": true, "data": null, "error": null}"
Saving configuration...
Returned result is "{"success": true, "data": "Saving configuration to '/config/config.boot'...\nDone\n", "error": null}"
Config for "R2" generated.
Config prepared.
Pushing config to "R2"
Returned result is "{"success": true, "data": null, "error": null}"
Saving configuration...
Returned result is "{"success": true, "data": "Saving configuration to '/config/config.boot'...\nDone\n", "error": null}"
Config for "F1" generated.
Config prepared.
Pushing config to "F1"
Returned result is "{"success": true, "data": null, "error": null}"
Saving configuration...
Returned result is "{"success": true, "data": "Saving configuration to '/config/config.boot'...\nDone\n", "error": null}"
Config for "F2" generated.
Config prepared.
Pushing config to "F2"
Returned result is "{"success": true, "data": null, "error": null}"
Saving configuration...
Returned result is "{"success": true, "data": "Saving configuration to '/config/config.boot'...\nDone\n", "error": null}"
[root@localhost vyos_api_example]#
Отличная работа. Пробовал управлять vyos с ansible. Работает.
ОтветитьУдалить