feat(zones): Early zone management
This commit is contained in:
parent
7e5608081d
commit
1ce8b76555
2 changed files with 50 additions and 31 deletions
15
example.yaml
15
example.yaml
|
@ -1,21 +1,20 @@
|
||||||
---
|
---
|
||||||
zones:
|
zones:
|
||||||
- name: users-internet-allowed
|
users-internet-allowed:
|
||||||
include:
|
|
||||||
files: [example.yaml]
|
files: [example.yaml]
|
||||||
|
|
||||||
- name: mgmt
|
mgmt:
|
||||||
include:
|
|
||||||
addrs: [10.203.0.0/16]
|
addrs: [10.203.0.0/16]
|
||||||
|
|
||||||
- name: adm
|
adm:
|
||||||
include:
|
|
||||||
addrs: [2a09:6840::/29, 10.128.0.0/16]
|
addrs: [2a09:6840::/29, 10.128.0.0/16]
|
||||||
|
|
||||||
- name: internet
|
internet:
|
||||||
exclude:
|
negate: true
|
||||||
zones: [adm, mgmt]
|
zones: [adm, mgmt]
|
||||||
|
|
||||||
|
# interne: negate KO
|
||||||
|
|
||||||
blacklist:
|
blacklist:
|
||||||
enabled: true
|
enabled: true
|
||||||
addr: [0.0.0.0]
|
addr: [0.0.0.0]
|
||||||
|
|
58
nftables.py
58
nftables.py
|
@ -1,7 +1,9 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from argparse import ArgumentParser, FileType
|
from argparse import ArgumentParser, FileType
|
||||||
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from graphlib import TopologicalSorter
|
||||||
from pydantic import (
|
from pydantic import (
|
||||||
BaseModel,
|
BaseModel,
|
||||||
Extra,
|
Extra,
|
||||||
|
@ -43,31 +45,43 @@ class PortRange(str):
|
||||||
return range(start, end)
|
return range(start, end)
|
||||||
|
|
||||||
|
|
||||||
|
# ===== First pass: Zones =====
|
||||||
|
|
||||||
|
|
||||||
# Zones
|
# Zones
|
||||||
class ZoneName(str):
|
class ZoneName(str):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ZoneEntries(RestrictiveBaseModel):
|
@dataclass
|
||||||
addrs: list[IPvAnyNetwork] | None
|
class Zone:
|
||||||
files: list[FilePath] | None
|
addrs: set[IPvAnyNetwork]
|
||||||
zones: list[ZoneName] | None
|
negate: bool
|
||||||
|
|
||||||
|
|
||||||
class Zone(RestrictiveBaseModel):
|
# Zones: Parsing YAML
|
||||||
name: ZoneName
|
class ZoneYAML(RestrictiveBaseModel):
|
||||||
exclude: ZoneEntries | None
|
addrs: set[IPvAnyNetwork] = set()
|
||||||
include: ZoneEntries | None
|
files: set[FilePath] = set()
|
||||||
|
negate: bool = False
|
||||||
|
zones: set[ZoneName] = set()
|
||||||
|
|
||||||
@root_validator()
|
|
||||||
def validate_mutually_exactly_one(cls, values):
|
|
||||||
if values.get("exclude") and values.get("include"):
|
|
||||||
raise ValueError("exclude and include are mutually exclusive")
|
|
||||||
|
|
||||||
if values.get("exclude") is None and values.get("include") is None:
|
# Zones: Graph resolver
|
||||||
raise ValueError("exactly one of exclude and include must be set")
|
def convert_to_zone_and_deps(zone_yaml: ZoneYAML) -> tuple[Zone, list[ZoneName]]:
|
||||||
|
return (Zone(addrs=zone_yaml.addrs, negate=zone_yaml.negate), zone_yaml.zones)
|
||||||
|
|
||||||
return values
|
|
||||||
|
def resolve_zones(zones):
|
||||||
|
zones = { name: convert_to_zone_and_deps(ZoneYAML(**zone)) for (name, zone) in zones.items() }
|
||||||
|
zone_name = { name: set(zones) for (name, (_, zones)) in zones.items() }
|
||||||
|
|
||||||
|
print(zones)
|
||||||
|
|
||||||
|
for name in TopologicalSorter(zone_name).static_order():
|
||||||
|
print(name)
|
||||||
|
|
||||||
|
# TODO: Check negation inclusion
|
||||||
|
|
||||||
|
|
||||||
# Blacklist
|
# Blacklist
|
||||||
|
@ -115,7 +129,8 @@ class Rule(RestrictiveBaseModel):
|
||||||
|
|
||||||
|
|
||||||
class ForwardRule(Rule):
|
class ForwardRule(Rule):
|
||||||
dest: ZoneEntries | None
|
# dest: ZoneEntries | None
|
||||||
|
dest: None
|
||||||
|
|
||||||
|
|
||||||
class Filter(RestrictiveBaseModel):
|
class Filter(RestrictiveBaseModel):
|
||||||
|
@ -131,13 +146,13 @@ class SNat(RestrictiveBaseModel):
|
||||||
|
|
||||||
|
|
||||||
class Nat(RestrictiveBaseModel):
|
class Nat(RestrictiveBaseModel):
|
||||||
src: ZoneEntries | None
|
# src: ZoneEntries | None
|
||||||
|
src: None
|
||||||
snat: SNat
|
snat: SNat
|
||||||
|
|
||||||
|
|
||||||
# Root model
|
# Root model
|
||||||
class Firewall(RestrictiveBaseModel):
|
class Firewall(RestrictiveBaseModel):
|
||||||
zones: list[Zone] = []
|
|
||||||
blacklist: BlackList | None
|
blacklist: BlackList | None
|
||||||
reverse_path_filter: ReversePathFilter | None
|
reverse_path_filter: ReversePathFilter | None
|
||||||
filter: Filter | None
|
filter: Filter | None
|
||||||
|
@ -149,9 +164,14 @@ def main():
|
||||||
parser.add_argument("file", type=FileType("r"), help="YAML rule file")
|
parser.add_argument("file", type=FileType("r"), help="YAML rule file")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
contents = safe_load(args.file)
|
||||||
|
|
||||||
rules = Firewall(**safe_load(args.file))
|
zones = resolve_zones(contents.pop("zones"))
|
||||||
|
print(zones)
|
||||||
|
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
rules = Firewall(**contents)
|
||||||
print(rules)
|
print(rules)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
Loading…
Reference in a new issue