job "lemonldap-ng" { datacenters = ["dc1"] region = "global" priority = 60 constraint { operator = "distinct_hosts" value = "true" } group "lemonldap-ng" { count = 1 shutdown_delay = "6s" constraint { operator = "distinct_hosts" value = "true" } network { mode = "bridge" } service { name = "lemonldap-ng" port = 8080 meta { alloc = "${NOMAD_ALLOC_INDEX}" datacenter = "${NOMAD_DC}" group = "${NOMAD_GROUP_NAME}" job = "${NOMAD_JOB_NAME}" namespace = "${NOMAD_NAMESPACE}" node = "${node.unique.name}" region = "${NOMAD_REGION}" } connect { sidecar_service { proxy { upstreams { destination_name = "postgres" local_bind_port = 5432 # Work arround, see https://github.com/hashicorp/nomad/issues/18538 destination_type = "service" } } } sidecar_task { config { args = [ "-c", "${NOMAD_SECRETS_DIR}/envoy_bootstrap.json", "-l", "${meta.connect.log_level}", "--concurrency", "${meta.connect.proxy_concurrency}", "--disable-hot-restart" ] } resources { cpu = 50 memory = 64 } } } tags = [ "traefik.http.routers.lemonldap-ng-portal.rule=Host(`sso.example.org`) && !PathRegexp(`^/index\\.psgi/(config|sessions)`)", "traefik.enable=true", "traefik.http.routers.lemonldap-ng-portal.entrypoints=https", "traefik.http.routers.lemonldap-ng-portal.middlewares=security-headers@file,rate-limit-std@file,forward-proto@file,inflight-std@file,hsts@file,compression@file", "traefik.enable=true", "traefik.http.routers.lemonldap-ng-manager.entrypoints=https", "traefik.http.routers.lemonldap-ng-manager.rule=Host(`manager.example.org`)", "traefik.http.routers.lemonldap-ng-manager.middlewares=security-headers@file,rate-limit-std@file,forward-proto@file,inflight-std@file,hsts@file,compression@file", "traefik.http.routers.lemonldap-ng-api.rule=Host(`sso.example.org`) && PathRegexp(`^/index\\.psgi/(config|sessions)`)", ] } # wait for required services tp be ready before starting the main task task "wait-for" { driver = "docker" user = 1053 config { image = "danielberteaud/wait-for:24.3-1" readonly_rootfs = true pids_limit = 20 } lifecycle { hook = "prestart" } env { SERVICE_0 = "postgres.service.consul" } resources { cpu = 10 memory = 10 memory_max = 30 } } task "lemonldap-ng" { driver = "docker" config { image = "danielberteaud/lemonldap-ng:2.18.2-2" 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", "local/minit.d/lemonldap-ng.yml:/etc/minit.d/lemonldap-ng.yml:ro" ] mount { type = "tmpfs" target = "/tmp" tmpfs_options { size = 5000000 } } } vault { policies = ["lemonldap-ng"] env = false disable_file = true change_mode = "noop" } artifact { source = "git::https://git.lapiole.org/nomad/lemonldap-ng.git//files/assets" destination = "local/assets/static/common" } env { LLNG_NGINX_LISTEN = "127.0.0.1:8080" LLNG_LISTEN = "unix:/tmp/llng.sock" LLNG_MANAGER_VHOST = "manager.example.org" LLNG_PORTAL_VHOST = "sso.example.org" LLNG_CUSTOM_ASSETS_DIR = "/local/assets" } # Use a template block instead of env {} so we can fetch values from vault template { data = <<_EOT LANG=fr_FR.utf8 LLNG_WORKERS=6 TZ=Europe/Paris _EOT destination = "secrets/.env" perms = 400 env = true } # Main Lemonldap::NG configuration template { data = <<_EOT [all] logLevel = notice localSessionStorage = Cache::FileCache localSessionStorageOptions = { \ 'namespace' => 'sessions', \ 'default_expires_in' => '60', \ 'directory_umask' => '007', \ 'cache_root' => '/tmp', \ 'cache_depth' => 3 \ } globalStorage = Apache::Session::Browseable::PgJSON globalStorageOptions = { \ 'DataSource' => 'DBI:Pg:', \ 'TableName' => 'sessions', \ 'generateModule' => 'Lemonldap::NG::Common::Apache::Session::Generate::SHA256', \ 'Commit' => 1 \ } persistentStorage = Apache::Session::Browseable::PgJSON persistentStorageOptions = { \ 'DataSource' => 'DBI:Pg:', \ 'TableName' => 'psessions', \ 'generateModule' => 'Lemonldap::NG::Common::Apache::Session::Generate::SHA256', \ 'Commit' => 1 \ } samlStorage = Apache::Session::Browseable::PgJSON samlStorageOptions = { \ 'DataSource' => 'DBI:Pg:', \ 'TableName' => 'samlsessions', \ 'generateModule' => 'Lemonldap::NG::Common::Apache::Session::Generate::SHA256', \ 'Commit' => 1 \ } oidcStorage = Apache::Session::Browseable::PgJSON oidcStorageOptions = { \ 'DataSource' => 'DBI:Pg:', \ 'TableName' => 'oidcsessions', \ 'generateModule' => 'Lemonldap::NG::Common::Apache::Session::Generate::SHA256', \ 'Commit' => 1 \ } casStorage = Apache::Session::Browseable::PgJSON casStorageOptions = { \ 'DataSource' => 'DBI:Pg:', \ 'TableName' => 'cassessions', \ 'generateModule' => 'Lemonldap::NG::Common::Apache::Session::Generate::SHA256', \ 'Commit' => 1 \ } notificationStorage = DBI notificationStorageOptions = { \ 'dbiChain' => 'DBI:Pg:', \ 'dbiTable' => 'notifications', \ 'type' => 'CDBI', \ } status = 1 [configuration] useServerEnv = 1 type = CDBI dbiChain = DBI:Pg: localStorage = Cache::FileCache localStorageOptions = { \ 'namespace' => 'config', \ 'default_expires_in' => '60', \ 'directory_umask' => '007', \ 'cache_root' => '/tmp', \ 'cache_depth' => 0 \ } [manager] protection = manager enabledModules = conf, sessions, notifications, 2ndFA staticPrefix = /static templateDir = /usr/share/lemonldap-ng/manager/htdocs/templates languages = en [portal] staticPrefix = /static templateDir = /usr/share/lemonldap-ng/portal/templates languages = en, fr, es, it, de forceGlobalStorageUpgradeOTT = 1 _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 [all] [configuration] type = File dirName = /local _EOT destination = "local/init.ini" } # Database settings template { data = <<_EOT PGHOST=127.0.0.1 PGDATABASE=lemonldap-ng PGSSLMODE=disable PGPORT=5432 PGUSER={{ with secret "database/creds/lemonldap-ng" }}{{ .Data.username }}{{ end }} PGPASSWORD={{ with secret "database/creds/lemonldap-ng" }}{{ .Data.password }}{{ end }} _EOT destination = "secrets/.db.env" uid = 100000 gid = 100000 perms = 400 env = true } # A small script to handle initialization (create tables, indexes, load initial config in the DB) template { data = <<_EOT #!/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 _EOT destination = "local/init-db.sh" perms = 755 } # Custom file based config, which will be migrated to the database template { data = <<_EOT { "applicationList" : { "2administration" : { "catname" : "Administration", "manager" : { "options" : { "description" : "Configure LemonLDAP::NG WebSSO", "display" : "auto", "logo" : "configure.png", "name" : "WebSSO Manager", "uri" : "https://manager.example.org/manager.html" }, "type" : "application" }, "notifications" : { "options" : { "description" : "Explore WebSSO notifications", "display" : "auto", "logo" : "database.png", "name" : "Notifications Explorer", "uri" : "https://manager.example.org/notifications.html" }, "type" : "application" }, "sessions" : { "options" : { "description" : "Explore WebSSO sessions", "display" : "auto", "logo" : "database.png", "name" : "Sessions Explorer", "uri" : "https://manager.example.org/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" : "https://manager.example.org/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" : "/tmp", "default_expires_in" : 600, "directory_umask" : "007", "namespace" : "sessions" }, "globalStorage" : "Apache::Session::Browseable::PgJSON", "globalStorageOptions" : { "DataSource" : "DBI:Pg:", "TableName" : "sessions", "generateModule" : "Lemonldap::NG::Common::Apache::Session::Generate::SHA256", "Commit" : 1 }, "locationRules" : { "sso.example.org" : { "(?#checkUser)^/checkuser" : "inGroup(\"timelords\")", "(?#errors)^/lmerror/" : "accept", "default" : "accept" }, "manager.example.org" : { "(?#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" : "https://sso.example.org/", "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" } _EOT destination = "local/lmConf-1.json" } # Configure minit to start uwsgi, nginx, cron tasks template { data = <<_EOT --- kind: daemon name: lemonldap command: ["lemonldap-ng"] --- kind: daemon name: nginx command: ["nginx", "-c", "${LLNG_NGINX_CONF}"] --- kind: cron name: local_cache command: ["/usr/libexec/lemonldap-ng/bin/purgeLocalCache"] cron: "@every 1h" {{- if eq (env "NOMAD_ALLOC_INDEX") "0" }} --- kind: cron name: clean_global_cache cron: "@every 10m" command: ["/usr/libexec/lemonldap-ng/bin/purgeCentralCache"] {{- end }} _EOT destination = "local/minit.d/lemonldap-ng.yml" } resources { cpu = 500 memory = 512 } } } }