Add tech_docs/networking/cue_jinja.md

This commit is contained in:
2025-08-06 13:54:07 -05:00
parent a67700c991
commit 0d7f593127

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