From 2e0cfb18457275d6880e0829c08b92bdbba927f5 Mon Sep 17 00:00:00 2001 From: Daniel Berteaud Date: Wed, 3 Jan 2024 21:04:11 +0100 Subject: [PATCH] Lemonldap::NG first job template --- README.md | 2 +- bundles.yml | 4 + .../config/service-defaults/lemonldap-ng.hcl | 3 + .../service-intentions/lemonldap-ng.hcl | 30 ++++ images/lemonldap-ng/Dockerfile | 54 +++++++ .../lemonldap-ng/root/entrypoint.d/10-conf.sh | 10 ++ images/lemonldap-ng/root/etc/caretakerd.yaml | 9 ++ .../root/etc/nginx/lemonldap-ng.conf.template | 106 ++++++++++++++ .../root/etc/yum.repos.d/lemonldap-ng.repo | 13 ++ .../root/usr/local/bin/lemonldap-ng | 24 ++++ .../ressources/lemonldap-ng.postgres.sql | 68 +++++++++ init/vault-database | 8 ++ lemonldap-ng.nomad.hcl | 130 +++++++++++++++++ prep.d/10-mv-conf.sh | 1 + templates/caretakerd.yaml.tpl | 19 +++ templates/cron.tpl | 5 + templates/init.ini.tpl | 6 + templates/init.sh.tpl | 13 ++ templates/lemonldap-ng.ini.tpl | 54 +++++++ templates/lmConf-1.json.tpl | 132 ++++++++++++++++++ variables.yml | 124 ++++++++++++++++ vault/policies/lemonldap-ng.hcl | 7 + 22 files changed, 821 insertions(+), 1 deletion(-) create mode 100644 bundles.yml create mode 100644 consul/config/service-defaults/lemonldap-ng.hcl create mode 100644 consul/config/service-intentions/lemonldap-ng.hcl create mode 100644 images/lemonldap-ng/Dockerfile create mode 100755 images/lemonldap-ng/root/entrypoint.d/10-conf.sh create mode 100644 images/lemonldap-ng/root/etc/caretakerd.yaml create mode 100644 images/lemonldap-ng/root/etc/nginx/lemonldap-ng.conf.template create mode 100644 images/lemonldap-ng/root/etc/yum.repos.d/lemonldap-ng.repo create mode 100755 images/lemonldap-ng/root/usr/local/bin/lemonldap-ng create mode 100644 images/lemonldap-ng/root/usr/share/lemonldap-ng/ressources/lemonldap-ng.postgres.sql create mode 100755 init/vault-database create mode 100644 lemonldap-ng.nomad.hcl create mode 100755 prep.d/10-mv-conf.sh create mode 100644 templates/caretakerd.yaml.tpl create mode 100644 templates/cron.tpl create mode 100644 templates/init.ini.tpl create mode 100644 templates/init.sh.tpl create mode 100644 templates/lemonldap-ng.ini.tpl create mode 100644 templates/lmConf-1.json.tpl create mode 100644 variables.yml create mode 100644 vault/policies/lemonldap-ng.hcl diff --git a/README.md b/README.md index e9c36c4..b27425f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # lemonldap-ng -Lemonldap::NG webSSO \ No newline at end of file +Lemonldap::NG webSSO diff --git a/bundles.yml b/bundles.yml new file mode 100644 index 0000000..5b9120e --- /dev/null +++ b/bundles.yml @@ -0,0 +1,4 @@ +--- + +dependencies: + - url: ../common.git diff --git a/consul/config/service-defaults/lemonldap-ng.hcl b/consul/config/service-defaults/lemonldap-ng.hcl new file mode 100644 index 0000000..f3ad32f --- /dev/null +++ b/consul/config/service-defaults/lemonldap-ng.hcl @@ -0,0 +1,3 @@ +Kind = "service-defaults" +Name = "[[ .instance ]][[ .consul.suffix ]]" +Protocol = "http" diff --git a/consul/config/service-intentions/lemonldap-ng.hcl b/consul/config/service-intentions/lemonldap-ng.hcl new file mode 100644 index 0000000..e055321 --- /dev/null +++ b/consul/config/service-intentions/lemonldap-ng.hcl @@ -0,0 +1,30 @@ +Kind = "service-intentions" +Name = "[[ .instance ]][[ .consul.suffix ]]" +Sources = [ + { + Name = "[[ (merge .llng.portal .).traefik.instance ]]" + Permissions = [ +[[- if not .llng.api.traefik.enabled ]] + # Prevent access to the API as it's disabled + { + Action = "deny" + HTTP { + PathRegex = "^/index\\.psgi/(config|sessions)" + } + }, +[[- end ]] + { + Action = "deny" + HTTP { + PathRegex = "^/reload" + } + }, + { + Action = "allow" + HTTP { + Methods = ["GET", "HEAD", "POST", "OPTIONS", "PUT", "DELETE", "PATCH"] + } + } + ] + } +] diff --git a/images/lemonldap-ng/Dockerfile b/images/lemonldap-ng/Dockerfile new file mode 100644 index 0000000..0ddd9b3 --- /dev/null +++ b/images/lemonldap-ng/Dockerfile @@ -0,0 +1,54 @@ +FROM [[ .docker.repo ]][[ .docker.base_images.alma9.image ]] +MAINTAINER [[ .docker.maintainer ]] + +ARG LLNG_VERSION=2.18.1 \ + CARETAKERD_VERSION=1.0.7 + +ENV PATH=/usr/libexec/lemonldap-ng/bin:${PATH} \ + LLNG_LISTEN=unix:/tmp/llng.sock \ + LLNG_WORKERS=6 \ + LLNG_NGINX_LISTEN=0.0.0.0:8080 \ + LLNG_NGINX_CONF=/tmp/nginx.conf \ + LLNG_SOCKET_PROTO=uwsgi \ + LLNG_MANAGER_VHOST=manager.example.org \ + LLNG_PORTAL_VHOST=auth.example.org \ + EDITOR=vi + +COPY root/etc/yum.repos.d/ /etc/yum.repos.d/ + +RUN set -eux &&\ + dnf -y install \ + glibc-langpack-en \ + glibc-langpack-fr \ + postgresql \ + lemonldap-ng-handler-${LLNG_VERSION} \ + lemonldap-ng-portal-${LLNG_VERSION} \ + lemonldap-ng-manager-${LLNG_VERSION} \ + lemonldap-ng-uwsgi-app-${LLNG_VERSION} \ + uwsgi \ + uwsgi-plugin-psgi \ + lasso \ + lasso-perl \ + perl-DBD-Pg \ + perl-Cache-Cache \ + perl-Apache-Session-Browseable \ + perl-Authen-Captcha \ + perl-Authen-WebAuthn \ + perl-Cookie-Baker \ + perl-WWW-Form-UrlEncoded \ + perl-Class-XSAccessor \ + perl-Email-Sender \ + nginx \ + &&\ + # dnf is an alias using --nodocs, so install doc manually \ + microdnf -y install lemonldap-ng-doc-${LLNG_VERSION} &&\ + curl -sSL https://github.com/echocat/caretakerd/releases/download/v${CARETAKERD_VERSION}/caretakerd-linux-amd64.tar.gz | \ + tar xvz --exclude caretakerd.html -C /usr/local/bin &&\ + chmod +x /usr/local/bin/caretakerd &&\ + dnf -y clean all &&\ + rm -rf /var/cache/yum/* /var/log/yum/* /var/lib/dnf/history* + +COPY root/ / + +USER apache +CMD ["caretakerd", "run"] diff --git a/images/lemonldap-ng/root/entrypoint.d/10-conf.sh b/images/lemonldap-ng/root/entrypoint.d/10-conf.sh new file mode 100755 index 0000000..0690a28 --- /dev/null +++ b/images/lemonldap-ng/root/entrypoint.d/10-conf.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +set -euo pipefail + +[ -e "/tmp/nginx.conf" ] || touch /tmp/nginx.conf || true +if [ -w "/tmp/nginx.conf" ]; then + echo Rendering /etc/nginx/lemonldap-ng.conf.template in /tmp/nginx.conf + envsubst < /etc/nginx/lemonldap-ng.conf.template > /tmp/nginx.conf +fi + diff --git a/images/lemonldap-ng/root/etc/caretakerd.yaml b/images/lemonldap-ng/root/etc/caretakerd.yaml new file mode 100644 index 0000000..6354113 --- /dev/null +++ b/images/lemonldap-ng/root/etc/caretakerd.yaml @@ -0,0 +1,9 @@ +services: + lemonldap: + type: master + command: ["lemonldap-ng"] + nginx: + command: ["nginx", "-c", "${LLNG_NGINX_CONF}"] + local_cache: + command: ["/usr/libexec/lemonldap-ng/bin/purgeLocalCache"] + cronExpression: '1 * * * *' diff --git a/images/lemonldap-ng/root/etc/nginx/lemonldap-ng.conf.template b/images/lemonldap-ng/root/etc/nginx/lemonldap-ng.conf.template new file mode 100644 index 0000000..d54336f --- /dev/null +++ b/images/lemonldap-ng/root/etc/nginx/lemonldap-ng.conf.template @@ -0,0 +1,106 @@ +worker_processes auto; +daemon off; + +error_log /dev/stderr notice; +pid /tmp/nginx.pid; + +events { + worker_connections 1024; +} + +http { + proxy_temp_path /tmp/proxy_temp; + client_body_temp_path /tmp/client_temp; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /dev/stdout main; + + sendfile on; + tcp_nopush on; + keepalive_timeout 65; + + server { + listen ${LLNG_NGINX_LISTEN}; + server_name ${LLNG_MANAGER_VHOST}; + root /usr/share/lemonldap-ng/manager/htdocs/; + + set_real_ip_from 127.0.0.1; + real_ip_header X-Forwarded-For; + proxy_request_buffering off; + + if ($uri !~ ^/(.*\.psgi|static|doc|lib|javascript|favicon)) { + rewrite ^/(.*)$ /manager.psgi/$1 break; + } + + location ~ ^(?/.*\.psgi)(?:$|/) { + include /etc/nginx/uwsgi_params; + uwsgi_pass ${LLNG_LISTEN}; + uwsgi_param LLTYPE psgi; + uwsgi_param SCRIPT_FILENAME $document_root$sc; + uwsgi_param SCRIPT_NAME $sc; + } + location / { + index manager.psgi; + try_files $uri $uri/ =404; + } + location /doc/ { + alias /usr/share/lemonldap-ng/doc/; + index index.html start.html; + } + location /lib/ { + alias /usr/share/lemonldap-ng/doc/pages/documentation/current/lib/; + } + location /static/ { + alias /usr/share/lemonldap-ng/manager/htdocs/static/; + } + } + + server { + listen ${LLNG_NGINX_LISTEN} default_server; + server_name ${LLNG_PORTAL_VHOST}; + root /usr/share/lemonldap-ng/portal/htdocs/; + + set_real_ip_from 127.0.0.1; + real_ip_header X-Forwarded-For; + proxy_request_buffering off; + + if ($uri !~ ^/((static|javascript|favicon).*|.*\.psgi)) { + rewrite ^/(.*)$ /index.psgi/$1 break; + } + + location = /reload { + allow 127.0.0.1; + deny all; + uwsgi_pass ${LLNG_LISTEN}; + uwsgi_param LLTYPE reload; + uwsgi_param SCRIPT_FILENAME $document_root$sc; + uwsgi_param SCRIPT_NAME $sc; + } + + location ~ ^(?/.*\.psgi)(?:$|/) { + include /etc/nginx/uwsgi_params; + uwsgi_pass ${LLNG_LISTEN}; + uwsgi_param LLTYPE psgi; + uwsgi_param SCRIPT_FILENAME $document_root$sc; + uwsgi_param SCRIPT_NAME $sc; + } + + index index.psgi; + location / { + try_files $uri $uri/ =404; + } + + location /static/ { + alias /usr/share/lemonldap-ng/portal/htdocs/static/; + } + } +} diff --git a/images/lemonldap-ng/root/etc/yum.repos.d/lemonldap-ng.repo b/images/lemonldap-ng/root/etc/yum.repos.d/lemonldap-ng.repo new file mode 100644 index 0000000..1bb2b1a --- /dev/null +++ b/images/lemonldap-ng/root/etc/yum.repos.d/lemonldap-ng.repo @@ -0,0 +1,13 @@ +[lemonldap-ng] +name = LemonLDAP::NG packages +baseurl = https://lemonldap-ng.org/redhat/stable/$releasever/noarch +enabled = 1 +gpgcheck = 1 +gpgkey = https://lemonldap-ng.org/_media/rpm-gpg-key-ow2 + +[lemonldap-ng-extras] +name = LemonLDAP::NG extra packages +baseurl = https://lemonldap-ng.org/redhat/extras/$releasever +enabled = 1 +gpgcheck = 1 +gpgkey = https://lemonldap-ng.org/_media/rpm-gpg-key-ow2 diff --git a/images/lemonldap-ng/root/usr/local/bin/lemonldap-ng b/images/lemonldap-ng/root/usr/local/bin/lemonldap-ng new file mode 100755 index 0000000..eaf76f9 --- /dev/null +++ b/images/lemonldap-ng/root/usr/local/bin/lemonldap-ng @@ -0,0 +1,24 @@ +#!/bin/sh + +set -euo pipefail + +# uwsgi wants just a path for unix socket +LISTEN=$(echo ${LLNG_LISTEN} | sed -E 's/^unix://') + +exec uwsgi \ + --plugin psgi \ + --psgi /usr/share/lemonldap-ng/llng-server/llng-server.psgi \ + --${LLNG_SOCKET_PROTO}-socket ${LISTEN} \ + --chmod-socket=660 \ + --master \ + --max-worker-lifetime 604800 \ + --max-requests 100000 \ + --disable-logging \ + --buffer-size 65535 \ + --limit-post 0 \ + --die-on-term \ + --cheaper 2 \ + --workers ${LLNG_WORKERS} \ + --cheaper-initial 2 \ + --cheaper-algo spare \ + --cheaper-step 1 diff --git a/images/lemonldap-ng/root/usr/share/lemonldap-ng/ressources/lemonldap-ng.postgres.sql b/images/lemonldap-ng/root/usr/share/lemonldap-ng/ressources/lemonldap-ng.postgres.sql new file mode 100644 index 0000000..1d1a623 --- /dev/null +++ b/images/lemonldap-ng/root/usr/share/lemonldap-ng/ressources/lemonldap-ng.postgres.sql @@ -0,0 +1,68 @@ +CREATE UNLOGGED TABLE IF NOT EXISTS sessions ( + id varchar(64) not null primary key, + a_session jsonb +); + +CREATE INDEX IF NOT EXISTS i_s__whatToTrace ON sessions ((a_session ->> '_whatToTrace')); +CREATE INDEX IF NOT EXISTS i_s__session_kind ON sessions ((a_session ->> '_session_kind')); +CREATE INDEX IF NOT EXISTS i_s__utime ON sessions ((cast (a_session ->> '_utime' as bigint))); +CREATE INDEX IF NOT EXISTS i_s_ipAddr ON sessions ((a_session ->> 'ipAddr')); +CREATE INDEX IF NOT EXISTS i_s__httpSessionType ON sessions ((a_session ->> '_httpSessionType')); +CREATE INDEX IF NOT EXISTS i_s_user ON sessions ((a_session ->> 'user')); +CREATE INDEX IF NOT EXISTS i_s_mail ON sessions ((a_session ->> 'mail')); +CREATE INDEX IF NOT EXISTS i_s__session_uid ON sessions ((a_session ->> '_session_uid')); + + +CREATE TABLE IF NOT EXISTS psessions ( + id varchar(64) not null primary key, + a_session jsonb +); +CREATE INDEX IF NOT EXISTS i_p__session_kind ON psessions ((a_session ->> '_session_kind')); +CREATE INDEX IF NOT EXISTS i_p__httpSessionType ON psessions ((a_session ->> '_httpSessionType')); +CREATE INDEX IF NOT EXISTS i_p__session_uid ON psessions ((a_session ->> '_session_uid')); +CREATE INDEX IF NOT EXISTS i_p_ipAddr ON psessions ((a_session ->> 'ipAddr')); +CREATE INDEX IF NOT EXISTS i_p__whatToTrace ON psessions ((a_session ->> '_whatToTrace')); + + +CREATE UNLOGGED TABLE IF NOT EXISTS samlsessions ( + id varchar(64) not null primary key, + a_session jsonb +); +CREATE INDEX IF NOT EXISTS i_a__session_kind ON samlsessions ((a_session ->> '_session_kind')); +CREATE INDEX IF NOT EXISTS i_a__utime ON samlsessions ((cast(a_session ->> '_utime' as bigint))); +CREATE INDEX IF NOT EXISTS i_a_ProxyID ON samlsessions ((a_session ->> 'ProxyID')); +CREATE INDEX IF NOT EXISTS i_a__nameID ON samlsessions ((a_session ->> '_nameID')); +CREATE INDEX IF NOT EXISTS i_a__assert_id ON samlsessions ((a_session ->> '_assert_id')); +CREATE INDEX IF NOT EXISTS i_a__art_id ON samlsessions ((a_session ->> '_art_id')); +CREATE INDEX IF NOT EXISTS i_a__saml_id ON samlsessions ((a_session ->> '_saml_id')); + +CREATE UNLOGGED TABLE IF NOT EXISTS oidcsessions ( + id varchar(64) not null primary key, + a_session jsonb +); +CREATE INDEX IF NOT EXISTS i_o__session_kind ON oidcsessions ((a_session ->> '_session_kind')); +CREATE INDEX IF NOT EXISTS i_o__utime ON oidcsessions ((cast(a_session ->> '_utime' as bigint ))); + +CREATE UNLOGGED TABLE IF NOT EXISTS cassessions ( + id varchar(64) not null primary key, + a_session jsonb +); +CREATE INDEX IF NOT EXISTS i_c__session_kind ON cassessions ((a_session ->> '_session_kind')); +CREATE INDEX IF NOT EXISTS i_c__utime ON cassessions ((cast(a_session ->> '_utime' as bigint))); +CREATE INDEX IF NOT EXISTS i_c__cas_id ON cassessions ((a_session ->> '_cas_id')); +CREATE INDEX IF NOT EXISTS i_c_pgtIou ON cassessions ((a_session ->> 'pgtIou')); + +CREATE TABLE IF NOT EXISTS lmConfig ( + cfgnum integer not null primary key, + data text +); + +CREATE TABLE IF NOT EXISTS notifications ( + date date NOT NULL, + uid varchar(255) NOT NULL, + ref varchar(255) NOT NULL, + cond varchar(255) DEFAULT NULL, + xml text NOT NULL, + done date DEFAULT NULL, + PRIMARY KEY (date, uid, ref) +) diff --git a/init/vault-database b/init/vault-database new file mode 100755 index 0000000..a44f309 --- /dev/null +++ b/init/vault-database @@ -0,0 +1,8 @@ +#!/bin/sh + +set -euo pipefail + +[[- template "common/vault.mkpgrole.sh" + dict "ctx" . + "config" (dict "role" .instance "database" "postgres") +]] diff --git a/lemonldap-ng.nomad.hcl b/lemonldap-ng.nomad.hcl new file mode 100644 index 0000000..8bce521 --- /dev/null +++ b/lemonldap-ng.nomad.hcl @@ -0,0 +1,130 @@ +job "[[ .instance ]]" { + +[[ template "common/job_start" merge .llng . ]] + + group "lemonldap-ng" { +[[- $c := merge .llng.engine . ]] + + count = [[ $c.count ]] + + network { + mode = "bridge" + } + + service { + name = "[[ .instance ]][[ .consul.suffix ]]" + port = 8080 + +[[ template "common/connect" $c ]] + +[[- $p := merge .llng.portal . ]] +[[- $m := merge .llng.manager . ]] +[[- $a := merge .llng.api . ]] + tags = [ + +[[- if $p.traefik.enabled ]] + "[[ $p.traefik.instance ]].enable=true", + "[[ $p.traefik.instance ]].http.routers.[[ .instance ]]-portal[[ .consul.suffix ]].rule=Host(`[[ (urlParse $p.public_url).Hostname ]]`) && !PathRegexp(`^/index\\.psgi/(config|sessions)`)", + "[[ $p.traefik.instance ]].http.routers.[[ .instance ]]-portal[[ .consul.suffix ]].entrypoints=[[ join $p.traefik.entrypoints "," ]]", + "[[ $p.traefik.instance ]].http.routers.[[ .instance ]]-portal[[ .consul.suffix ]].priority=100", + "[[ $p.traefik.instance ]].http.routers.[[ .instance ]]-portal[[ .consul.suffix ]].middlewares=[[ template "common/traefik_middlewares" $p.traefik ]]", +[[- end ]] + +[[- if $m.traefik.enabled ]] + [[- if ne $m.traefik.instance $c.traefik.instance ]] + "[[ $m.traefik.instance ]].enable=true", + [[- end ]] + "[[ $m.traefik.instance ]].http.routers.[[ .instance ]]-manager[[ .consul.suffix ]].rule=Host(`[[ (urlParse $m.public_url).Hostname ]]`)", + "[[ $m.traefik.instance ]].http.routers.[[ .instance ]]-manager[[ .consul.suffix ]].entrypoints=[[ join $m.traefik.entrypoints "," ]]", + "[[ $m.traefik.instance ]].http.routers.[[ .instance ]]-manager[[ .consul.suffix ]].middlewares=[[ template "common/traefik_middlewares" $m.traefik ]]", +[[- end ]] + +[[- if $a.traefik.enabled ]] + [[- if $a.traefik.instance ne $p.traefik.instance ]] + "[[ $a.traefik.instance ]].enable=true", + [[- end ]] + "[[ $a.traefik.instance ]].http.routers.[[ .instance ]]-api[[ .consul.suffix ]].rule=Host(`[[ (urlParse $p.public_url).Hostname ]]`) && PathRegexp(`^/index\\.psgi/(config|sessions)`)", + "[[ $a.traefik.instance ]].http.routers.[[ .instance ]]-api[[ .consul.suffix ]].entrypoints=[[ join $a.traefik.entrypoints "," ]]", + "[[ $a.traefik.instance ]].http.routers.[[ .instance ]]-api[[ .consul.suffix ]].priority=200", + "[[ $a.traefik.instance ]].http.routers.[[ .instance ]]-api[[ .consul.suffix ]].middlewares=[[ template "common/traefik_middlewares" $a.traefik ]]", +[[- end ]] + + ] + } + + task "lemonldap-ng" { + driver = [[ $c.nomad.driver | toJSON ]] + + config { + image = [[ $c.image | toJSON ]] + readonly_rootfs = true + pids_limit = 200 + volumes = [ + "secrets/lemonldap-ng.ini:/etc/lemonldap-ng/lemonldap-ng.ini:ro", + "local/init-db.sh:/entrypoint.d/20-initdb.sh:ro", + ] +[[ template "common/tmpfs" dict "size" "5000000" "target" "/tmp" ]] + } + + vault { + policies = ["[[ .instance ]][[ .consul.suffix ]]"] + } + + env { + LLNG_NGINX_LISTEN = "127.0.0.1:8080" + LLNG_LISTEN = "unix:/tmp/llng.sock" + LLNG_MANAGER_VHOST = [[ (urlParse .llng.manager.public_url).Hostname | toJSON ]] + LLNG_PORTAL_VHOST = [[ (urlParse .llng.portal.public_url).Hostname | toJSON ]] + CTD_CONFIG = "/local/caretakerd.yaml" + } + +[[ template "common/file_env" $c.env ]] + + # Main Lemonldap::NG configuration + template { + data =<<_EOT +[[ template "lemonldap-ng/lemonldap-ng.ini.tpl" $c ]] +_EOT + destination = "secrets/lemonldap-ng.ini" + uid = 100048 + gid = 100048 + perms = 440 + } + + # This is a striped down config, just used to migrate file based config to database on first start + template { + data =<<_EOT +[[ template "lemonldap-ng/init.ini.tpl" $c ]] +_EOT + destination = "local/init.ini" + } + + # A small script to handle initialization (create tables, indexes, load initial config in the DB) + template { + data = <<_EOT +[[ template "lemonldap-ng/init.sh.tpl" . ]] +_EOT + destination = "local/init-db.sh" + perms = 755 + } + + # Custom file based config, which will be migrated to the database + template { + data = <<_EOT +[[ template "lemonldap-ng/lmConf-1.json.tpl" . ]] +_EOT + destination = "local/lmConf-1.json" + } + + # caretakerd is the init system used to run LL::NG, nginx and cron tasks + template { + data =<<_EOT +[[ template "lemonldap-ng/caretakerd.yaml.tpl" . ]] +_EOT + destination = "local/caretakerd.yaml" + } + +[[ template "common/resources" $c.resources ]] + } + } +} diff --git a/prep.d/10-mv-conf.sh b/prep.d/10-mv-conf.sh new file mode 100755 index 0000000..73a6903 --- /dev/null +++ b/prep.d/10-mv-conf.sh @@ -0,0 +1 @@ +[[ template "common/mv_conf.sh" dict "ctx" . "services" (dict "lemonldap-ng" .instance) ]] diff --git a/templates/caretakerd.yaml.tpl b/templates/caretakerd.yaml.tpl new file mode 100644 index 0000000..1196696 --- /dev/null +++ b/templates/caretakerd.yaml.tpl @@ -0,0 +1,19 @@ +logger: + level: debug +services: + lemonldap: + type: master + command: ["lemonldap-ng"] + local_cache: + command: ["/usr/libexec/lemonldap-ng/bin/purgeLocalCache"] + cronExpression: '1 * * * *' + nginx: + command: ["nginx", "-c", "${LLNG_NGINX_CONF}"] +{{- if eq (env "NOMAD_ALLOC_INDEX") "0" }} + global_cache: + command: ["/usr/libexec/lemonldap-ng/bin/purgeCentralCache"] + cronExpression: '10 * * * *' + rotate_oidc_keys: + command: ["/usr/libexec/lemonldap-ng/bin/rotateOidcKeys"] + cronExpression: '5 5 * * 6' +{{- end }} diff --git a/templates/cron.tpl b/templates/cron.tpl new file mode 100644 index 0000000..3c00201 --- /dev/null +++ b/templates/cron.tpl @@ -0,0 +1,5 @@ +1 * * * * /usr/libexec/lemonldap-ng/bin/purgeLocalCache +{{- if eq (env "NOMAD_ALLOC_INDEX") "0" }} +10 * * * * /usr/libexec/lemonldap-ng/bin/purgeCentralCache +5 5 * * 6 /usr/libexec/lemonldap-ng/bin/rotateOidcKeys +{{- end }} diff --git a/templates/init.ini.tpl b/templates/init.ini.tpl new file mode 100644 index 0000000..71ddd1f --- /dev/null +++ b/templates/init.ini.tpl @@ -0,0 +1,6 @@ +[all] + +[configuration] +type = File +dirName = /local + diff --git a/templates/init.sh.tpl b/templates/init.sh.tpl new file mode 100644 index 0000000..e67ada3 --- /dev/null +++ b/templates/init.sh.tpl @@ -0,0 +1,13 @@ +#!/bin/sh + +set -euo pipefail + +echo "Create tables if needed" +psql -f /usr/share/lemonldap-ng/ressources/lemonldap-ng.postgres.sql + +if [ "$(psql -qAtc 'SELECT COUNT(1) FROM lmConfig')" = "0" ]; then + echo "Load initial configuration in database" + /usr/libexec/lemonldap-ng/bin/convertConfig \ + --current=/local/init.ini \ + --new=/etc/lemonldap-ng/lemonldap-ng.ini +fi diff --git a/templates/lemonldap-ng.ini.tpl b/templates/lemonldap-ng.ini.tpl new file mode 100644 index 0000000..f932ea1 --- /dev/null +++ b/templates/lemonldap-ng.ini.tpl @@ -0,0 +1,54 @@ +[all] +logLevel = notice +localSessionStorage = Cache::FileCache +localSessionStorageOptions = { \ + 'namespace' => 'sessions', \ + 'default_expires_in' => '300', \ + 'directory_umask' => '007', \ + 'cache_root' => '/tmp', \ + 'cache_depth' => 3 \ +} + +[configuration] +useServerEnv = 1 +type = CDBI +dbiChain = DBI:Pg: +localStorage = Cache::FileCache +localStorageOptions = { \ + 'namespace' => 'config', \ + 'default_expires_in' => '600', \ + 'directory_umask' => '007', \ + 'cache_root' => '/tmp', \ + 'cache_depth' => 0 \ +} + +[manager] +protection = [[ .llng.manager.protection ]] +enabledModules = conf, sessions, notifications, 2ndFA +staticPrefix = /static +templateDir = /usr/share/lemonldap-ng/manager/htdocs/templates +languages = [[ join .llng.manager.languages ", " ]] + +[portal] +staticPrefix = /static +templateDir = /usr/share/lemonldap-ng/portal/templates +languages = [[ join .llng.portal.languages ", " ]] +forceGlobalStorageUpgradeOTT = 1 + +notificationStorage = DBI +notificationStorageOptions = { \ + 'dbiChain' => 'DBI:Pg:', \ + 'dbiTable' => 'notifications', \ + 'type' => 'CDBI', \ +} + +[[- range $stor := coll.Slice "global" "persistent" "saml" "oidc" "cas" ]] +[[ $stor ]]Storage = Apache::Session::Browseable::PgJSON +[[ $stor ]]StorageOptions = { \ + 'DataSource' => 'DBI:Pg:', \ + 'TableName' => '[[ if eq $stor "global" ]][[ else if eq $stor "persistent" ]]p[[ else ]][[ $stor ]][[ end ]]sessions', \ + 'generateModule' => 'Lemonldap::NG::Common::Apache::Session::Generate::SHA256', \ + 'Commit' => 1 \ +} + +[[- end ]] diff --git a/templates/lmConf-1.json.tpl b/templates/lmConf-1.json.tpl new file mode 100644 index 0000000..4121aa9 --- /dev/null +++ b/templates/lmConf-1.json.tpl @@ -0,0 +1,132 @@ +{ + "applicationList" : { + "2administration" : { + "catname" : "Administration", + "manager" : { + "options" : { + "description" : "Configure LemonLDAP::NG WebSSO", + "display" : "auto", + "logo" : "configure.png", + "name" : "WebSSO Manager", + "uri" : "[[ .llng.manager.public_url ]]/manager.html" + }, + "type" : "application" + }, + "notifications" : { + "options" : { + "description" : "Explore WebSSO notifications", + "display" : "auto", + "logo" : "database.png", + "name" : "Notifications Explorer", + "uri" : "[[ .llng.manager.public_url ]]/notifications.html" + }, + "type" : "application" + }, + "sessions" : { + "options" : { + "description" : "Explore WebSSO sessions", + "display" : "auto", + "logo" : "database.png", + "name" : "Sessions Explorer", + "uri" : "[[ .llng.manager.public_url ]]/sessions.html" + }, + "type" : "application" + }, + "type" : "category" + }, + "3documentation" : { + "catname" : "Documentation", + "localdoc" : { + "options" : { + "description" : "Documentation supplied with LemonLDAP::NG", + "display" : "on", + "logo" : "help.png", + "name" : "Local documentation", + "uri" : "[[ .llng.manager.public_url ]]/doc/" + }, + "type" : "application" + }, + "officialwebsite" : { + "options" : { + "description" : "Official LemonLDAP::NG Website", + "display" : "on", + "logo" : "network.png", + "name" : "Official Website", + "uri" : "https://lemonldap-ng.org/" + }, + "type" : "application" + }, + "type" : "category" + } + }, + "authentication" : "Demo", + "cfgAuthor" : "Daniel Berteaud", + "cfgDate" : "1627287638", + "cfgNum" : 1, + "cfgVersion" : "2.18.0", + "cookieName" : "lemonldap", + "demoExportedVars" : { + "cn" : "cn", + "mail" : "mail", + "uid" : "uid" + }, + "domain" : "example.com", + "exportedHeaders" : { + "test1.example.com" : { + "Auth-Groups" : "$groups", + "Auth-User" : "$uid" + }, + "test2.example.com" : { + "Auth-Groups" : "$groups", + "Auth-User" : "$uid" + } + }, + "exportedVars" : {}, + "groups" : {}, + "localSessionStorage" : "Cache::FileCache", + "localSessionStorageOptions" : { + "cache_depth" : 3, + "cache_root" : "/var/cache/lemonldap-ng", + "default_expires_in" : 600, + "directory_umask" : "007", + "namespace" : "lemonldap-ng-sessions" + }, + "globalStorage" : "Lemonldap::NG::Common::Apache::Session::REST", + "globalStorageOptions" : { + "baseUrl" : "[[ .llng.portal.public_url ]]/index.psgi/sessions/global", + "generateModule" : "Lemonldap::NG::Common::Apache::Session::Generate::SHA256" + }, + "locationRules" : { + "[[ (urlParse .llng.portal.public_url).Hostname ]]" : { + "(?#checkUser)^/checkuser" : "inGroup(\"timelords\")", + "(?#errors)^/lmerror/" : "accept", + "default" : "accept" + }, + "[[ (urlParse .llng.manager.public_url).Hostname ]]" : { + "(?#Configuration)^/(.*?\\.(fcgi|psgi)/)?(manager\\.html|confs|prx/|$)" : "inGroup(\"timelords\")", + "(?#Notifications)/(.*?\\.(fcgi|psgi)/)?notifications" : "inGroup(\"timelords\") or $uid eq \"rtyler\"", + "(?#Sessions)/(.*?\\.(fcgi|psgi)/)?sessions" : "inGroup(\"timelords\") or $uid eq \"rtyler\"", + "default" : "inGroup(\"timelords\") or $uid eq \"rtyler\"" + } + }, + "loginHistoryEnabled" : 1, + "macros" : { + "UA" : "$ENV{HTTP_USER_AGENT}", + "_whatToTrace" : "$_auth eq 'SAML' ? lc($_user.'@'.$_idpConfKey) : $_auth eq 'OpenIDConnect' ? lc($_user.'@'.$_oidc_OP) : lc($_user)" + }, + "notification" : 1, + "oidcServiceIgnoreScopeForClaims" : 1, + "passwordDB" : "Demo", + "portal" : "[[ .llng.portal.public_url ]]/", + "portalSkin" : "bootstrap", + "portalSkinBackground" : "1280px-Cedar_Breaks_National_Monument_partially.jpg", + "registerDB" : "Demo", + "reloadUrls" : { + "localhost" : "http://localhost:8080/reload" + }, + "securedCookie" : 0, + "sessionDataToRemember" : {}, + "timeout" : 72000, + "userDB" : "Same", + "whatToTrace" : "_whatToTrace" +} diff --git a/variables.yml b/variables.yml new file mode 100644 index 0000000..5044917 --- /dev/null +++ b/variables.yml @@ -0,0 +1,124 @@ +--- + +instance: lemonldap-ng + +llng: + + # The same engine can run portal, manager and handler + # The settings below are for the uwsgi engine itself + engine: + + # Number of instance to run + count: 1 + + # Docker image to use + image: '[[ .docker.repo ]]lemonldap-ng:2.18.1-1' + + # Resource allocation + resources: + cpu: 500 + memory: 512 + + # Connect to the database from the service mesh + consul: + connect: + upstreams: + - destination_name: postgres[[ .consul.suffix ]] + local_bind_port: 5432 + + # Additional env vars to pass to the container + env: + # Database settings + PGHOST: 127.0.0.1 + PGPORT: 5432 + PGUSER: '{{ with secret "[[ .vault.prefix ]]database/creds/[[ .instance ]]" }}{{ .Data.username }}{{ end }}' + PGPASSWORD: '{{ with secret "[[ .vault.prefix ]]database/creds/[[ .instance ]]" }}{{ .Data.password }}{{ end }}' + PGDATABASE: '[[ .instance ]]' + # No need for SSL if postgres is accessed from the mesh + PGSSLMODE: disable + # Max number of uwsgi workers. You might need to allocation more memory + # if you need to rise this value (or you can just run several instances instead) + LLNG_WORKERS: 6 + + # Settings for the portal itself + portal: + # URL used by users to reach the portal + public_url: https://sso.example.org + + # Available languages on the portal + languages: + - en + - fr + - es + - it + - de + + # Traefik settings + traefik: + + # If disabled, the portal won't be exposed by Traefik + enabled: true + + # Override base_middleware to remove csp-relaxed@file as Lemonldap::NG handle CSP itself + base_middlewares: + - rate-limit-std@file + - inflight-std@file + - security-headers@file + - hsts@file + - compression@file + + # List of middlewares to apply + middlewares: {} + + # Settings for the configuration manager + manager: + # protection can be "none" (no authentication) or "manager" (the manager is protected by Lemonldap::NG itself, and you must add appropriate + # access rules to access the manager). On initial configuration, you will likely need to set this to none, and switch back to manager only after + # the configuration is working + protection: manager + + # The URL to reach the manager + public_url: https://manager.example.org + + # Available languages for the manager + languages: + - en + + # Traefik settings + traefik: + + # If disabled, the manager will not be exposed by Traefik + enabled: true + + # Override base_middleware to remove csp-relaxed@file as Lemonldap::NG handle CSP itself + base_middlewares: + - rate-limit-std@file + - inflight-std@file + - security-headers@file + - hsts@file + - compression@file + + # List of middlewares to apply + middlewares: {} + + # The API is exposed by the portal, but usually must be secured differently + # The following settings only apply to the REST/SOAP API + api: + + # Traefik settings + traefik: + + # If disabled, Traefik won't expose the API + enabled: false + + # Override base_middleware to remove csp-relaxed@file as Lemonldap::NG handle CSP itself + base_middlewares: + - rate-limit-std@file + - inflight-std@file + - security-headers@file + - hsts@file + - compression@file + + # List of middlewares to apply + middlewares: {} + diff --git a/vault/policies/lemonldap-ng.hcl b/vault/policies/lemonldap-ng.hcl new file mode 100644 index 0000000..67c1f8a --- /dev/null +++ b/vault/policies/lemonldap-ng.hcl @@ -0,0 +1,7 @@ +path "[[ .vault.prefix ]]kv/sevrice/[[ .instance ]]" { + capabilities = ["read"] +} + +path "[[ .vault.prefix ]]database/creds/[[ .instance ]]" { + capabilities = ["read"] +}