From 7f8400ff58e1cef0e97c5a66f21d1ed1be264f49 Mon Sep 17 00:00:00 2001 From: Noah Masur <7386960+nmasur@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:12:59 +0000 Subject: [PATCH] feat(immich): configure oidc authentication --- docs/CHANGELOG.md | 7 +++++ docs/oidc-services.md | 8 ++--- .../services/immich/immich-oidc-secret.age | 17 +++++++++++ .../presets/services/{ => immich}/immich.nix | 29 ++++++++++++++++++- 4 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 platforms/nixos/modules/nmasur/presets/services/immich/immich-oidc-secret.age rename platforms/nixos/modules/nmasur/presets/services/{ => immich}/immich.nix (57%) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 27f6ab3a..0512549f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,13 @@ ## 2026-09-06 +- **Configured OpenID Connect (OIDC) authentication for Immich**: + - Configured `services.immich.settings.oauth` with OIDC settings pointing to Pocket ID (`auth.masu.rs`), using client ID `1f4e0f8d-6cee-4d67-8d53-74bf6c18ae09`. + - Added secret management for `immich-oidc-secret.age` via `secrets.immich-oidc-secret` with owner `immich` and group `shared` (0440). + - Wired client secret substitution using NixOS's native `clientSecret._secret = config.secrets.immich-oidc-secret.dest`, leveraging `utils.genJqSecretsReplacement` with systemd `LoadCredential`. + - Configured `systemd.services.immich-server` to order after `immich-oidc-secret-secret.service`. + - Updated `docs/oidc-services.md` with the verified configuration and redirect URIs. + - **Fixed OpenSSH authorized principals certificate authentication for Cloudflare Tunnel**: - Replaced manual `environment.etc."ssh/authorized_principals/${username}"` symlink and `Match User` config with NixOS native `users.users..openssh.authorizedPrincipals`. - Root cause: `environment.etc` without an explicit `mode` creates symlinks pointing into `/nix/store`, which has group-writable mode `0775` (`nixbld` group). Under `StrictModes yes`, sshd refused authentication with `bad ownership or modes for directory /nix/store`, causing certificate principal matching to fail with `Certificate does not contain an authorized principal`. diff --git a/docs/oidc-services.md b/docs/oidc-services.md index 19a5488c..df9a2feb 100644 --- a/docs/oidc-services.md +++ b/docs/oidc-services.md @@ -91,13 +91,13 @@ All client secrets should be encrypted with `agenix` under the respective servic - **Pocket ID Redirect URIs:** - Web: `https://photos.masu.rs/auth/login` - Mobile: `app.immich:///oauth-callback` -- **NixOS Configuration (`immich.nix`):** +- **Setup in `immich/immich.nix`:** ```nix services.immich.settings.oauth = { enabled = true; - issuerUrl = "https://auth.masu.rs"; - clientId = "immich"; - clientSecret = "..."; # or IMMICH_OAUTH_CLIENT_SECRET via environment file + issuerUrl = "https://${hostnames.auth}"; + clientId = "1f4e0f8d-6cee-4d67-8d53-74bf6c18ae09"; + clientSecret._secret = config.secrets.immich-oidc-secret.dest; scope = "openid profile email"; autoRegister = true; buttonText = "Login with Pocket ID"; diff --git a/platforms/nixos/modules/nmasur/presets/services/immich/immich-oidc-secret.age b/platforms/nixos/modules/nmasur/presets/services/immich/immich-oidc-secret.age new file mode 100644 index 00000000..b3bf14b3 --- /dev/null +++ b/platforms/nixos/modules/nmasur/presets/services/immich/immich-oidc-secret.age @@ -0,0 +1,17 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyBOUXFn +MmFqRWIxOUltQ3lPd1czZ3prbElXaURRV01rSUJGczhka0FOZkNrCkVFaXp2cnd0 +eTFhQUVlY001WnhRUVowSEdORS9MQWdTajhiUWtBVnpaZjAKLT4gc3NoLWVkMjU1 +MTkgWXlTVU1RIHg2ZGVSVDRSRVBUenBSTWhVbTFTRzFESXRyTEl6OEdoUDJmV2FZ +T0tPMXMKZ281MmhJd1EyV3VsNTVlNHYxcWE5cGdmTW5mVkNiM1J5ejNWQlBVQ3hM +UQotPiBzc2gtZWQyNTUxOSBuanZYNUEgUDhObFRyMCsxWkhGK1lKU25WenllZmRj +NnB5RHJyQjlscGE4NW1uVEJuQQpUQTFXNklzdEcrZjlOSUM3ZzV5OGlvREdJRjlu +MERGUkdjcXR6REtNUU1ZCi0+IHNzaC1lZDI1NTE5IENxSU9VQSBob2ZLQnNYVFpx +aVBEb2ZVOWVpUGZtU3FTUmNoS214cUxGeVZhM1NFZkNZCk5IV2VDMnc3L2Y4OEEx +b1ZSVys0dHpDUlo5UFA2akNKSHFNTi9JTUlnNEEKLT4gc3NoLWVkMjU1MTkgejFP +Y1p3IDZBd3k2ZE85Nm53MGdhNnJkUWFWVXEzTFRWU1lMSWp2L1FaYkdZRy9zQ2cK +TENkNFhsUi9VY2xPSFMwVmRNeXBxS1dFR1VtRDZsQ0xRSTdJQkJxODhiSQotLS0g +bnpxUGRtVVYvc1R5VUk5VDE4Mmx0WEVNMDZNc0tSSktCTzJlcjJmL3Y0bwqr2hdI +U3VNIk0qy4sWUdRCPqdyGK+F4qmcbD5MrPBQYNrc2aQ/zr5E/OkZn7k6vCvGMC9k +zrjpVlAxLB2cRsBmVw== +-----END AGE ENCRYPTED FILE----- diff --git a/platforms/nixos/modules/nmasur/presets/services/immich.nix b/platforms/nixos/modules/nmasur/presets/services/immich/immich.nix similarity index 57% rename from platforms/nixos/modules/nmasur/presets/services/immich.nix rename to platforms/nixos/modules/nmasur/presets/services/immich/immich.nix index acabc9af..358bb928 100644 --- a/platforms/nixos/modules/nmasur/presets/services/immich.nix +++ b/platforms/nixos/modules/nmasur/presets/services/immich/immich.nix @@ -10,6 +10,18 @@ in options.nmasur.presets.services.immich.enable = lib.mkEnableOption "Immich photo manager"; config = lib.mkIf cfg.enable { + secrets.immich-oidc-secret = { + source = ./immich-oidc-secret.age; + dest = "${config.secretsDirectory}/immich-oidc-secret"; + owner = config.services.immich.user; + group = config.services.immich.group; + permissions = "0440"; + }; + systemd.services.immich-oidc-secret-secret = { + requiredBy = [ "immich-server.service" ]; + before = [ "immich-server.service" ]; + }; + services.immich = { enable = true; port = 2283; @@ -19,7 +31,18 @@ in machine-learning.environment = { }; mediaLocation = "/data/images"; secretsFile = null; - settings.server.externalDomain = "https://${hostnames.photos}"; + settings = { + server.externalDomain = "https://${hostnames.photos}"; + oauth = { + enabled = true; + issuerUrl = "https://${hostnames.auth}"; + clientId = "1f4e0f8d-6cee-4d67-8d53-74bf6c18ae09"; + clientSecret._secret = config.secrets.immich-oidc-secret.dest; + scope = "openid profile email"; + autoRegister = true; + buttonText = "Login with Pocket ID"; + }; + }; environment = { IMMICH_ENV = "production"; IMMICH_LOG_LEVEL = "log"; @@ -28,6 +51,10 @@ in }; }; + systemd.services.immich-server = { + after = [ "immich-oidc-secret-secret.service" ]; + }; + nmasur.presets.services.caddy.routes = [ { match = [ { host = [ hostnames.photos ]; } ];