diff --git a/.dockerignore b/.dockerignore index b614c67..727697e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,4 @@ policies requirements.txt *.ipt *.acl +data diff --git a/.gitignore b/.gitignore index 334f777..78b4bb3 100644 --- a/.gitignore +++ b/.gitignore @@ -260,3 +260,5 @@ cython_debug/ # acl files *.acl *.ipt + +data/ diff --git a/freepydius.py b/freepydius.py index 00e491f..b59b888 100644 --- a/freepydius.py +++ b/freepydius.py @@ -2,6 +2,8 @@ import json import os import os.path from collections import defaultdict +from typing import List +import time import netaddr import paho.mqtt.client as mqtt @@ -15,7 +17,7 @@ def log(level, s): # The callback for when the client receives a CONNACK response from the server. def on_connect(client, userdata, flags, rc): - print("Connected with result code " + str(rc)) + log(radiusd.L_INFO, "Connected with result code " + str(rc)) # Subscribing in on_connect() means that if we lose the connection and # reconnect then subscriptions will be renewed. @@ -23,39 +25,19 @@ def on_connect(client, userdata, flags, rc): def get_leases(): - msgs = defaultdict(lambda: defaultdict(str)) + with open("/config/leases.json", encoding="utf-8") as file: + msgs = defaultdict(lambda: defaultdict(str), json.loads(file.read())) - # The callback for when a PUBLISH message is received from the server. - def on_message(client, userdata, msg): - lease = json.loads(msg.payload) - lease["identifier"] = os.path.basename(msg.topic) - msgs[lease["identifier"]] = defaultdict(str, lease) - - client = mqtt.Client() - client.username_pw_set(os.getenv("MQTT_USERNAME"), os.getenv("MQTT_PASSWORD")) - client.on_connect = on_connect - client.on_message = on_message - - client.connect("hassio.narnian.us", 1883, 60) - - cont = True - count = 0 - while cont: - client.loop(timeout=0.5) - client.loop(timeout=0.5) - client.loop(timeout=0.5) - client.loop(timeout=0.5) - if count == len(msgs): - break - count = len(msgs) return msgs def select_acls(lease): + log(radiusd.L_INFO, str(lease)) + log(radiusd.L_INFO, str(lease["vendor_identifier"])) acls = [] - if lease["vendor_identifier"].contains("PS4"): - acls.append("DENY") - acls.append("WEB") + if "MSFT" in lease["vendor_identifier"]: + acls.append("windows") + acls.append("web") return acls @@ -65,21 +47,39 @@ def ciscoize_acl_names(acls): for acl in acls: cisco.append( tuple( - "Cisco-AVPair", - "+=", - f"ACS:CiscoSecure-Defined-ACL=#ACSACL#-${acl}-fuckcisc", + [ + "Cisco-AVPair", + "+=", + f"ACS:CiscoSecure-Defined-ACL=#ACSACL#-IP-{acl}-588b68cd", + ] ) ) + log(radiusd.L_INFO, str(cisco)) return cisco def deciscoize_acl_name(acl_name): - return acl_name.split("#ACSACL#-")[1][0:-9] + return acl_name.split("#ACSACL#-IP-")[1][0:-9] def get_acl(acl_name): - with open(acl_name, encoding="utf-8") as f: - return f.read() + with open(f"acl/{acl_name}.acl", encoding="utf-8") as file: + return file.read().splitlines() + + +def ciscoize_acl(acl: List[str]): + cisco = [] + for i, line in enumerate(acl): + cisco.append( + tuple( + [ + "Cisco-AVPair", + "+=", + f"ip:inacl#{i+1}={line}", + ] + ) + ) + return cisco def authorize(p): @@ -99,11 +99,14 @@ def authorize(p): ] if netaddr.valid_mac(request["User-Name"]): + mac = netaddr.EUI(request["User-Name"]) + mac.dialect = netaddr.mac_unix_expanded leases = get_leases() + log(radiusd.L_INFO, str(leases)) - reply.extend(ciscoize_acl_names(select_acls(leases[request["User-Name"]]))) + reply.extend(ciscoize_acl_names(select_acls(leases[str(mac)]))) elif "#ACSACL#" in request["User-Name"]: - deciscoize_acl_name(request["User-Name"]) + reply.extend(ciscoize_acl(get_acl(deciscoize_acl_name(request["User-Name"])))) conf = [ ("Auth-Type", "Accept"), diff --git a/policies/pol/sample_multitarget.pol b/policies/pol/sample_multitarget.pol index a544104..aa75101 100644 --- a/policies/pol/sample_multitarget.pol +++ b/policies/pol/sample_multitarget.pol @@ -7,7 +7,7 @@ header { # NOTES: iptables produces filter 'lines' that must be used as args to the # '$ iptables' cmd, while Speedway produces stateful iptables filters # compatible with iptables-restore (most people will prefer speedway) - target:: cisco default-web extended + target:: cisco web extended target:: speedway INPUT } diff --git a/root/etc/services.d/mqtt/run b/root/etc/services.d/mqtt/run new file mode 100755 index 0000000..e564c23 --- /dev/null +++ b/root/etc/services.d/mqtt/run @@ -0,0 +1,4 @@ +#!/usr/bin/with-contenv bash + +exec \ + s6-setuidgid abc /usr/bin/mqtt.py /config/leases.json diff --git a/root/usr/bin/mqtt.py b/root/usr/bin/mqtt.py new file mode 100755 index 0000000..9472e31 --- /dev/null +++ b/root/usr/bin/mqtt.py @@ -0,0 +1,49 @@ +#!/usr/bin/python3 +import json +import os.path +import sys +from collections import defaultdict + +import paho.mqtt.client as mqtt + + +# The callback for when the client receives a CONNACK response from the server. +def on_connect(client, userdata, flags, rc): + print("Connected with result code " + str(rc)) + + # Subscribing in on_connect() means that if we lose the connection and + # reconnect then subscriptions will be renewed. + client.subscribe("router7/#") + + +def get_leases(): + msgs = defaultdict(lambda: defaultdict(str)) + + # The callback for when a PUBLISH message is received from the server. + def on_message(client, userdata, msg): + lease = json.loads(msg.payload) + lease["identifier"] = os.path.basename(msg.topic) + msgs[lease["hardware_addr"].lower()] = defaultdict(str, lease) + with open(sys.argv[1], mode="w", encoding="utf-8") as file: + file.write(json.dumps(msgs) + "\n") + + client = mqtt.Client() + client.username_pw_set(os.getenv("MQTT_USERNAME"), os.getenv("MQTT_PASSWORD")) + client.on_connect = on_connect + client.on_message = on_message + + client.connect("hassio.narnian.us", 1883, 60) + + # Blocking call that processes network traffic, dispatches callbacks and + # handles reconnecting. + # Other loop*() functions are available that give a threaded interface and a + # manual interface. + + cont = True + while cont: + client.loop(timeout=0.5) + return msgs + + +for m in get_leases(): + print(json.dumps(m))