diff --git a/machines/manager/default.nix b/machines/manager/default.nix index 64515bb..d736d3d 100644 --- a/machines/manager/default.nix +++ b/machines/manager/default.nix @@ -1,5 +1,5 @@ _: -{ lib, config, ... }: +{ pkgs, lib, config, ... }: with lib; diff --git a/machines/manager/users.nix b/machines/manager/users.nix index d73185e..104369f 100644 --- a/machines/manager/users.nix +++ b/machines/manager/users.nix @@ -28,4 +28,8 @@ with lib; owner = "root"; group = "root"; }; + + users.users."root".packages = [ + (pkgs.callPackage ../../packages/usermgr { }) + ]; } diff --git a/packages/usermgr/default.nix b/packages/usermgr/default.nix new file mode 100644 index 0000000..7bd7608 --- /dev/null +++ b/packages/usermgr/default.nix @@ -0,0 +1,23 @@ +{ python3Packages +, ... +}: + +with python3Packages; + +buildPythonApplication { + pname = "usermgr"; + version = "0.1"; + + format = "pyproject"; + + nativeBuildInputs = [ + setuptools + ]; + propagatedBuildInputs = [ + click + ldap3 + ]; + + src = ./.; +} + diff --git a/packages/usermgr/pyproject.toml b/packages/usermgr/pyproject.toml new file mode 100644 index 0000000..73bd7d9 --- /dev/null +++ b/packages/usermgr/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "usermgr" +version = "0.1" +requires-python = ">=3.9" +dependencies = [ + "click>=8", + "ldap3>=2.9" +] + +[project.scripts] +usermgr = "usermgr:cli" + diff --git a/packages/usermgr/usermgr.py b/packages/usermgr/usermgr.py new file mode 100644 index 0000000..8dcb069 --- /dev/null +++ b/packages/usermgr/usermgr.py @@ -0,0 +1,83 @@ +import click +import ssl +from ldap3 import Server, Connection, Tls + + +@click.group() +@click.option('--server', default='edir1.rz.hs-fulda.de', help='LDAP server URL') +@click.option('--username', prompt=True, default='cn=fdhpc,ou=AI,o=FH-Fulda', help='LDAP bind username') +@click.option('--password', prompt=True, hide_input=True, help='LDAP bind password') +@click.pass_context +def cli(ctx, server, username, password): + tls = Tls(validate=ssl.CERT_REQUIRED, + version=ssl.PROTOCOL_TLSv1_2, + ciphers="AES256-GCM-SHA384") + server = Server(server, tls=tls, get_info="ALL") + ctx.obj = Connection(server, + username, + password, + auto_bind=True) + + +@cli.command() +@click.pass_context +def list(ctx): + ctx.obj.search('o=FH-Fulda', '''(& + (cn=fd*) + (objectClass=inetOrgPerson) + (groupMembership=cn=ORG-AI-HPC,ou=AI,o=FH-Fulda) + (! + (| + (description=*funktion*) + (loginDisabled=true) + (sn=fd*) + ) + ) + )''', + attributes = ['cn', 'member', 'sn', 'givenName']) + + for e in ctx.obj.entries: + click.echo(f'{click.style(e.cn, fg="blue", bold=True)}: {e.sn}, {e.givenName}') + + +def find(ctx, name): + from ldap3.utils.conv import escape_filter_chars + + ctx.obj.search('o=FH-Fulda', f'''(& + (cn={escape_filter_chars(name)}) + (objectClass=inetOrgPerson) + (! + (| + (description=*funktion*) + (loginDisabled=true) + (sn=fd*) + ) + ) + )''') + + if not ctx.obj.entries: + ctx.fail(f'No user found: {name}') + + return ctx.obj.entries[0].entry_dn + + + +@cli.command() +@click.argument('name', nargs=-1, required=True) +@click.pass_context +def add(ctx, name): + members = [find(ctx, name) for name in name] + ctx.obj.extend.novell.add_members_to_groups(members, 'cn=ORG-AI-HPC,ou=AI,o=FH-Fulda') + + +@cli.command() +@click.pass_context +@click.argument('name', nargs=-1, required=True) +def remove(ctx, name): + members = [find(ctx, name) for name in name] + ctx.obj.extend.novell.remove_members_from_groups(members, 'cn=ORG-AI-HPC,ou=AI,o=FH-Fulda') + + +if __name__ == '__main__': + cli() +