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"
|
||||
"nextcloud"
|
||||
"outline"
|
||||
"satosa"
|
||||
];
|
||||
in
|
||||
|
||||
|
|
|
@ -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))
|
||||
];
|
||||
}
|
||||
|
|
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);
|
||||
|
||||
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
|
||||
|
|
|
@ -47,6 +47,8 @@ python3.pkgs.buildPythonPackage rec {
|
|||
];
|
||||
};
|
||||
|
||||
passthru.python = python3;
|
||||
|
||||
pythonImportsCheck = [ "satosa" ];
|
||||
|
||||
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-smtp_password_file"
|
||||
"outline-storage_secret_key_file"
|
||||
"satosa-env_file"
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue