Initial Commit

This commit is contained in:
Mario Voigt 2024-12-10 10:07:55 +01:00
commit af2a3e714a
5 changed files with 228 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
env
users.toml

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Mario Voigt / Stadtfabrikanten e.V. / The FabInfra Community
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

100
README.md Normal file
View File

@ -0,0 +1,100 @@
# FabAccess users.toml LDAP Import
# Zweck
Dieses Script verbindet sich bei Ausführung mit den angegebenen Credentials zu einem LDAP(S)-Server und sucht nach passenden Nutzern, um eine FabAccess-kompatible `users.toml` Datei zu erzeugen.
Das Script dient außerdem auch als Beispielvorlage für andere Entwickler, die ggf. andere Anwendungen bzw. Benutzerquellen an FabAccess anbinden wollen und nach geeigneten Code-Quellen suchen.
**Wichtig**: Dieses Script ersetzt **keine** native LDAP-Integration in FabAccess!
# Installation
```bash
cd /opt/fabinfra/scripts/
git clone https://github.com/vmario89/fabaccess-users-toml-ldap.git
cd /opt/fabinfra/scripts/fabaccess-users-toml-ldap/
chmod +x /opt/fabinfra/scripts/fabaccess-users-toml-ldap/main.py
chown -R bffh:bffh /opt/fabinfra/scripts/fabaccess-users-toml-ldap/
```
# Benutzung
## Hilfe / Parameter anzeigen
```shell
cd /opt/fabinfra/scripts/fabaccess-users-toml-ldap/
python3 main.py --help
```
```shell
usage: main.py [-h] [-s SERVER] [-u USER] [-p PASSWORD] [-b BASEDN] [--filter_user FILTER_USER] [--regex_groups REGEX_GROUPS] [--attrib_user ATTRIB_USER] [--attrib_groups ATTRIB_GROUPS]
[--attrib_password ATTRIB_PASSWORD] [--output OUTPUT]
options:
-h, --help show this help message and exit
-s SERVER, --server SERVER
LDAP Server (Syntax: <protocol>://host:port, e.g. ldap://192.168.1.1:389 or ldaps://192.168.1.1.:646)
-u USER, --user USER User, e.g. 'uid=root,cn=users,dc=yourserver,dc=com'
-p PASSWORD, --password PASSWORD
Password
-b BASEDN, --basedn BASEDN
BaseDN, for example 'cn=users,dc=yourserver,dc=com'
--filter_user FILTER_USER
LDAP user filter, e.g. '(&(uid=*)(objectClass=posixAccount))'
--regex_groups REGEX_GROUPS
LDAP group regex, e.g. 'cn=(.*),cn=groups,dc=yourserver,dc=com'. If your group result is 'cn=administrator,cn=groups,dc=yourserver,dc=com', then the word
'administrator' gets properly exctracted. You can use https://regex101.com for testing.
--attrib_user ATTRIB_USER
Attribute name for FabAccess user name, e.g. 'uid'
--attrib_groups ATTRIB_GROUPS
Attribute name for FabAccess user roles, e.g. 'memberOf'
--attrib_password ATTRIB_PASSWORD
Attribute name for FabAccess user password hash, e.g. 'sambaNTPassword'. For OpenLDAP there is Argon2 hash support!
--output OUTPUT Target directory + file where to write the toml file. Please provide the full name. If not given, users.toml will be written
```
## `users.toml` Datei schreiben
```shell
cd /opt/fabinfra/scripts/fabaccess-users-toml-ldap/
python3 main.py --server ldap://192.168.188.1:389 --user="uid=admin,cn=users,dc=yourserver,dc=com" --password pw --basedn "cn=users,dc=yourserver,dc=com" --filter_user "uid=*" --regex_groups "cn=(.*),cn=groups,dc=yourserver,dc=com" --attrib_user uid --attrib_groups memberOf --attrib_password sambaNTPassword --output /opt/fabinfra/bffh-data/config/users.toml
```
Nach dem Erstellen der Datei sollte diese überprüft und im Anschluss per `bffhd --load users.toml` geladen werden, um die Änderungen entsprechend zu reflektieren.
## Weiterführende Dokumentation
Für das Erstellen von `regex_groups` kann [https://regex101.com](https://regex101.com) genutzt werden. Eine Anleitung für einen beispielhaften Synology LDAP Server findet sich in der FabAccess Dokumentation https://docs.fab-access.org/books/plugins-und-schnittstellen/page/ldap-anbindung.
# Bekannte Probleme
Dieses Script stellt keine saubere Lösung für die Nutzung von LDAP mit FabAccess bffh dar (zumindest nicht mit Version 0.4.2):
* etwaige Passwortänderungen am zentralen LDAP-Server müssen erneut in die `users.toml` Datei übertragen werden
* ändert der Nutzer oder der Administrator für den Nutzer das Passwort über die Client App, dann würde der Nutzer beim nächstens `users.toml` Import wieder überschrieben. Diese Änderungen werden also auch nicht an den LDAP-Server gesendet
* die Password Hashes aus dem LDAP Server können in der Regel nicht mit FabAccess verwendet werden, da sie kein Argon2-Format aufweisen. Einzig OpenLDAP unterstützt Argon2 überhaupt. Aus adminstrativen Gründen macht eine Umstellung aller Nutzerpassworthashes auf Argon2 jedoch keinen Sinn. Eine native Integration von LDAP direkt in FabAccess ist also unumgänglich.
# Hinweise
Das Script basiert auf der Idee von https://gitlab.bht-berlin.de/innovisionlab/ldapbodge
## Python Module
Das Script wurde getestet mit:
```bash
python -V
3.12.3
pip list installed
pip 24.0
pyasn1 0.6.1
pyasn1_modules 0.4.1
python-ldap 3.4.4
toml 0.10.2
```

101
main.py Normal file
View File

@ -0,0 +1,101 @@
import ldap
import toml
import sys
import secrets
import string
import argparse
import re
import pathlib
def init_ldap(server, user, pw):
con = ldap.initialize(server)
con.protocol_version=ldap.VERSION3
con.simple_bind_s(user, pw)
return con
if __name__ == '__main__':
pars = argparse.ArgumentParser()
pars.add_argument("-s", "--server", type=str, dest="server", help="LDAP Server (Syntax: <protocol>://host:port, e.g. ldap://192.168.1.1:389 or ldaps://192.168.1.1.:646)")
pars.add_argument("-u", "--user", type=str, dest="user", help="User, e.g. 'uid=root,cn=users,dc=yourserver,dc=com'")
pars.add_argument("-p", "--password", type=str, dest="password", help="Password")
pars.add_argument("-b", "--basedn", type=str, dest="basedn", help="BaseDN, for example 'cn=users,dc=yourserver,dc=com'")
pars.add_argument("--filter_user", type=str, dest="filter_user", help="LDAP user filter, e.g. '(&(uid=*)(objectClass=posixAccount))'")
pars.add_argument("--regex_groups", type=str, dest="regex_groups", help="LDAP group regex, e.g. 'cn=(.*),cn=groups,dc=yourserver,dc=com'. If your group result is 'cn=administrator,cn=groups,dc=yourserver,dc=com', then the word 'administrator' gets properly exctracted. You can use https://regex101.com for testing.")
pars.add_argument("--attrib_user", type=str, dest="attrib_user", help="Attribute name for FabAccess user name, e.g. 'uid'")
pars.add_argument("--attrib_groups", type=str, dest="attrib_groups", help="Attribute name for FabAccess user roles, e.g. 'memberOf'")
pars.add_argument("--attrib_password", type=str, dest="attrib_password", help="Attribute name for FabAccess user password hash, e.g. 'sambaNTPassword'. For OpenLDAP there is Argon2 hash support!")
pars.add_argument("--output", type=pathlib.Path, dest="output", help="Target directory + file where to write the toml file. Please provide the full name. If not given, users.toml will be written")
options = pars.parse_args()
if not any(vars(options).values()):
pars.print_help()
exit(1)
if options.filter_user is None or options.filter_user == "":
print("Please provide --filter_user argument")
exit(1)
if options.regex_groups is None or options.regex_groups == "":
print("Please provide --regex_groups argument")
exit(1)
if options.attrib_user is None or options.attrib_user == "":
print("Please provide --attrib_user argument")
exit(1)
if options.attrib_groups is None or options.attrib_groups == "":
print("Please provide --attrib_groups argument")
exit(1)
if options.attrib_password is None or options.attrib_password == "":
print("Please provide --attrib_password argument")
exit(1)
try:
con = init_ldap(options.server, options.user, options.password)
except Exception as e:
print("Could not connect to LDAP server: {}".format(str(e)))
sys.exit(1)
ldap_attributes = [options.attrib_user, options.attrib_groups, options.attrib_password] # List of attributes that you want to fetch.
#ldap_attributes = ["*"] # Wildcard filter - show all!
query_user = options.filter_user
ldap_users = con.search_s(options.basedn, ldap.SCOPE_SUBTREE, options.filter_user, ldap_attributes)
users = []
for data_dict in [entry for dn, entry in ldap_users if isinstance(entry, dict)]:
users.append(data_dict[options.attrib_user][0].decode("utf-8"))
pw = []
for user in users:
for data_dict in [entry for dn, entry in ldap_users if isinstance(entry, dict)]:
if data_dict[options.attrib_user][0].decode("utf-8") == user:
groups = []
for group in data_dict[options.attrib_groups]:
group_dec = group.decode("utf-8")
pattern = r"{}".format(options.regex_groups)
groups += re.findall(pattern, group_dec)
if len(groups) == 0:
groups.append("default_role")
password = data_dict[options.attrib_password][0].decode("utf-8")
userdict = dict()
userdict["passwd"] = password
userdict["roles"] = groups
pw.append(userdict)
if options.output is not None:
target = options.output
else:
target = pathlib.Path("users.toml")
with open(target, "w") as f:
# the trailing comma in users.toml comes from command "toml.dump()". These are fine!
toml.dump(dict(zip(users, pw)), f)
print("{} users dumped to {}".format(len(users), target.absolute()))

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
pyasn1
pyasn1-modules
python-ldap
toml