Skip to content

Commit 88571f1

Browse files
committed
update acls
1 parent 583e398 commit 88571f1

1 file changed

Lines changed: 141 additions & 1 deletion

File tree

packages/helpermodules/mosquitto_dynsec.py

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from json import load as json_load
2+
import json
23
import logging
34
from pathlib import Path
4-
from typing import TypedDict, Optional
5+
import re
6+
from typing import Tuple, TypedDict, Optional
57

68
from helpermodules.subdata import SubData
79
from helpermodules.utils.run_command import run_command
@@ -83,6 +85,7 @@ def remove_acl_role(role_template: str, id: int):
8385

8486

8587
def check_roles_at_start():
88+
update_acls()
8689
flag_path = Path(Path(__file__).resolve().parents[2]/"ramdisk"/"init_user_management")
8790
if flag_path.is_file():
8891
with open(flag_path, "r") as file:
@@ -104,3 +107,140 @@ def check_roles_at_start():
104107
if "io" in key:
105108
add_acl_role("io-device-<id>-access", value.config.id)
106109
flag_path.unlink()
110+
111+
112+
def _extract_id_from_role_name(role_name: str) -> Optional[int]:
113+
numbers = re.findall(r'\d+', role_name)
114+
if numbers:
115+
return int(numbers[0])
116+
return None
117+
118+
119+
def _compare_acl(template_acl: MosquittoAcl, configured_acl: MosquittoAcl) -> bool:
120+
if template_acl["acltype"] == configured_acl["acltype"]:
121+
if re.sub(r'/\d+/', '/<id>/', template_acl["topic"]) == re.sub(r'/\d+/', '/<id>/', configured_acl["topic"]):
122+
if template_acl["allow"] == configured_acl["allow"]:
123+
if template_acl["priority"] == configured_acl["priority"]:
124+
return True
125+
return False
126+
127+
128+
def _get_configured_role_data(role_name: str) -> Optional[MosquittoRole]:
129+
role_output = run_command([
130+
"mosquitto_ctrl", "dynsec", "getRole", role_name])
131+
# Parse the text output since it's not JSON
132+
role_data = {"rolename": role_name, "acls": []}
133+
lines = role_output.strip().split('\n')
134+
for line in lines[1:]: # Skip first line with rolename
135+
if "ACLs:" in line:
136+
line = line.replace("ACLs:", "")
137+
if line.strip() and ':' in line:
138+
parts = [p.strip() for p in line.split(':')]
139+
if len(parts) >= 4:
140+
acl = {
141+
"acltype": parts[0],
142+
"allow": parts[1] == "allow",
143+
"topic": parts[2].split('(')[0].strip(),
144+
"priority": int(parts[3].replace(')', '').strip())
145+
}
146+
role_data["acls"].append(acl)
147+
return role_data
148+
149+
150+
def update_acls():
151+
def is_version_updated() -> Tuple[str, str]:
152+
for role in roles:
153+
if "openwb-version" in role:
154+
current_version = role.split(":")[1]
155+
break
156+
else:
157+
raise RuntimeError("openwb-version role not found")
158+
159+
for role in dynsec_config["roles"]:
160+
try:
161+
if "openwb-version" in role["rolename"]:
162+
template_version = role["rolename"].split(":")[1]
163+
break
164+
except Exception:
165+
continue
166+
else:
167+
raise RuntimeError("openwb-version role not found in default-dynamic-security.json")
168+
169+
if current_version != template_version:
170+
log.debug(f"Updating ACLs from version {current_version} to {template_version}")
171+
return current_version, template_version
172+
173+
def get_template_role_data() -> Optional[MosquittoRole]:
174+
template_role_data = None
175+
for config_role in dynsec_config["roles"]:
176+
if (config_role["rolename"] == role_data["rolename"] or
177+
("openwb-version" in config_role["rolename"] and "openwb-version" in role_data["rolename"])):
178+
template_role_data = config_role
179+
break
180+
else:
181+
for config_role in role_templates_config:
182+
pattern = config_role["rolename"].replace("<id>", r"\d+")
183+
if re.match(pattern, role_data["rolename"]):
184+
template_role_data = config_role
185+
break
186+
else:
187+
raise RuntimeError(f"Role {role_data['rolename']} not found in default-dynamic-security.json")
188+
return template_role_data
189+
190+
try:
191+
roles = _list_acl_roles()
192+
with open(_get_packages_path()/"data/config/mosquitto/public/default-dynamic-security.json", "r") as f:
193+
dynsec_config = json.load(f)
194+
current_version, template_version = is_version_updated()
195+
if current_version != template_version:
196+
with open(_get_packages_path()/"data/config/mosquitto/public/role-templates.json", "r") as f:
197+
role_templates_config = json.load(f)
198+
for role_name in roles:
199+
try:
200+
role_data = _get_configured_role_data(role_name)
201+
template_role_data = get_template_role_data()
202+
if template_role_data:
203+
# vegleiche die zwei ACL dicts, welche ACLs hinzugefügt, geändert oder entfernt wurden
204+
for acl in role_data["acls"]:
205+
for template_acl in template_role_data["acls"]:
206+
if _compare_acl(template_acl, acl):
207+
break
208+
else:
209+
log.debug(f"ACL {acl['acltype']}:{'allow' if acl['allow'] else 'deny'}:{acl['topic']}:"
210+
f"{acl['priority']} in Rolle {role_data['rolename']} wird entfernt.")
211+
run_command([
212+
"mosquitto_ctrl", "dynsec", "removeRoleAcl", role_data["rolename"],
213+
acl["acltype"], acl["topic"]
214+
])
215+
for acl in template_role_data["acls"]:
216+
for role_acl in role_data["acls"]:
217+
if _compare_acl(acl, role_acl):
218+
break
219+
else:
220+
rolename_id = _extract_id_from_role_name(role_data["rolename"])
221+
if rolename_id is not None:
222+
acl["topic"] = acl["topic"].replace("<id>", str(rolename_id))
223+
log.debug(f"ACL {acl['acltype']}:{'allow' if acl['allow'] else 'deny'}:{acl['topic']}:"
224+
f"{acl['priority']} in Rolle {role_data['rolename']} wird hinzugefügt.")
225+
run_command([
226+
"mosquitto_ctrl", "dynsec", "addRoleAcl", role_data["rolename"],
227+
acl["acltype"], acl["topic"],
228+
"allow" if acl["allow"] else "deny",
229+
str(acl["priority"])
230+
])
231+
elif template_role_data is None:
232+
log.debug(f"Rolle '{role_data['rolename']}' existiert nicht in den Konfigurationsdateien und"
233+
" wird gelöscht.")
234+
run_command(["mosquitto_ctrl", "dynsec", "deleteRole", role_data["rolename"]])
235+
except Exception:
236+
log.exception(f"Fehler beim Aktualisieren der Rolle '{role_name}'")
237+
# bei allen anderen Rollen dürfen nur die ACLs editiert werden,
238+
# damit diese in den Benutzergruppen erhalten bleiben
239+
run_command(["mosquitto_ctrl", "dynsec", "deleteRole", f"openwb-version:{current_version}"])
240+
run_command(["mosquitto_ctrl", "dynsec", "createRole", f"openwb-version:{template_version}"])
241+
except Exception:
242+
log.exception("Fehler beim Aktualisieren der ACLs")
243+
244+
245+
def _get_packages_path() -> Path:
246+
return Path(__file__).resolve().parents[2]

0 commit comments

Comments
 (0)