forked from DGNum/infrastructure
feat(compute01): Deploy satosa on saml-idp.dgnum.eu
This commit is contained in:
parent
26b2fa656f
commit
be60bb5cbe
7 changed files with 353 additions and 4 deletions
|
@ -18,6 +18,7 @@ let
|
||||||
"mastodon"
|
"mastodon"
|
||||||
"nextcloud"
|
"nextcloud"
|
||||||
"outline"
|
"outline"
|
||||||
|
"satosa"
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,154 @@
|
||||||
{ lib, pkgs, ... }:
|
{ config, lib, dgn-lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
package = import ./package { inherit lib pkgs; };
|
inherit (dgn-lib) setDefault;
|
||||||
|
|
||||||
host = "saml.dgnum.eu";
|
host = "saml-idp.dgnum.eu";
|
||||||
in {
|
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))
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
198
machines/compute01/satosa/module.nix
Normal file
198
machines/compute01/satosa/module.nix
Normal 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 = { };
|
||||||
|
};
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ let
|
||||||
callPackage = lib.callPackageWith (pkgs // self);
|
callPackage = lib.callPackageWith (pkgs // self);
|
||||||
|
|
||||||
self = {
|
self = {
|
||||||
|
satosa = callPackage ./satosa.nix { };
|
||||||
|
|
||||||
cookies-samesite-compat = callPackage ./cookies-samesite-compat.nix { };
|
cookies-samesite-compat = callPackage ./cookies-samesite-compat.nix { };
|
||||||
pyop = callPackage ./pyop.nix { };
|
pyop = callPackage ./pyop.nix { };
|
||||||
oic = callPackage ./oic.nix { };
|
oic = callPackage ./oic.nix { };
|
||||||
|
@ -12,4 +14,4 @@ let
|
||||||
pydantic-core = callPackage ./pydantic-core.nix { };
|
pydantic-core = callPackage ./pydantic-core.nix { };
|
||||||
};
|
};
|
||||||
|
|
||||||
in callPackage ./satosa.nix { }
|
in self.satosa
|
||||||
|
|
|
@ -47,6 +47,8 @@ python3.pkgs.buildPythonPackage rec {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
passthru.python = python3;
|
||||||
|
|
||||||
pythonImportsCheck = [ "satosa" ];
|
pythonImportsCheck = [ "satosa" ];
|
||||||
|
|
||||||
meta = with lib; {
|
meta = with lib; {
|
||||||
|
|
BIN
machines/compute01/secrets/satosa-env_file
Normal file
BIN
machines/compute01/secrets/satosa-env_file
Normal file
Binary file not shown.
|
@ -11,4 +11,5 @@ lib.setDefault { inherit publicKeys; } [
|
||||||
"outline-oidc_client_secret_file"
|
"outline-oidc_client_secret_file"
|
||||||
"outline-smtp_password_file"
|
"outline-smtp_password_file"
|
||||||
"outline-storage_secret_key_file"
|
"outline-storage_secret_key_file"
|
||||||
|
"satosa-env_file"
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue