init flake with package, module and test

This commit is contained in:
WilliButz 2023-06-02 16:17:47 +02:00
parent 235ba8ada1
commit 4a424259e4
No known key found for this signature in database
GPG key ID: FB0513677AB15BEA
9 changed files with 19230 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.*.swo
.*.swp
result*
/.direnv

23
README.md Normal file
View file

@ -0,0 +1,23 @@
# authentik-nix
A Nix flake providing a package, NixOS module and basic VM test for [authentik](https://github.com/goauthentik/authentik)
## TOC
- [Important Note](#important-note)
- [Overview](#overview)
- [Usage](#usage)
- [Updating](#updating)
- [License](#license)
## Important Note
Please note that this project is not directly affiliated with the official [authentik](https://github.com/goauthentik/authentik) project. Most importantly this means that there is no official support for this packaging and deployment approach. Therefore, please refrain from opening issues for the official project when running into problems with this flake. Feel free to open issues here. If in doubt, please open an issue here first so we can make sure that it's not directly related to this packaging/deployment approach before escalating to the official project.
## Overview
## Usage
## Updating
## License
This project is released under the terms of the MIT License. See [LICENSE](./LICENSE).
Consult [the upstream project](https://github.com/goauthentik/authentik) for information about authentik licensing.

109
flake.lock generated Normal file
View file

@ -0,0 +1,109 @@
{
"nodes": {
"authentik-src": {
"flake": false,
"locked": {
"lastModified": 1684438418,
"narHash": "sha256-f/cKMuaQWDvNCID6QviaY04pfK9r9KmjCT2Dc5nHK+g=",
"owner": "goauthentik",
"repo": "authentik",
"rev": "6900ffffd8ba586ea27f212a3ca077c23f9baedd",
"type": "github"
},
"original": {
"owner": "goauthentik",
"ref": "version/2023.5.1",
"repo": "authentik",
"type": "github"
}
},
"flake-utils": {
"locked": {
"lastModified": 1676283394,
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"napalm": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1672245824,
"narHash": "sha256-i596lbPiA/Rfx3DiJiCluxdgxWY7oGSgYMT7OmM+zik=",
"owner": "nix-community",
"repo": "napalm",
"rev": "7c25a05cef52dc405f4688422ce0046ca94aadcf",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "napalm",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1685533922,
"narHash": "sha256-y4FCQpYafMQ42l1V+NUrMel9RtFtZo59PzdzflKR/lo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3a70dd92993182f8e514700ccf5b1ae9fc8a3b8d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.05",
"repo": "nixpkgs",
"type": "github"
}
},
"poetry2nix": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1685418143,
"narHash": "sha256-q2ORekI8au0pGMtOLQI8WMCJBxjzWgYRHpiEOVSBq3w=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "f11cc14e28078c701072f2d1fb34a6495c9376b1",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "poetry2nix",
"type": "github"
}
},
"root": {
"inputs": {
"authentik-src": "authentik-src",
"flake-utils": "flake-utils",
"napalm": "napalm",
"nixpkgs": "nixpkgs",
"poetry2nix": "poetry2nix"
}
}
},
"root": "root",
"version": 7
}

179
flake.nix Normal file
View file

@ -0,0 +1,179 @@
{
description = "Nix package, NixOS module and VM integration test for authentik";
inputs = {
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
poetry2nix = {
url = "github:nix-community/poetry2nix";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
napalm = {
url = "github:nix-community/napalm";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
authentik-src = { # change version string in outputs as well when updating
url = "github:goauthentik/authentik/version/2023.5.1";
flake = false;
};
};
outputs = {
self,
nixpkgs,
flake-utils,
poetry2nix,
napalm,
authentik-src
}:
flake-utils.lib.eachDefaultSystem (system:
let
authentik-version = "2023.5.1"; # to pass to the drvs of some components
inherit (poetry2nix.legacyPackages.${system})
mkPoetryEnv
defaultPoetryOverrides;
pkgs = nixpkgs.legacyPackages.${system};
in
rec {
nixosModules = {
default = import ./module.nix;
};
overlays = {
default = final: prev: {
authentik = {
inherit (packages) celery staticWorkdirDeps migrate pythonEnv frontend gopkgs docs;
};
};
};
packages = rec {
inherit authentik-src;
docs = napalm.legacyPackages.${system}.buildPackage "${authentik-src}/website" {
version = authentik-version; # 0.0.0 specified upstream
NODE_ENV = "production";
nodejs = pkgs.nodejs_20;
npmCommands = [
"cp -v ${authentik-src}/SECURITY.md ../SECURITY.md"
"cp -vr ${authentik-src}/blueprints ../blueprints"
"npm install --include=dev"
"npm run build-docs-only"
];
installPhase = ''
mv -v ../website $out
'';
};
frontend = napalm.legacyPackages.${system}.buildPackage "${authentik-src}/web" {
version = authentik-version; # 0.0.0 specified upstream
packageLock = ./web-package-lock.json; # needs to be lock file version 2 for napalm, upstream uses v3
NODE_ENV = "production";
nodejs = pkgs.nodejs_20;
preBuild = ''
ln -sv ${docs} ../website
'';
npmCommands = [
"npm install --include=dev"
"sed -i'' -e 's,/usr/bin/env node,/bin/node,' node_modules/@lingui/cli/dist/lingui.js"
"patchShebangs node_modules/@lingui/cli/dist/lingui.js"
"npm run build"
];
installPhase = ''
mkdir $out
mv dist $out/dist
cp -r authentik icons $out
'';
};
pythonEnv = mkPoetryEnv {
projectDir = authentik-src;
python = pkgs.python311;
overrides = [ defaultPoetryOverrides ] ++ (import ./poetry2nix-python-overrides.nix pkgs);
};
# server + outposts
gopkgs = pkgs.buildGo120Module {
pname = "authentik-gopgks";
version = authentik-version;
prePatch = ''
sed -i"" -e 's,./web/dist/,${frontend}/dist/,' web/static.go
sed -i"" -e 's,./web/dist/,${frontend}/dist/,' internal/web/static.go
sed -i"" -e 's,./lifecycle/gunicorn.conf.py,${staticWorkdirDeps}/lifecycle/gunicorn.conf.py,' internal/gounicorn/gounicorn.go
'';
src = pkgs.lib.cleanSourceWith {
src = authentik-src;
filter = (path: _:
(builtins.any (x: x) (
(map (infix: pkgs.lib.hasInfix infix path) [
"/cmd"
"/internal"
])
++
(map (suffix: pkgs.lib.hasSuffix suffix path) [
"/web"
"/web/static.go"
"/web/robots.txt"
"/web/security.txt"
"go.mod"
"go.sum"
])
))
);
};
subPackages = [
"cmd/ldap"
"cmd/server"
"cmd/proxy"
];
vendorSha256 = "sha256-QOYKsYb6TpzHRI8vSI5zpRHr2aCeUN67KABTRE2Y2kg=";
nativeBuildInputs = [ pkgs.makeWrapper ];
postInstall = ''
wrapProgram $out/bin/server --prefix PATH : ${pythonEnv}/bin
wrapProgram $out/bin/server --prefix PYTHONPATH : ${staticWorkdirDeps}
'';
};
staticWorkdirDeps = pkgs.linkFarm "authentik-static-workdir-deps" [
{ name = "authentik"; path = "${authentik-src}/authentik"; }
{ name = "locale"; path = "${authentik-src}/locale"; }
{ name = "blueprints"; path = "${authentik-src}/blueprints"; }
{ name = "internal"; path = "${authentik-src}/internal"; }
{ name = "lifecycle"; path = "${authentik-src}/lifecycle"; }
{ name = "schemas"; path = "${authentik-src}/schemas"; }
{ name = "web"; path = frontend; }
];
migrate = pkgs.runCommandLocal "authentik-migrate.py" {
nativeBuildInputs = [ pkgs.makeWrapper ];
} ''
mkdir -vp $out/bin
cp ${authentik-src}/lifecycle/migrate.py $out/bin/migrate.py
chmod +w $out/bin/migrate.py
patchShebangs $out/bin/migrate.py
wrapProgram $out/bin/migrate.py \
--prefix PATH : ${pythonEnv}/bin \
--prefix PYTHONPATH : ${staticWorkdirDeps}
'';
# worker
celery = pkgs.runCommandLocal "authentik-celery" {
nativeBuildInputs = [ pkgs.makeWrapper ];
} ''
mkdir -vp $out/bin
ln -sv ${pythonEnv}/bin/celery $out/bin/celery
wrapProgram $out/bin/celery \
--prefix PYTHONPATH : ${staticWorkdirDeps}
'';
};
checks.default = (import ./test.nix {
inherit pkgs overlays nixosModules;
});
devShells.default = pkgs.mkShell {
packages = [
# to generate a v2 lockfile from the v3 lockfile provided by upstream:
# npm install --lockfile-version 2 --package-lock-only
pkgs.nodejs
];
};
});
}

120
module.nix Normal file
View file

@ -0,0 +1,120 @@
{ config
, lib
, pkgs
, ...
}:
let
cfg = config.services.authentik;
inherit (lib)
types;
inherit (lib.modules)
mkDefault
mkIf;
inherit (lib.options)
mkEnableOption
mkOption;
inherit (pkgs.authentik)
migrate
gopkgs
celery
staticWorkdirDeps;
settingsFormat = pkgs.formats.yaml {};
in
{
options.services.authentik = {
enable = mkEnableOption "authentik";
settings = mkOption {
type = types.submodule {
freeformType = settingsFormat.type;
options = {};
};
};
};
config = mkIf cfg.enable {
services = {
authentik.settings = {
blueprints_dir = mkDefault "${pkgs.authentik.staticWorkdirDeps}/blueprints";
template_dir = mkDefault "${pkgs.authentik.staticWorkdirDeps}/templates";
};
redis.servers.authentik = {
enable = true;
port = 6379;
};
postgresql = {
enable = true;
package = pkgs.postgresql_14;
};
};
# https://goauthentik.io/docs/installation/docker-compose#explanation
time.timeZone = "UTC";
environment.etc."authentik/config.yml".source = settingsFormat.generate "authentik.yml" cfg.settings;
systemd.services = {
authentik-migrate = {
requiredBy = [ "authentik.service" ];
requires = [ "postgresql.service" ];
after = [ "postgresql.service" ];
before = [ "authentik.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
Environment = [
"AUTHENTIK_POSTGRESQL__USER=authentik"
"AUTHENTIK_POSTGRESQL__NAME=authentik"
];
DynamicUser = true;
ExecStart = "${pkgs.authentik.migrate}/bin/migrate.py";
};
};
authentik-worker = {
requiredBy = [ "authentik.service" ];
before = [ "authentik.service" ];
serviceConfig = {
Environment = [
"AUTHENTIK_POSTGRESQL__USER=authentik"
"AUTHENTIK_POSTGRESQL__NAME=authentik"
];
RuntimeDirectory = "authentik";
WorkingDirectory = "%t/authentik";
DynamicUser = true;
# TODO maybe make this configurable
ExecStart = "${pkgs.authentik.celery}/bin/celery -A authentik.root.celery worker -Ofair --max-tasks-per-child=1 --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q authentik,authentik_scheduled,authentik_events";
};
};
authentik = {
wantedBy = [ "multi-user.target" ];
after = [
"network-online.target"
"postgresql.service"
"redis-authentik.service"
];
preStart = ''
ln -svf ${pkgs.authentik.staticWorkdirDeps}/* /var/lib/authentik/
'';
serviceConfig = {
Environment = [
"AUTHENTIK_ERROR_REPORTING__ENABLED=false"
"AUTHENTIK_DISABLE_UPDATE_CHECK=true"
"AUTHENTIK_DISABLE_STARTUP_ANALYTICS=true"
"AUTHENTIK_AVATARS=initials"
];
StateDirectory = "authentik";
UMask = "0027";
# TODO /run might be sufficient
WorkingDirectory = "%S/authentik";
DynamicUser = true;
ExecStart = "${pkgs.authentik.gopkgs}/bin/server";
};
};
};
};
}

View file

@ -0,0 +1,113 @@
pkgs:
[
# modules missing only setuptools
(final: prev:
(builtins.listToAttrs (map (name: {
inherit name;
value = prev.${name}.overrideAttrs (oA: {
nativeBuildInputs = (oA.nativeBuildInputs or []) ++ [ final.setuptools ];
});
}) [
"asgiref"
"bump2version"
"codespell"
"colorama"
"dumb-init"
"opencontainers"
"pytest-github-actions-annotate-failures"
"drf-jsonschema-serializer"
]))
)
(final: prev: {
ruff = null; # don't need a linter for the package %), groups = [] && checkGroups = [] doesn't seem to work
pydantic-scim = prev.pydantic-scim.overrideAttrs (oA: {
nativeBuildInputs = oA.nativeBuildInputs ++ [
final.setuptools-scm
];
});
asyncio = prev.asyncio.overrideAttrs (oA: {
nativeBuildInputs = oA.nativeBuildInputs ++ [
final.setuptools final.setuptools-scm
];
});
click-didyoumean = prev.click-didyoumean.overrideAttrs (oA: {
nativeBuildInputs = oA.nativeBuildInputs ++ [
final.poetry
final.setuptools
];
});
pyrad = prev.pyrad.overrideAttrs (oA: {
nativeBuildInputs = oA.nativeBuildInputs ++ [
final.poetry
];
});
kombu = prev.kombu.overrideAttrs (oA: rec {
version = "5.3.0b3"; # 5.2.4 broken build from source
src = final.fetchPypi {
inherit version;
pname = "kombu";
sha256 = "316df5e840f284d0671b9000bbf747da2b00f3b81433c720de66a5f659e5711d";
};
nativeBuildInputs = oA.nativeBuildInputs ++ [
final.setuptools
];
});
urllib3-secure-extra = prev.urllib3-secure-extra.overrideAttrs (oA: {
buildInputs = [ final.flit-core ];
});
django-otp = prev.django-otp.overrideAttrs (oA: {
buildInputs = [ final.hatchling ];
});
tenacity = prev.tenacity.overrideAttrs (oA: rec {
buildInputs = [ final.pbr final.setuptools final.setuptools-scm ];
propagatedBuildInputs = [ final.pbr ];
});
opencontainers = prev.opencontainers.overrideAttrs (oA: {
nativeBuildInputs = oA.nativeBuildInputs ++ [
final.pytest-runner final.pytest
];
});
lxml = prev.lxml.overrideAttrs (oA: {
buildInputs = [ pkgs.xmlsec ];
});
xmlsec = prev.xmlsec.overridePythonAttrs (oA: {
nativeBuildInputs = oA.nativeBuildInputs ++ [ final.setuptools final.pkgconfig ];
buildInputs = [ pkgs.xmlsec.dev pkgs.xmlsec pkgs.libxml2 pkgs.libtool ];
});
cryptography = prev.cryptography.overridePythonAttrs (oA: {
cargoDeps = pkgs.rustPlatform.fetchCargoTarball {
src = oA.src;
sourceRoot = "${oA.pname}-${oA.version}/src/rust";
name = "${oA.pname}-${oA.version}";
sha256 = "sha256-0x+KIqJznDEyIUqVuYfIESKmHBWfzirPeX2R/cWlngc=";
};
});
#mistune = prev.mistune.override (oA: rec {
# version = "0.8.4";
# src = final.fetchPypi {
# inherit version;
# pname = "mistune";
# sha256 = "59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e";
# };
# buildInputs = [ final.nose ];
# #meta.knownVulnerabilities = [ "CVE-2022-34749" ];
#});
#twilio = prev.twilio.overrideAttrs (oA: rec {
# version = "8.1.0"; # unnecessary dependency on asyncio breaks build
# src = final.fetchPypi {
# inherit version;
# pname = "twilio";
# sha256 = "a31863119655cd3643f788099f6ea3fe74eea59ce3f65600f9a4931301311c08";
# };
# propagatedBuildInputs = [
# final.tenacity
# final.pytz
# final.requests
# final.pyjwt
# final.aiohttp
# final.aiohttp-retry
# ];
#});
}
)
]

88
test.nix Normal file
View file

@ -0,0 +1,88 @@
{ pkgs
, overlays
, nixosModules
}:
let
# use a root-owned EnvironmentFile in production instead (systemd.services.<name>.serviceConfig.EnvironmentFile)
secrets = {
authentiksecret = "thissecretwillbeinthenixstore";
postgresql = "dontusethisinproduction";
};
in
pkgs.nixosTest {
name = "authentik";
nodes = {
authentik = {
virtualisation = {
cores = 3;
memorySize = 2048;
};
imports = [
nixosModules.default
"${pkgs.path}/nixos/tests/common/user-account.nix"
"${pkgs.path}/nixos/tests/common/x11.nix"
];
nixpkgs.overlays = [ overlays.default ];
services.authentik.enable = true;
services.postgresql.initialScript = pkgs.writeText "psql-init.sql" ''
CREATE DATABASE authentik;
CREATE USER authentik WITH PASSWORD '${secrets.postgresql}';
GRANT ALL PRIVILEGES ON DATABASE authentik TO authentik
'';
systemd.services.authentik-migrate.serviceConfig.Environment = [
"AUTHENTIK_POSTGRESQL__PASSWORD=${secrets.postgresql}"
"AUTHENTIK_SECRET_KEY=${secrets.authentiksecret}"
];
systemd.services.authentik-worker.serviceConfig.Environment = [
"AUTHENTIK_POSTGRESQL__PASSWORD=${secrets.postgresql}"
"AUTHENTIK_SECRET_KEY=${secrets.authentiksecret}"
];
systemd.services.authentik.serviceConfig.Environment = [
"AUTHENTIK_POSTGRESQL__PASSWORD=${secrets.postgresql}"
"AUTHENTIK_SECRET_KEY=${secrets.authentiksecret}"
];
services.xserver.enable = true;
test-support.displayManager.auto.user = "alice";
environment.systemPackages = with pkgs; [
firefox
xdotool
];
};
};
enableOCR = true;
# TODO maybe use bootstrap env vars instead of testing manual workflow?
testScript = ''
start_all()
authentik.wait_for_unit("postgresql.service")
authentik.wait_for_unit("redis-authentik.service")
authentik.wait_for_unit("authentik-migrate.service")
authentik.wait_for_unit("authentik-worker.service")
authentik.wait_for_unit("authentik.service")
authentik.wait_for_open_port(9000)
authentik.wait_until_succeeds("curl -fL http://localhost:9000/if/flow/initial-setup >&2")
with subtest("Frontend renders"):
machine.succeed("su - alice -c 'firefox http://localhost:9000/if/flow/initial-setup' >&2 &")
machine.wait_for_text("Welcome to authentik")
machine.screenshot("initial-setup_1")
with subtest("admin account setup works"):
machine.send_key("tab")
machine.send_key("tab")
machine.send_chars("akadmin@localhost")
machine.send_key("tab")
machine.send_chars("foobar")
machine.send_key("tab")
machine.send_chars("foobar")
machine.send_key("ret")
machine.wait_for_text("My applications")
machine.send_key("esc")
machine.screenshot("initial-setup_2")
'';
}

18593
web-package-lock.json generated Normal file

File diff suppressed because it is too large Load diff