Add tech_docs/networking/cue_jinja.md
This commit is contained in:
607
tech_docs/networking/cue_jinja.md
Normal file
607
tech_docs/networking/cue_jinja.md
Normal file
@@ -0,0 +1,607 @@
|
||||
# Cisco Router Configuration Template System using CUE and Jinja2
|
||||
|
||||
I'll create a system to generate Cisco router configurations using CUE for data modeling and validation, and Jinja2 for template rendering. Here's how we can structure this:
|
||||
|
||||
## 1. CUE Schema Definition (router.cue)
|
||||
|
||||
```cue
|
||||
package router
|
||||
|
||||
#Device: {
|
||||
// Basic device information
|
||||
hostname: string
|
||||
model: "ISR4451-X/K9" | "ISR4431" | "ASR1001-X" // etc.
|
||||
serial: string
|
||||
|
||||
// Location information
|
||||
location: {
|
||||
city: string
|
||||
state: string
|
||||
address1: string
|
||||
address2?: string
|
||||
zipcode: string
|
||||
floor?: string
|
||||
grid?: string
|
||||
rack: string
|
||||
ru: string
|
||||
room: string
|
||||
}
|
||||
|
||||
// Device role
|
||||
role: "wan-router" | "core-router" | "distribution-router"
|
||||
|
||||
// Authentication
|
||||
credentials: {
|
||||
enableSecret: string
|
||||
consolePassword: string
|
||||
vtyPassword: string
|
||||
tacacsKey: string
|
||||
snmp: {
|
||||
roCommunity: string
|
||||
rwCommunity: string
|
||||
users: [...{
|
||||
username: string
|
||||
authType: "sha" | "md5"
|
||||
authKey: string
|
||||
privType: "aes" | "des"
|
||||
privKey: string
|
||||
access: "read" | "write"
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
// Network services
|
||||
services: {
|
||||
ntp: {
|
||||
servers: [...string]
|
||||
preferredServer: string
|
||||
}
|
||||
dns: {
|
||||
servers: [...string]
|
||||
domain: string
|
||||
}
|
||||
logging: {
|
||||
hosts: [...string]
|
||||
facility: string
|
||||
trapLevel: "informational" | "debug" | "warning" | "error"
|
||||
}
|
||||
}
|
||||
|
||||
// Interfaces
|
||||
interfaces: {
|
||||
loopback0: {
|
||||
ip: string
|
||||
mask: "255.255.255.255"
|
||||
}
|
||||
|
||||
wan: {
|
||||
type: "MPLS" | "DMVPN" | "Internet"
|
||||
provider: "AT&T" | "Verizon" | "Lumen"
|
||||
circuitId: string
|
||||
bandwidth: uint
|
||||
ipAddress: string
|
||||
subnetMask: string
|
||||
dot1qVlan?: uint
|
||||
shapingRate: uint
|
||||
}
|
||||
|
||||
lan: [...{
|
||||
name: string
|
||||
description: string
|
||||
ipAddress: string
|
||||
subnetMask: string
|
||||
neighbor: string
|
||||
ospf: {
|
||||
area: string
|
||||
authKey: string
|
||||
helloInt: uint
|
||||
deadInt: uint
|
||||
}
|
||||
}]
|
||||
|
||||
tunnels: [...{
|
||||
id: uint
|
||||
description: string
|
||||
ipAddress: string
|
||||
subnetMask: string
|
||||
bandwidth: uint
|
||||
nhs: string
|
||||
nbma: string
|
||||
networkId: uint
|
||||
key: uint
|
||||
}]
|
||||
}
|
||||
|
||||
// Routing protocols
|
||||
routing: {
|
||||
ospf: {
|
||||
processId: uint
|
||||
routerId: string
|
||||
areas: [...{
|
||||
id: string
|
||||
authType: "message-digest"
|
||||
}]
|
||||
networks: [...{
|
||||
prefix: string
|
||||
wildcard: string
|
||||
area: string
|
||||
}]
|
||||
}
|
||||
|
||||
bgp: {
|
||||
asn: uint
|
||||
routerId: string
|
||||
neighbors: [...{
|
||||
ip: string
|
||||
remoteAs: uint | "internal"
|
||||
description: string
|
||||
password?: string
|
||||
routeMaps: {
|
||||
in?: string
|
||||
out?: string
|
||||
}
|
||||
}]
|
||||
networks: [...{
|
||||
prefix: string
|
||||
mask: string
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
// QoS Policy
|
||||
qos: {
|
||||
classes: [...{
|
||||
name: string
|
||||
match: string
|
||||
bandwidth: string
|
||||
queueLimit?: uint
|
||||
dscp: [...{
|
||||
value: uint
|
||||
minTh: uint
|
||||
maxTh: uint
|
||||
}]
|
||||
}]
|
||||
shapingRate: uint
|
||||
}
|
||||
|
||||
// Access control
|
||||
acls: {
|
||||
management: [...{
|
||||
source: string
|
||||
service: "ssh" | "snmp" | "ntp" | "tacacs"
|
||||
direction: "in" | "out"
|
||||
}]
|
||||
}
|
||||
|
||||
// Security policies
|
||||
security: {
|
||||
aaa: {
|
||||
methods: [...{
|
||||
type: "login" | "enable" | "exec"
|
||||
primary: "tacacs+" | "local"
|
||||
fallback?: "local" | "line"
|
||||
}]
|
||||
accounting: [...{
|
||||
type: "exec" | "commands" | "network"
|
||||
action: "start-stop"
|
||||
method: "tacacs+"
|
||||
}]
|
||||
}
|
||||
passwordPolicy: {
|
||||
minLength: uint
|
||||
maxFail: uint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#validate: {
|
||||
// Validation rules
|
||||
if !strings.HasPrefix(hostname, "RTR-") {
|
||||
"hostname must start with RTR-"
|
||||
}
|
||||
|
||||
if len(credentials.enableSecret) < 8 {
|
||||
"enable secret must be at least 8 characters"
|
||||
}
|
||||
|
||||
// More validation rules...
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Example Device Configuration (device.cue)
|
||||
|
||||
```cue
|
||||
package router
|
||||
|
||||
device: #Device & {
|
||||
hostname: "RTR-ATL01-WAN01"
|
||||
model: "ISR4451-X/K9"
|
||||
serial: "FOC12345678"
|
||||
|
||||
location: {
|
||||
city: "Atlanta"
|
||||
state: "GA"
|
||||
address1: "123 Main St"
|
||||
zipcode: "30303"
|
||||
rack: "RACK-A"
|
||||
ru: "22"
|
||||
room: "MDF-1"
|
||||
}
|
||||
|
||||
credentials: {
|
||||
enableSecret: "$1$s3cr3t$"
|
||||
consolePassword: "c0ns0l3P@ss"
|
||||
vtyPassword: "vtYP@ssw0rd"
|
||||
tacacsKey: "tacacsKey123"
|
||||
|
||||
snmp: {
|
||||
roCommunity: "publicRO"
|
||||
rwCommunity: "privateRW"
|
||||
users: [
|
||||
{
|
||||
username: "admin"
|
||||
authType: "sha"
|
||||
authKey: "authKey123"
|
||||
privType: "aes"
|
||||
privKey: "privKey123"
|
||||
access: "write"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
services: {
|
||||
ntp: {
|
||||
servers: ["135.89.160.2", "135.89.160.18"]
|
||||
preferredServer: "135.89.160.18"
|
||||
}
|
||||
dns: {
|
||||
servers: ["10.83.108.251", "10.65.72.251"]
|
||||
domain: "example.com"
|
||||
}
|
||||
logging: {
|
||||
hosts: ["10.254.254.238"]
|
||||
facility: "local1"
|
||||
trapLevel: "informational"
|
||||
}
|
||||
}
|
||||
|
||||
interfaces: {
|
||||
loopback0: {
|
||||
ip: "10.255.255.1"
|
||||
mask: "255.255.255.255"
|
||||
}
|
||||
|
||||
wan: {
|
||||
type: "MPLS"
|
||||
provider: "AT&T"
|
||||
circuitId: "ABC123456"
|
||||
bandwidth: 1000
|
||||
ipAddress: "172.30.65.102"
|
||||
subnetMask: "255.255.255.252"
|
||||
dot1qVlan: 249
|
||||
shapingRate: 1000
|
||||
}
|
||||
|
||||
lan: [
|
||||
{
|
||||
name: "GigabitEthernet0/0/1"
|
||||
description: "Core Switch Connection"
|
||||
ipAddress: "10.1.1.1"
|
||||
subnetMask: "255.255.255.254"
|
||||
neighbor: "CORE-ATL01"
|
||||
ospf: {
|
||||
area: "0"
|
||||
authKey: "ospfKey123"
|
||||
helloInt: 1
|
||||
deadInt: 4
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
tunnels: [
|
||||
{
|
||||
id: 11
|
||||
description: "DMVPN-MPLS to Charlotte"
|
||||
ipAddress: "172.30.4.2"
|
||||
subnetMask: "255.255.254.0"
|
||||
bandwidth: 1000
|
||||
nhs: "172.30.4.1"
|
||||
nbma: "172.30.65.101"
|
||||
networkId: 200
|
||||
key: 1002
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
routing: {
|
||||
ospf: {
|
||||
processId: 1
|
||||
routerId: "10.255.255.1"
|
||||
areas: [
|
||||
{id: "0", authType: "message-digest"}
|
||||
]
|
||||
networks: [
|
||||
{prefix: "10.1.1.0", wildcard: "0.0.0.1", area: "0"},
|
||||
{prefix: "10.255.255.1", wildcard: "0.0.0.0", area: "0"}
|
||||
]
|
||||
}
|
||||
|
||||
bgp: {
|
||||
asn: 65401
|
||||
routerId: "10.255.255.1"
|
||||
neighbors: [
|
||||
{
|
||||
ip: "172.30.4.1"
|
||||
remoteAs: "internal"
|
||||
description: "DMVPN Headend"
|
||||
routeMaps: {
|
||||
in: "BGP_INBOUND_FROM_DMVPN"
|
||||
out: "BGP_OUTBOUND_TO_DMVPN"
|
||||
}
|
||||
},
|
||||
]
|
||||
networks: [
|
||||
{prefix: "10.1.1.0", mask: "255.255.255.0"},
|
||||
{prefix: "10.255.255.1", mask: "255.255.255.255"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
qos: {
|
||||
classes: [
|
||||
{
|
||||
name: "VOICE_PAYLOAD"
|
||||
match: "dscp ef cs5"
|
||||
bandwidth: "priority percent 30"
|
||||
},
|
||||
{
|
||||
name: "INTERACTIVE_VIDEO"
|
||||
match: "dscp cs4 af41 af42 af43"
|
||||
bandwidth: "bandwidth remaining percent 15"
|
||||
queueLimit: 300
|
||||
dscp: [
|
||||
{value: 32, minTh: 300, maxTh: 300},
|
||||
{value: 34, minTh: 150, maxTh: 250},
|
||||
]
|
||||
},
|
||||
]
|
||||
shapingRate: 1000
|
||||
}
|
||||
|
||||
security: {
|
||||
aaa: {
|
||||
methods: [
|
||||
{type: "login", primary: "tacacs+", fallback: "local"},
|
||||
{type: "enable", primary: "tacacs+", fallback: "enable"},
|
||||
]
|
||||
accounting: [
|
||||
{type: "exec", action: "start-stop", method: "tacacs+"},
|
||||
{type: "commands", action: "start-stop", method: "tacacs+"},
|
||||
]
|
||||
}
|
||||
passwordPolicy: {
|
||||
minLength: 8
|
||||
maxFail: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Jinja2 Template (cisco_template.j2)
|
||||
|
||||
```jinja2
|
||||
! Generated from template on {{ now() }}
|
||||
! Device: {{ device.hostname }}
|
||||
! Location: {{ device.location.city }}, {{ device.location.state }}
|
||||
|
||||
hostname {{ device.hostname }}
|
||||
!
|
||||
boot system bootflash:isr4400-universalk9.16.09.05.SPA.bin
|
||||
!
|
||||
service password-encryption
|
||||
no ip domain lookup
|
||||
ip domain name {{ device.services.dns.domain }}
|
||||
!
|
||||
{% for server in device.services.dns.servers %}
|
||||
ip name-server {{ server }}
|
||||
{% endfor %}
|
||||
!
|
||||
! AAA Configuration
|
||||
aaa new-model
|
||||
aaa authentication attempts login {{ device.security.passwordPolicy.maxFail }}
|
||||
!
|
||||
{% for method in device.security.aaa.methods %}
|
||||
aaa authentication {{ method.type }} default group {{ method.primary }}{% if method.fallback %} {{ method.fallback }}{% endif %}
|
||||
{% endfor %}
|
||||
!
|
||||
{% for accounting in device.security.aaa.accounting %}
|
||||
aaa accounting {{ accounting.type }} default {{ accounting.action }} group {{ accounting.method }}
|
||||
{% endfor %}
|
||||
!
|
||||
! SNMP Configuration
|
||||
{% for user in device.credentials.snmp.users %}
|
||||
snmp-server user {{ user.username }} {{ user.access }} v3 auth {{ user.authType }} {{ user.authKey }} priv {{ user.privType }} {{ user.privKey }}
|
||||
{% endfor %}
|
||||
!
|
||||
! NTP Configuration
|
||||
ntp source Loopback0
|
||||
ntp server {{ device.services.ntp.preferredServer }} prefer
|
||||
{% for server in device.services.ntp.servers %}
|
||||
{% if server != device.services.ntp.preferredServer %}
|
||||
ntp server {{ server }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
!
|
||||
! Logging Configuration
|
||||
logging source-interface Loopback0
|
||||
logging trap {{ device.services.logging.trapLevel }}
|
||||
logging facility {{ device.services.logging.facility }}
|
||||
{% for host in device.services.logging.hosts %}
|
||||
logging host {{ host }}
|
||||
{% endfor %}
|
||||
!
|
||||
! Interface Configuration
|
||||
interface Loopback0
|
||||
description Loopback Address
|
||||
ip address {{ device.interfaces.loopback0.ip }} {{ device.interfaces.loopback0.mask }}
|
||||
!
|
||||
interface GigabitEthernet0/0/0
|
||||
description {{ device.interfaces.wan.provider }} MPLS - {{ device.interfaces.wan.circuitId }}
|
||||
bandwidth {{ device.interfaces.wan.bandwidth }}
|
||||
no ip address
|
||||
!
|
||||
interface GigabitEthernet0/0/0.{{ device.interfaces.wan.dot1qVlan }}
|
||||
description {{ device.interfaces.wan.provider }} MPLS - {{ device.interfaces.wan.circuitId }}
|
||||
encapsulation dot1Q {{ device.interfaces.wan.dot1qVlan }}
|
||||
ip address {{ device.interfaces.wan.ipAddress }} {{ device.interfaces.wan.subnetMask }}
|
||||
service-policy output SHAPE_{{ device.interfaces.wan.shapingRate }}M
|
||||
!
|
||||
{% for lan in device.interfaces.lan %}
|
||||
interface {{ lan.name }}
|
||||
description {{ lan.description }}
|
||||
ip address {{ lan.ipAddress }} {{ lan.subnetMask }}
|
||||
ip ospf authentication message-digest
|
||||
ip ospf message-digest-key 1 md5 {{ lan.ospf.authKey }}
|
||||
ip ospf hello-interval {{ lan.ospf.helloInt }}
|
||||
ip ospf dead-interval {{ lan.ospf.deadInt }}
|
||||
!
|
||||
{% endfor %}
|
||||
!
|
||||
{% for tunnel in device.interfaces.tunnels %}
|
||||
interface Tunnel{{ tunnel.id }}
|
||||
description {{ tunnel.description }}
|
||||
bandwidth {{ tunnel.bandwidth }}
|
||||
ip address {{ tunnel.ipAddress }} {{ tunnel.subnetMask }}
|
||||
ip nhrp network-id {{ tunnel.networkId }}
|
||||
ip nhrp nhs {{ tunnel.nhs }} nbma {{ tunnel.nbma }} multicast
|
||||
tunnel key {{ tunnel.key }}
|
||||
!
|
||||
{% endfor %}
|
||||
!
|
||||
! OSPF Configuration
|
||||
router ospf {{ device.routing.ospf.processId }}
|
||||
router-id {{ device.routing.ospf.routerId }}
|
||||
{% for area in device.routing.ospf.areas %}
|
||||
area {{ area.id }} authentication {{ area.authType }}
|
||||
{% endfor %}
|
||||
{% for net in device.routing.ospf.networks %}
|
||||
network {{ net.prefix }} {{ net.wildcard }} area {{ net.area }}
|
||||
{% endfor %}
|
||||
!
|
||||
! BGP Configuration
|
||||
router bgp {{ device.routing.bgp.asn }}
|
||||
bgp router-id {{ device.routing.bgp.routerId }}
|
||||
{% for net in device.routing.bgp.networks %}
|
||||
network {{ net.prefix }} mask {{ net.mask }}
|
||||
{% endfor %}
|
||||
{% for neighbor in device.routing.bgp.neighbors %}
|
||||
neighbor {{ neighbor.ip }} remote-as {{ neighbor.remoteAs }}
|
||||
neighbor {{ neighbor.ip }} description {{ neighbor.description }}
|
||||
{% if neighbor.routeMaps.in %} neighbor {{ neighbor.ip }} route-map {{ neighbor.routeMaps.in }} in{% endif %}
|
||||
{% if neighbor.routeMaps.out %} neighbor {{ neighbor.ip }} route-map {{ neighbor.routeMaps.out }} out{% endif %}
|
||||
{% if neighbor.password %} neighbor {{ neighbor.ip }} password {{ neighbor.password }}{% endif %}
|
||||
{% endfor %}
|
||||
!
|
||||
! QoS Configuration
|
||||
policy-map WAN_QOS_OUTBOUND
|
||||
{% for class in device.qos.classes %}
|
||||
class {{ class.name }}
|
||||
{{ class.bandwidth }}
|
||||
{% if class.queueLimit %} queue-limit {{ class.queueLimit }} packets{% endif %}
|
||||
{% for dscp in class.dscp %}
|
||||
random-detect dscp {{ dscp.value }} {{ dscp.minTh }} {{ dscp.maxTh }}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
!
|
||||
policy-map SHAPE_{{ device.qos.shapingRate }}M
|
||||
class class-default
|
||||
shape average {{ device.qos.shapingRate }}000000
|
||||
service-policy WAN_QOS_OUTBOUND
|
||||
!
|
||||
! Banner
|
||||
banner motd ^
|
||||
********************************************************************************
|
||||
* Warning Notice *
|
||||
* *
|
||||
* This system is restricted solely to authorized users for legitimate *
|
||||
* business purposes only. *
|
||||
********************************************************************************
|
||||
^
|
||||
!
|
||||
line con 0
|
||||
password {{ device.credentials.consolePassword }}
|
||||
login local
|
||||
!
|
||||
line vty 0 15
|
||||
password {{ device.credentials.vtyPassword }}
|
||||
login local
|
||||
transport input ssh
|
||||
!
|
||||
end
|
||||
```
|
||||
|
||||
## 4. Generation Script (generate.py)
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import jinja2
|
||||
import json
|
||||
import datetime
|
||||
from cue import load
|
||||
|
||||
def now():
|
||||
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate Cisco configurations from CUE definitions")
|
||||
parser.add_argument("cue_file", help="Input CUE file with device definition")
|
||||
parser.add_argument("template_file", help="Jinja2 template file")
|
||||
parser.add_argument("-o", "--output", help="Output file", default="output.cfg")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load CUE data
|
||||
cue_data = load.cue(args.cue_file)
|
||||
device = cue_data["device"]
|
||||
|
||||
# Set up Jinja2 environment
|
||||
env = jinja2.Environment(loader=jinja2.FileSystemLoader('.'))
|
||||
env.globals['now'] = now
|
||||
template = env.get_template(args.template_file)
|
||||
|
||||
# Render template
|
||||
config = template.render(device=device)
|
||||
|
||||
# Write output
|
||||
with open(args.output, 'w') as f:
|
||||
f.write(config)
|
||||
|
||||
print(f"Configuration generated and saved to {args.output}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
## 5. Workflow Explanation
|
||||
|
||||
1. **Define the Schema**: The CUE schema (`router.cue`) defines all possible configuration options with types and constraints.
|
||||
|
||||
2. **Create Device Config**: The device-specific configuration (`device.cue`) uses the schema to define a concrete device configuration.
|
||||
|
||||
3. **Validate**: The CUE engine validates the device configuration against the schema before processing.
|
||||
|
||||
4. **Render Template**: The Jinja2 template (`cisco_template.j2`) uses the validated device configuration to generate the Cisco config.
|
||||
|
||||
5. **Generate Config**: The Python script (`generate.py`) ties everything together, loading the CUE data and rendering the template.
|
||||
|
||||
## Benefits of This Approach
|
||||
|
||||
1. **Validation**: CUE ensures all configurations are valid before template rendering.
|
||||
2. **Reusability**: Templates can be reused across many devices.
|
||||
3. **Maintainability**: Changes to the schema automatically validate all configurations.
|
||||
4. **Documentation**: The schema serves as documentation for all configurable options.
|
||||
5. **Version Control**: All components can be version controlled separately.
|
||||
|
||||
Would you like me to elaborate on any particular aspect of this system?
|
||||
Reference in New Issue
Block a user