Add a Nix flake with NixOS module and test
This commit is contained in:
parent
8324adaef6
commit
10b6b350bd
81
flake.lock
generated
Normal file
81
flake.lock
generated
Normal file
@ -0,0 +1,81 @@
|
||||
{
|
||||
"nodes": {
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1713520724,
|
||||
"narHash": "sha256-CO8MmVDmqZX2FovL75pu5BvwhW+Vugc7Q6ze7Hj8heI=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "c5037590290c6c7dae2e42e7da1e247e54ed2d49",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1716218643,
|
||||
"narHash": "sha256-i/E7gzQybvcGAYDRGDl39WL6yVk30Je/NXypBz6/nmM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a8695cbd09a7ecf3376bd62c798b9864d20f86ee",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-23.11",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
77
flake.nix
Normal file
77
flake.nix
Normal file
@ -0,0 +1,77 @@
|
||||
{
|
||||
description = "idCoop: lightweight identity provider";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixos-23.11";
|
||||
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
|
||||
naersk.url = "github:nix-community/naersk";
|
||||
naersk.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, utils, naersk }:
|
||||
utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = nixpkgs.legacyPackages."${system}";
|
||||
lib = pkgs.lib;
|
||||
|
||||
naersk' = pkgs.callPackage naersk {
|
||||
};
|
||||
|
||||
idcoop = naersk'.buildPackage {
|
||||
pname = "idcoop";
|
||||
|
||||
# filter out .nix files from the input so they don't cause a recompilation when changed
|
||||
src = lib.cleanSourceWith {
|
||||
filter = path: type: !lib.hasSuffix ".nix" path;
|
||||
src = ./.;
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgs.makeWrapper
|
||||
|
||||
pkgs.pkg-config
|
||||
pkgs.openssl
|
||||
];
|
||||
|
||||
postInstall = ''
|
||||
mkdir -p $out/share/idcoop
|
||||
echo :::
|
||||
ls
|
||||
echo :::
|
||||
cp -r static templates translations -t $out/share/idcoop/
|
||||
|
||||
wrapProgram $out/bin/idcoop --set HORNBEAM_BASE $out/share/idcoop
|
||||
'';
|
||||
};
|
||||
in {
|
||||
# `nix build`
|
||||
packages = {
|
||||
inherit idcoop;
|
||||
default = idcoop;
|
||||
};
|
||||
|
||||
# `nix run`
|
||||
apps = rec {
|
||||
idcoop = utils.lib.mkApp {
|
||||
drv = idcoop;
|
||||
};
|
||||
default = idcoop;
|
||||
};
|
||||
}) // (let
|
||||
forAllNixosSystems = nixpkgs.lib.genAttrs ["x86_64-linux" "aarch64-linux"];
|
||||
in {
|
||||
# NixOS Modules
|
||||
nixosModules = {
|
||||
idcoop = import ./nixos_module.nix self;
|
||||
};
|
||||
checks = forAllNixosSystems (system: let
|
||||
checkArgs = {
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
inherit self;
|
||||
};
|
||||
in {
|
||||
starts = import ./nixos_tests/starts.nix checkArgs;
|
||||
});
|
||||
});
|
||||
}
|
192
nixos_module.nix
Normal file
192
nixos_module.nix
Normal file
@ -0,0 +1,192 @@
|
||||
flake: { config, pkgs, ... }:
|
||||
let
|
||||
inherit (flake.packages.${pkgs.stdenv.hostPlatform.system}) idcoop;
|
||||
inherit (pkgs.lib) mkOption mkDefault mkIf types literalExpression mdDoc;
|
||||
inherit (pkgs.writers) writeTOML;
|
||||
inherit (builtins) mapAttrs;
|
||||
|
||||
defaultUser = "idcoop";
|
||||
|
||||
cfg = config.services.idcoop;
|
||||
format = pkgs.formats.toml { };
|
||||
|
||||
oidcClientSubmodule = types.submodule {
|
||||
# freeformType = format.type; — one day we may want to enable freeform types, but for now just keep it strongly defined
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
User-friendly name of the OIDC Client.
|
||||
'';
|
||||
};
|
||||
redirect_uris = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
List of redirect URIs that the client can use to redirect login attempts back to itself.
|
||||
Consult the documentation for the other service if you aren't sure.
|
||||
'';
|
||||
};
|
||||
allow_user_classes = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
List of user classes which are authorised (allowed) to use this client (access this service).
|
||||
As of idCoop v0.0.1, this setting is unimplemented and has no effect.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options.services.idcoop = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable idCoop, a simple identity provider.
|
||||
'';
|
||||
};
|
||||
|
||||
configurePostgres = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to configure a Postgres database for idCoop.
|
||||
Enabled by default.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = defaultUser;
|
||||
description = ''
|
||||
User to run the service as.
|
||||
Will be created automatically if it is left at the default.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = defaultUser;
|
||||
description = ''
|
||||
User to run the service as.
|
||||
Will be created automatically if it is left at the default.
|
||||
'';
|
||||
};
|
||||
|
||||
secretsPath = mkOption {
|
||||
type = types.path;
|
||||
default = builtins.toFile "blank.toml" "";
|
||||
description = ''
|
||||
Path to a file containing secrets. This file should be kept out of the Nix store.
|
||||
Consult the idCoop documentation for the format of the secrets file.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
default = {};
|
||||
description = "idCoop configuration.";
|
||||
|
||||
type = types.submodule {
|
||||
# freeformType = format.type; — one day we may want to enable freeform types, but for now just keep it strongly defined
|
||||
options = {
|
||||
listen = {
|
||||
bind = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1:8072";
|
||||
description = ''
|
||||
Host and port combination upon which to bind the web interface.
|
||||
'';
|
||||
};
|
||||
public_base_uri = mkOption {
|
||||
type = types.str;
|
||||
default = "http://${cfg.settings.listen.bind}";
|
||||
defaultText = "`http://{listen.bind}`";
|
||||
description = ''
|
||||
Public-facing HTTP(S) base URL.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
oidc = {
|
||||
issuer = mkOption {
|
||||
type = types.str;
|
||||
default = cfg.settings.listen.public_base_uri;
|
||||
defaultText = "`listen.public_base_uri`";
|
||||
description = ''
|
||||
The identity provider's 'issuer' identifier, as used in OpenID Connect.
|
||||
This should be configured in clients (relying parties) and should likely not be changed.
|
||||
'';
|
||||
};
|
||||
|
||||
rsa_keypair = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to an RSA keypair used for signing JSON Web Tokens.
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
clients = mkOption {
|
||||
type = types.attrsOf oidcClientSubmodule;
|
||||
default = {};
|
||||
description = ''
|
||||
OpenID Connect 'clients' (also known as relying parties).
|
||||
These entries are for the different services you want users to be able to log in to using idCoop.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
postgres = {
|
||||
connect = mkOption {
|
||||
type = types.str;
|
||||
default = "postgres:";
|
||||
description = mdDoc ''
|
||||
Connection string for the Postgres database. The default of `postgres:` uses the [libpq environment variables] to form a connection;
|
||||
usually this by default connects to the local UNIX socket with the current user's name as a username and database name,
|
||||
if no environment variables are set.
|
||||
|
||||
[libpq environment variables]: https://www.postgresql.org/docs/current/libpq-envars.html
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
users.users.idcoop = mkIf (cfg.enable && cfg.user == defaultUser) {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
home = mkDefault "/var/lib/idcoop";
|
||||
createHome = true;
|
||||
};
|
||||
users.groups.idcoop = mkIf (cfg.enable && cfg.group == defaultUser) {};
|
||||
|
||||
systemd.services.idcoop = mkIf cfg.enable {
|
||||
description = "idCoop: simple identity provider";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "networking.target" "network-online.target" "postgresql.service" ];
|
||||
|
||||
serviceConfig =
|
||||
let
|
||||
configPath = writeTOML "idcoop_config.toml" cfg.settings;
|
||||
in
|
||||
{
|
||||
ExecStart = "${idcoop}/bin/idcoop --config ${configPath} --secrets ${cfg.secretsPath} serve";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
};
|
||||
};
|
||||
|
||||
services.postgresql = mkIf cfg.configurePostgres {
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "idcoop";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
ensureDatabases = ["idcoop"];
|
||||
};
|
||||
};
|
||||
}
|
21
nixos_tests/lib.nix
Normal file
21
nixos_tests/lib.nix
Normal file
@ -0,0 +1,21 @@
|
||||
# tests/lib.nix
|
||||
# The first argument to this function is the test module itself
|
||||
test:
|
||||
# These arguments are provided by `flake.nix` on import, see checkArgs
|
||||
{ pkgs, self}:
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
# this imports the nixos library that contains our testing framework
|
||||
nixos-lib = import (pkgs.path + "/nixos/lib") {};
|
||||
in
|
||||
(nixos-lib.runTest {
|
||||
hostPkgs = pkgs;
|
||||
# This speeds up the evaluation by skipping evaluating documentation (optional)
|
||||
defaults.documentation.enable = lib.mkDefault false;
|
||||
# This makes `self` available in the NixOS configuration of our virtual machines.
|
||||
# This is useful for referencing modules or packages from your own flake
|
||||
# as well as importing from other flakes.
|
||||
node.specialArgs = { inherit self; };
|
||||
imports = [ test ];
|
||||
}).config.result
|
||||
|
71
nixos_tests/starts.nix
Normal file
71
nixos_tests/starts.nix
Normal file
@ -0,0 +1,71 @@
|
||||
let
|
||||
rsaKeypair = ''
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDu6acOa+3ae2S
|
||||
0llp5oMsXjBMd5QJeQJCcY5Q9NAITF2U9VBwAiMf2wmaTZ1aWWFGSb/zWef7Hx1e
|
||||
qNhsK9MYL+QdJih2I+KpMtDWm7hhy9FtCHVc9i1z9PruXb0om2jDWLuBkPdCqJZT
|
||||
C58ObZKmgL4OH5F1Qv5JR/ZX21OjLolXPJo1sonLv9mlgufvhUmnC17onSSqFLBA
|
||||
nhUedjbfdnLShkp0xa8G0nW7Ls7Idaxyo8M5S2M+azJyLI87eqjjfz0yIW0Am890
|
||||
mRa81hO2D+YNcVA2wIE9MEI/ie480YxLQ0VHCjX4DcVir4ceExysYkdL8VK+U14g
|
||||
NO67k4NVAgMBAAECggEABnseJyoZ0V7miOgCIemKClwMCVwkQLQLCRwtdCzG/p9Y
|
||||
sef1g9/uPc3I4Z0USruO5v7mJi6h6cS7+jhpAhvpX3GmgfiTemXxyVxvYcvCLSrM
|
||||
gmm3SR61npNMA7yC2OdcbqtvefjM1x4x7AoEeDvUkULOCDWvYUyYkuCZHYubl1mS
|
||||
Rtcp9rxzky2tjdp8CHySBa9Kz9LEjWdFGky7g3vSyqZtw6tkK5CTwMPb9aHwiEq1
|
||||
yDWCbqAPPnb300dXSqx7z3AcsxBi/lpCs79fQS1vSJ1/L9POpYxX0SMtffkR5+Bl
|
||||
Mkg9dZUVenfbP4n40FdMypTTX2KJiMkc8+f0+Tz9QQKBgQDrdT7xs0lqMeZWp2rD
|
||||
y2KLzRl0iO/+yKse+BgkeBggZ/Vh2TeF9ylTRYdmimxkJ8eZRipTr0F64BS/LPEk
|
||||
RgWviuf9dUl0gyuhYTOJgUw1wcgtB6e+04UKEUsQW6JoNZekkM+xTa7FskLmlJ4J
|
||||
zzowjF1lgJeEyX1tvWXKIVe+/QKBgQDUzy5nZoUqBrVTYxL2uadIUh8oiBKPU93U
|
||||
Gz3DUq90yfDa7lFhwMRQRXfNqGUy6tshsaF4fT1b62hZDSz1OH3h/y1LKQOdF5kc
|
||||
JJyk/4b7NJna16kwBLzWje5SjQKr51aQWU8JftZ5/8uck2j7vMi+mgwzpG45J7kv
|
||||
Q1I5decBOQKBgFq1sKotB/uBfdukY91KXYy+VzAuEUd2x3YG3kYufhz97+riZCGY
|
||||
NrN99cvrSBbNvHewMF5NBkzwRw3foob28vnN6dIbfVEFt6lUaSZwSYvsO9IdQOKj
|
||||
Wn2ma+TBaK/89Y7QuzLzWoGPS3bJipj83M4XRWP1RmpBtbCxZqWYctWBAoGAMZPi
|
||||
16wGsffGHpsiO+CcnDilkafByypar6N5DBwjTC4PsrF6vC9QjPLiKkNk8CvOyVa8
|
||||
q3lh5hw9vyFWq/pxOUldn/j6Iorw3KGa7MWrCLMEdPtxKwKvi7ydHRZE3Q+UFyT3
|
||||
SNsH1HxHTz74Yk1k5yK0XQOduisK9XvVmBVjr+ECgYEAyoSbo/1cyLKWgrIr0K/f
|
||||
stiKL9SmBmYbaGaxtQToB5Hnqso7Hz5YEDlrcr8s1ukEFghgeNYuDYw3ZKKGGfZm
|
||||
yVQKAt8ouoO8rfkLrtt0H+/0uJgouhewDEqf/O+MfzwDnFcT89J5ZTEf+9n6pjry
|
||||
fuiQnuwEsPYGCCFuWWlrdHQ=
|
||||
-----END PRIVATE KEY-----
|
||||
'';
|
||||
|
||||
|
||||
in
|
||||
(import ./lib.nix) {
|
||||
name = "idcoop-starts";
|
||||
nodes = {
|
||||
# `self` here is set by using specialArgs in `lib.nix`
|
||||
node1 = { self, pkgs, ... }: {
|
||||
imports = [ self.nixosModules.idcoop ];
|
||||
environment.systemPackages = [ pkgs.curl pkgs.jq ];
|
||||
|
||||
services.idcoop = {
|
||||
enable = true;
|
||||
|
||||
settings = {
|
||||
oidc.rsa_keypair = builtins.toFile "rsa_keypair.pem" rsaKeypair;
|
||||
|
||||
# TODO for some reason the default doesn't work ???
|
||||
postgres.connect = "postgres://%2Frun%2fpostgresql";
|
||||
};
|
||||
};
|
||||
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
enableTCPIP = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
# This is the test code that will check if our service is running correctly:
|
||||
testScript = ''
|
||||
start_all()
|
||||
# wait for our service to start
|
||||
node1.wait_for_unit("idcoop")
|
||||
# wait for the port to open
|
||||
node1.wait_for_open_port(8072)
|
||||
# check the OpenID Connect discovery is served
|
||||
output = node1.succeed("curl http://localhost:8072/.well-known/openid-configuration | jq -e .")
|
||||
'';
|
||||
}
|
||||
|
0
static/.gitkeep
Normal file
0
static/.gitkeep
Normal file
0
templates/.gitkeep
Normal file
0
templates/.gitkeep
Normal file
0
translations/.gitkeep
Normal file
0
translations/.gitkeep
Normal file
Loading…
Reference in New Issue
Block a user