Authentik has “expression policies”, which are essentially are arbitrary python code. You are supposed to have them evaluate some stuff, and then return true/false depending on whether or not you want to allow the action/login.
But instead of doing that, I wrote an Authentik expression policy that makes requests to the Openfga server, and removes/adds users depending on which Authentik groups they are in.
import json
from authentik.core.models import Group
system_groups = ["authentik Read-only", "authentik Admins"]
all_groups = [group.name for group in Group.objects.all()]
all_groups = list(set(all_groups) - set(system_groups))
user_groups = [group.name for group in request.user.all_groups()]
# I have my openfga server gated behind a token, but it's not by default
openfga_api_key = "secretkey"
writes = {
"writes": {
"tuple_keys": [
{
"user": f"user:{request.user.username}",
"relation": "member",
"object": "group"
}
]
},
"authorization_model_id": "01K70P5HAQ8K2J3AN978F7Y1EC"
}
deletes = {
"deletes": {
"tuple_keys": [
{
"user": f"user:{request.user.username}",
"relation": "member",
"object": "group"
}
]
},
"authorization_model_id": "01K70P5HAQ8K2J3AN978F7Y1EC"
}
headers = {
"Authorization": f"Bearer {openfga_api_key}",
"content-type": "application/json"
}
for group in list(set(all_groups) - set(user_groups)):
deletes["deletes"]["tuple_keys"][0]["object"] = f"group:{group}"
requests.post('https://openfga.moonpiedumpl.ing/stores/01K70P3ZX9DJEHF85XDJS4PXN2/write', data=json.dumps(deletes), headers=headers)
for group in user_groups:
writes["writes"]["tuple_keys"][0]["object"] = f"group:{group}"
requests.post('https://openfga.moonpiedumpl.ing/stores/01K70P3ZX9DJEHF85XDJS4PXN2/write', data=json.dumps(writes), headers=headers)
# It's supposed to return true/false depending on whether or not you want to pass/fail the action but I just always return true here
return True
Then I bound this expression policy to the Incus application in authentik, so when users log into Incus, their groups are synced.
The “proper” way to do this is probably to have webhooks that make calls to a small custom app you have written which does the role sync. As far as I can tell, this feature of arbitrary code is unique to Authentik.
Anyway, this seems to work great. My boot SSD died and since I had the chance to rebuild, I decided to do this, instead of what I was previously doing, which was a one time openfga user add that only happened when they signed up via an invite link on Authentik. Now the groups sync constantly.
I’ve been using Incus for my college’s cybersecurity club and I am thinking that this will make it easy to do some kind of PvP CTF, where I can just spin up projects for each team and then add/remove users from Authentik groups where I have synced the group name with view only permissions to the relevant projects.