From 5676b1a4680dbe706686f38902f5607ec33330ff Mon Sep 17 00:00:00 2001 From: June <june@jsts.xyz> Date: Tue, 14 Jan 2025 20:49:14 +0100 Subject: [PATCH] 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/ --- README.md | 3 +- config/hosts/netbox/netbox.nix | 21 ++++++- flake.nix | 8 +++ ...oup_and_role_mapping_custom_pipeline.patch | 61 +++++++++++++++++++ 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 patches/0001_oidc_group_and_role_mapping_custom_pipeline.patch diff --git a/README.md b/README.md index 186f14a..def4e60 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/config/hosts/netbox/netbox.nix b/config/hosts/netbox/netbox.nix index e0f2df9..f816016 100644 --- a/config/hosts/netbox/netbox.nix +++ b/config/hosts/netbox/netbox.nix @@ -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" + ]; }; }; diff --git a/flake.nix b/flake.nix index dd85023..fb4ed26 100644 --- a/flake.nix +++ b/flake.nix @@ -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 ]; } ]; }; diff --git a/patches/0001_oidc_group_and_role_mapping_custom_pipeline.patch b/patches/0001_oidc_group_and_role_mapping_custom_pipeline.patch new file mode 100644 index 0000000..89f805a --- /dev/null +++ b/patches/0001_oidc_group_and_role_mapping_custom_pipeline.patch @@ -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()