1
0
Fork 0

[tests] add tests

This commit is contained in:
ibizaman 2023-02-22 23:04:44 -08:00
parent e2b3a1d944
commit 642f8e9793
10 changed files with 1470 additions and 0 deletions

26
default.nix Normal file
View file

@ -0,0 +1,26 @@
{ pkgs ? import <nixpkgs> {}
}:
let
utils = pkgs.callPackage ./utils.nix {};
in
with builtins;
with pkgs.lib.attrsets;
with pkgs.lib.lists;
with pkgs.lib.strings;
rec {
tests = pkgs.callPackage ./tests { inherit utils; };
runtests =
let
onlytests = filterAttrs (name: value: name != "override" && name != "overrideDerivation") tests;
failingtests = filterAttrs (name: value: length value > 0) onlytests;
formatFailure = failure: toString failure; # TODO: make this more pretty
formattedFailureGroups = mapAttrsToList (name: failures: "${name}:\n${concatMapStringsSep "\n" formatFailure failures}") failingtests;
in
if length formattedFailureGroups == 0 then
"no failing test"
else
concatStringsSep "\n" formattedFailureGroups;
disnixtests = pkgs.callPackage ./tests/disnix/keycloak.nix {};
}

12
tests/default.nix Normal file
View file

@ -0,0 +1,12 @@
# to run all tests:
# nix-instantiate --eval --strict . -A tests
{ pkgs
, utils
}:
{
haproxy = pkgs.callPackage ./haproxy.nix { inherit utils; };
keycloak = pkgs.callPackage ./keycloak.nix {};
keycloak-cli-config = pkgs.callPackage ./keycloak-cli-config.nix {};
}

93
tests/disnix/keycloak.nix Normal file
View file

@ -0,0 +1,93 @@
{ nixpkgs ? <nixpkgs>
, system ? builtins.currentSystem
}:
let
pkgs = import nixpkgs {inherit system;};
disnixos = import "${pkgs.disnixos}/share/disnixos/testing.nix" {
inherit nixpkgs system;
};
version = "1.0";
in
rec {
tarball = disnixos.sourceTarball {
name = "testproject-zip";
inherit version;
src = ./.;
officialRelease = false;
};
manifest =
disnixos.buildManifest {
name = "test-project-manifest";
version = builtins.readFile ./version;
inherit tarball;
servicesFile = "keycloak/services.nix";
networkFile = "keycloak/network.nix";
distributionFile = "keycloak/distribution.nix";
};
tests =
disnixos.disnixTest {
name = "test-project-tests";
inherit tarball manifest;
networkFile = "keycloak/network.nix";
dysnomiaStateDir = /var/state/dysnomia;
testScript =
''
# Wait until the front-end application is deployed
$test1->waitForFile("/var/tomcat/webapps/testapp");
# Wait a little longer and capture the output of the entry page
my $result = $test1->mustSucceed("sleep 10; curl --fail http://test2:8080/testapp");
'';
};
}
# let
# utils = import ../../utils.nix {
# inherit pkgs;
# inherit (pkgs) stdenv lib;
# };
# keycloak = import ../../pkgs/keycloak/unit.nix {
# inherit pkgs utils;
# inherit (pkgs) stdenv lib;
# };
# in
# makeTest {
# nodes = {
# machine = {config, pkgs, ...}:
# {
# virtualisation.memorySize = 1024;
# virtualisation.diskSize = 4096;
# environment.systemPackages = [ dysnomia pkgs.curl ];
# };
# };
# testScript = ''
# def check_keycloak_activated():
# machine.succeed("sleep 5")
# machine.succeed("curl --fail http://keycloak.test.tiserbox.com")
# def check_keycloak_deactivated():
# machine.succeed("sleep 5")
# machine.fail("curl --fail http://keycloak.test.tiserbox.com")
# start_all()
# # Test the keycloak module. Start keycloak and see if we can query the endpoint.
# machine.succeed(
# "dysnomia --type docker-container --operation activate --component ${keycloak} --environment"
# )
# check_keycloak_activated()
# # Deactivate keycloak. Check if it is not running anymore
# machine.succeed(
# "dysnomia --type docker-container --operation deactivate --component ${keycloak} --environment"
# )
# check_keycloak_deactivated()
# '';
# }

View file

@ -0,0 +1,7 @@
{ infrastructure }:
with infrastructure;
{
KeycloakPostgresDB = [ test1 ];
KeycloakService = [ test1 ];
}

View file

@ -0,0 +1,75 @@
rec {
test1 = { system
, pkgs
, lib
, ... }:
let
domain = "local";
utils = pkgs.lib.callPackageWith pkgs ../../../utils.nix { };
customPkgs = import ../../../pkgs/all-packages.nix {
inherit system pkgs utils;
};
in
rec {
users.groups = {
keycloak = {
name = "keycloak";
};
};
users.users = {
keycloak = {
name = "keycloak";
group = "keycloak";
isSystemUser = true;
};
};
deployment.keys = {
keycloakinitialadmin.text = ''
KEYCLOAK_ADMIN_PASSWORD="${builtins.extraBuiltins.pass "keycloak.${domain}/admin"}"
'';
};
services = {
openssh = {
enable = true;
};
disnix = {
enable = true;
# useWebServiceInterface = true;
};
postgresql = {
enable = true;
package = pkgs.postgresql_14;
port = 5432;
enableTCPIP = true;
authentication = pkgs.lib.mkOverride 10 ''
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
'';
};
};
dysnomia = {
enable = true;
enableLegacyModules = false;
extraContainerProperties = {
system = {
inherit domain;
};
postgresql-database = {
service_name = "postgresql.service";
port = builtins.toString services.postgresql.port;
};
};
};
networking.firewall.allowedTCPPorts = [ services.postgresql.port ];
};
}

View file

@ -0,0 +1,45 @@
{ system, pkgs, distribution, invDistribution }:
let
utils = pkgs.lib.callPackageWith pkgs ../../../utils.nix { };
customPkgs = import ../../../pkgs/all-packages.nix {
inherit system pkgs utils;
};
in
{
KeycloakPostgresDB = customPkgs.mkPostgresDB {
name = "KeycloakPostgresDB";
database = "keycloak";
username = "keycloak";
# TODO: use passwordFile
password = "keycloak";
};
KeycloakService = customPkgs.mkKeycloakService {
name = "KeycloakService";
# Get these from infrastructure.nix
user = "keycloak";
group = "keycloak";
postgresServiceName = (utils.getTarget "KeycloakPostgresDB").containers.postgresql-database.service_name;
initialAdminUsername = "admin";
keys = {
dbPassword = "keycloakdbpassword";
initialAdminPassword = "keycloakinitialadmin";
};
logLevel = "INFO";
hostname = "keycloak.${getDomain "KeycloakService"}";
dbType = "postgres";
dbDatabase = KeycloakPostgresDB.database;
dbUsername = KeycloakPostgresDB.username;
dbHost = {KeycloakPostgresDB}: KeycloakPostgresDB.target.properties.hostname;
dbPort = (getTarget "KeycloakPostgresDB").containers.postgresql-database.port;
inherit KeycloakPostgresDB;
};
}

549
tests/haproxy.nix Normal file
View file

@ -0,0 +1,549 @@
# to run these tests:
# nix-instantiate --eval --strict . -A tests.haproxy
{ lib
, stdenv
, pkgs
, utils
}:
let
configcreator = pkgs.callPackage ./../haproxy/configcreator.nix { inherit utils; };
mksiteconfig = pkgs.callPackage ./../haproxy/siteconfig.nix {};
diff = testResult:
with builtins;
with lib.strings;
if isString testResult.expected && isString testResult.result then
let
p = commonPrefixLength testResult.expected testResult.result;
s = commonSuffixLength testResult.expected testResult.result;
expectedSuffixLen = stringLength testResult.expected - s - p;
resultSuffixLen = stringLength testResult.result - s - p;
expectedDiff = substring p expectedSuffixLen testResult.expected;
resultDiff = substring p resultSuffixLen testResult.result;
omitted = len: if len == 0 then "" else "[... ${toString len} omitted]";
in
{inherit (testResult) name;
commonPrefix = substring 0 p testResult.expected;
commonSuffix = substring (stringLength testResult.expected - s) s testResult.expected;
expected = "${omitted p}${expectedDiff}${omitted s}";
result = "${omitted p}${resultDiff}${omitted s}";
allExpected = testResult.expected;
allResult = testResult.result;
}
else testResult;
runTests = x: map diff (lib.runTests x);
in
with lib.attrsets;
runTests {
testDiffSame = {
expr = "abdef";
expected = "abdef";
};
testUpdateByPath1 = {
expr = configcreator.updateByPath ["a"] (x: x+1) {
a = 1;
b = 1;
};
expected = {
a = 2;
b = 1;
};
};
testUpdateByPath2 = {
expr = configcreator.updateByPath ["a" "a"] (x: x+1) {
a = {
a = 1;
b = 1;
};
b = 1;
};
expected = {
a = {
a = 2;
b = 1;
};
b = 1;
};
};
testUpdateByPath3 = {
expr = configcreator.updateByPath ["a" "a" "a"] (x: x+1) {
a = {
a = {
a = 1;
b = 1;
};
b = 1;
};
b = 1;
};
expected = {
a = {
a = {
a = 2;
b = 1;
};
b = 1;
};
b = 1;
};
};
testRecursiveMerge1 = {
expr = configcreator.recursiveMerge [
{a = 1;}
{b = 2;}
];
expected = {
a = 1;
b = 2;
};
};
testRecursiveMerge2 = {
expr = configcreator.recursiveMerge [
{a = {a = 1; b = 2;};}
{a = {a = 2;};}
];
expected = {
a = {a = 2; b = 2;};
};
};
tesFlattenArgs1 = {
expr = configcreator.flattenAttrs {
a = 1;
b = 2;
};
expected = {
a = 1;
b = 2;
};
};
tesFlattenArgs2 = {
expr = configcreator.flattenAttrs {
a = {
a = 1;
b = {
c = 3;
d = 4;
};
};
b = 2;
};
expected = {
"a.a" = 1;
"a.b.c" = 3;
"a.b.d" = 4;
b = 2;
};
};
testHaproxyConfigDefaultRender = {
expr = configcreator.render (configcreator.default {
user = "me";
group = "mygroup";
certPath = "/cert/path";
plugins = {
zone = {
luapaths = "lib";
source = pkgs.writeText "one.lua" "a binary";
};
two = {
load = "right/two.lua";
luapaths = ".";
cpaths = "right";
source = pkgs.writeText "two.lua" "a binary";
};
};
globalEnvs = {
ABC = "hello";
};
stats = null;
debug = false;
});
expected = ''
global
group mygroup
log /dev/log local0 info
maxconn 20000
lua-prepend-path /nix/store/ybcka9g095hp8s1hnm2ncfh1hp56v9yq-haproxyplugins/two/?.lua path
lua-prepend-path /nix/store/ybcka9g095hp8s1hnm2ncfh1hp56v9yq-haproxyplugins/two/right/?.so cpath
lua-prepend-path /nix/store/ybcka9g095hp8s1hnm2ncfh1hp56v9yq-haproxyplugins/zone/lib/?.lua path
lua-load /nix/store/ybcka9g095hp8s1hnm2ncfh1hp56v9yq-haproxyplugins/two/right/two.lua
setenv ABC hello
tune.ssl.default-dh-param 2048
user me
defaults
log global
option httplog
timeout client 15s
timeout connect 10s
timeout queue 100s
timeout server 30s
frontend http-to-https
bind *:80
mode http
redirect scheme https code 301 if !{ ssl_fc }
frontend https
bind *:443 ssl crt /cert/path
http-request add-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Port %[dst_port]
http-request set-header X-Forwarded-For %[src]
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"
mode http
'';
};
testHaproxyConfigDefaultRenderWithStatsAndDebug = {
expr = configcreator.render (configcreator.default {
user = "me";
group = "mygroup";
certPath = "/cert/path";
stats = {
port = 8405;
uri = "/stats";
refresh = "10s";
prometheusUri = "/prom/etheus";
hide-version = true;
};
debug = true;
});
expected = ''
global
group mygroup
log /dev/log local0 info
maxconn 20000
tune.ssl.default-dh-param 2048
user me
defaults
log global
option httplog
timeout client 15s
timeout connect 10s
timeout queue 100s
timeout server 30s
frontend http-to-https
bind *:80
mode http
redirect scheme https code 301 if !{ ssl_fc }
frontend https
bind *:443 ssl crt /cert/path
http-request add-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Port %[dst_port]
http-request set-header X-Forwarded-For %[src]
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"
log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r %sslv %sslc %[ssl_fc_cipherlist_str]"
mode http
frontend stats
bind localhost:8405
http-request use-service prometheus-exporter if { path /prom/etheus }
mode http
stats enable
stats hide-version
stats refresh 10s
stats uri /stats
'';
};
testRenderHaproxyConfigWithSite = {
expr = configcreator.render (configcreator.default {
user = "me";
group = "mygroup";
certPath = "/cert/path";
stats = null;
debug = false;
sites = {
siteName = {
frontend = {
capture = [
"request header origin len 128"
];
acl = {
acl_siteName = "hdr_beg(host) siteName.";
acl_siteName_path = "path_beg /siteName";
};
http-response = {
add-header = [
"Access-Control-Allow-Origin1 $[capture]"
"Access-Control-Allow-Origin2 $[capture]"
];
};
use_backend = "if acl_siteName OR acl_siteName_path";
};
backend = {
servers = [
{
name = "serviceName1";
address = "serviceSocket";
}
];
options = [
"cookie JSESSIONID prefix"
];
};
};
};
});
expected = ''
global
group mygroup
log /dev/log local0 info
maxconn 20000
tune.ssl.default-dh-param 2048
user me
defaults
log global
option httplog
timeout client 15s
timeout connect 10s
timeout queue 100s
timeout server 30s
frontend http-to-https
bind *:80
mode http
redirect scheme https code 301 if !{ ssl_fc }
frontend https
acl acl_siteName hdr_beg(host) siteName.
acl acl_siteName_path path_beg /siteName
bind *:443 ssl crt /cert/path
capture request header origin len 128
http-request add-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Port %[dst_port]
http-request set-header X-Forwarded-For %[src]
http-response add-header Access-Control-Allow-Origin1 $[capture]
http-response add-header Access-Control-Allow-Origin2 $[capture]
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"
mode http
use_backend siteName if acl_siteName OR acl_siteName_path
backend siteName
cookie JSESSIONID prefix
mode http
option forwardfor
server serviceName1 serviceSocket
'';
};
testRenderHaproxyConfigWith2Sites = {
expr = configcreator.render (configcreator.default {
user = "me";
group = "mygroup";
certPath = "/cert/path";
stats = null;
debug = false;
sites = {
siteName = {
frontend = {
capture = [
"request header origin len 128"
];
acl = {
acl_siteName = "hdr_beg(host) siteName.";
acl_siteName_path = "path_beg /siteName";
};
http-response = {
add-header = [
"Access-Control-Allow-Origin1 $[capture]"
"Access-Control-Allow-Origin2 $[capture]"
];
};
use_backend = "if acl_siteName OR acl_siteName_path";
};
backend = {
servers = [
{
name = "serviceName1";
address = "serviceSocket";
}
];
options = [
"cookie JSESSIONID prefix"
];
};
};
siteName2 = {
frontend = {
capture = [
"request header origin len 128"
];
acl = {
acl_siteName2 = "hdr_beg(host) siteName2.";
acl_siteName2_path = "path_beg /siteName2";
};
http-response = {
add-header = [
"Access-Control-Allow-Origin3 $[capture]"
"Access-Control-Allow-Origin4 $[capture]"
];
};
use_backend = "if acl_siteName2 OR acl_siteName2_path";
};
backend = {
servers = [
{
name = "serviceName2";
address = "serviceSocket";
}
];
options = [
"cookie JSESSIONID prefix"
];
};
};
};
});
expected = ''
global
group mygroup
log /dev/log local0 info
maxconn 20000
tune.ssl.default-dh-param 2048
user me
defaults
log global
option httplog
timeout client 15s
timeout connect 10s
timeout queue 100s
timeout server 30s
frontend http-to-https
bind *:80
mode http
redirect scheme https code 301 if !{ ssl_fc }
frontend https
acl acl_siteName hdr_beg(host) siteName.
acl acl_siteName2 hdr_beg(host) siteName2.
acl acl_siteName2_path path_beg /siteName2
acl acl_siteName_path path_beg /siteName
bind *:443 ssl crt /cert/path
capture request header origin len 128
capture request header origin len 128
http-request add-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Port %[dst_port]
http-request set-header X-Forwarded-For %[src]
http-response add-header Access-Control-Allow-Origin1 $[capture]
http-response add-header Access-Control-Allow-Origin2 $[capture]
http-response add-header Access-Control-Allow-Origin3 $[capture]
http-response add-header Access-Control-Allow-Origin4 $[capture]
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"
mode http
use_backend siteName if acl_siteName OR acl_siteName_path
use_backend siteName2 if acl_siteName2 OR acl_siteName2_path
backend siteName
cookie JSESSIONID prefix
mode http
option forwardfor
server serviceName1 serviceSocket
backend siteName2
cookie JSESSIONID prefix
mode http
option forwardfor
server serviceName2 serviceSocket
'';
};
testRenderHaproxyConfigWithSiteDebugHeaders = {
expr = configcreator.render (configcreator.default {
user = "me";
group = "mygroup";
certPath = "/cert/path";
stats = null;
debug = false;
sites = {
siteName = {
frontend = {
capture = [
"request header origin len 128"
];
acl = {
acl_siteName = "hdr_beg(host) siteName.";
acl_siteName_path = "path_beg /siteName";
};
http-response = {
add-header = [
"Access-Control-Allow-Origin1 $[capture]"
"Access-Control-Allow-Origin2 $[capture]"
];
};
use_backend = "if acl_siteName OR acl_siteName_path";
};
backend = {
servers = [
{
name = "serviceName1";
address = "serviceSocket";
}
];
options = [
"cookie JSESSIONID prefix"
];
};
debugHeaders = "acl_siteName";
};
};
});
expected = ''
global
group mygroup
log /dev/log local0 info
maxconn 20000
tune.ssl.default-dh-param 2048
user me
defaults
log global
option httplog
timeout client 15s
timeout connect 10s
timeout queue 100s
timeout server 30s
frontend http-to-https
bind *:80
mode http
redirect scheme https code 301 if !{ ssl_fc }
frontend https
acl acl_siteName hdr_beg(host) siteName.
acl acl_siteName_path path_beg /siteName
bind *:443 ssl crt /cert/path
capture request header origin len 128
http-request add-header X-Forwarded-Proto https
http-request capture req.hdrs len 512 if acl_siteName
http-request set-header X-Forwarded-Port %[dst_port]
http-request set-header X-Forwarded-For %[src]
http-response add-header Access-Control-Allow-Origin1 $[capture]
http-response add-header Access-Control-Allow-Origin2 $[capture]
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"
log-format "%ci:%cp [%tr] %ft [[%hr]] %hs %{+Q}r"
mode http
option httplog
use_backend siteName if acl_siteName OR acl_siteName_path
backend siteName
cookie JSESSIONID prefix
mode http
option forwardfor
server serviceName1 serviceSocket
'';
};
}

View file

@ -0,0 +1,343 @@
# to run these tests:
# nix-instantiate --eval --strict . -A tests.keycloak-cli-config
{ lib
, stdenv
, pkgs
}:
let
configcreator = pkgs.callPackage ./../keycloak-cli-config/configcreator.nix { };
default_config = {
realm = "myrealm";
domain = "mydomain.com";
};
keep_fields = fields:
lib.filterAttrs (n: v: lib.any (n_: n_ == n) fields);
in
lib.runTests {
testDefault = {
expr = configcreator default_config;
expected = {
id = "myrealm";
realm = "myrealm";
enabled = true;
clients = [];
roles = {
realm = [];
client = {};
};
groups = [];
users = [];
};
};
testUsers = {
expr = (configcreator (default_config // {
users = {
me = {
email = "me@mydomain.com";
firstName = "me";
lastName = "stillme";
};
};
})).users;
expected = [
{
username = "me";
enabled = true;
email = "me@mydomain.com";
emailVerified = true;
firstName = "me";
lastName = "stillme";
}
];
};
testUsersWithGroups = {
expr = (configcreator (default_config // {
users = {
me = {
email = "me@mydomain.com";
firstName = "me";
lastName = "stillme";
groups = [ "MyGroup" ];
};
};
})).users;
expected = [
{
username = "me";
enabled = true;
email = "me@mydomain.com";
emailVerified = true;
firstName = "me";
lastName = "stillme";
groups = [ "MyGroup" ];
}
];
};
testUsersWithRoles = {
expr = (configcreator (default_config // {
users = {
me = {
email = "me@mydomain.com";
firstName = "me";
lastName = "stillme";
roles = [ "MyRole" ];
};
};
})).users;
expected = [
{
username = "me";
enabled = true;
email = "me@mydomain.com";
emailVerified = true;
firstName = "me";
lastName = "stillme";
realmRoles = [ "MyRole" ];
}
];
};
testUsersWithInitialPassword = {
expr = (configcreator (default_config // {
users = {
me = {
email = "me@mydomain.com";
firstName = "me";
lastName = "stillme";
initialPassword = true;
};
};
})).users;
expected = [
{
username = "me";
enabled = true;
email = "me@mydomain.com";
emailVerified = true;
firstName = "me";
lastName = "stillme";
credentials = [
{
type = "password";
userLabel = "initial";
value = "$(keycloak.users.me.password)";
}
];
}
];
};
testGroups = {
expr = (configcreator (default_config // {
groups = [ "MyGroup" ];
})).groups;
expected = [
{
name = "MyGroup";
path = "/MyGroup";
attributes = {};
realmRoles = [];
clientRoles = {};
subGroups = [];
}
];
};
testRealmRoles = {
expr = (configcreator (default_config // {
roles = {
A = [ "B" ];
B = [ ];
};
})).roles;
expected = {
client = {};
realm = [
{
name = "A";
composite = true;
composites = {
realm = [ "B" ];
};
}
{
name = "B";
composite = false;
}
];
};
};
testClientRoles = {
expr = (configcreator (default_config // {
clients = {
clientA = {
roles = [ "cA" ];
};
};
})).roles;
expected = {
client = {
clientA = [
{
name = "cA";
clientRole = true;
}
];
};
realm = [];
};
};
testClient = {
expr = map (keep_fields [
"clientId"
"rootUrl"
"redirectUris"
"webOrigins"
"authorizationSettings"
]) (configcreator (default_config // {
clients = {
clientA = {};
};
})).clients;
expected = [
{
clientId = "clientA";
rootUrl = "https://clientA.mydomain.com";
redirectUris = ["https://clientA.mydomain.com/oauth2/callback"];
webOrigins = ["https://clientA.mydomain.com"];
authorizationSettings = {
policyEnforcementMode = "ENFORCING";
resources = [];
policies = [];
};
}
];
};
testClientAuthorization = with builtins; {
expr = (head (configcreator (default_config // {
clients = {
clientA = {
resourcesUris = {
adminPath = ["/admin/*"];
userPath = ["/*"];
};
access = {
admin = {
roles = [ "admin" ];
resources = [ "adminPath" ];
};
user = {
roles = [ "user" ];
resources = [ "userPath" ];
};
};
};
};
})).clients).authorizationSettings;
expected = {
policyEnforcementMode = "ENFORCING";
resources = [
{
name = "adminPath";
type = "urn:clientA:resources:adminPath";
ownerManagedAccess = false;
uris = ["/admin/*"];
}
{
name = "userPath";
type = "urn:clientA:resources:userPath";
ownerManagedAccess = false;
uris = ["/*"];
}
];
policies = [
{
name = "admin has access";
type = "role";
logic = "POSITIVE";
decisionStrategy = "UNANIMOUS";
config = {
roles = ''[{"id":"admin","required":true}]'';
};
}
{
name = "user has access";
type = "role";
logic = "POSITIVE";
decisionStrategy = "UNANIMOUS";
config = {
roles = ''[{"id":"user","required":true}]'';
};
}
{
name = "admin has access to adminPath";
type = "resource";
logic = "POSITIVE";
decisionStrategy = "UNANIMOUS";
config = {
resources = ''["adminPath"]'';
applyPolicies = ''["admin has access"]'';
};
}
{
name = "user has access to userPath";
type = "resource";
logic = "POSITIVE";
decisionStrategy = "UNANIMOUS";
config = {
resources = ''["userPath"]'';
applyPolicies = ''["user has access"]'';
};
}
];
};
};
testClientAudience =
let
audienceProtocolMapper = config:
with builtins;
let
protocolMappers = (head config.clients).protocolMappers;
protocolMapperByName = name: protocolMappers: head (filter (x: x.name == name) protocolMappers);
in
protocolMapperByName "Audience" protocolMappers;
in
{
expr = audienceProtocolMapper (configcreator (default_config // {
clients = {
clientA = {};
};
}));
expected = {
name = "Audience";
protocol = "openid-connect";
protocolMapper = "oidc-audience-mapper";
config = {
"included.client.audience" = "clientA";
"id.token.claim" = "false";
"access.token.claim" = "true";
"included.custom.audience" = "clientA";
};
};
};
}

232
tests/keycloak.nix Normal file
View file

@ -0,0 +1,232 @@
# to run these tests:
# nix-instantiate --eval --strict . -A tests.keycloak
{ lib
, stdenv
, pkgs
}:
let
configcreator = pkgs.callPackage ./../keycloak-cli-config/configcreator.nix {};
in
with lib.attrsets;
lib.runTests {
testConfigEmpty = {
expr = configcreator {
realm = "myrealm";
domain = "domain.com";
};
expected = {
id = "myrealm";
realm = "myrealm";
enabled = true;
clients = [];
groups = [];
roles = {
client = {};
realm = [];
};
users = [];
};
};
testConfigRole = {
expr = configcreator {
realm = "myrealm";
domain = "domain.com";
roles = {
user = [];
admin = ["user"];
};
};
expected = {
id = "myrealm";
realm = "myrealm";
enabled = true;
clients = [];
groups = [];
roles = {
realm = [
{
name = "admin";
composite = true;
composites = {
realm = ["user"];
};
}
{
name = "user";
composite = false;
}
];
client = {};
};
users = [];
};
};
testConfigClient = {
expr =
let
c = configcreator {
realm = "myrealm";
domain = "domain.com";
clients = {
myclient = {};
myclient2 = {
roles = ["uma"];
};
};
};
in
updateManyAttrsByPath [
{
path = [ "clients" ];
# We don't care about the value of the protocolMappers
# field because its value is hardcoded.
update = clients: map (filterAttrs (n: v: n != "protocolMappers")) clients;
}
] c;
expected = {
id = "myrealm";
realm = "myrealm";
enabled = true;
clients = [
{
clientId = "myclient";
rootUrl = "https://myclient.domain.com";
clientAuthenticatorType = "client-secret";
redirectUris = [
"https://myclient.domain.com/oauth2/callback"
];
webOrigins = [
"https://myclient.domain.com"
];
authorizationServicesEnabled = true;
serviceAccountsEnabled = true;
protocol = "openid-connect";
publicClient = false;
authorizationSettings = {
policyEnforcementMode = "ENFORCING";
resources = [];
policies = [];
};
}
{
clientId = "myclient2";
rootUrl = "https://myclient2.domain.com";
clientAuthenticatorType = "client-secret";
redirectUris = [
"https://myclient2.domain.com/oauth2/callback"
];
webOrigins = [
"https://myclient2.domain.com"
];
authorizationServicesEnabled = true;
serviceAccountsEnabled = true;
protocol = "openid-connect";
publicClient = false;
authorizationSettings = {
policyEnforcementMode = "ENFORCING";
resources = [];
policies = [];
};
}
];
groups = [];
roles = {
client = {
myclient = [];
myclient2 = [
{
name = "uma";
clientRole = true;
}
];
};
realm = [];
};
users = [];
};
};
testConfigUser = {
expr = configcreator {
realm = "myrealm";
domain = "domain.com";
users = {
me = {
email = "me@me.com";
firstName = null;
lastName = "Me";
realmRoles = [ "role" ];
};
};
};
expected = {
id = "myrealm";
realm = "myrealm";
enabled = true;
clients = [];
groups = [];
roles = {
client = {};
realm = [];
};
users = [
{
enabled = true;
username = "me";
email = "me@me.com";
emailVerified = true;
firstName = null;
lastName = "Me";
}
];
};
};
testConfigUserInitialPassword = {
expr = configcreator {
realm = "myrealm";
domain = "domain.com";
users = {
me = {
email = "me@me.com";
firstName = null;
lastName = "Me";
initialPassword = true;
};
};
};
expected = {
id = "myrealm";
realm = "myrealm";
enabled = true;
clients = [];
groups = [];
roles = {
client = {};
realm = [];
};
users = [
{
enabled = true;
username = "me";
email = "me@me.com";
emailVerified = true;
firstName = null;
lastName = "Me";
credentials = [
{
type = "password";
userLabel = "initial";
value = "$(keycloak.users.me.password)";
}
];
}
];
};
};
}

88
utils.nix Normal file
View file

@ -0,0 +1,88 @@
{ stdenv
, pkgs
, lib
}:
with lib;
with lib.lists;
with lib.attrsets;
rec {
tmpFilesFromDirectories = user: group: d:
let
wrapTmpfiles = dir: mode: "d '${dir}' ${mode} ${user} ${group} - -";
in
mapAttrsToList wrapTmpfiles d;
systemd = {
mkService = {name, content, timer ? null}: stdenv.mkDerivation {
inherit name;
src = pkgs.writeTextDir "${name}.service" content;
timerSrc = pkgs.writeTextDir "${name}.timer" timer;
installPhase = ''
mkdir -p $out/etc/systemd/system
cp $src/*.service $out/etc/systemd/system
'' + (if timer == null then "" else ''
cp $timerSrc/*.timer $out/etc/systemd/system
'');
};
};
mkConfigFile = {dir, name, content}: stdenv.mkDerivation rec {
inherit name;
src = pkgs.writeTextDir name content;
buildCommand = ''
mkdir -p $out
cp ${src}/${name} $out/${name}
echo "${dir}" > $out/.dysnomia-targetdir
cat > $out/.dysnomia-fileset <<FILESET
symlink $out/${name}
target .
FILESET
'';
};
dnsmasqConfig = domain: subdomains:
''
${concatMapStringsSep "\n" (x: "address=/${x}.${domain}/127.0.0.1") subdomains}
domain=${domain}
'';
keyEnvironmentFile = path: "EnvironmentFile=/run/keys/${path}";
keyEnvironmentFiles = names: concatMapStrings (path: "${keyEnvironmentFile path}\n") (attrValues names);
keyServiceDependencies = names: concatMapStringsSep " " (path: "${path}-key.service") (attrValues names);
recursiveMerge = attrList:
let f = attrPath:
zipAttrsWith (n: values:
if all isList values then
concatLists values
else if all isAttrs values then
f (attrPath ++ [n]) values
else
last values
);
in f [] attrList;
getTarget = distribution: name: builtins.elemAt (builtins.getAttr name distribution) 0;
getDomain = distribution: name: (getTarget distribution name).containers.system.domain;
unitDepends = verb: dependsOn:
let
withSystemdUnitFile = filter (hasAttr "systemdUnitFile") (attrValues dependsOn);
systemdUnitFiles = map (x: x.systemdUnitFile) withSystemdUnitFile;
in
if length systemdUnitFiles == 0 then
""
else
"${verb}=${concatStringsSep " " systemdUnitFiles}";
}