feat(compute01): Deploy satosa on saml-idp.dgnum.eu

This commit is contained in:
Tom Hubrecht 2023-09-27 22:33:18 +02:00
parent 26b2fa656f
commit be60bb5cbe
7 changed files with 353 additions and 4 deletions

View file

@ -18,6 +18,7 @@ let
"mastodon"
"nextcloud"
"outline"
"satosa"
];
in

View file

@ -1,9 +1,154 @@
{ lib, pkgs, ... }:
{ config, lib, dgn-lib, ... }:
let
package = import ./package { inherit lib pkgs; };
inherit (dgn-lib) setDefault;
host = "saml.dgnum.eu";
host = "saml-idp.dgnum.eu";
in {
imports = [ ./module.nix ];
services.satosa = {
enable = true;
inherit host;
port = 8090;
envFile = config.age.secrets."satosa-env_file".path;
frontendModules = {
saml2IDP = {
module = "satosa.frontends.saml2.SAMLFrontend";
name = "Saml2IDP";
config = {
endpoints.single_sign_on_service = {
"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" = "sso/post";
"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" =
"sso/redirect";
};
entityid_endpoint = true;
enable_metadata_reload = false;
idp_config = {
organization = {
display_name = "Délégation Générale Numérique";
name = "DGNum";
url = "https://dgnum.eu";
};
contact_person = [{
contact_type = "technical";
email_address = "mailto:tom.hubrecht@dgnum.eu";
given_name = "Tom Hubrecht";
}];
key_file = "/var/lib/satosa/ssl/key.pem";
cert_file = "/var/lib/satosa/ssl/cert.pem";
metadata.local = [ ];
entityid = "https://${host}/Saml2IDP";
accepted_time_diff = 60;
service = {
idp = {
endpoints.single_sign_on_service = [ ];
name = "DGNum proxy IdP";
ui_info = {
display_name = [{
lang = "fr";
text = "Service de connexion DGNum";
}];
};
name_id_format = [
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
];
policy = {
default = {
attribute_restrictions = null;
fail_on_missing_requested = false;
lifetime = { minutes = 15; };
name_form =
"urn:oasis:names:tc:SAML:2.0:attrname-format:uri";
encrypt_assertion = false;
encrypted_advice_attributes = false;
};
};
};
};
};
};
};
};
backendModules = {
# module: satosa.backends.openid_connect.OpenIDConnectBackend
# name: openid_connect
# config:
# provider_metadata:
# issuer: https://op.example.com
# client:
# verify_ssl: yes
# auth_req_params:
# response_type: code
# scope: [openid, profile, email, address, phone]
# client_metadata:
# application_name: SATOSA
# application_type: web
# contacts: [ops@example.com]
# redirect_uris: [<base_url>/<name>]
# subject_type: public
# entity_info:
# contact_person:
# - contact_type: "technical"
# email_address: ["technical_test@example.com", "support_test@example.com"]
# given_name: "Test"
# sur_name: "OP"
# - contact_type: "support"
# email_address: ["support_test@example.com"]
# given_name: "Support_test"
# organization:
# display_name:
# - ["OP Identities", "en"]
# name:
# - ["En test-OP", "se"]
# - ["A test OP", "en"]
# url:
# - ["http://www.example.com", "en"]
# - ["http://www.example.se", "se"]
# ui_info:
# description:
# - ["This is a test OP", "en"]
# display_name:
# - ["OP - TEST", "en"]
kanidm = {
module = "satosa.backends.openid_connect.OpenIDConnectBackend";
name = "kanidm";
config = {
provider_metadata.issuer =
"https://sso.dgnum.eu/oauth2/openid/satosa_dgn/";
client = {
auth_req_params = {
response_type = "code";
scope = [ "openid" "profile" "email" ];
};
client_metadata = {
client_id = "satosa_dgn";
client_secret = "ENV! SATOSA_FRONTEND_KANIDM_CLIENT_SECRET";
redirect_uris = [ "https://${host}/kanidm" ];
};
};
};
};
};
};
services.nginx.virtualHosts.${host} = {
enableACME = true;
forceSSL = true;
};
dgn-secrets.options = [
(setDefault { owner = "satosa"; }
(builtins.filter (lib.hasPrefix "satosa-") config.dgn-secrets.names))
];
}

View file

@ -0,0 +1,198 @@
{ config, lib, pkgs, ... }:
let
inherit (lib) mkDefault mkEnableOption mkIf mkOption types;
yamlFormat = pkgs.formats.yaml { };
configFile = yamlFormat.generate "proxy_conf.yaml" cfg.proxyConf;
cfg = config.services.satosa;
mkYamlFiles = files:
builtins.attrValues
(builtins.mapAttrs (name: yamlFormat.generate "${name}.yaml") files);
pyEnv = cfg.package.python.withPackages (ps: [ cfg.package ps.gunicorn ]);
in {
options.services.satosa = {
enable = mkEnableOption "SATOSA, a SAML and OIDC proxy.";
package = mkOption {
type = types.package;
default = let pkgs = import <nixpkgs> { };
in import ./package {
inherit pkgs;
inherit (pkgs) lib;
};
};
port = mkOption {
type = types.port;
default = 8080;
};
host = mkOption { type = types.str; };
workers = mkOption {
type = types.int;
default = 1;
};
configureNginx = mkOption {
type = types.bool;
default = true;
};
proxyConf = mkOption {
inherit (yamlFormat) type;
default = { };
};
envFile = mkOption {
type = with types; nullOr path;
default = null;
};
internalAttributes = mkOption {
inherit (yamlFormat) type;
default = { };
};
frontendModules = mkOption {
type = types.attrsOf yamlFormat.type;
default = { };
};
backendModules = mkOption {
type = types.attrsOf yamlFormat.type;
default = { };
};
microServices = mkOption {
type = types.attrsOf yamlFormat.type;
default = { };
};
};
config = mkIf cfg.enable {
services.satosa.proxyConf = builtins.mapAttrs (_: mkDefault) {
BASE = "https://${cfg.host}";
COOKIE_STATE_NAME = "satosa_state";
COOKIE_SECURE = true;
COOKIE_HTTPONLY = true;
COOKIE_SAMESITE = "None";
COOKIE_MAX_AGE = "1200";
CONTEXT_STATE_DELETE = true;
INTERNAL_ATTRIBUTES = yamlFormat.generate "internal_attributes.yaml" {
attributes = cfg.internalAttributes;
};
BACKEND_MODULES = mkYamlFiles cfg.backendModules;
FRONTEND_MODULES = mkYamlFiles cfg.frontendModules;
MICRO_SERVICES = mkYamlFiles cfg.microServices;
LOGGING = {
version = 1;
formatters.simple.format =
"[%(asctime)s] [%(levelname)s] [%(name)s.%(funcName)s] %(message)s";
handlers.stdout = {
class = "logging.StreamHandler";
stream = "ext://sys.stdout";
level = "DEBUG";
formatter = "simple";
};
loggers = {
satosa.level = "DEBUG";
saml2.level = "DEBUG";
oidcendpoint.level = "DEBUG";
pyop.level = "DEBUG";
oic.level = "DEBUG";
root = {
level = "DEBUG";
handlers = [ "stdout" ];
};
};
};
};
systemd.services = {
satosa-metadata = {
script = ''
umask 077
# Generate a secret key/certificate if none are present
mkdir -p ssl
if [ ! -f "ssl/.created" ]; then
${pkgs.openssl}/bin/openssl req -x509 \
-newkey rsa:2048 \
-keyout ssl/key.pem \
-out ssl/cert.pem \
-sha256 \
-days 3650 \
-nodes \
-subj "/C=FR/ST=Île de France/L=Paris/O=DGNum/OU=./CN=saml-idp.dgnum.eu" \
&& touch ssl/.created
fi
mkdir -p metadata
${cfg.package}/bin/satosa-saml-metadata \
--dir metadata \
--sign ${configFile} ssl/key.pem ssl/cert.pem
'';
serviceConfig = {
Type = "oneshot";
User = "satosa";
Group = "satosa";
DynamicUser = true;
StateDirectory = "satosa";
WorkingDirectory = "/var/lib/satosa";
EnvironmentFile = lib.optional (cfg.envFile != null) cfg.envFile;
};
};
satosa = {
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
wants = [ "satosa-metadata.service" ];
serviceConfig = {
User = "satosa";
Group = "satosa";
DynamicUser = true;
Type = "notify";
RuntimeDirectory = "satosa";
StateDirectory = "satosa";
WorkingDirectory = cfg.package;
ExecStart = ''
${pyEnv}/bin/gunicorn \
-w ${builtins.toString cfg.workers} \
-b 127.0.0.1:${builtins.toString cfg.port} \
--pythonpath ${pyEnv}/${pkgs.python3.sitePackages} \
satosa.wsgi:app
'';
ExecReload = "${pkgs.util-linux}/bin/kill -s HUP $MAINPID";
KillMode = "mixed";
TimeoutStopSec = "5";
EnvironmentFile = lib.optional (cfg.envFile != null) cfg.envFile;
};
environment = { SATOSA_CONFIG = configFile; };
};
};
services.nginx = mkIf cfg.configureNginx {
enable = true;
virtualHosts.${cfg.host} = {
locations."/".proxyPass =
"http://127.0.0.1:${builtins.toString cfg.port}";
};
};
users.users.satosa = {
isSystemUser = true;
group = "satosa";
home = "/var/lib/satosa";
};
users.groups.satosa = { };
};
}

View file

@ -4,6 +4,8 @@ let
callPackage = lib.callPackageWith (pkgs // self);
self = {
satosa = callPackage ./satosa.nix { };
cookies-samesite-compat = callPackage ./cookies-samesite-compat.nix { };
pyop = callPackage ./pyop.nix { };
oic = callPackage ./oic.nix { };
@ -12,4 +14,4 @@ let
pydantic-core = callPackage ./pydantic-core.nix { };
};
in callPackage ./satosa.nix { }
in self.satosa

View file

@ -47,6 +47,8 @@ python3.pkgs.buildPythonPackage rec {
];
};
passthru.python = python3;
pythonImportsCheck = [ "satosa" ];
meta = with lib; {

Binary file not shown.

View file

@ -11,4 +11,5 @@ lib.setDefault { inherit publicKeys; } [
"outline-oidc_client_secret_file"
"outline-smtp_password_file"
"outline-storage_secret_key_file"
"satosa-env_file"
]