proxmox: add LDAP user sync script
Since OIDC auth doesn’t support groups, get them from AD over LDAP. Add a script to fetch user and groups, and update /etc/pve/user.cfg. The script is only installed on one node (first alphabetically), with a cron job to run it daily. The script is installed for clusters with the sync-ldap context key set to a corresponding OIDC realm. The keys ldap_user and ldap_pass must be present in the password store under cluster/<name>.
This commit is contained in:
parent
5762236ac2
commit
3f53c84865
|
@ -52,3 +52,5 @@
|
|||
- include_tasks: firewall.yml
|
||||
|
||||
- include_tasks: frr.yml
|
||||
|
||||
- include_tasks: user.yml
|
||||
|
|
27
roles/proxmox/tasks/user.yml
Normal file
27
roles/proxmox/tasks/user.yml
Normal file
|
@ -0,0 +1,27 @@
|
|||
- block:
|
||||
- set_fact:
|
||||
primary: '{{ nodes | map(attribute="inventory_hostname") | sort | first }}'
|
||||
|
||||
- name: Install LDAP sync script
|
||||
template:
|
||||
dest: /usr/local/bin/sync-ldap.py
|
||||
src: sync-ldap.py.j2
|
||||
mode: 0700
|
||||
when: primary == inventory_hostname
|
||||
|
||||
- name: Remove LDAP sync script
|
||||
file:
|
||||
path: /usr/local/bin/sync-ldap.py
|
||||
state: absent
|
||||
when: primary != inventory_hostname
|
||||
|
||||
- name: Configure cronjob
|
||||
cron:
|
||||
name: 'sync LDAP users and groups'
|
||||
job: 'ip vrf exec default /usr/local/bin/sync-ldap.py'
|
||||
user: root
|
||||
cron_file: sync-ldap
|
||||
hour: "2"
|
||||
minute: "51"
|
||||
state: '{{ "present" if inventory_hostname == primary else "absent" }}'
|
||||
when: '"sync-ldap" in hostvars[inventory_hostname]'
|
57
roles/proxmox/templates/sync-ldap.py.j2
Normal file
57
roles/proxmox/templates/sync-ldap.py.j2
Normal file
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import collections
|
||||
import os
|
||||
import re
|
||||
|
||||
import ldap3
|
||||
|
||||
{% set password = lookup('passwordstore', "cluster/"+cluster, returnall=true) | from_yaml %}
|
||||
realm = '{{ hostvars[inventory_hostname]["sync-ldap"] }}'
|
||||
ldap_host = '{{ domain }}'
|
||||
ldap_user = '{{ password.ldap_user }}'
|
||||
ldap_pass = '{{ password.ldap_pass }}'
|
||||
ldap_base = '{{ domain | split(".") | map("regex_replace", "^", "dc=") | join(",") }}'
|
||||
|
||||
# build LDAP query for users
|
||||
filters = [
|
||||
'(objectClass=user)', # only users
|
||||
'(objectCategory=person)', # that are people
|
||||
'(schacHomeOrganization=*)', # presumably
|
||||
'(!(userAccountControl:1.2.840.113556.1.4.803:=2))' # with enabled accounts
|
||||
]
|
||||
|
||||
# run query
|
||||
server = ldap3.Server(ldap_host, use_ssl=True)
|
||||
ldap = ldap3.Connection(server, ldap_user, ldap_pass, auto_bind=True)
|
||||
ldap.search(ldap_base,
|
||||
f'(&{"".join(filters)})', # conjuction (&(…)(…)(…)) of queries
|
||||
attributes=['userPrincipalName', 'givenName', 'sn', 'mail', 'memberOf'])
|
||||
|
||||
# build user and group dicts
|
||||
all_users = {}
|
||||
all_groups = collections.defaultdict(set)
|
||||
for e in ldap.entries:
|
||||
user = f'{e.userPrincipalName.value}@{realm}'
|
||||
all_users[user] = { k: e[k].value for k in e.entry_attributes }
|
||||
for group in e.memberOf:
|
||||
if m := re.match(r'^CN=([^,]*)', group.replace('\\,', '-')):
|
||||
group = re.sub(r'[^A-Za-z0-9_.-]', '-', m[1])
|
||||
all_groups[group].add(user)
|
||||
|
||||
with open('/etc/pve/user.cfg.new', 'w') as f:
|
||||
# user:{username}@{realm}:1:0:{name}:{surname}:{mail}:AD sync::
|
||||
for user, info in sorted(all_users.items()):
|
||||
print(f'user:{user}:1:0:{info["givenName"]}:{info["sn"]}:{info["mail"]}:AD sync::', file=f)
|
||||
|
||||
# group:{name}:{users}:AD sync:
|
||||
print(f'group:ALL:{",".join(sorted(all_users))}:AD sync:', file=f)
|
||||
for group, users in all_groups.items():
|
||||
print(f'group:{group}:{",".join(sorted(users))}:AD sync:', file=f)
|
||||
|
||||
# keep everything not added by us
|
||||
for line in open('/etc/pve/user.cfg'):
|
||||
if not re.match('^(user|group):.*:AD sync:', line):
|
||||
print(line, end='', file=f)
|
||||
|
||||
os.rename('/etc/pve/user.cfg.new', '/etc/pve/user.cfg')
|
Loading…
Reference in a new issue