mirror of
https://github.com/vmario89/fabaccess-users-toml-ldap.git
synced 2025-03-11 14:31:41 +01:00
Initial Commit
This commit is contained in:
commit
af2a3e714a
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
env
|
||||
users.toml
|
21
LICENSE.md
Normal file
21
LICENSE.md
Normal 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
100
README.md
Normal 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
101
main.py
Normal 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
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
pyasn1
|
||||
pyasn1-modules
|
||||
python-ldap
|
||||
toml
|
Loading…
x
Reference in New Issue
Block a user