diff --git a/README.md b/README.md index 668c1d6..ba009ce 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ This repository contains Docker Compose setups for a variety of applications, en - Traefik (`networking/proxies/traefik`) - **Security** - HashiCorp Vault (`security/vaults/hashicorp`) + - Authelia (`security/access/authelia`) - **Operating Systems** - Windows (`oses/windows`) diff --git a/security/access/authelia/configuration/configuration.yml b/security/access/authelia/configuration/configuration.yml new file mode 100644 index 0000000..2e68e1a --- /dev/null +++ b/security/access/authelia/configuration/configuration.yml @@ -0,0 +1,68 @@ +--- +# Authelia configuration. +# This file is mounted read-only; on every container start (see +# `docker-compose.yml`) it's rendered into a container-local copy with the +# domain placeholders below substituted from DOMAIN in `.env`, so this +# tracked copy is never modified. Edit the rest of this file directly for +# anything else, the same way Traefik's `configuration/*.yml` files are +# hand-edited. +# +# Secrets, hosts, and ports for the shared `postgresql`/`redis` containers are +# supplied via the `AUTHELIA_*` environment variables in `docker-compose.yml` +# (Authelia lets any config key be overridden this way), so they are left as +# empty stubs below rather than duplicated here. + +server: + address: 'tcp://:9091' + +log: + level: 'info' + +totp: + issuer: 'authelia.${DOMAIN}' + +identity_validation: + reset_password: + jwt_secret: '' + +authentication_backend: + file: + path: '/tmp/users_database.yml' + +access_control: + default_policy: 'deny' + rules: + # TODO: Add more rules as needed, for example a `bypass` policy for + # publicly-reachable apps under this domain. + - domain: '*.${DOMAIN}' + policy: 'two_factor' + +session: + secret: '' + cookies: + - name: 'authelia_session' + domain: '${DOMAIN}' + authelia_url: 'https://authelia.${DOMAIN}' + expiration: '1 hour' + inactivity: '5 minutes' + redis: + host: '' + port: 0 + +regulation: + max_retries: 3 + find_time: '2 minutes' + ban_time: '5 minutes' + +storage: + encryption_key: '' + postgres: + address: '' + database: '' + username: '' + password: '' + +notifier: + filesystem: + filename: '/tmp/notification.txt' +... diff --git a/security/access/authelia/docker-compose.yml b/security/access/authelia/docker-compose.yml new file mode 100644 index 0000000..e3cdf23 --- /dev/null +++ b/security/access/authelia/docker-compose.yml @@ -0,0 +1,79 @@ +# TODO: Use Secrets to store the passwords to all containers. +# `postgresql` and `redis` live in separate compose projects (`databases/postgresql`, +# `databases/redis`), so `depends_on` cannot reference them. Authelia's image is +# a minimal static binary without a confirmed shell/`nc`, so rather than risk +# breaking its entrypoint with a wait script, rely on `restart: unless-stopped` +# to retry until they're reachable. +# +# After starting Postgres, add `authelia` to `POSTGRESQL_ADDITIONAL_DATABASES` +# in `databases/postgresql`'s `.env` and create a matching role/password. +networks: + internal: + name: "internal" + # External means the container will be accessible from other containers in the same network. + # If you want to isolate the container create a new network, with different name for each container and without being external. + external: true + +services: + authelia: + image: "authelia/authelia:4.39.20" + container_name: "authelia" + restart: "unless-stopped" + healthcheck: + test: ["CMD", "/app/healthcheck.sh"] + interval: "10s" + timeout: "5s" + retries: 5 + networks: + - "internal" + ports: + - "${AUTHELIA_PORT?Variable not set}:9091" + volumes: + - "./configuration/configuration.yml:/config/configuration.yml:ro" + environment: + - "TZ=${TIMEZONE?Variable not set}" + - "DOMAIN=${DOMAIN?Variable not set}" + - "ADMIN_USERNAME=${ADMIN_USERNAME?Variable not set}" + - "ADMIN_PASSWORD=${ADMIN_PASSWORD?Variable not set}" + - "ADMIN_EMAIL=${ADMIN_EMAIL?Variable not set}" + - "AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET=${AUTHELIA_JWT_SECRET?Variable not set}" + - "AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET?Variable not set}" + - "AUTHELIA_SESSION_REDIS_HOST=redis" + - "AUTHELIA_SESSION_REDIS_PORT=${REDIS_PORT?Variable not set}" + - "AUTHELIA_SESSION_REDIS_PASSWORD=${REDIS_PASSWORD?Variable not set}" + - "AUTHELIA_STORAGE_ENCRYPTION_KEY=${AUTHELIA_STORAGE_ENCRYPTION_KEY?Variable not set}" + - "AUTHELIA_STORAGE_POSTGRES_ADDRESS=tcp://postgresql:${POSTGRESQL_PORT?Variable not set}" + - "AUTHELIA_STORAGE_POSTGRES_DATABASE=authelia" + - "AUTHELIA_STORAGE_POSTGRES_USERNAME=${POSTGRESQL_USER?Variable not set}" + - "AUTHELIA_STORAGE_POSTGRES_PASSWORD=${POSTGRESQL_PASSWORD?Variable not set}" + # Renders a container-local `/tmp/configuration.yml` from the read-only + # mounted `configuration.yml` (so the tracked copy is never modified) and + # generates `/tmp/users_database.yml`, both from plain env vars, on every + # start (`ADMIN_*`/`DOMAIN` are kept outside the `AUTHELIA_` prefix so + # Authelia doesn't warn about an unrecognized config override), hashing + # the password with Authelia's own bundled `crypto` subcommand. Passes + # `--config` to the original entrypoint (rather than args directly), + # which still handles the `/config` chown and privilege drop to + # PUID/PGID afterwards, using our custom config path. + entrypoint: ["sh", "-c"] + command: + # All `$` below are escaped as `$$` so Compose leaves them for the + # container's shell to evaluate at runtime, instead of interpolating + # them from `.env` while parsing this file. + - | + sed "s|\$${DOMAIN}|$$DOMAIN|g" /config/configuration.yml > /tmp/configuration.yml + HASH=$$(authelia crypto hash generate bcrypt --password "$$ADMIN_PASSWORD" | awk '{print $$2}') + cat > /tmp/users_database.yml <<-CONFIG + --- + users: + $${ADMIN_USERNAME}: + disabled: false + displayname: 'Admin' + password: '$${HASH}' + email: '$${ADMIN_EMAIL}' + groups: + - 'admins' + CONFIG + exec /app/entrypoint.sh --config /tmp/configuration.yml + labels: + - "traefik.enable=false" diff --git a/security/access/authelia/template.env b/security/access/authelia/template.env new file mode 100644 index 0000000..650f57b --- /dev/null +++ b/security/access/authelia/template.env @@ -0,0 +1,29 @@ +# For all strings containing special characters, use single quotes. +# In case of older docker compose versions (<3.9), do not use single or double quotes at all. + +# Container's docker environment configuration. +COMPOSE_PROJECT_NAME='lowercase_string_without_special_characters' +TIMEZONE="Region/Country" + +# Shared PostgreSQL and Redis connection details (must match the values in +# `databases/postgresql`'s and `databases/redis`'s `.env` files). +POSTGRESQL_PORT=1234 +POSTGRESQL_USER='string_with_special_characters' +POSTGRESQL_PASSWORD='string_with_special_characters' +REDIS_PORT=1234 +REDIS_PASSWORD="string_without_special_characters" + +# Authelia's docker environment configuration. +AUTHELIA_PORT=5678 +# Generate each of the 3 secrets below with: openssl rand -hex 32 +AUTHELIA_JWT_SECRET='string_without_special_characters' +AUTHELIA_SESSION_SECRET='string_without_special_characters' +AUTHELIA_STORAGE_ENCRYPTION_KEY='string_without_special_characters_more_than_twenty_chars_long' +# Root domain this instance protects, rendered into `configuration.yml` (from +# `configuration.yml.template`) on every container start, see that file's +# comment. Authelia's own portal is served at `authelia.`. +DOMAIN='string_with_special_characters' +# Admin user, hashed into `users_database.yml` on every container start. +ADMIN_USERNAME='admin' +ADMIN_PASSWORD='string_without_special_characters' +ADMIN_EMAIL='string_with_special_characters'