Move mqtt to a separate service

mqtt does not want to run under freeradius
This commit is contained in:
lordwelch 2022-05-01 19:33:49 -07:00
parent d8bec448ad
commit abd70a0d72
6 changed files with 96 additions and 37 deletions

View File

@ -5,3 +5,4 @@ policies
requirements.txt requirements.txt
*.ipt *.ipt
*.acl *.acl
data

2
.gitignore vendored
View File

@ -260,3 +260,5 @@ cython_debug/
# acl files # acl files
*.acl *.acl
*.ipt *.ipt
data/

View File

@ -2,6 +2,8 @@ import json
import os import os
import os.path import os.path
from collections import defaultdict from collections import defaultdict
from typing import List
import time
import netaddr import netaddr
import paho.mqtt.client as mqtt 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. # The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc): 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 # Subscribing in on_connect() means that if we lose the connection and
# reconnect then subscriptions will be renewed. # reconnect then subscriptions will be renewed.
@ -23,39 +25,19 @@ def on_connect(client, userdata, flags, rc):
def get_leases(): 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 return msgs
def select_acls(lease): def select_acls(lease):
log(radiusd.L_INFO, str(lease))
log(radiusd.L_INFO, str(lease["vendor_identifier"]))
acls = [] acls = []
if lease["vendor_identifier"].contains("PS4"): if "MSFT" in lease["vendor_identifier"]:
acls.append("DENY") acls.append("windows")
acls.append("WEB") acls.append("web")
return acls return acls
@ -65,21 +47,39 @@ def ciscoize_acl_names(acls):
for acl in acls: for acl in acls:
cisco.append( cisco.append(
tuple( tuple(
[
"Cisco-AVPair", "Cisco-AVPair",
"+=", "+=",
f"ACS:CiscoSecure-Defined-ACL=#ACSACL#-${acl}-fuckcisc", f"ACS:CiscoSecure-Defined-ACL=#ACSACL#-IP-{acl}-588b68cd",
]
) )
) )
log(radiusd.L_INFO, str(cisco))
return cisco return cisco
def deciscoize_acl_name(acl_name): 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): def get_acl(acl_name):
with open(acl_name, encoding="utf-8") as f: with open(f"acl/{acl_name}.acl", encoding="utf-8") as file:
return f.read() 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): def authorize(p):
@ -99,11 +99,14 @@ def authorize(p):
] ]
if netaddr.valid_mac(request["User-Name"]): if netaddr.valid_mac(request["User-Name"]):
mac = netaddr.EUI(request["User-Name"])
mac.dialect = netaddr.mac_unix_expanded
leases = get_leases() 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"]: 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 = [ conf = [
("Auth-Type", "Accept"), ("Auth-Type", "Accept"),

View File

@ -7,7 +7,7 @@ header {
# NOTES: iptables produces filter 'lines' that must be used as args to the # NOTES: iptables produces filter 'lines' that must be used as args to the
# '$ iptables' cmd, while Speedway produces stateful iptables filters # '$ iptables' cmd, while Speedway produces stateful iptables filters
# compatible with iptables-restore (most people will prefer speedway) # compatible with iptables-restore (most people will prefer speedway)
target:: cisco default-web extended target:: cisco web extended
target:: speedway INPUT target:: speedway INPUT
} }

4
root/etc/services.d/mqtt/run Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/with-contenv bash
exec \
s6-setuidgid abc /usr/bin/mqtt.py /config/leases.json

49
root/usr/bin/mqtt.py Executable file
View File

@ -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))