Skip to content

Commit

Permalink
Print as LDIF (optionally)
Browse files Browse the repository at this point in the history
  • Loading branch information
lvps committed Apr 4, 2024
1 parent 1831c84 commit 4e39961
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 55 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ The `aci` directory contains some ACIs for 389DS and tests related to those.
pasted into an Ansible playbook. For details on how and where to paste it, see
[the "sso" repo](https://github.com/WEEE-Open/sso).

Alternatively, `make_acis.py` can also output a LDIF file.

`test_acis.py` uses pytest to test that the ACIs are working as expected. It also tests the password policy set in
[the "sso" repo](https://github.com/WEEE-Open/sso). It requires 389DS configured as in that repo. If you follow the
instructions there, you'll clone this repo anyway, so it all makes sense, hopefully.
Expand All @@ -43,8 +45,10 @@ cd aci
python3 -m venv venv
source venv/bin/activate
pip install
./make_acis.py
# Paste the output into the playbook
# For Ansible, past the output into the playbook:
./make_acis.py -y -s "{{ dirsrv_suffix }}"
# Alternatively, as a LDIF file (replace with real suffix):
./make_acis.py -l -s "dc=example,dc=com"
./test_acis.py
# Watch test output
```
134 changes: 81 additions & 53 deletions aci/make_acis.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3

import argparse
from typing import Iterable

# There's also lib389: https://lib389.readthedocs.io/en/latest/
Expand All @@ -21,60 +21,88 @@ def print_aci(aci: str):
print(f" - '{aci}'")


def acis():
# suffix = 'dc=example,dc=local'
suffix = '{{ dirsrv_suffix }}'
# TODO: tests for bot
# TODO: tests for weeelab
def acis(suffix: str) -> dict[str, tuple]:
result = dict()

result[suffix] = (
make_aci('Allow all to read suffix', (f'target = "ldap:///{suffix}"', 'targetattr = "objectClass"','targetfilter = "(objectClass=domain)"'), {'read', 'search'}, f'userdn = "ldap:///all"'),
)

result[f"ou=People,{suffix}"] = (
# nsAccountLock is required to search for (!(nsAccountLock=true)), placing it in targetfilter means that it MUST be present (with the specified value).
# mail is for password recovery
make_aci('Allow Keycloak to read users', ('targetfilter = "(uid=*)"', 'targetattr = "objectClass || memberOf || cn || uid || mail || createTimestamp || nsAccountLock || creatorsName || entrydn || entryid || hasSubordinates || modifiersName || modifyTimestamp || nsUniqueId || numSubordinates || parentid || subschemaSubentry"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=Keycloak,ou=Services,{suffix}"'),
make_aci('Allow Nextcloud to read users', ('targetfilter = "(uid=*)"', 'targetattr = "objectClass || memberOf || sn || cn || givenName || uid || mail || jpegPhoto || createTimestamp || creatorsName || entrydn || entryid || hasSubordinates || modifiersName || modifyTimestamp || nsUniqueId || numSubordinates || parentid || subschemaSubentry"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=nextcloud,ou=Services,{suffix}"'),
# make_aci('Allow Keycloak to change OTP secrets', ('targetfilter = "(uid=*)"', 'targetattr = "otpSecretKey"'), {'write'}, f'userdn = "ldap:///cn=Keycloak,ou=Services,{suffix}"'),
# make_aci('Allow users to change their password', ('targetfilter = "(uid=*)"', 'targetattr = "userPassword"'), {'write'}, f'userdn = "ldap:///self"'),
make_aci('Allow Crauto to read users', ('targetfilter = "(uid=*)"', 'targetattr = "uid || cn || givenname || sn || memberof || mail || schacpersonaluniquecode || degreecourse || schacdateofbirth || schacplaceofbirth || mobile || safetytestdate || telegramid || telegramnickname || weeelabnickname || hasKey || signedSir || websiteDescription || pronouns || sshpublickey || description || nsaccountlock || createTimestamp || modifyTimestamp || objectClass"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'),
make_aci('Allow Crauto to edit users', ('targetfilter="(&(uid=*)(objectClass=inetOrgPerson)(objectClass=schacPersonalCharacteristics)(objectClass=schacLinkageIdentifiers)(objectClass=telegramAccount)(objectClass=weeeOpenPerson))"', 'targetattr = "objectClass || cn || givenname || sn || memberof || mail || schacpersonaluniquecode || degreecourse || schacdateofbirth || schacplaceofbirth || mobile || safetytestdate || telegramid || telegramnickname || weeelabnickname || hasKey || signedSir || websiteDescription || pronouns || description || nsaccountlock || description"'), {'add', 'write', 'delete'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'),
make_aci('Allow Crauto to change users password', ('targetfilter = "(uid=*)"', 'targetattr = "userPassword"'), {'add', 'write'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'),

make_aci('Allow bot to read users', ('targetfilter = "(uid=*)"', 'targetattr = "uid || cn || givenname || sn || memberof || telegramid || telegramnickname || schacDateOfBirth || safetyTestDate || hasKey || signedSir || nsaccountlock || objectClass"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=bot,ou=Services,{suffix}"'),
make_aci('Allow bot to update Telegram nickname and id', ('targetfilter = "(uid=*)"', 'targetattr = "telegramnickname || telegramid"'), {'write'}, f'userdn = "ldap:///cn=bot,ou=Services,{suffix}"'),

make_aci('Allow Wiki to read users', ('targetfilter = "(uid=*)"', 'targetattr = "objectClass || memberOf || sn || cn || givenName || uid || mail || jpegPhoto || entrydn || nsUniqueId || nsAccountLock"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=wiki,ou=Services,{suffix}"'),

make_aci('Allow weeehire to read users', ('targetfilter = "(uid=*)"', 'targetattr = "uid || cn || telegramnickname || nsaccountlock || memberof || objectclass"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=weeehire,ou=Services,{suffix}"'),

make_aci('Allow weeelab to read users', ('targetfilter = "(uid=*)"', 'targetattr = "uid || givenname || cn || schacpersonaluniquecode || weeelabnickname || signedsir || nsaccountlock || objectClass"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=weeelab,ou=Services,{suffix}"'),
)

result[f"ou=Invites,{suffix}"] = (
make_aci('Allow Crauto to read invites', ('targetfilter = "(cn=*)"', 'targetattr = "inviteCode || cn || givenname || sn || mail || schacpersonaluniquecode || degreecourse || telegramid || telegramnickname"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'),
make_aci('Allow Crauto to delete invites', ('targetfilter = "(cn=*)"',), {'delete'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'),

make_aci('Allow weeehire to read and create invites', ('targetfilter = "(cn=*)"', 'targetattr = "inviteCode || cn || givenname || sn || mail || schacpersonaluniquecode || degreecourse"'), {'read', 'search', 'compare', 'write', 'add'}, f'userdn = "ldap:///cn=weeehire,ou=Services,{suffix}"'),

make_aci('Allow Bot to read invites', ('targetattr = "inviteCode || telegramid || telegramnickname"',), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=bot,ou=Services,{suffix}"'),
make_aci('Allow Bot to update invites', ('targetattr = "telegramid || telegramnickname"',), {'write'}, f'userdn = "ldap:///cn=bot,ou=Services,{suffix}"'),
)

result[f"ou=Groups,{suffix}"] = (
make_aci('Allow Keycloak to read groups', ('targetfilter = "(cn=*)"', 'targetattr = "objectClass || cn || ou || description || member || uniqueMember || nsUniqueId"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=Keycloak,ou=Services,{suffix}"'),
make_aci('Allow Nextcloud to read groups', ('targetfilter = "(cn=*)"', 'targetattr = "objectClass || cn || ou || description || member || uniqueMember || nsUniqueId"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=Nextcloud,ou=Services,{suffix}"'),
make_aci('Allow Wiki to read groups', ('targetfilter = "(cn=*)"', 'targetattr = "objectClass || cn || ou || member || uniqueMember || nsUniqueId"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=wiki,ou=Services,{suffix}"'),
make_aci('Allow Crauto to read groups', ('targetfilter = "(cn=*)"', 'targetattr = "objectClass || cn || ou || description || member || uniqueMember || nsUniqueId"',), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'),
make_aci('Allow Crauto to add and remove people from groups', ('targetfilter = "(cn=*)"', 'targetattr = "member || uniqueMember"',), {'write'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'),
)

return result


def yaml(suffix: str):
things: dict[str, tuple] = acis(suffix)

print("Paste this into your playbook:\n")
print(f""" -
dn: "{suffix}"
acis:""")
print_aci(make_aci('Allow all to read suffix', (f'target = "ldap:///{suffix}"', 'targetattr = "objectClass"','targetfilter = "(objectClass=domain)"'), {'read', 'search'}, f'userdn = "ldap:///all"'))
print(f""" -
dn: "ou=People,{suffix}"
acis:""")
# nsAccountLock is required to search for (!(nsAccountLock=true)), placing it in targetfilter means that it MUST be present (with the specified value).
# mail is for password recovery
print_aci(make_aci('Allow Keycloak to read users', ('targetfilter = "(uid=*)"', 'targetattr = "objectClass || memberOf || cn || uid || mail || createTimestamp || nsAccountLock || creatorsName || entrydn || entryid || hasSubordinates || modifiersName || modifyTimestamp || nsUniqueId || numSubordinates || parentid || subschemaSubentry"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=Keycloak,ou=Services,{suffix}"'))
print_aci(make_aci('Allow Nextcloud to read users', ('targetfilter = "(uid=*)"', 'targetattr = "objectClass || memberOf || sn || cn || givenName || uid || mail || jpegPhoto || createTimestamp || creatorsName || entrydn || entryid || hasSubordinates || modifiersName || modifyTimestamp || nsUniqueId || numSubordinates || parentid || subschemaSubentry"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=nextcloud,ou=Services,{suffix}"'))
# print_aci(make_aci('Allow Keycloak to change OTP secrets', ('targetfilter = "(uid=*)"', 'targetattr = "otpSecretKey"'), {'write'}, f'userdn = "ldap:///cn=Keycloak,ou=Services,{suffix}"'))
# print_aci(make_aci('Allow users to change their password', ('targetfilter = "(uid=*)"', 'targetattr = "userPassword"'), {'write'}, f'userdn = "ldap:///self"'))

print_aci(make_aci('Allow Crauto to read users', ('targetfilter = "(uid=*)"', 'targetattr = "uid || cn || givenname || sn || memberof || mail || schacpersonaluniquecode || degreecourse || schacdateofbirth || schacplaceofbirth || mobile || safetytestdate || telegramid || telegramnickname || weeelabnickname || hasKey || signedSir || websiteDescription || pronouns || sshpublickey || description || nsaccountlock || createTimestamp || modifyTimestamp || objectClass"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'))
print_aci(make_aci('Allow Crauto to edit users', ('targetfilter="(&(uid=*)(objectClass=inetOrgPerson)(objectClass=schacPersonalCharacteristics)(objectClass=schacLinkageIdentifiers)(objectClass=telegramAccount)(objectClass=weeeOpenPerson))"', 'targetattr = "objectClass || cn || givenname || sn || memberof || mail || schacpersonaluniquecode || degreecourse || schacdateofbirth || schacplaceofbirth || mobile || safetytestdate || telegramid || telegramnickname || weeelabnickname || hasKey || signedSir || websiteDescription || pronouns || description || nsaccountlock || description"'), {'add', 'write', 'delete'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'))
print_aci(make_aci('Allow Crauto to change users password', ('targetfilter = "(uid=*)"', 'targetattr = "userPassword"'), {'add', 'write'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'))

print_aci(make_aci('Allow bot to read users', ('targetfilter = "(uid=*)"', 'targetattr = "uid || cn || givenname || sn || memberof || telegramid || telegramnickname || schacDateOfBirth || safetyTestDate || hasKey || signedSir || nsaccountlock || objectClass"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=bot,ou=Services,{suffix}"'))
print_aci(make_aci('Allow bot to update Telegram nickname and id', ('targetfilter = "(uid=*)"', 'targetattr = "telegramnickname || telegramid"'), {'write'}, f'userdn = "ldap:///cn=bot,ou=Services,{suffix}"'))

print_aci(make_aci('Allow Wiki to read users', ('targetfilter = "(uid=*)"', 'targetattr = "objectClass || memberOf || sn || cn || givenName || uid || mail || jpegPhoto || entrydn || nsUniqueId || nsAccountLock"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=wiki,ou=Services,{suffix}"'))

print_aci(make_aci('Allow weeehire to read users', ('targetfilter = "(uid=*)"', 'targetattr = "uid || cn || telegramnickname || nsaccountlock || memberof || objectclass"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=weeehire,ou=Services,{suffix}"'))

print_aci(make_aci('Allow weeelab to read users', ('targetfilter = "(uid=*)"', 'targetattr = "uid || givenname || cn || schacpersonaluniquecode || weeelabnickname || signedsir || nsaccountlock || objectClass"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=weeelab,ou=Services,{suffix}"'))
print(f""" -
dn: "ou=Invites,{suffix}"
acis:""")
# targetfilter makes no sense, but these CANNOT be left empty and "target = ldap:\\\*,ou=Invites,..." does NOT work.
print_aci(make_aci('Allow Crauto to read invites', ('targetfilter = "(cn=*)"', 'targetattr = "inviteCode || cn || givenname || sn || mail || schacpersonaluniquecode || degreecourse || telegramid || telegramnickname"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'))
print_aci(make_aci('Allow Crauto to delete invites', ('targetfilter = "(cn=*)"',), {'delete'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'))

print_aci(make_aci('Allow weeehire to read and create invites', ('targetfilter = "(cn=*)"', 'targetattr = "inviteCode || cn || givenname || sn || mail || schacpersonaluniquecode || degreecourse"'), {'read', 'search', 'compare', 'write', 'add'}, f'userdn = "ldap:///cn=weeehire,ou=Services,{suffix}"'))

print_aci(make_aci('Allow Bot to read invites', ('targetattr = "inviteCode || telegramid || telegramnickname"',), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=bot,ou=Services,{suffix}"'))
print_aci(make_aci('Allow Bot to update invites', ('targetattr = "telegramid || telegramnickname"',), {'write'}, f'userdn = "ldap:///cn=bot,ou=Services,{suffix}"'))

print(f""" -
dn: "ou=Groups,{suffix}"
acis:""")
print_aci(make_aci('Allow Keycloak to read groups', ('targetfilter = "(cn=*)"', 'targetattr = "objectClass || cn || ou || description || member || uniqueMember || nsUniqueId"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=Keycloak,ou=Services,{suffix}"'))
print_aci(make_aci('Allow Nextcloud to read groups', ('targetfilter = "(cn=*)"', 'targetattr = "objectClass || cn || ou || description || member || uniqueMember || nsUniqueId"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=Nextcloud,ou=Services,{suffix}"'))
print_aci(make_aci('Allow Wiki to read groups', ('targetfilter = "(cn=*)"', 'targetattr = "objectClass || cn || ou || member || uniqueMember || nsUniqueId"'), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=wiki,ou=Services,{suffix}"'))
print_aci(make_aci('Allow Crauto to read groups', ('targetfilter = "(cn=*)"', 'targetattr = "objectClass || cn || ou || description || member || uniqueMember || nsUniqueId"',), {'read', 'search', 'compare'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'))
print_aci(make_aci('Allow Crauto to add and remove people from groups', ('targetfilter = "(cn=*)"', 'targetattr = "member || uniqueMember"',), {'write'}, f'userdn = "ldap:///cn=crauto,ou=Services,{suffix}"'))
for dn in things:
print(f""" -
dn: "{dn}"
acis:""")
for aci in things[dn]:
print_aci(aci)


def ldif(suffix: str):
things: dict[str, tuple] = acis(suffix)

for dn in things:
print(f"dn: {dn}\n\
changetype: modify\n\
replace: aci")
for aci in things[dn]:
print(f"aci: {aci}")
print("")


if __name__ == '__main__':
acis()
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-y", "--yaml", action="store_true")
group.add_argument("-l", "--ldif", action="store_true")
parser.add_argument('-s', '--suffix', help='Directory suffix or a variable that will be substituted, e.g. dc=example,dc=com or {{ dirsrv_suffix }}', required=True)
args = parser.parse_args()

if args.yaml:
yaml(args.suffix)
else:
ldif(args.suffix)

0 comments on commit 4e39961

Please sign in to comment.