bekkalokk: set up idp + mediawiki #25
17
base.nix
|
@ -3,6 +3,7 @@
|
|||
{
|
||||
imports = [
|
||||
./users
|
||||
./modules/snakeoil-certs.nix
|
||||
];
|
||||
|
||||
networking.domain = "pvv.ntnu.no";
|
||||
|
@ -82,5 +83,21 @@
|
|||
settings.PermitRootLogin = "yes";
|
||||
};
|
||||
|
||||
# nginx return 444 for all nonexistent virtualhosts
|
||||
|
||||
systemd.services.nginx.after = [ "generate-snakeoil-certs.service" ];
|
||||
|
||||
environment.snakeoil-certs = lib.mkIf (config.services.nginx.enable) {
|
||||
"/etc/certs/nginx" = {
|
||||
owner = "nginx";
|
||||
group = "nginx";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."_" = lib.mkIf (config.services.nginx.enable) {
|
||||
sslCertificate = "/etc/certs/nginx.crt";
|
||||
sslCertificateKey = "/etc/certs/nginx.key";
|
||||
addSSL = true;
|
||||
extraConfig = "return 444;";
|
||||
};
|
||||
}
|
||||
|
|
45
flake.nix
|
@ -42,6 +42,7 @@
|
|||
];
|
||||
in {
|
||||
nixosConfigurations = let
|
||||
unstablePkgs = nixpkgs-unstable.legacyPackages.x86_64-linux;
|
||||
nixosConfig = nixpkgs: name: config: nixpkgs.lib.nixosSystem (nixpkgs.lib.recursiveUpdate
|
||||
rec {
|
||||
system = "x86_64-linux";
|
||||
|
@ -53,16 +54,14 @@
|
|||
modules = [
|
||||
./hosts/${name}/configuration.nix
|
||||
sops-nix.nixosModules.sops
|
||||
];
|
||||
] ++ config.modules or [];
|
||||
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [
|
||||
inputs.pvv-calendar-bot.overlays.${system}.default
|
||||
];
|
||||
overlays = [ ] ++ config.overlays or [ ];
|
||||
};
|
||||
}
|
||||
config
|
||||
(removeAttrs config [ "modules" "overlays" ])
|
||||
);
|
||||
|
||||
stableNixosConfig = nixosConfig nixpkgs;
|
||||
|
@ -70,19 +69,24 @@
|
|||
in {
|
||||
bicep = stableNixosConfig "bicep" {
|
||||
modules = [
|
||||
./hosts/bicep/configuration.nix
|
||||
sops-nix.nixosModules.sops
|
||||
|
||||
inputs.matrix-next.nixosModules.default
|
||||
inputs.pvv-calendar-bot.nixosModules.default
|
||||
];
|
||||
overlays = [
|
||||
inputs.pvv-calendar-bot.overlays.x86_64-linux.default
|
||||
];
|
||||
};
|
||||
bekkalokk = stableNixosConfig "bekkalokk" {
|
||||
overlays = [
|
||||
(final: prev: {
|
||||
heimdal = unstablePkgs.heimdal;
|
||||
mediawiki-extensions = final.callPackage ./packages/mediawiki-extensions { };
|
||||
simplesamlphp = final.callPackage ./packages/simplesamlphp { };
|
||||
})
|
||||
];
|
||||
};
|
||||
bekkalokk = stableNixosConfig "bekkalokk" { };
|
||||
bob = stableNixosConfig "bob" {
|
||||
modules = [
|
||||
./hosts/bob/configuration.nix
|
||||
sops-nix.nixosModules.sops
|
||||
|
||||
disko.nixosModules.disko
|
||||
{ disko.devices.disk.disk1.device = "/dev/vda"; }
|
||||
];
|
||||
|
@ -93,28 +97,17 @@
|
|||
|
||||
brzeczyszczykiewicz = stableNixosConfig "brzeczyszczykiewicz" {
|
||||
modules = [
|
||||
./hosts/brzeczyszczykiewicz/configuration.nix
|
||||
sops-nix.nixosModules.sops
|
||||
|
||||
inputs.grzegorz.nixosModules.grzegorz-kiosk
|
||||
inputs.grzegorz-clients.nixosModules.grzegorz-webui
|
||||
];
|
||||
};
|
||||
georg = stableNixosConfig "georg" {
|
||||
modules = [
|
||||
./hosts/georg/configuration.nix
|
||||
sops-nix.nixosModules.sops
|
||||
|
||||
inputs.grzegorz.nixosModules.grzegorz-kiosk
|
||||
inputs.grzegorz-clients.nixosModules.grzegorz-webui
|
||||
];
|
||||
};
|
||||
buskerud = stableNixosConfig "buskerud" {
|
||||
modules = [
|
||||
./hosts/buskerud/configuration.nix
|
||||
sops-nix.nixosModules.sops
|
||||
];
|
||||
};
|
||||
buskerud = stableNixosConfig "buskerud" { };
|
||||
};
|
||||
|
||||
devShells = forAllSystems (system: {
|
||||
|
@ -130,6 +123,10 @@
|
|||
(nixlib.getAttrs importantMachines self.packages.x86_64-linux);
|
||||
all-machines = pkgs.linkFarm "all-machines"
|
||||
(nixlib.getAttrs allMachines self.packages.x86_64-linux);
|
||||
|
||||
simplesamlphp = pkgs.callPackage ./packages/simplesamlphp { };
|
||||
|
||||
mediawiki-extensions = pkgs.callPackage ./packages/mediawiki-extensions { };
|
||||
} // nixlib.genAttrs allMachines
|
||||
(machine: self.nixosConfigurations.${machine}.config.system.build.toplevel);
|
||||
};
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
# ./services/website.nix
|
||||
./services/nginx
|
||||
./services/gitea/default.nix
|
||||
./services/kerberos
|
||||
./services/webmail
|
||||
# ./services/mediawiki.nix
|
||||
./services/mediawiki
|
||||
./services/idp-simplesamlphp
|
||||
];
|
||||
|
||||
sops.defaultSopsFile = ../../secrets/bekkalokk/bekkalokk.yaml;
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
oysteikt marked this conversation as resolved
Outdated
|
||||
|
||||
/**
|
||||
* Authenticate using HTTP login.
|
||||
*
|
||||
* @author Yorn de Jong
|
||||
* @author Oystein Kristoffer Tveit
|
||||
* @package simpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Module\authpwauth\Auth\Source;
|
||||
|
||||
class PwAuth extends \SimpleSAML\Module\core\Auth\UserPassBase
|
||||
{
|
||||
protected $pwauth_bin_path;
|
||||
protected $mail_domain;
|
||||
|
||||
public function __construct(array $info, array &$config) {
|
||||
assert('is_array($info)');
|
||||
assert('is_array($config)');
|
||||
|
||||
/* Call the parent constructor first, as required by the interface. */
|
||||
parent::__construct($info, $config);
|
||||
|
||||
$this->pwauth_bin_path = $config['pwauth_bin_path'];
|
||||
if (array_key_exists('mail_domain', $config)) {
|
||||
$this->mail_domain = '@' . ltrim($config['mail_domain'], '@');
|
||||
}
|
||||
}
|
||||
|
||||
public function login(string $username, string $password): array {
|
||||
$username = strtolower( $username );
|
||||
|
||||
if (!file_exists($this->pwauth_bin_path)) {
|
||||
die("Could not find pwauth binary");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_executable($this->pwauth_bin_path)) {
|
||||
die("pwauth binary is not executable");
|
||||
return false;
|
||||
}
|
||||
|
||||
$handle = popen($this->pwauth_bin_path, 'w');
|
||||
if ($handle === FALSE) {
|
||||
die("Error opening pipe to pwauth");
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = "$username\n$password\n";
|
||||
if (fwrite($handle, $data) !== strlen($data)) {
|
||||
die("Error writing to pwauth pipe");
|
||||
return false;
|
||||
}
|
||||
|
||||
# Is the password valid?
|
||||
$result = pclose( $handle );
|
||||
if ($result !== 0) {
|
||||
if (!in_array($result, [1, 2, 3, 4, 5, 6, 7], true)) {
|
||||
die("pwauth returned $result for username $username");
|
||||
}
|
||||
throw new \SimpleSAML\Error\Error('WRONGUSERPASS');
|
||||
}
|
||||
/*
|
||||
felixalb
commented
Cleanup: Remove commented block if not needed Cleanup: Remove commented block if not needed
|
||||
$ldap = ldap_connect('129.241.210.159', 389);
|
||||
felixalb
commented
Hardcoded IP address in our DHCP range? Hardcoded IP address in our DHCP range?
What machine is it supposed to be referencing, the KDC or another LDAP host?
oysteikt
commented
Commented stuff from Yorn's days, no idea what the original host was. Presumably LDAP host, considering it's using Commented stuff from Yorn's days, no idea what the original host was. Presumably LDAP host, considering it's using `ldap_connect` with port `389`
|
||||
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
|
||||
ldap_start_tls($ldap);
|
||||
ldap_bind($ldap, 'passordendrer@pvv.ntnu.no', 'Oi7aekoh');
|
||||
$search = ldap_search($ldap, 'DC=pvv,DC=ntnu,DC=no', '(sAMAccountName='.ldap_escape($username, '', LDAP_ESCAPE_FILTER).')');
|
||||
$entry = ldap_first_entry($ldap, $search);
|
||||
$dn = ldap_get_dn($ldap, $entry);
|
||||
$newpassword = mb_convert_encoding("\"$password\"", 'UTF-16LE', 'UTF-8');
|
||||
ldap_modify_batch($ldap, $dn, [
|
||||
#[
|
||||
# 'modtype' => LDAP_MODIFY_BATCH_REMOVE,
|
||||
# 'attrib' => 'unicodePwd',
|
||||
# 'values' => [$password],
|
||||
#],
|
||||
[
|
||||
#'modtype' => LDAP_MODIFY_BATCH_ADD,
|
||||
'modtype' => LDAP_MODIFY_BATCH_REPLACE,
|
||||
'attrib' => 'unicodePwd',
|
||||
'values' => [$newpassword],
|
||||
],
|
||||
]);
|
||||
*/
|
||||
|
||||
#0 - Login OK.
|
||||
#1 - Nonexistant login or (for some configurations) incorrect password.
|
||||
#2 - Incorrect password (for some configurations).
|
||||
#3 - Uid number is below MIN_UNIX_UID value configured in config.h.
|
||||
#4 - Login ID has expired.
|
||||
#5 - Login's password has expired.
|
||||
#6 - Logins to system have been turned off (usually by /etc/nologin file).
|
||||
#7 - Limit on number of bad logins exceeded.
|
||||
#50 - pwauth was not run with real uid SERVER_UID. If you get this
|
||||
# this error code, you probably have SERVER_UID set incorrectly
|
||||
# in pwauth's config.h file.
|
||||
#51 - pwauth was not given a login & password to check. The means
|
||||
# the passing of data from mod_auth_external to pwauth is messed
|
||||
# up. Most likely one is trying to pass data via environment
|
||||
# variables, while the other is trying to pass data via a pipe.
|
||||
#52 - one of several possible internal errors occured.
|
||||
|
||||
|
||||
$uid = $username;
|
||||
# TODO: Reinstate this code once passwd is working...
|
||||
/*
|
||||
$cn = trim(shell_exec('getent passwd '.escapeshellarg($uid).' | cut -d: -f5 | cut -d, -f1'));
|
||||
|
||||
$groups = preg_split('_\\s_', shell_exec('groups '.escapeshellarg($uid)));
|
||||
array_shift($groups);
|
||||
array_shift($groups);
|
||||
array_pop($groups);
|
||||
|
||||
$info = posix_getpwnam($uid);
|
||||
$group = $info['gid'];
|
||||
if (!in_array($group, $groups)) {
|
||||
$groups[] = $group;
|
||||
}
|
||||
*/
|
||||
$cn = "Unknown McUnknown";
|
||||
$groups = array();
|
||||
|
||||
$result = array(
|
||||
'uid' => array($uid),
|
||||
'cn' => array($cn),
|
||||
'group' => $groups,
|
||||
);
|
||||
if (isset($this->mail_domain)) {
|
||||
$result['mail'] = array($uid.$this->mail_domain);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
pwAuthScript = pkgs.writeShellApplication {
|
||||
name = "pwauth";
|
||||
runtimeInputs = with pkgs; [ coreutils heimdal ];
|
||||
text = ''
|
||||
read -r user1
|
||||
user2="$(echo -n "$user1" | tr -c -d '0123456789abcdefghijklmnopqrstuvwxyz')"
|
||||
if test "$user1" != "$user2"
|
||||
then
|
||||
read -r _
|
||||
exit 2
|
||||
fi
|
||||
kinit --password-file=STDIN "''${user1}@PVV.NTNU.NO" >/dev/null 2>/dev/null
|
||||
oysteikt marked this conversation as resolved
Outdated
felixalb
commented
nit: Should/Could we automatically delete these TGTs after authentication? I don't believe they are ever reused. nit: Should/Could we automatically delete these TGTs after authentication? I don't believe they are ever reused.
oysteikt
commented
Sure! Added Sure!
Added `kdestroy` below
|
||||
kdestroy >/dev/null 2>/dev/null
|
||||
'';
|
||||
};
|
||||
|
||||
package = pkgs.simplesamlphp.override {
|
||||
extra_files = {
|
||||
# NOTE: Using self signed certificate created 30. march 2024, with command:
|
||||
# openssl req -newkey rsa:4096 -new -x509 -days 365 -nodes -out idp.crt -keyout idp.pem
|
||||
"metadata/saml20-idp-hosted.php" = pkgs.writeText "saml20-idp-remote.php" ''
|
||||
<?php
|
||||
$metadata['https://idp2.pvv.ntnu.no/'] = array(
|
||||
'host' => '__DEFAULT__',
|
||||
'privatekey' => '${config.sops.secrets."idp/privatekey".path}',
|
||||
'certificate' => '${./idp.crt}',
|
||||
'auth' => 'pwauth',
|
||||
);
|
||||
?>
|
||||
'';
|
||||
|
||||
"metadata/saml20-sp-remote.php" = pkgs.writeText "saml20-sp-remote.php" ''
|
||||
<?php
|
||||
${ lib.pipe config.services.idp.sp-remote-metadata [
|
||||
(map (url: ''
|
||||
$metadata['${url}'] = [
|
||||
'SingleLogoutService' => [
|
||||
[
|
||||
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
|
||||
'Location' => '${url}module.php/saml/sp/saml2-logout.php/default-sp',
|
||||
],
|
||||
[
|
||||
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP',
|
||||
'Location' => '${url}module.php/saml/sp/saml2-logout.php/default-sp',
|
||||
],
|
||||
],
|
||||
'AssertionConsumerService' => [
|
||||
[
|
||||
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
|
||||
'Location' => '${url}module.php/saml/sp/saml2-acs.php/default-sp',
|
||||
'index' => 0,
|
||||
],
|
||||
[
|
||||
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact',
|
||||
'Location' => '${url}module.php/saml/sp/saml2-acs.php/default-sp',
|
||||
'index' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
''))
|
||||
(lib.concatStringsSep "\n")
|
||||
]}
|
||||
?>
|
||||
'';
|
||||
|
||||
"config/authsources.php" = pkgs.writeText "idp-authsources.php" ''
|
||||
<?php
|
||||
$config = array(
|
||||
'admin' => array(
|
||||
'core:AdminPassword'
|
||||
),
|
||||
'pwauth' => array(
|
||||
'authpwauth:PwAuth',
|
||||
'pwauth_bin_path' => '${lib.getExe pwAuthScript}',
|
||||
'mail_domain' => '@pvv.ntnu.no',
|
||||
),
|
||||
);
|
||||
?>
|
||||
'';
|
||||
|
||||
"config/config.php" = pkgs.runCommandLocal "simplesamlphp-config.php" { } ''
|
||||
cp ${./config.php} "$out"
|
||||
|
||||
substituteInPlace "$out" \
|
||||
--replace '$SAML_COOKIE_SECURE' 'true' \
|
||||
--replace '$SAML_COOKIE_SALT' 'file_get_contents("${config.sops.secrets."idp/cookie_salt".path}")' \
|
||||
--replace '$SAML_ADMIN_NAME' '"Drift"' \
|
||||
--replace '$SAML_ADMIN_EMAIL' '"drift@pvv.ntnu.no"' \
|
||||
--replace '$SAML_ADMIN_PASSWORD' 'file_get_contents("${config.sops.secrets."idp/admin_password".path}")' \
|
||||
--replace '$SAML_TRUSTED_DOMAINS' 'array( "idp2.pvv.ntnu.no" )' \
|
||||
--replace '$SAML_DATABASE_DSN' '"pgsql:host=postgres.pvv.ntnu.no;port=5432;dbname=idp"' \
|
||||
--replace '$SAML_DATABASE_USERNAME' '"idp"' \
|
||||
--replace '$SAML_DATABASE_PASSWORD' 'file_get_contents("${config.sops.secrets."idp/postgres_password".path}")' \
|
||||
--replace '$CACHE_DIRECTORY' '/var/cache/idp'
|
||||
'';
|
||||
|
||||
"modules/authpwauth/src/Auth/Source/PwAuth.php" = ./authpwauth.php;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
oysteikt marked this conversation as resolved
Outdated
oysteikt
commented
Note for future: This is cursed. simplesamlphp supports an extension that can autofetch metadata, but it requires edits to composer.json and the lockfile, which will have to be updated together with the project. Maybe it would be better to maintain this hash together with the package, and make sure to bump it whenever we bump the package version? Note for future:
This is *cursed*. simplesamlphp supports an [extension that can autofetch metadata](https://simplesamlphp.org/docs/contrib_modules/metarefresh/simplesamlphp-automated_metadata.html), but it requires edits to composer.json and the lockfile, which will have to be updated together with the project. Maybe it would be better to maintain this hash together with the package, and make sure to bump it whenever we bump the package version?
oysteikt
commented
Leaving as future issue Leaving as future issue
|
||||
options.services.idp.sp-remote-metadata = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of urls point to (simplesamlphp) service profiders, which the idp should trust.
|
||||
|
||||
:::{.note}
|
||||
Make sure the url ends with a `/`
|
||||
:::
|
||||
'';
|
||||
};
|
||||
|
||||
config = {
|
||||
sops.secrets = {
|
||||
"idp/privatekey" = {
|
||||
owner = "idp";
|
||||
group = "idp";
|
||||
mode = "0770";
|
||||
};
|
||||
"idp/admin_password" = {
|
||||
owner = "idp";
|
||||
group = "idp";
|
||||
};
|
||||
"idp/postgres_password" = {
|
||||
owner = "idp";
|
||||
group = "idp";
|
||||
};
|
||||
"idp/cookie_salt" = {
|
||||
owner = "idp";
|
||||
group = "idp";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups."idp" = { };
|
||||
users.users."idp" = {
|
||||
description = "PVV Identity Provider Service User";
|
||||
group = "idp";
|
||||
createHome = false;
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
systemd.tmpfiles.settings."10-idp" = {
|
||||
"/var/cache/idp".d = {
|
||||
user = "idp";
|
||||
group = "idp";
|
||||
mode = "0770";
|
||||
};
|
||||
"/var/lib/idp".d = {
|
||||
user = "idp";
|
||||
group = "idp";
|
||||
mode = "0770";
|
||||
};
|
||||
};
|
||||
|
||||
services.phpfpm.pools.idp = {
|
||||
user = "idp";
|
||||
group = "idp";
|
||||
settings = let
|
||||
listenUser = config.services.nginx.user;
|
||||
listenGroup = config.services.nginx.group;
|
||||
in {
|
||||
"pm" = "dynamic";
|
||||
"pm.max_children" = 32;
|
||||
"pm.max_requests" = 500;
|
||||
"pm.start_servers" = 2;
|
||||
"pm.min_spare_servers" = 2;
|
||||
"pm.max_spare_servers" = 4;
|
||||
"listen.owner" = listenUser;
|
||||
"listen.group" = listenGroup;
|
||||
|
||||
"catch_workers_output" = true;
|
||||
"php_admin_flag[log_errors]" = true;
|
||||
# "php_admin_value[error_log]" = "stderr";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."idp2.pvv.ntnu.no" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
root = "${package}/share/php/simplesamlphp/public";
|
||||
locations = {
|
||||
# based on https://simplesamlphp.org/docs/stable/simplesamlphp-install.html#configuring-nginx
|
||||
"/" = {
|
||||
alias = "${package}/share/php/simplesamlphp/public/";
|
||||
index = "index.php";
|
||||
|
||||
extraConfig = ''
|
||||
location ~ ^/(?<phpfile>.+?\.php)(?<pathinfo>/.*)?$ {
|
||||
include ${pkgs.nginx}/conf/fastcgi_params;
|
||||
fastcgi_pass unix:${config.services.phpfpm.pools.idp.socket};
|
||||
fastcgi_param SCRIPT_FILENAME ${package}/share/php/simplesamlphp/public/$phpfile;
|
||||
fastcgi_param SCRIPT_NAME /$phpfile;
|
||||
fastcgi_param PATH_INFO $pathinfo if_not_empty;
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
UxTpf01QJnZkMqf5NQ==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,22 @@
|
|||
''
|
||||
<?php
|
||||
$metadata['https://idp2.pvv.ntnu.no/'] = [
|
||||
'metadata-set' => 'saml20-idp-hosted',
|
||||
'entityid' => 'https://idp2.pvv.ntnu.no/',
|
||||
'SingleSignOnService' => [
|
||||
[
|
||||
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
|
||||
'Location' => 'https://idp2.pvv.ntnu.no/module.php/saml/idp/singleSignOnService',
|
||||
],
|
||||
],
|
||||
'SingleLogoutService' => [
|
||||
[
|
||||
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
|
||||
'Location' => 'https://idp2.pvv.ntnu.no/module.php/saml/idp/singleLogout',
|
||||
],
|
||||
],
|
||||
'NameIDFormat' => [ 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' ],
|
||||
'certificate' => '${./idp.crt}',
|
||||
];
|
||||
?>
|
||||
''
|
|
@ -0,0 +1,27 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
#######################
|
||||
# TODO: remove these once nixos 24.05 gets released
|
||||
#######################
|
||||
imports = [
|
||||
./krb5.nix
|
||||
./pam.nix
|
||||
];
|
||||
disabledModules = [
|
||||
"config/krb5/default.nix"
|
||||
"security/pam.nix"
|
||||
];
|
||||
#######################
|
||||
|
||||
security.krb5 = {
|
||||
enable = true;
|
||||
settings = {
|
||||
libdefaults = {
|
||||
default_realm = "PVV.NTNU.NO";
|
||||
dns_lookup_realm = "yes";
|
||||
dns_lookup_kdc = "yes";
|
||||
};
|
||||
realms."PVV.NTNU.NO".admin_server = "kdc.pvv.ntnu.no";
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
{ pkgs, lib, ... }:
|
||||
|
||||
# Based on
|
||||
# - https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html
|
||||
# - https://manpages.debian.org/unstable/heimdal-docs/krb5.conf.5heimdal.en.html
|
||||
|
||||
let
|
||||
inherit (lib) boolToString concatMapStringsSep concatStringsSep filter
|
||||
isAttrs isBool isList mapAttrsToList mdDoc mkOption singleton splitString;
|
||||
inherit (lib.types) attrsOf bool coercedTo either int listOf oneOf path
|
||||
str submodule;
|
||||
in
|
||||
{ }: {
|
||||
type = let
|
||||
section = attrsOf relation;
|
||||
relation = either (attrsOf value) value;
|
||||
value = either (listOf atom) atom;
|
||||
atom = oneOf [int str bool];
|
||||
in submodule {
|
||||
freeformType = attrsOf section;
|
||||
options = {
|
||||
include = mkOption {
|
||||
default = [ ];
|
||||
description = mdDoc ''
|
||||
Files to include in the Kerberos configuration.
|
||||
'';
|
||||
type = coercedTo path singleton (listOf path);
|
||||
};
|
||||
includedir = mkOption {
|
||||
default = [ ];
|
||||
description = mdDoc ''
|
||||
Directories containing files to include in the Kerberos configuration.
|
||||
'';
|
||||
type = coercedTo path singleton (listOf path);
|
||||
};
|
||||
module = mkOption {
|
||||
default = [ ];
|
||||
description = mdDoc ''
|
||||
Modules to obtain Kerberos configuration from.
|
||||
'';
|
||||
type = coercedTo path singleton (listOf path);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
generate = let
|
||||
indent = str: concatMapStringsSep "\n" (line: " " + line) (splitString "\n" str);
|
||||
|
||||
formatToplevel = args @ {
|
||||
include ? [ ],
|
||||
includedir ? [ ],
|
||||
module ? [ ],
|
||||
...
|
||||
}: let
|
||||
sections = removeAttrs args [ "include" "includedir" "module" ];
|
||||
in concatStringsSep "\n" (filter (x: x != "") [
|
||||
(concatStringsSep "\n" (mapAttrsToList formatSection sections))
|
||||
(concatMapStringsSep "\n" (m: "module ${m}") module)
|
||||
(concatMapStringsSep "\n" (i: "include ${i}") include)
|
||||
(concatMapStringsSep "\n" (i: "includedir ${i}") includedir)
|
||||
]);
|
||||
|
||||
formatSection = name: section: ''
|
||||
[${name}]
|
||||
${indent (concatStringsSep "\n" (mapAttrsToList formatRelation section))}
|
||||
'';
|
||||
|
||||
formatRelation = name: relation:
|
||||
if isAttrs relation
|
||||
then ''
|
||||
${name} = {
|
||||
${indent (concatStringsSep "\n" (mapAttrsToList formatValue relation))}
|
||||
}''
|
||||
else formatValue name relation;
|
||||
|
||||
formatValue = name: value:
|
||||
if isList value
|
||||
then concatMapStringsSep "\n" (formatAtom name) value
|
||||
else formatAtom name value;
|
||||
|
||||
formatAtom = name: atom: let
|
||||
v = if isBool atom then boolToString atom else toString atom;
|
||||
in "${name} = ${v}";
|
||||
in
|
||||
name: value: pkgs.writeText name ''
|
||||
${formatToplevel value}
|
||||
'';
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
inherit (lib) mdDoc mkIf mkOption mkPackageOption mkRemovedOptionModule;
|
||||
inherit (lib.types) bool;
|
||||
|
||||
mkRemovedOptionModule' = name: reason: mkRemovedOptionModule ["krb5" name] reason;
|
||||
mkRemovedOptionModuleCfg = name: mkRemovedOptionModule' name ''
|
||||
The option `krb5.${name}' has been removed. Use
|
||||
`security.krb5.settings.${name}' for structured configuration.
|
||||
'';
|
||||
|
||||
cfg = config.security.krb5;
|
||||
format = import ./krb5-conf-format.nix { inherit pkgs lib; } { };
|
||||
in {
|
||||
imports = [
|
||||
(mkRemovedOptionModuleCfg "libdefaults")
|
||||
(mkRemovedOptionModuleCfg "realms")
|
||||
(mkRemovedOptionModuleCfg "domain_realm")
|
||||
(mkRemovedOptionModuleCfg "capaths")
|
||||
(mkRemovedOptionModuleCfg "appdefaults")
|
||||
(mkRemovedOptionModuleCfg "plugins")
|
||||
(mkRemovedOptionModuleCfg "config")
|
||||
(mkRemovedOptionModuleCfg "extraConfig")
|
||||
(mkRemovedOptionModule' "kerberos" ''
|
||||
The option `krb5.kerberos' has been moved to `security.krb5.package'.
|
||||
'')
|
||||
];
|
||||
|
||||
options = {
|
||||
security.krb5 = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
description = mdDoc "Enable and configure Kerberos utilities";
|
||||
type = bool;
|
||||
};
|
||||
|
||||
package = mkPackageOption pkgs "krb5" {
|
||||
example = "heimdal";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
default = { };
|
||||
type = format.type;
|
||||
description = mdDoc ''
|
||||
Structured contents of the {file}`krb5.conf` file. See
|
||||
{manpage}`krb5.conf(5)` for details about configuration.
|
||||
'';
|
||||
example = {
|
||||
include = [ "/run/secrets/secret-krb5.conf" ];
|
||||
includedir = [ "/run/secrets/secret-krb5.conf.d" ];
|
||||
|
||||
libdefaults = {
|
||||
default_realm = "ATHENA.MIT.EDU";
|
||||
};
|
||||
|
||||
realms = {
|
||||
"ATHENA.MIT.EDU" = {
|
||||
admin_server = "athena.mit.edu";
|
||||
kdc = [
|
||||
"athena01.mit.edu"
|
||||
"athena02.mit.edu"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
domain_realm = {
|
||||
"mit.edu" = "ATHENA.MIT.EDU";
|
||||
};
|
||||
|
||||
logging = {
|
||||
kdc = "SYSLOG:NOTICE";
|
||||
admin_server = "SYSLOG:NOTICE";
|
||||
default = "SYSLOG:NOTICE";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment = {
|
||||
systemPackages = [ cfg.package ];
|
||||
etc."krb5.conf".source = format.generate "krb5.conf" cfg.settings;
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = builtins.attrValues {
|
||||
inherit (lib.maintainers) dblsaiko h7x4;
|
||||
};
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
{ pkgs, lib, config, values, ... }: let
|
||||
cfg = config.services.mediawiki;
|
||||
|
||||
# "mediawiki"
|
||||
user = config.systemd.services.mediawiki-init.serviceConfig.User;
|
||||
|
||||
# "mediawiki"
|
||||
group = config.users.users.${user}.group;
|
||||
in {
|
||||
sops.secrets = {
|
||||
"mediawiki/password" = {
|
||||
restartUnits = [ "mediawiki-init.service" "phpfpm-mediawiki.service" ];
|
||||
owner = user;
|
||||
group = group;
|
||||
};
|
||||
"keys/postgres/mediawiki" = {
|
||||
restartUnits = [ "mediawiki-init.service" "phpfpm-mediawiki.service" ];
|
||||
owner = user;
|
||||
group = group;
|
||||
};
|
||||
};
|
||||
|
||||
services.mediawiki = {
|
||||
enable = true;
|
||||
name = "Programvareverkstedet";
|
||||
passwordFile = config.sops.secrets."mediawiki/password".path;
|
||||
passwordSender = "drift@pvv.ntnu.no";
|
||||
|
||||
database = {
|
||||
type = "postgres";
|
||||
host = "postgres.pvv.ntnu.no";
|
||||
port = config.services.postgresql.port;
|
||||
passwordFile = config.sops.secrets."keys/postgres/mediawiki".path;
|
||||
createLocally = false;
|
||||
# TODO: create a normal database and copy over old data when the service is production ready
|
||||
name = "mediawiki_test";
|
||||
};
|
||||
|
||||
# Host through nginx
|
||||
webserver = "none";
|
||||
poolConfig = let
|
||||
listenUser = config.services.nginx.user;
|
||||
listenGroup = config.services.nginx.group;
|
||||
in {
|
||||
inherit user group;
|
||||
"pm" = "dynamic";
|
||||
"pm.max_children" = 32;
|
||||
"pm.max_requests" = 500;
|
||||
"pm.start_servers" = 2;
|
||||
"pm.min_spare_servers" = 2;
|
||||
"pm.max_spare_servers" = 4;
|
||||
"listen.owner" = listenUser;
|
||||
"listen.group" = listenGroup;
|
||||
"php_admin_value[error_log]" = "stderr";
|
||||
"php_admin_flag[log_errors]" = "on";
|
||||
"env[PATH]" = lib.makeBinPath [ pkgs.php ];
|
||||
"catch_workers_output" = true;
|
||||
# to accept *.html file
|
||||
"security.limit_extensions" = "";
|
||||
};
|
||||
|
||||
extensions = {
|
||||
DeleteBatch = pkgs.fetchzip {
|
||||
url = "https://extdist.wmflabs.org/dist/extensions/DeleteBatch-REL1_39-995ea6f.tar.gz";
|
||||
sha256 = "sha256-0F4GLCy2f5WcWIY2YgF1tVxgYbglR0VOsj/pMrW93b8=";
|
||||
};
|
||||
UserMerge = pkgs.fetchzip {
|
||||
url = "https://extdist.wmflabs.org/dist/extensions/UserMerge-REL1_39-b10d50e.tar.gz";
|
||||
sha256 = "sha256-bXhj1+OlOUJDbvEuc8iwqb1LLEu6cN6+C/7cAvnWPOQ=";
|
||||
};
|
||||
PluggableAuth = pkgs.fetchzip {
|
||||
url = "https://extdist.wmflabs.org/dist/extensions/PluggableAuth-REL1_39-1210fc3.tar.gz";
|
||||
sha256 = "sha256-F6bTMCzkK3kZwZGIsNE87WlZWqXXmTMhEjApO99YKR0=";
|
||||
};
|
||||
SimpleSAMLphp = pkgs.fetchzip {
|
||||
url = "https://extdist.wmflabs.org/dist/extensions/SimpleSAMLphp-REL1_39-dcf0acb.tar.gz";
|
||||
sha256 = "sha256-tCvFmb2+q2rxms+lRo5pgoI3h6GjCwXAR8XisPg03TQ=";
|
||||
};
|
||||
};
|
||||
|
||||
extraConfig = let
|
||||
|
||||
SimpleSAMLphpRepo = pkgs.stdenvNoCC.mkDerivation rec {
|
||||
pname = "configuredSimpleSAML";
|
||||
version = "2.0.4";
|
||||
src = pkgs.fetchzip {
|
||||
url = "https://github.com/simplesamlphp/simplesamlphp/releases/download/v${version}/simplesamlphp-${version}.tar.gz";
|
||||
sha256 = "sha256-pfMV/VmqqxgtG7Nx4s8MW4tWSaxOkVPtCRJwxV6RDSE=";
|
||||
};
|
||||
|
||||
buildPhase = ''
|
||||
cat > config/authsources.php << EOF
|
||||
<?php
|
||||
$config = array(
|
||||
'default-sp' => array(
|
||||
'saml:SP',
|
||||
'idp' => 'https://idp.pvv.ntnu.no/',
|
||||
),
|
||||
);
|
||||
EOF
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
cp -r . $out
|
||||
'';
|
||||
};
|
||||
|
||||
in ''
|
||||
$wgServer = "https://bekkalokk.pvv.ntnu.no";
|
||||
$wgLocaltimezone = "Europe/Oslo";
|
||||
|
||||
# Only allow login through SSO
|
||||
$wgEnableEmail = false;
|
||||
$wgEnableUserEmail = false;
|
||||
$wgEmailAuthentication = false;
|
||||
$wgGroupPermissions['*']['createaccount'] = false;
|
||||
$wgGroupPermissions['*']['autocreateaccount'] = true;
|
||||
$wgPluggableAuth_EnableAutoLogin = true;
|
||||
|
||||
# Disable anonymous editing
|
||||
$wgGroupPermissions['*']['edit'] = false;
|
||||
|
||||
# Styling
|
||||
$wgLogo = "/PNG/PVV-logo.png";
|
||||
$wgDefaultSkin = "monobook";
|
||||
|
||||
# Misc
|
||||
$wgEmergencyContact = "${cfg.passwordSender}";
|
||||
$wgShowIPinHeader = false;
|
||||
$wgUseTeX = false;
|
||||
$wgLocalInterwiki = $wgSitename;
|
||||
|
||||
# SimpleSAML
|
||||
$wgSimpleSAMLphp_InstallDir = "${SimpleSAMLphpRepo}";
|
||||
$wgSimpleSAMLphp_AuthSourceId = "default-sp";
|
||||
$wgSimpleSAMLphp_RealNameAttribute = "cn";
|
||||
$wgSimpleSAMLphp_EmailAttribute = "mail";
|
||||
$wgSimpleSAMLphp_UsernameAttribute = "uid";
|
||||
|
||||
# Fix https://github.com/NixOS/nixpkgs/issues/183097
|
||||
$wgDBserver = "${toString cfg.database.host}";
|
||||
'';
|
||||
};
|
||||
|
||||
# Override because of https://github.com/NixOS/nixpkgs/issues/183097
|
||||
systemd.services.mediawiki-init.script = let
|
||||
# According to module
|
||||
stateDir = "/var/lib/mediawiki";
|
||||
pkg = cfg.finalPackage;
|
||||
mediawikiConfig = config.services.phpfpm.pools.mediawiki.phpEnv.MEDIAWIKI_CONFIG;
|
||||
inherit (lib) optionalString mkForce;
|
||||
in mkForce ''
|
||||
if ! test -e "${stateDir}/secret.key"; then
|
||||
tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c 64 > ${stateDir}/secret.key
|
||||
fi
|
||||
|
||||
echo "exit( wfGetDB( DB_MASTER )->tableExists( 'user' ) ? 1 : 0 );" | \
|
||||
${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/eval.php --conf ${mediawikiConfig} && \
|
||||
${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \
|
||||
--confpath /tmp \
|
||||
--scriptpath / \
|
||||
--dbserver "${cfg.database.host}" \
|
||||
--dbport ${toString cfg.database.port} \
|
||||
--dbname ${cfg.database.name} \
|
||||
${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${cfg.database.tablePrefix}"} \
|
||||
--dbuser ${cfg.database.user} \
|
||||
${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${cfg.database.passwordFile}"} \
|
||||
--passfile ${cfg.passwordFile} \
|
||||
--dbtype ${cfg.database.type} \
|
||||
${cfg.name} \
|
||||
admin
|
||||
|
||||
${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick
|
||||
'';
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
{ pkgs, lib, config, values, pkgs-unstable, ... }: let
|
||||
cfg = config.services.mediawiki;
|
||||
|
||||
# "mediawiki"
|
||||
user = config.systemd.services.mediawiki-init.serviceConfig.User;
|
||||
|
||||
# "mediawiki"
|
||||
group = config.users.users.${user}.group;
|
||||
|
||||
simplesamlphp = pkgs.simplesamlphp.override {
|
||||
extra_files = {
|
||||
"metadata/saml20-idp-remote.php" = pkgs.writeText "mediawiki-saml20-idp-remote.php" (import ../idp-simplesamlphp/metadata.php.nix);
|
||||
|
||||
"config/authsources.php" = ./simplesaml-authsources.php;
|
||||
|
||||
"config/config.php" = pkgs.runCommandLocal "mediawiki-simplesamlphp-config.php" { } ''
|
||||
cp ${./simplesaml-config.php} "$out"
|
||||
|
||||
substituteInPlace "$out" \
|
||||
--replace '$SAML_COOKIE_SECURE' 'true' \
|
||||
--replace '$SAML_COOKIE_SALT' 'file_get_contents("${config.sops.secrets."mediawiki/simplesamlphp/cookie_salt".path}")' \
|
||||
--replace '$SAML_ADMIN_NAME' '"Drift"' \
|
||||
--replace '$SAML_ADMIN_EMAIL' '"drift@pvv.ntnu.no"' \
|
||||
--replace '$SAML_ADMIN_PASSWORD' 'file_get_contents("${config.sops.secrets."mediawiki/simplesamlphp/admin_password".path}")' \
|
||||
--replace '$SAML_TRUSTED_DOMAINS' 'array( "wiki2.pvv.ntnu.no" )' \
|
||||
--replace '$SAML_DATABASE_DSN' '"pgsql:host=postgres.pvv.ntnu.no;port=5432;dbname=mediawiki_simplesamlphp"' \
|
||||
--replace '$SAML_DATABASE_USERNAME' '"mediawiki_simplesamlphp"' \
|
||||
--replace '$SAML_DATABASE_PASSWORD' 'file_get_contents("${config.sops.secrets."mediawiki/simplesamlphp/postgres_password".path}")' \
|
||||
--replace '$CACHE_DIRECTORY' '/var/cache/mediawiki/idp'
|
||||
oysteikt marked this conversation as resolved
Outdated
oysteikt
commented
It would be nice if we could find a better solution for this. Optimally it should
Maybe it would be possible to use includes and some php magic to merge the two? ping: @felixalb It would be nice if we could find a better solution for this. Optimally it should
- Use the upstream config as base
- Override only specified options
- Avoid writing a whole php parser in nix
- Avoid maintaining a nixified copy of the upstream base config
Maybe it would be possible to use includes and some php magic to merge the two?
ping: @felixalb
oysteikt
commented
Leaving as future issue. Leaving as future issue.
|
||||
'';
|
||||
};
|
||||
};
|
||||
in {
|
||||
services.idp.sp-remote-metadata = [ "https://wiki2.pvv.ntnu.no/simplesaml/" ];
|
||||
|
||||
sops.secrets = lib.pipe [
|
||||
"mediawiki/password"
|
||||
"mediawiki/postgres_password"
|
||||
"mediawiki/simplesamlphp/postgres_password"
|
||||
"mediawiki/simplesamlphp/cookie_salt"
|
||||
"mediawiki/simplesamlphp/admin_password"
|
||||
] [
|
||||
(map (key: lib.nameValuePair key {
|
||||
owner = user;
|
||||
group = group;
|
||||
}))
|
||||
lib.listToAttrs
|
||||
];
|
||||
|
||||
services.mediawiki = {
|
||||
enable = true;
|
||||
name = "Programvareverkstedet";
|
||||
passwordFile = config.sops.secrets."mediawiki/password".path;
|
||||
passwordSender = "drift@pvv.ntnu.no";
|
||||
|
||||
database = {
|
||||
type = "mysql";
|
||||
host = "mysql.pvv.ntnu.no";
|
||||
port = 3306;
|
||||
user = "mediawiki";
|
||||
passwordFile = config.sops.secrets."mediawiki/postgres_password".path;
|
||||
createLocally = false;
|
||||
# TODO: create a normal database and copy over old data when the service is production ready
|
||||
name = "mediawiki";
|
||||
};
|
||||
|
||||
# Host through nginx
|
||||
webserver = "none";
|
||||
poolConfig = let
|
||||
listenUser = config.services.nginx.user;
|
||||
listenGroup = config.services.nginx.group;
|
||||
in {
|
||||
inherit user group;
|
||||
"pm" = "dynamic";
|
||||
"pm.max_children" = 32;
|
||||
"pm.max_requests" = 500;
|
||||
"pm.start_servers" = 2;
|
||||
"pm.min_spare_servers" = 2;
|
||||
"pm.max_spare_servers" = 4;
|
||||
"listen.owner" = listenUser;
|
||||
"listen.group" = listenGroup;
|
||||
|
||||
"catch_workers_output" = true;
|
||||
"php_admin_flag[log_errors]" = true;
|
||||
# "php_admin_value[error_log]" = "stderr";
|
||||
|
||||
# to accept *.html file
|
||||
"security.limit_extensions" = "";
|
||||
};
|
||||
|
||||
extensions = {
|
||||
inherit (pkgs.mediawiki-extensions) DeleteBatch UserMerge PluggableAuth SimpleSAMLphp;
|
||||
oysteikt marked this conversation as resolved
Outdated
oysteikt
commented
Should we make some kind of It's very nice to be able to remember how these options were set whenever we need to debug Should we make some kind of `services.mediawiki.debug: bool` option? Or even `debug.mediawiki: bool` (as well as the other services)?
It's very nice to be able to remember how these options were set whenever we need to debug
oysteikt
commented
Leaving these on for now, since they are considered test setups. Let's leave this as a future issue. Leaving these on for now, since they are considered test setups. Let's leave this as a future issue.
|
||||
};
|
||||
|
||||
extraConfig = ''
|
||||
$wgServer = "https://wiki2.pvv.ntnu.no";
|
||||
$wgLocaltimezone = "Europe/Oslo";
|
||||
|
||||
# Only allow login through SSO
|
||||
$wgEnableEmail = false;
|
||||
$wgEnableUserEmail = false;
|
||||
$wgEmailAuthentication = false;
|
||||
$wgGroupPermissions['*']['createaccount'] = false;
|
||||
$wgGroupPermissions['*']['autocreateaccount'] = true;
|
||||
$wgPluggableAuth_EnableAutoLogin = false;
|
||||
|
||||
# Misc. permissions
|
||||
$wgGroupPermissions['*']['edit'] = false;
|
||||
$wgGroupPermissions['*']['read'] = true;
|
||||
|
||||
# Misc. URL rules
|
||||
$wgUsePathInfo = true;
|
||||
$wgScriptExtension = ".php";
|
||||
$wgNamespacesWithSubpages[NS_MAIN] = true;
|
||||
|
||||
# Styling
|
||||
$wgLogos = array(
|
||||
"2x" => "/PNG/PVV-logo.png",
|
||||
"icon" => "/PNG/PVV-logo.svg",
|
||||
);
|
||||
$wgDefaultSkin = "vector-2022";
|
||||
# from https://github.com/wikimedia/mediawiki-skins-Vector/blob/master/skin.json
|
||||
$wgVectorDefaultSidebarVisibleForAnonymousUser = true;
|
||||
$wgVectorResponsive = true;
|
||||
|
||||
# Misc
|
||||
$wgEmergencyContact = "${cfg.passwordSender}";
|
||||
$wgShowIPinHeader = false;
|
||||
$wgUseTeX = false;
|
||||
$wgLocalInterwiki = $wgSitename;
|
||||
oysteikt marked this conversation as resolved
Outdated
oysteikt
commented
yeet yeet
|
||||
|
||||
# SimpleSAML
|
||||
$wgSimpleSAMLphp_InstallDir = "${simplesamlphp}/share/php/simplesamlphp/";
|
||||
$wgPluggableAuth_Config['Log in using my SAML'] = [
|
||||
'plugin' => 'SimpleSAMLphp',
|
||||
'data' => [
|
||||
'authSourceId' => 'default-sp',
|
||||
'usernameAttribute' => 'uid',
|
||||
'emailAttribute' => 'mail',
|
||||
'realNameAttribute' => 'cn',
|
||||
]
|
||||
];
|
||||
|
||||
# Fix https://github.com/NixOS/nixpkgs/issues/183097
|
||||
$wgDBserver = "${toString cfg.database.host}";
|
||||
'';
|
||||
};
|
||||
|
||||
# Cache directory for simplesamlphp
|
||||
# systemd.services.phpfpm-mediawiki.serviceConfig.CacheDirectory = "mediawiki/simplesamlphp";
|
||||
systemd.tmpfiles.settings."10-mediawiki"."/var/cache/mediawiki/simplesamlphp".d = {
|
||||
user = "mediawiki";
|
||||
group = "mediawiki";
|
||||
mode = "0770";
|
||||
};
|
||||
|
||||
users.groups.mediawiki.members = [ "nginx" ];
|
||||
|
||||
services.nginx.virtualHosts."wiki2.pvv.ntnu.no" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
root = "${config.services.mediawiki.finalPackage}/share/mediawiki";
|
||||
locations = {
|
||||
"/" = {
|
||||
index = "index.php";
|
||||
};
|
||||
|
||||
oysteikt marked this conversation as resolved
Outdated
eirikwit
commented
The issue this mentions is fixed. Is this still needed? The issue this mentions is fixed. Is this still needed?
oysteikt
commented
Indeed not needed, will fix Indeed not needed, will fix
|
||||
"~ /(.+\\.php)" = {
|
||||
extraConfig = ''
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_pass unix:${config.services.phpfpm.pools.mediawiki.socket};
|
||||
include ${pkgs.nginx}/conf/fastcgi_params;
|
||||
include ${pkgs.nginx}/conf/fastcgi.conf;
|
||||
'';
|
||||
};
|
||||
|
||||
# based on https://simplesamlphp.org/docs/stable/simplesamlphp-install.html#configuring-nginx
|
||||
"^~ /simplesaml/" = {
|
||||
alias = "${simplesamlphp}/share/php/simplesamlphp/public/";
|
||||
index = "index.php";
|
||||
|
||||
extraConfig = ''
|
||||
location ~ ^/simplesaml/(?<phpfile>.+?\.php)(?<pathinfo>/.*)?$ {
|
||||
include ${pkgs.nginx}/conf/fastcgi_params;
|
||||
fastcgi_pass unix:${config.services.phpfpm.pools.mediawiki.socket};
|
||||
fastcgi_param SCRIPT_FILENAME ${simplesamlphp}/share/php/simplesamlphp/public/$phpfile;
|
||||
|
||||
# Must be prepended with the baseurlpath
|
||||
fastcgi_param SCRIPT_NAME /simplesaml/$phpfile;
|
||||
|
||||
fastcgi_param PATH_INFO $pathinfo if_not_empty;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
"/images/".alias = "${config.services.mediawiki.uploadsDir}/";
|
||||
|
||||
"= /PNG/PVV-logo.svg".alias = ../../../../assets/logo_blue_regular.svg;
|
||||
"= /PNG/PVV-logo.png".alias = ../../../../assets/logo_blue_regular.png;
|
||||
"= /favicon.ico".alias = pkgs.runCommandLocal "mediawiki-favicon.ico" {
|
||||
buildInputs = with pkgs; [ imagemagick ];
|
||||
} ''
|
||||
convert \
|
||||
-resize x64 \
|
||||
-gravity center \
|
||||
-crop 64x64+0+0 \
|
||||
${../../../../assets/logo_blue_regular.png} \
|
||||
-flatten \
|
||||
-colors 256 \
|
||||
-background transparent \
|
||||
$out
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
$config = array(
|
||||
'admin' => array(
|
||||
'core:AdminPassword'
|
||||
),
|
||||
'default-sp' => array(
|
||||
'saml:SP',
|
||||
'entityID' => 'https://wiki2.pvv.ntnu.no/simplesaml/',
|
||||
'idp' => 'https://idp2.pvv.ntnu.no/',
|
||||
),
|
||||
);
|
|
@ -0,0 +1,83 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
cfg = config.environment.snakeoil-certs;
|
||||
in
|
||||
{
|
||||
options.environment.snakeoil-certs = lib.mkOption {
|
||||
default = { };
|
||||
description = "Self signed certs, which are rotated regularly";
|
||||
type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
|
||||
options = {
|
||||
owner = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "root";
|
||||
};
|
||||
group = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "root";
|
||||
};
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "0660";
|
||||
};
|
||||
daysValid = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "90";
|
||||
};
|
||||
extraOpenSSLArgs = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [ ];
|
||||
};
|
||||
certificate = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "${name}.crt";
|
||||
};
|
||||
certificateKey = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "${name}.key";
|
||||
};
|
||||
subject = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/C=NO/O=Programvareverkstedet/CN=*.pvv.ntnu.no/emailAddress=drift@pvv.ntnu.no";
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
config = {
|
||||
systemd.services."generate-snakeoil-certs" = {
|
||||
enable = true;
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = let
|
||||
openssl = lib.getExe pkgs.openssl;
|
||||
in lib.concatMapStringsSep "\n----------------\n" ({ name, value }: ''
|
||||
mkdir -p $(dirname "${value.certificate}") $(dirname "${value.certificateKey}")
|
||||
if ! ${openssl} x509 -checkend 86400 -noout -in ${value.certificate}
|
||||
then
|
||||
echo "Regenerating '${value.certificate}'"
|
||||
${openssl} req \
|
||||
-newkey rsa:4096 \
|
||||
-new -x509 \
|
||||
-days "${toString value.daysValid}" \
|
||||
-nodes \
|
||||
-subj "${value.subject}" \
|
||||
-out "${value.certificate}" \
|
||||
-keyout "${value.certificateKey}" \
|
||||
${lib.escapeShellArgs value.extraOpenSSLArgs}
|
||||
fi
|
||||
chown "${value.owner}:${value.group}" "${value.certificate}"
|
||||
chown "${value.owner}:${value.group}" "${value.certificateKey}"
|
||||
chmod "${value.mode}" "${value.certificate}"
|
||||
chmod "${value.mode}" "${value.certificateKey}"
|
||||
'') (lib.attrsToList cfg);
|
||||
};
|
||||
systemd.timers."generate-snakeoil-certs" = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "*-*-* 02:00:00";
|
||||
Persistent = true;
|
||||
Unit = "generate-snakeoil-certs.service";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{ pkgs, lib }:
|
||||
lib.makeScope pkgs.newScope (self: {
|
||||
DeleteBatch = self.callPackage ./delete-batch { };
|
||||
PluggableAuth = self.callPackage ./pluggable-auth { };
|
||||
SimpleSAMLphp = self.callPackage ./simple-saml-php { };
|
||||
UserMerge = self.callPackage ./user-merge { };
|
||||
})
|
|
@ -0,0 +1,7 @@
|
|||
{ fetchzip }:
|
||||
|
||||
fetchzip {
|
||||
name = "mediawiki-delete-batch";
|
||||
url = "https://extdist.wmflabs.org/dist/extensions/DeleteBatch-REL1_41-5774fdd.tar.gz";
|
||||
hash = "sha256-ROkn93lf0mNXBvij9X2pMhd8LXZ0azOz7ZRaqZvhh8k=";
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{ fetchzip }:
|
||||
|
||||
fetchzip {
|
||||
name = "mediawiki-pluggable-auth-source";
|
||||
url = "https://extdist.wmflabs.org/dist/extensions/PluggableAuth-REL1_41-d5b3ad8.tar.gz";
|
||||
hash = "sha256-OLlkKeSlfNgWXWwDdINrYRZpYuSGRwzZHgU8EYW6rYU=";
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{ fetchzip }:
|
||||
|
||||
fetchzip {
|
||||
name = "mediawiki-simple-saml-php-source";
|
||||
url = "https://extdist.wmflabs.org/dist/extensions/SimpleSAMLphp-REL1_41-9ae0678.tar.gz";
|
||||
hash = "sha256-AmCaG5QXMJvi3N6zFyWylwYDt8GvyIk/0GFpM1Y0vkY=";
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i python3 -p "python3.withPackages(ps: with ps; [ beautifulsoup4 requests ])"
|
||||
danio marked this conversation as resolved
Outdated
danio
commented
This is pretty funny, hekin web scraper This is pretty funny, hekin web scraper
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
from pprint import pprint
|
||||
|
||||
import bs4
|
||||
import requests
|
||||
|
||||
BASE_URL = "https://extdist.wmflabs.org/dist/extensions"
|
||||
|
||||
def fetch_plugin_list(skip_master=True) -> dict[str, list[str]]:
|
||||
content = requests.get(BASE_URL).text
|
||||
soup = bs4.BeautifulSoup(content, features="html.parser")
|
||||
result = defaultdict(list)
|
||||
for a in soup.find_all('a'):
|
||||
if skip_master and 'master' in a.text:
|
||||
continue
|
||||
split = a.text.split('-')
|
||||
result[split[0]].append(a.text)
|
||||
return result
|
||||
|
||||
def update(package_file: Path, plugin_list: dict[str, list[str]]) -> None:
|
||||
assert package_file.is_file()
|
||||
with open(package_file) as file:
|
||||
content = file.read()
|
||||
|
||||
tarball = re.search(f'url = "{BASE_URL}/(.+\.tar\.gz)";', content).group(1)
|
||||
split = tarball.split('-')
|
||||
updated_tarball = plugin_list[split[0]][-1]
|
||||
|
||||
_hash = re.search(f'hash = "(.+?)";', content).group(1)
|
||||
|
||||
out, err = subprocess.Popen(
|
||||
["nix-prefetch-url", "--unpack", "--type", "sha256", f"{BASE_URL}/{updated_tarball}"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
).communicate()
|
||||
out, err = subprocess.Popen(
|
||||
["nix", "hash", "to-sri", "--type", "sha256", out.decode().strip()],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
).communicate()
|
||||
|
||||
updated_hash = out.decode().strip()
|
||||
|
||||
if tarball == updated_tarball and _hash == updated_hash:
|
||||
return
|
||||
|
||||
print(f"Updating: {tarball} ({_hash[7:14]}) -> {updated_tarball} ({updated_hash[7:14]})")
|
||||
|
||||
updated_text = re.sub(f'url = "{BASE_URL}/.+?\.tar\.gz";', f'url = "{BASE_URL}/{updated_tarball}";', content)
|
||||
updated_text = re.sub('hash = ".+";', f'hash = "{updated_hash}";', updated_text)
|
||||
with open(package_file, 'w') as file:
|
||||
file.write(updated_text)
|
||||
|
||||
if __name__ == "__main__":
|
||||
plugin_list = fetch_plugin_list()
|
||||
|
||||
for direntry in os.scandir(Path(__file__).parent):
|
||||
if direntry.is_dir():
|
||||
update(Path(direntry) / "default.nix", plugin_list)
|
|
@ -0,0 +1,7 @@
|
|||
{ fetchzip }:
|
||||
|
||||
fetchzip {
|
||||
name = "mediawiki-user-merge-source";
|
||||
url = "https://extdist.wmflabs.org/dist/extensions/UserMerge-REL1_41-a53af3b.tar.gz";
|
||||
hash = "sha256-TxUkEqMW79thYl1la2r+w9laRnd3uSYYg1xDB+1he1g=";
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{ lib
|
||||
, php
|
||||
, writeText
|
||||
, fetchFromGitHub
|
||||
, extra_files ? { }
|
||||
|
||||
}:
|
||||
|
||||
php.buildComposerProject rec {
|
||||
pname = "simplesamlphp";
|
||||
version = "2.2.1";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "simplesamlphp";
|
||||
repo = "simplesamlphp";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-jo7xma60M4VZgeDgyFumvJp1Sm+RP4XaugDkttQVB+k=";
|
||||
};
|
||||
|
||||
composerStrictValidation = false;
|
||||
|
||||
vendorHash = "sha256-n6lJ/Fb6xI124PkKJMbJBDiuISlukWQcHl043uHoBb4=";
|
||||
|
||||
# TODO: metadata could be fetched automagically with these:
|
||||
# - https://simplesamlphp.org/docs/contrib_modules/metarefresh/simplesamlphp-automated_metadata.html
|
||||
# - https://idp.pvv.ntnu.no/simplesaml/saml2/idp/metadata.php
|
||||
postPatch = lib.pipe extra_files [
|
||||
(lib.mapAttrsToList (target_path: source_path: ''
|
||||
mkdir -p $(dirname "${target_path}")
|
||||
cp -r "${source_path}" "${target_path}"
|
||||
''))
|
||||
(lib.concatStringsSep "\n")
|
||||
];
|
||||
|
||||
postInstall = ''
|
||||
ln -sr $out/share/php/simplesamlphp/vendor/simplesamlphp/simplesamlphp-assets-base $out/share/php/simplesamlphp/public/assets/base
|
||||
'';
|
||||
}
|
|
@ -10,9 +10,18 @@ gitea:
|
|||
epsilon: ENC[AES256_GCM,data:JMnZVBdiy+5oPyXgDpfYvy7qLzIEfHy09fQSBDpNG4zDXTil2pSKBKxk09h5xg==,iv:/8oXKJW6+sMBjDt51MqVAWjQPM5nk02Lv5QqbZsZ5ms=,tag:+Rx7ursfVWc0EcExCLgLhQ==,type:str]
|
||||
mediawiki:
|
||||
password: ENC[AES256_GCM,data:HsBuA1E7187roGnKuFPfPDYxA16GFjAUucgUtrdUFmcOzmTNiFH+NWY2ZQ==,iv:vDYUmmZftcrkDtJxNYKAJSx9j+AQcmQarC62QRHR4IM=,tag:3TKjNrGRivFWoK3djC748g==,type:str]
|
||||
database: ENC[AES256_GCM,data:EvVK3Mo6cZiIZS+gTxixU4r9SXN41VqwaWOtortZRNH+WPJ4xcYvzYMJNg==,iv:JtFTRLn3fzKIfgAPRqRgQjct7EdkEHtiyQKPy8/sZ2Q=,tag:nqzseG6BC0X5UNI/3kZZ3A==,type:str]
|
||||
postgres_password: ENC[AES256_GCM,data:XIOmrOVXWvMMcPJtmovhdyZvLlhmrsrwjuMMkdEY1NIXWjevj5XEkp6Cpw==,iv:KMPTRzu3H/ewfEhc/O0q3o230QNkABfPYF/D1SYL2R8=,tag:sFZiFPHWxwzD9HndPmH3pQ==,type:str]
|
||||
simplesamlphp:
|
||||
postgres_password: ENC[AES256_GCM,data:FzykBVtJbA+Bey1GE5VqnSuv2GeobH1j,iv:wayQH3+y0FYFkr3JjmulI53SADk0Ikur/2mUS5kFrTk=,tag:d+nQ/se2bDA5aaQfBicnPQ==,type:str]
|
||||
cookie_salt: ENC[AES256_GCM,data:BioRPAvL4F9ORBJDFdqHot81RhVpAOf32v1ah3pvOLq8E88bxGyKFQZxAwpIL3UkWQIsWMnEerm5MEMYL1C2OQ==,iv:yMVqiPTQ8hO1IVAax6PIkD0V9YTOEunwDTtnGcmy6Kc=,tag:Z4+bZF4olLlkx7YpXeQiUw==,type:str]
|
||||
admin_password: ENC[AES256_GCM,data:4eUXvcO7NLOWke9XShfKzj+x3FvqPONa,iv:3iZ+BTBTZ7yMJ0HT14cEMebKZattWUcYEevRsl/6WOk=,tag:CU0iDhPP2ndztdX5U5A4cw==,type:str]
|
||||
keycloak:
|
||||
database: ENC[AES256_GCM,data:76+AZnNR5EiturTP7BdOCKE90bFFkfGlRtviSP5NHxPbb3RfFPJEMlwtzA==,iv:nS7VTossHdlrHjPeethhX+Ysp9ukrb5JD7kjG28OFpY=,tag:OMpiEv9nQA7v6lWJfNxEEw==,type:str]
|
||||
idp:
|
||||
cookie_salt: ENC[AES256_GCM,data:cyV6HDCPHKQIa8T1+rFBFh6EuHtG5B508lg6uFYENK7qVpYuiTUIokdVQhY8SRLs2mECx/ampgnUHxCRB/Cc/A==,iv:QRrRUhzRQrLkmg38rrYtCEfF8U4/7ZHZUDSEq++BlbI=,tag:fLqFSLd+CKqJvmCh1fx8vg==,type:str]
|
||||
admin_password: ENC[AES256_GCM,data:Vf33Oenk6x6BIij1uW8RQDjTPcKhUVYA,iv:RNeyCNpTAYdBPrZwE3Y6CCjoAML/3XUvjfJCrr06IEU=,tag:zVOrx1oXnEyr/VwFCFaCDQ==,type:str]
|
||||
postgres_password: ENC[AES256_GCM,data:HGwKLbn/umPLPgH+qpXtugvXzOcXdlhK,iv:ypTW0VLSape8K5aCYu3BdjG/oMmqvfDSLw9uGLthb0Q=,tag:qlDMGz59qzMwEwBYxsC0XQ==,type:str]
|
||||
privatekey: ENC[AES256_GCM,data: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,iv:8cb1FcIm0oGkcrfLNqXamx4aDA3owBZoHur8+uFsdmA=,tag:oFPP/Yene6QrxFDKlmoVcA==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
|
@ -46,8 +55,8 @@ sops:
|
|||
akVjeTNTeGorZjJQOVlMeCtPRUVYL3MK+VMvGxrbzGz4Q3sdaDDWjal+OiK+JYKX
|
||||
GHiMXVHQJZu/RrlxMjHKN6V3iaqxZpuvLAEJ2Lzy5EOHPtuiiRyeHQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2023-09-17T02:02:24Z"
|
||||
mac: ENC[AES256_GCM,data:Lkvj9UOdE/WZtFReMs6n8ucFuJNPb76ZhPHFpYAEqYEe8d9FdMPMzq05DBAJe9IqpFS0jc9SWxJUPHfGgoMR8nPciZuR/mpJ+4s/cRkPbApwBPcLlvatE/qkbcxzoLlb1vN0gth5G/U7UEfk5Pp9gIz6Yo4sEIS3Za42tId1MpI=,iv:s3VELgU/RJ98/lbQV3vPtOLXtwFzB3KlY7bMKbAzp/g=,tag:D8s0XyGnd8UhbCseB/TyFg==,type:str]
|
||||
lastmodified: "2024-03-30T21:22:02Z"
|
||||
mac: ENC[AES256_GCM,data:o3buZqOYZXiNyJ7zDtaBDFwbtP5i0QNvHxVVxtVWdLdRASVmau/ZXdQ8MNsExe6gUF4dS6Sv7QYXRfUO7ccmUDP4zABlIOcxjwsRTs5lE45S6pVIB98OIAODHdyl6LVsgxEkhdPmSoYRjLIWO56KlKArxPQGiprCI7AIBe6DYik=,iv:sAEeBMuJ8JwI3STZuy4miZhXA9Lopbof+3aaprtWVJ4=,tag:LBIRH7KwZ0CuuXuioVL10Q==,type:str]
|
||||
pgp:
|
||||
- created_at: "2023-05-21T00:28:40Z"
|
||||
enc: |
|
||||
|
@ -70,4 +79,4 @@ sops:
|
|||
-----END PGP MESSAGE-----
|
||||
fp: F7D37890228A907440E1FD4846B9228E814A2AAC
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.7.3
|
||||
version: 3.8.1
|
||||
|
|
Many potentially spooky things in a file like this, but I haven't found any vulnerabilities through manual reviews, snyk or psalm, so I think it's good :)