netbox: configure and patch NetBox for OIDC group and role mapping

The custom pipeline code is licensed under the Creative Commons: CC
BY-SA 4.0 license.

See:
https://github.com/goauthentik/authentik/blob/main/LICENSE
https://github.com/goauthentik/authentik/blob/main/website/integrations/services/netbox/index.md
https://docs.goauthentik.io/integrations/services/netbox/
This commit is contained in:
June 2025-01-14 20:49:14 +01:00
parent d36ff73123
commit 5676b1a468
Signed by: june
SSH key fingerprint: SHA256:o9EAq4Y9N9K0pBQeBTqhSDrND5E7oB+60ZNx0U1yPe0
4 changed files with 91 additions and 2 deletions

View file

@ -76,4 +76,5 @@ nix build .#proxmox-chaosknoten-nixos-template
## License
This CCCHH nix-infra repository is licensed under the [MIT License](./LICENSE).
This CCCHH nix-infra repository is licensed under the [MIT License](./LICENSE).
[`0001_oidc_group_and_role_mapping_custom_pipeline.patch`](patches/0001_oidc_group_and_role_mapping_custom_pipeline.patch) is licensed under the Creative Commons: CC BY-SA 4.0 license.

View file

@ -9,7 +9,8 @@
{
services.netbox = {
enable = true;
package = pkgs.netbox;
# Explicitly use the patched NetBox package.
package = pkgs.netbox_4_1;
secretKeyFile = "/run/secrets/netbox_secret_key";
keycloakClientSecret = "/run/secrets/netbox_keycloak_secret";
settings = {
@ -24,6 +25,24 @@
SOCIAL_AUTH_KEYCLOAK_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi/Shi+b2OyYNGVFPsa6qf9SesEpRl5U5rpwgmt8H7NawMvwpPUYVW9o46QW0ulYcDmysT3BzpP3tagO/SFNoOjZdYe0D9nJ7vEp8KHbzR09KCfkyQIi0wLssKnDotVHL5JeUY+iKk+gjiwF9FSFSHPBqsST7hXVAut9LkOvs2aDod9AzbTH/uYbt4wfUm5l/1Ii8D+K7YcsFGUIqxv4XS/ylKqObqN4M2dac69iIwapoh6reaBQEm66vrOzJ+3yi4DZuPrkShJqi2hddtoyZihyCkF+eJJKEI5LrBf1KZB3Ec2YUrqk93ZGUGs/XY6R87QSfR3hJ82B1wnF+c2pw+QIDAQAB";
SOCIAL_AUTH_KEYCLOAK_AUTHORIZATION_URL = "https://id.hamburg.ccc.de/realms/ccchh/protocol/openid-connect/auth";
SOCIAL_AUTH_KEYCLOAK_ACCESS_TOKEN_URL = "https://id.hamburg.ccc.de/realms/ccchh/protocol/openid-connect/token";
SOCIAL_AUTH_PIPELINE = [
# The default pipeline as can be found in:
# /nix/store/q2jsn56bgkj0nkz0j4w48x3klyn2x4gp-netbox-4.1.7/opt/netbox/netbox/netbox/settings.py
"social_core.pipeline.social_auth.social_details"
"social_core.pipeline.social_auth.social_uid"
"social_core.pipeline.social_auth.social_user"
"social_core.pipeline.user.get_username"
"social_core.pipeline.user.create_user"
"social_core.pipeline.social_auth.associate_user"
"netbox.authentication.user_default_groups_handler"
"social_core.pipeline.social_auth.load_extra_data"
"social_core.pipeline.user.user_details"
# Use custom pipeline functions patched in via netbox41OIDCMappingOverlay.
# See: https://docs.goauthentik.io/integrations/services/netbox/
"netbox.custom_pipeline.add_groups"
"netbox.custom_pipeline.remove_groups"
"netbox.custom_pipeline.set_roles"
];
};
};

View file

@ -40,6 +40,13 @@
proxmox-vm = ./config/proxmox-vm;
prometheus-exporter = ./config/extra/prometheus-exporter.nix;
};
overlays = {
netbox41OIDCMappingOverlay = final: prev: {
netbox_4_1 = prev.netbox_4_1.overrideAttrs (finalAttr: previousAttr: {
patches = previousAttr.patches ++ [ ./patches/0001_oidc_group_and_role_mapping_custom_pipeline.patch ];
});
};
};
nixosConfigurations = {
audio-hauptraum-kueche = nixpkgs.lib.nixosSystem {
inherit system specialArgs;
@ -85,6 +92,7 @@
sops-nix.nixosModules.sops
self.nixosModules.prometheus-exporter
./config/hosts/netbox
{ nixpkgs.overlays = [ self.overlays.netbox41OIDCMappingOverlay ]; }
];
};

View file

@ -0,0 +1,61 @@
diff --git a/netbox/netbox/custom_pipeline.py b/netbox/netbox/custom_pipeline.py
new file mode 100644
index 000000000..470f388dc
--- /dev/null
+++ b/netbox/netbox/custom_pipeline.py
@@ -0,0 +1,55 @@
+# Licensed under Creative Commons: CC BY-SA 4.0 license.
+# https://github.com/goauthentik/authentik/blob/main/LICENSE
+# https://github.com/goauthentik/authentik/blob/main/website/integrations/services/netbox/index.md
+# https://docs.goauthentik.io/integrations/services/netbox/
+from netbox.authentication import Group
+
+class AuthFailed(Exception):
+ pass
+
+def add_groups(response, user, backend, *args, **kwargs):
+ try:
+ groups = response['groups']
+ except KeyError:
+ pass
+
+ # Add all groups from oAuth token
+ for group in groups:
+ group, created = Group.objects.get_or_create(name=group)
+ user.groups.add(group)
+
+def remove_groups(response, user, backend, *args, **kwargs):
+ try:
+ groups = response['groups']
+ except KeyError:
+ # Remove all groups if no groups in oAuth token
+ user.groups.clear()
+ pass
+
+ # Get all groups of user
+ user_groups = [item.name for item in user.groups.all()]
+ # Get groups of user which are not part of oAuth token
+ delete_groups = list(set(user_groups) - set(groups))
+
+ # Delete non oAuth token groups
+ for delete_group in delete_groups:
+ group = Group.objects.get(name=delete_group)
+ user.groups.remove(group)
+
+
+def set_roles(response, user, backend, *args, **kwargs):
+ # Remove Roles temporary
+ user.is_superuser = False
+ user.is_staff = False
+ try:
+ groups = response['groups']
+ except KeyError:
+ # When no groups are set
+ # save the user without Roles
+ user.save()
+ pass
+
+ # Set roles is role (superuser or staff) is in groups
+ user.is_superuser = True if 'superusers' in groups else False
+ user.is_staff = True if 'staff' in groups else False
+ user.save()