Домашний сервер на Docker - 3

Вот и закончилась фундаментальная настройка. Теперь мы можем перейти к самому интересному - использованию того, что было настроено в предыдущих статьях. В текущей статье попробуем настроить следующие сервисы:

  • traefik - реверс-прокси нужен нам для того, чтобы направлять запросы нужным контейнерам вместо того, чтобы запоминать порты и обращаться непосредственно к этим портам;
  • authelia - единая система входа, которая позволит нам использовать аутентификацию для сервисов, которые не предусматривают такой возможности; для примера можно привести traefik, который пусть через web-интерфейс и не дает возможности управлять, но зачем давать вероятному злоумышленнику информацию к размышлению?
  • watchtower - позволит держать нам контейнеры в актуальном состоянии путем автоматического скачивания обновлений образов и перезапуска контейнеров на новом образе.

Traefik и Authelia

Для начала займемся настройкой “входа” на сервер с помощью traefik. Я предлагаю объединить traefik и authelia в один сервис, так как они взаимосвязаны: к authelia не будет доступа, если не будет работать traefik, а как раз traefik я и предлагал защитить с помощью authelia.

Traefik

Для начала предлагаю разобраться с traefik и начать с изучения документации. Он позволяет указывать параметры в секции label, что дает гибкость в настройке. Можно вообще всю конфигурацию максимально перенести в docker-compose, но предлагаю все-таки воспользоваться файлами конфигурации, дабы не увеличивать размеры docker-compose.yml. К тому же это позволит отделить конфигурацию для удосбта работы с ней.

В первую очередь создадим необходимые файлы в директории, где будет располагаться docker-compose.yml:

  • acme.json - если Вы хотите, чтобы traefik использовал Lets Ecnrypt для сертификатов (можно будет создать отдельно и дополнительно проследить, чтобы права на файл были 600);
  • static.yml, dynamic.yml - для соответственно статической и динамической конфигурации traefik.

Пример файла static.yml:

global:
checkNewVersion: true
sendAnonymousUsage: true
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
http:
middlewares:
- hsts@file
providers:
docker:
exposedByDefault: false
network: traefik
file:
filename: /etc/traefik/dynamic.yml
api:
dashboard: true
log:
level: warning
certificatesResolvers:
le:
acme:
email: mail+le@mymail.com
httpChallenge:
entryPoint: web
tlsChallenge: {}

Пример файла dynamic.yml:

http:
middlewares:
hsts:
headers:
stsSeconds: 15552000
stsIncludeSubdomains: true
tls:
certificates:
- certFile: /certs/mydomain.com/fullchain.pem
keyFile: /certs/mydomain.com/key.pem
options:
default:
minVersion: VersionTLS12
sniStrict: false
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305

В первом файле обратите внмание на почту, на которую регистрируется аккаунт acme для оповещений. Во втором файле если требуется поменяйте пути к используемым сертификатам, либо удалите этот блок. Эту конфигурацию я собирал под себя, дополнительно собрав параметры для достижения оценки A+ на SSL Labs.

Также приведу пример файла docker-compose.yml для запуска traefik:

version: "3.3"

services:
traefik:
container_name: traefik
image: traefik
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./static.yml:/etc/traefik/traefik.yml
- ./dynamic.yml:/etc/traefik/dynamic.yml
- ./acme.json:/acme.json
- ../../../certs/:/certs/:ro
labels:
- "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.enable=true"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.rule=Host(`traefik.mydomain.com`)"
- "traefik.http.routers.traefik.tls=true"
- "traefik.http.services.traefik.loadbalancer.server.port=8080"

networks:
default:
name: traefik

В соответствии с указанным конфигом у traefik будет доступ к хостовому докеру, файлам конфигурации, файлу настройки Lets Encrypt, папке с сертификатами. Все запросы по незащищенному протоколу http будут переадресованы на https. Панель управления будет доступна по адресу traefik.mydomain.com. Также обратим внимание на то, что сеть по умолчанию была названа traefik, как и указано в настройках.

Authelia

Теперь попробуем защитить доступ к панели управления traefik. На мой взгляд authelia - самый простой способ это реализовать. Как я писал выше, совместим traefik и authelia в одном сервисе.

Оставлю ссылку на документацию и приведу содержимое файлов конфигурации.

configuration.yml:

###############################################################
# Authelia configuration #
###############################################################

# This secret can also be set using the env variables AUTHELIA_JWT_SECRET_FILE
jwt_secret: sNne3LZG0+IKr6sxD4c/SjXHvz4iikdwXb//5P/mPgekYZ50Qnk+/h1cxUUFhL7y9gn+48PJMadK7OaBimJwIw==
default_redirection_url: https://auth.mydomain.com

server:
host: 0.0.0.0
port: 9091

log:
level: warn

totp:
issuer: auth.mydomain.com

authentication_backend:
file:
path: /config/users_database.yml
password:
algorithm: argon2id
iterations: 1
salt_length: 16
parallelism: 8
memory: 128

access_control:
default_policy: deny
rules:
# Rules applied to everyone
- domain: auth.mydomain.com
policy: bypass
- domain: "*.mydomain.com"
policy: two_factor

session:
name: authelia_session
# This secret can also be set using the env variables AUTHELIA_SESSION_SECRET_FILE
secret: jd13G+jO0w2hHaC1zRRBU/1dwYRez68zUumIxg1e1Bg+tqx9L7a1e0OmwQukvcum+5NtE5GxyOkq11YtGjy/8Q==
expiration: 3600 # 1 hour
inactivity: 300 # 5 minutes
domain: mydomain.com # Should match whatever your root protected domain is

regulation:
max_retries: 3
find_time: 120
ban_time: 300

storage:
local:
path: /config/db.sqlite3

notifier:
smtp:
username: noreply@mymail.com
# This secret can also be set using the env variables AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE
password: MailPassWord
host: mymail.com
port: 465
sender: noreply@mymail.com

users_database.yml:

###############################################################
# Users Database #
###############################################################

# This file can be used if you do not have an LDAP set up.
# Create new hashed password: docker run authelia/authelia:latest authelia hash-password <new_password>
# https://docs.authelia.com/configuration/authentication/file.html

# List of users
users:
user:
displayname: "User"
password: "$argon2id$v=19$m=65536,t=1,p=8$aU9VK0hnOGF2QXBiQzRNeQ$2LwKnFS63k7zgmL6uG2EXHQ/Jg2JrFkqvng5WMgpZ5U"
email: user@mymail.com
groups:
- admins

В соответствии с настройками, указанными выше, будет создан пользователь с паролем 00000000, для всех поддоменов будет использована двухфакторная аутентификация, которую надо будет установить после первого входа. Также настойчиво предлагаю сменить пароль, дабы он не хранился в репозитории.

Теперь добавим информацию о контейнере в docker-compose.yml:

...
services:
authelia:
container_name: authelia
image: authelia/authelia
restart: always
environment:
- TZ=Europe/Moscow
volumes:
- .:/config
labels:
- "traefik.enable=true"
- "traefik.http.routers.authelia.entrypoints=websecure"
- "traefik.http.routers.authelia.rule=Host(`auth.mydomain.com`)"
- "traefik.http.routers.authelia.tls=true"
...

Добавим новый middleware в динамичекие настройки traefik

http:
middlewares:
authelia:
forwardauth:
address: http://authelia:9091/api/verify?rd=https://auth.mydomain.com
trustForwardHeader: true
authResponseHeaders: Remote-User,Remote-Groups,Remote-Name,Remote-Email

В блок traefik’а предлагаю еще закинуть строчку, что он зависит от сервиса authelia

...
depends_on:
- authelia
...

Чтобы защита сервиса работала, надо ее явно добавить в middleware

...
- "traefik.http.routers.traefik.middlewares=authelia@docker"
...

Теперь можно закоммитить изменения и проверить изменения в работе.

Автоматическое обновление

Образа время от времени обновляются чтобы добавить новый функционал либо исправить уязвимости. Можно постоянно следить за обновлениями, а можно это отдать на откуп сервису. Один из сервисов, который мне очень понравился в плане подобных проверок это watchtower. Настраивается он очень просто. Достаточно развернуть сам сервис и в нужных контейнерах прописать метку.

Для начала, создадим новый сервис воспользовавшись образом containrrr/watchtower (документация здесь)

version: "3.3"

services:
watchtower:
image: containrrr/watchtower
restart: always
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_LABEL_ENABLE=true
- WATCHTOWER_POLL_INTERVAL=86400
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro

Теперь достаточно каждому образу, который требуется обновлять, добавить в labels строку

- "com.centurylinklabs.watchtower.enable=true"

Заключение

Этот пост получился очень длинным за счет листинга файлов конфигураций, а у нас уже есть вся основная инфраструктура. Управление сервисами происходит из репозитория, все секреты находятся отдельно на сервере, извне сервисы доступны по защищенному протоколу https, можно настроить общий wildcard-сертификат и отдельные сертификаты через Lets Encrypt, прокси помогает направить запросы в нужные места, есть дополнительная аутентификация для отедльных сервисов, образа даже могут сами обновлятся.

Можно уже полноценно использовать сервер для своих нужд. Только хотелось бы его еще немного облагородить. В соответствии со списком из предыдущего поста надо еще добавить отдельную сеть с контейнерами под базы данных, мониторинг и резервное копирование.