zulip / docker-zulip

Container configurations, images, and examples for Zulip.
https://zulip.com/
Apache License 2.0
586 stars 244 forks source link

Using docker secrets #272

Open arthurpankiewicz opened 4 years ago

arthurpankiewicz commented 4 years ago

I was wondering if somebody could help me out here, I am trying to convert the docker-compose.yml from the repo to use docker secrets. However, it seems like certain services defined with a command fail when trying to access the secrets. Also, the zulip service gives an error when trying to access certain secrets in the entrypoint.sh

Relevant code snippets:

docker-compose

secrets:
  zulip_secrets.yaml:
    file: './secrets/zulip_secrets.yaml'

...

  zulip:
    build: .
    ...
    secrets:
      - zulip_secrets.yaml

zulip-secrets.yaml

secrets:
  rabbitmq:
    password: 'REPLACE_WITH_SECURE_RABBITMQ_PASSWORD'
  postgres:
    password: 'REPLACE_WITH_SECURE_POSTGRES_PASSWORD'
  memcached:
    password: 'REPLACE_WITH_SECURE_MEMCACHED_PASSWORD'
  redis:
    password: 'REPLACE_WITH_SECURE_REDIS_PASSWORD'
  secret:
    key: 'REPLACE_WITH_SECURE_SECRET_KEY'
  email:
    password: '123456789'

error:

zulip_1      | Waiting for database server to allow connections ...
zulip_1      | /sbin/entrypoint.sh: line 311: SECRETS_postgres_password: parameter null or not set
docker-zulip_zulip_1 exited with code 1

Thanks :)

yasiruRathnayaka97 commented 3 years ago

@arthurpankiewicz you should have to name the password as "SECRETS_postgres_password:. Then it will work.

amolvishwakarma commented 2 years ago

You can also create a .env file and call in docker-compose.yml. Hope this will help you @arthurpankiewicz

.env

SECRETS_rabbitmq_password=abc
SECRETS_postgres_password=def
SECRETS_memcached_password=ghi
SECRETS_redis_password=jkl
SECRETS_secret_key=mno
SECRETS_email_password=xyz

docker-compose.yml

database:
    image: 'zulip/zulip-postgresql:10'
    container_name: "zulip_db"
    restart: unless-stopped
    env_file:
      - .env
mnzaki commented 2 years ago

You also need to handle POSTGRES_PASSWORD in the database service, probably set it to the value of SECRETS_postgres_password

so-rose commented 1 year ago

+1 for native docker secrets support; it's bad practice to pass secrets via environment variable.

docker secrets mounts secrets to files located at /run/secrets/<secret_name> at runtime. Thus, adding docker secrets support is easy: Just allow for a *_FILE environment variable as an alternative.

For example, the zulip service would be defined with:

    secrets:
      - chat__postgres_pass
      - chat__memcached_pass
      - chat__rabbitmq_pass
      - chat__redis_pass
      - chat__secret_key
      - chat__email_pass

    environment:
      ....
      SECRETS_rabbitmq_password_FILE: /run/secrets/chat__rabbitmq_pass
      SECRETS_postgres_password_FILE: /run/secrets/chat__postgres_pass
      SECRETS_memcached_password_FILE: /run/secrets/chat__memcached_pass
      SECRETS_redis_password_FILE: /run/secrets/chat__redis_pass

      SECRETS_secret_key_FILE: /run/secrets/chat__secret_key
      SECRETS_email_password_FILE: /run/secrets/chat__email_pass

Right now, this doesn't work by itself. But there's a workaround by modifying command to export them after a cat:

    entrypoint: []
    command: [
      "/bin/sh", "-c",
      '
      export SECRETS_rabbitmq_password="$$(cat $$SECRETS_rabbitmq_password_FILE)" &&
      export SECRETS_postgres_password="$$(cat $$SECRETS_postgres_password_FILE)" &&
      export SECRETS_memcached_password="$$(cat $$SECRETS_memcached_password_FILE)" &&
      export SECRETS_redis_password="$$(cat $$SECRETS_redis_password_FILE)" &&
      export SECRETS_secret_key="$$(cat $$SECRETS_secret_key_FILE)" &&
      export SECRETS_email_password="$$(cat $$SECRETS_email_password_FILE)" &&
      /sbin/entrypoint.sh app:run
      '
    ]

Complete Working Stack w/Secrets

Here's a full working stack, compatible with Docker Swarm (using Traefik), which uses docker secrets. Notice the POSTGRES_PASSWORD_FILE, and the mods to the redis and memcached command.

version: "3.8"

services:
  db:
    image: "zulip/zulip-postgresql:14"

    secrets:
      - chat__postgres_pass

    environment:
      POSTGRES_DB: "zulip"
      POSTGRES_USER: "zulip"
      POSTGRES_PASSWORD_FILE: /run/secrets/chat__postgres_pass

    volumes:
      - /data/nest/runtime/chat/db:/var/lib/postgresql/data:rw

    networks:
      - private

  memcached:
    image: memcached:alpine
    command:
      - "sh"
      - "-euc"
      - |
        echo 'mech_list: plain' > "$$SASL_CONF_PATH"
        echo "zulip@$$HOSTNAME:$$(cat $$MEMCACHED_PASSWORD_FILE)" > "$$MEMCACHED_SASL_PWDB"
        echo "zulip@localhost:$$(cat $$MEMCACHED_PASSWORD_FILE)" >> "$$MEMCACHED_SASL_PWDB"
        exec memcached -S

    secrets:
      - chat__memcached_pass

    environment:
      SASL_CONF_PATH: "/home/memcache/memcached.conf"
      MEMCACHED_SASL_PWDB: "/home/memcache/memcached-sasl-db"
      MEMCACHED_PASSWORD_FILE: /run/secrets/chat__memcached_pass

    networks:
      - private

  rabbitmq:
    image: rabbitmq:3.7.7

    secrets:
      - chat__rabbitmq_pass

    environment:
      RABBITMQ_DEFAULT_USER: "zulip"
      RABBITMQ_DEFAULT_PASS_FILE: /run/secrets/chat__rabbitmq_pass

    volumes:
      - /data/nest/runtime/chat/rabbitmq:/var/lib/rabbitmq:rw

    networks:
      - private

  redis:
    image: redis:alpine

    secrets:
      - chat__redis_pass

    command:
      - "sh"
      - "-euc"
      - |
        echo "requirepass '$$(cat $$REDIS_PASS_FILE)'" > /etc/redis.conf
        exec redis-server /etc/redis.conf

    environment:
      REDIS_PASS_FILE: /run/secrets/chat__redis_pass

    volumes:
      - /data/nest/static/chat/redis:/data:rw

    networks:
      - private

  zulip:
    image: zulip/docker-zulip:5.7-0
    entrypoint: []
    command: [
      "/bin/sh", "-c",
      '
      export SECRETS_rabbitmq_password="$$(cat $$SECRETS_rabbitmq_password_FILE)" &&
      export SECRETS_postgres_password="$$(cat $$SECRETS_postgres_password_FILE)" &&
      export SECRETS_memcached_password="$$(cat $$SECRETS_memcached_password_FILE)" &&
      export SECRETS_redis_password="$$(cat $$SECRETS_redis_password_FILE)" &&
      export SECRETS_secret_key="$$(cat $$SECRETS_secret_key_FILE)" &&
      export SECRETS_email_password="$$(cat $$SECRETS_email_password_FILE)" &&
      /sbin/entrypoint.sh app:run
      '
    ]

    ulimits:
      nofile:
        soft: 1000000
        hard: 1048576

    secrets:
      - chat__postgres_pass
      - chat__memcached_pass
      - chat__rabbitmq_pass
      - chat__redis_pass
      - chat__secret_key
      - chat__email_pass

    environment:
      DB_HOST: "db"
      DB_HOST_PORT: "5432"
      DB_USER: "zulip"

      SETTING_MEMCACHED_LOCATION: "memcached:11211"
      SETTING_RABBITMQ_HOST: "rabbitmq"
      SETTING_REDIS_HOST: "redis"

      SECRETS_rabbitmq_password_FILE: /run/secrets/chat__rabbitmq_pass
      SECRETS_postgres_password_FILE: /run/secrets/chat__postgres_pass
      SECRETS_memcached_password_FILE: /run/secrets/chat__memcached_pass
      SECRETS_redis_password_FILE: /run/secrets/chat__redis_pass

      SECRETS_secret_key_FILE: /run/secrets/chat__secret_key
      SECRETS_email_password_FILE: /run/secrets/chat__email_pass

      DISABLE_HTTPS: "True"
      SSL_CERTIFICATE_GENERATION: "self-signed"
      SETTING_EXTERNAL_HOST: "chat.<redacted>"

      SETTING_ZULIP_ADMINISTRATOR: "zulip@<redacted>"
      SETTING_NOREPLY_EMAIL_ADDRESS: "zulip@<redacted>"
      SETTING_ADD_TOKENS_TO_NOREPLY_ADDRESS: "False"

      SETTING_EMAIL_HOST: "mx.<redacted>"
      SETTING_EMAIL_PORT: "465"
      SETTING_EMAIL_USE_SSL: "True"
      SETTING_EMAIL_HOST_USER: "zulip@<redacted>"

      ZULIP_AUTH_BACKENDS: "EmailAuthBackend"
      # Uncomment this when configuring the mobile push notifications service
      # SETTING_PUSH_NOTIFICATION_BOUNCER_URL: 'https://push.zulipchat.com'

    volumes:
      - /data/nest/static/chat/zulip:/data:rw

    deploy:
      labels:
        traefik.enable: "true"

        # Web Config
        traefik.http.routers.chat_zulip.rule: "Host(`chat.<redacted>`)"
        traefik.http.routers.chat_zulip.entrypoints: "web,websecure"
        traefik.http.routers.chat_zulip.service: "chat_zulip"
        traefik.http.services.chat_zulip.loadbalancer.server.port: 80

        traefik.http.routers.chat_zulip.tls: "true"
        traefik.http.routers.chat_zulip.tls.certresolver: "letsencrypt"

    networks:
      - mesh_public
      - private

secrets:
  chat__postgres_pass:
    external: true

  chat__memcached_pass:
    external: true

  chat__rabbitmq_pass:
    external: true

  chat__redis_pass:
    external: true

  chat__secret_key:
    external: true

  chat__email_pass:
    external: true

networks:
  mesh_public:
    external: true

  private:
    driver: overlay

Perhaps it'd be worth documenting this usage?