Initial DSL
This commit is contained in:
commit
5d062daf33
2 changed files with 192 additions and 0 deletions
65
example_rules.py
Normal file
65
example_rules.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
zones:
|
||||
- name: users-internet-allowed
|
||||
include:
|
||||
- rules.yaml
|
||||
- name: mgmt
|
||||
include:
|
||||
- 10.203.0.0/16
|
||||
- name: adm
|
||||
include:
|
||||
- 2a09:6840::/29
|
||||
- 10.128.0.0/16
|
||||
- name: internet
|
||||
exclude:
|
||||
- adm
|
||||
- mgmt
|
||||
|
||||
blacklist:
|
||||
enabled: true
|
||||
addr:
|
||||
- 0.0.0.0
|
||||
|
||||
reverse_path_filter:
|
||||
enabled: true
|
||||
|
||||
filter:
|
||||
input:
|
||||
- iif: lo
|
||||
verdict: accept
|
||||
- src: mgmt
|
||||
protocols:
|
||||
tcp:
|
||||
dport: "22,240..242"
|
||||
verdict: accept
|
||||
- src: backbone
|
||||
protocols:
|
||||
ospf: true
|
||||
vrrp: true
|
||||
tcp:
|
||||
dport: 179
|
||||
verdict: accept
|
||||
- protocols:
|
||||
icmp: true
|
||||
verdict: accept
|
||||
output:
|
||||
- verdict: accept
|
||||
forward:
|
||||
- src: interco-crans
|
||||
verdict: accept
|
||||
- src: users-internet-allowed
|
||||
tcp:
|
||||
dport: 25
|
||||
verdict: drop
|
||||
- src: users-internet-allowed
|
||||
dest:
|
||||
- internet
|
||||
- 10.0.0.1
|
||||
verdict: accept
|
||||
|
||||
nat:
|
||||
- src: mgmt
|
||||
snat:
|
||||
addr: 45.66.108.14
|
||||
persistent: true
|
||||
...
|
127
nftables.py
Executable file
127
nftables.py
Executable file
|
@ -0,0 +1,127 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import annotations
|
||||
from argparse import ArgumentParser, FileType
|
||||
from enum import Enum
|
||||
from pydantic import BaseModel, FilePath, IPvAnyAddress, IPvAnyNetwork, validator, root_validator
|
||||
from yaml import safe_load
|
||||
|
||||
def parse_range_string(s):
|
||||
parts = s.split(",")
|
||||
values = []
|
||||
|
||||
for part in parts:
|
||||
if ".." in part:
|
||||
start, end = part.split("..")
|
||||
start = int(start)
|
||||
end = int(end)
|
||||
values += [start + i for i in range(end - start + 1)]
|
||||
else:
|
||||
values.append(int(part))
|
||||
|
||||
return values
|
||||
|
||||
# Zones
|
||||
|
||||
class ZoneName(str):
|
||||
pass
|
||||
|
||||
class Zone(BaseModel):
|
||||
name: ZoneName
|
||||
exclude: list[IPvAnyNetwork | FilePath | ZoneName] | None
|
||||
include: list[IPvAnyNetwork | FilePath | ZoneName] | None
|
||||
|
||||
@root_validator()
|
||||
def validate_mutually_exclusive(cls, values):
|
||||
if values.get("exclude") and values.get("include"):
|
||||
raise ValueError("exclude and include are mutually exclusive")
|
||||
return values
|
||||
|
||||
# Blacklist
|
||||
|
||||
class BlackList(BaseModel):
|
||||
enabled: bool = False
|
||||
addr: list[IPvAnyAddress] = []
|
||||
|
||||
# Reverse Path Filter
|
||||
|
||||
class ReversePathFilter(BaseModel):
|
||||
enabled: bool = False
|
||||
|
||||
# Filters
|
||||
|
||||
class Verdict(str, Enum):
|
||||
accept = "accept"
|
||||
drop = "drop"
|
||||
reject = "reject"
|
||||
|
||||
class TcpProtocol(BaseModel):
|
||||
dport: str | None
|
||||
sport: str | None
|
||||
|
||||
@validator("dport", "sport")
|
||||
def parse_range(cls, v):
|
||||
return parse_range_string(v)
|
||||
|
||||
class UdpProtocol(BaseModel):
|
||||
dport: str | None
|
||||
sport: str | None
|
||||
|
||||
@validator("dport", "sport")
|
||||
def parse_range(cls, v):
|
||||
return parse_range_string(v)
|
||||
|
||||
class Protocols(BaseModel):
|
||||
icmp: bool = False
|
||||
ospf: bool = False
|
||||
tcp: TcpProtocol | None
|
||||
udp: UdpProtocol | None
|
||||
vrrp: bool = False
|
||||
|
||||
class Rule(BaseModel):
|
||||
iff: str | None
|
||||
protocols: Protocols = Protocols()
|
||||
src: ZoneName | list[IPvAnyNetwork | FilePath | ZoneName] | None
|
||||
verdict: Verdict = Verdict.accept
|
||||
|
||||
class ForwardRule(Rule):
|
||||
dest: ZoneName | list[IPvAnyNetwork | FilePath | ZoneName] | None
|
||||
|
||||
class Filter(BaseModel):
|
||||
input: list[Rule] = []
|
||||
output: list[Rule] = []
|
||||
forward: list[ForwardRule] = []
|
||||
|
||||
# Nat
|
||||
|
||||
class SNat(BaseModel):
|
||||
addr: IPvAnyAddress
|
||||
persistent: bool = True
|
||||
|
||||
class Nat(BaseModel):
|
||||
src: ZoneName | list[IPvAnyNetwork | FilePath | ZoneName] | None
|
||||
snat: SNat
|
||||
|
||||
# Root model
|
||||
|
||||
class Firewall(BaseModel):
|
||||
zones: list[Zone] = []
|
||||
blacklist: BlackList | None
|
||||
reverse_path_filter: ReversePathFilter | None
|
||||
filter: Filter | None
|
||||
nat: list[Nat] = []
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("file", type=FileType("r"), help="YAML rule file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
rules = Firewall(**safe_load(args.file))
|
||||
|
||||
print(rules)
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue