Skip to main content

Among Us の便利ボット AutoMuteUs の通信を Nginx と Let’s Encrypt で暗号化する


はじめに

Among Us のゲームの進行に応じて Discord 上の各参加者のミュート・アンミュートを自動制御してくれるとても便利なボット、AutoMuteUs を初めて触りました。

動作にはボットのインスタンスを立てる必要があり、公開インスタンスが満員だったため、その場で自前サーバでのセルフホスト構成を作ったのですが、Docker でホストできることもあって、動かすだけならとても簡単です。Discord でのボット操作を一手に引き受けられるのであれば、インタネット上で公開する必要すらなく、LAN 内に配置するだけで済みます。

が、自分が居ないときでも仲間内で共同利用できるようにしたい場合、どうしてもボットのインスタンスはインタネット上でホストする必要があり、であれば少しでもセキュアにしたい感触があります。

そんなわけで、平文で行われている一部の HTTP や WebSocket の通信を、Let’s Encrypt の SSL 証明書とリバースプロキシ(Nginx)で暗号化したので、実装例の紹介です。

通信と登場人物

通信が少し複雑なので、登場人物と併せて簡単に整理します。デフォルト状態で普通にホストすると、こんな感じになります。

  • AmongUsCapture
    • 実行中の Steam 版 Among Us のメモリ(たぶん)を横から覗いて、ゲームの状態を監視し続けるアプリケーション
    • 監視結果(ゲームの状態)は AutoMuteUs の galactus に随時投げる
    • ボット経由での Discord の操作も担うことがある(たぶん)
  • AutoMuteUs
    • Docker ホスト上で動作するボットのインスタンス
    • ゲームの状態を AmongUsCapture から受け取り、 Discord の操作(参加者のミュートやアンミュート)を担う(galactus
    • Discord でのユーザインタラクションやメッセージ操作を担う(amongusdiscord)
    • ほか、galactus と amongusdiscord の連携用に Redis、統計情報の保存などのために PostgreSQL も動作する

課題

AmongUsCapture と galactus 間の通信が、平文の HTTP や WebSocket で行われています。

このため、AutoMuteUs を下図のようにインタネット上でホストした場合、インタネット上に非暗号化トラフィックが流れることになり、経路上での盗聴に対して脆弱になります。誤情報を流し込むこともできてしまいそうですね。

実際にパケットを覗くと、WebSocket のペイロードが露出していることがわかります。

なお、AutoMuteUs を LAN 内でホストする(ゲームの実行端末で Docker を動かすか、または仮想マシンなどで Docker ホストを用意する)場合は、この通信も LAN 内で閉じることになるので、ほとんどの場合は気にしなくても大丈夫でしょう。

対策

galactus の手前に Nginx でリバースプロキシを構成して、経路を HTTPS/WSS 化させます。

この時の SSL 証明書には、自己署名証明書ではなく、Let’s Encrypt から発行された正規の証明書を利用します。

実装

実装していきます。

方針

大げさな実装にはしたくなかったので、

  • AutoMuteUs 用の Compose ファイルのカスタマイズ
  • AutoMuteUs 用の .env ファイルのカスタマイズ

だけで完結するようにしました。

つまり、Nginx 用の設定ファイルの用意や ACME クライアントの手動構成などは不要です。この目的で、既成の nginx-proxyletsencrypt-nginx-proxy-companion を組み込みます。

できたもの

最新のものは Gist に載せました。

前提

  • AutoMuteUs 用のサブドメインを保有していること
  • そのサブドメインが、AutoMuteUs ホストのグローバル IP アドレスに解決できること
  • AutoMuteUs ホストで必要なポートがブロックされていないこと
    • Let’s Encrypt の HTTP-01 チャレンジで利用するポート(80/tcp
    • リバースプロキシの待ち受けに利用するポート(後述の環境変数 NGINXPROXY_EXTERNAL_PORT で指定)

.env ファイル

# Please refer to the latest versions of Galactus and AutoMuteUs when specifying these versions:
# Automuteus: https://github.com/denverquane/automuteus/releases
# or https://hub.docker.com/repository/docker/denverquane/amongusdiscord/tags?page=1&ordering=last_updated

# Galactus: https://github.com/automuteus/galactus/releases
# or https://hub.docker.com/repository/docker/automuteus/galactus/tags?page=1&ordering=last_updated

AUTOMUTEUS_TAG=6.10.1
GALACTUS_TAG=2.4.1

# change these, but see comment below about HOST/PORT
DISCORD_BOT_TOKEN=<YOUR_BOT_TOKEN>
GALACTUS_HOST=https://automuteus.example.com:8443
GALACTUS_EXTERNAL_PORT=8123

# Please refer to the latest versions of nginx-proxy and letsencrypt-nginx-proxy-companion when specifying these versions:
# nginx-proxy: https://github.com/nginx-proxy/nginx-proxy/releases
# or https://hub.docker.com/r/jwilder/nginx-proxy/tags?page=1&ordering=last_updated

# letsencrypt-nginx-proxy-companion: https://github.com/nginx-proxy/docker-letsencrypt-nginx-proxy-companion/releases
# or https://hub.docker.com/r/jrcs/letsencrypt-nginx-proxy-companion/tags?page=1&ordering=last_updated

NGINXPROXY_TAG=0.8.0
NGINXPROXY_COMPANION_TAG=2.0.2
NGINXPROXY_EXTERNAL_PORT=8443
LETSENCRYPT_HOST=automuteus.example.com
LETSENCRYPT_EMAIL=automuteus@example.com

# recommend changing these to something more secure
POSTGRES_USER=postgres
POSTGRES_PASS=putsomesecretpasswordhere

# GALACTUS_HOST can include the port or not. If you don't include the port,
# it will default to :80/:443 depending on http:// v https://
# **Make sure that GALACTUS_EXTERNAL_PORT matches the Port for the above host, UNLESS you use a reverse proxy/nginx!!!**
# Ex: if GALACTUS_HOST=http://localhost, then GALACTUS_EXTERNAL_PORT should be 80 (HTTP)
# Ex: if GALACTUS_HOST=https://localhost, then GALACTUS_EXTERNAL_PORT should be 443 (HTTPS)
# Ex: if GALACTUS_HOST=http://localhost:8123, then GALACTUS_EXTERNAL_PORT should be 8123
# If you use a reverse proxy, then GALACTUS_HOST should have the port of your reverse proxy, and it should proxy to the
# GALACTUS_EXTERNAL_PORT (ex 443 -> 8123)

# Optional, leave alone by default
EMOJI_GUILD_ID=
# comma-separated
WORKER_BOT_TOKENS=
CAPTURE_TIMEOUT=
AUTOMUTEUS_LISTENING=

# DO NOT change these unless you really know what you're doing
BROKER_PORT=8123
GALACTUS_PORT=5858
GALACTUS_REDIS_ADDR=redis:6379
AUTOMUTEUS_REDIS_ADDR=redis:6379
GALACTUS_ADDR=http://galactus:5858
POSTGRES_ADDR=postgres:5432

環境変数を 5 つ追加しています。

また、既存の環境変数群は次のように工夫します。

  • GALACTUS_HOST
    • HTTPS で記述し、ポート番号は前述の NGINXPROXY_EXTERNAL_PORT と一致させます
  • GALACTUS_EXTERNAL_PORT
    • galactus の待ち受けポート番号なので、つまり、リバースプロキシが転送する先のポート番号でもあります。例ではデフォルトの 8123 としています。

docker-compose.yml ファイル

version: "3"

services:
  automuteus:
    # Either:
    # - Use a prebuilt image
    image: denverquane/amongusdiscord:${AUTOMUTEUS_TAG:?err}
    # - Build image from local source
    #build: .
    # - Build image from github directly
    #build: http://github.com/denverquane/automuteus.git

    restart: always
    ports:
      # 5000 is the default service port
      # Format is HostPort:ContainerPort
      - ${SERVICE_PORT:-5000}:5000
    environment:
      # These are required and will fail if not present
      - DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN:?err}
      - HOST=${GALACTUS_HOST:?err}
      - POSTGRES_USER=${POSTGRES_USER:?err}
      - POSTGRES_PASS=${POSTGRES_PASS:?err}

      # These Variables are optional
      - WORKER_BOT_TOKENS=${WORKER_BOT_TOKENS:-}
      - EMOJI_GUILD_ID=${EMOJI_GUILD_ID:-}
      - CAPTURE_TIMEOUT=${CAPTURE_TIMEOUT:-}
      - AUTOMUTEUS_LISTENING=${AUTOMUTEUS_LISTENING:-}

      # Do **NOT** change this
      - REDIS_ADDR=${AUTOMUTEUS_REDIS_ADDR}
      - GALACTUS_ADDR=${GALACTUS_ADDR}
      - POSTGRES_ADDR=${POSTGRES_ADDR}
    depends_on:
      - redis
      - galactus
      - postgres
      - nginx-proxy-letsencrypt
    volumes:
      - "bot-logs:/app/logs"
  galactus:
    ports:
      # See sample.env for details, but in general, match the GALACTUS_EXTERNAL_PORT w/ the GALACTUS_HOST's port
      - ${GALACTUS_EXTERNAL_PORT:-8123}:${BROKER_PORT}
    image: automuteus/galactus:${GALACTUS_TAG:?err}
    restart: always
    environment:
      # Do **NOT** change these
      - DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN:?err}
      - BROKER_PORT=${BROKER_PORT}
      - REDIS_ADDR=${GALACTUS_REDIS_ADDR}
      - GALACTUS_PORT=${GALACTUS_PORT}
      - VIRTUAL_HOST=${LETSENCRYPT_HOST}
      - VIRTUAL_PORT=${GALACTUS_EXTERNAL_PORT:-8123}
      - LETSENCRYPT_HOST=${LETSENCRYPT_HOST}
      - LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL}
    depends_on:
      - redis
      - nginx-proxy-letsencrypt

  redis:
    image: redis:alpine
    restart: always
    volumes:
      - "redis-data:/data"

  postgres:
    image: postgres:12-alpine
    restart: always
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASS}
    volumes:
      - "postgres-data:/var/lib/postgresql/data"

  nginx-proxy:
    image: jwilder/nginx-proxy:${NGINXPROXY_TAG:?err}
    restart: always
    container_name: nginx-proxy
    ports:
      - 80:80
      - ${NGINXPROXY_EXTERNAL_PORT:-443}:443
    volumes:
      - certs:/etc/nginx/certs:ro
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro

  nginx-proxy-letsencrypt:
    image: jrcs/letsencrypt-nginx-proxy-companion:${NGINXPROXY_COMPANION_TAG:?err}
    restart: always
    environment:
      - NGINX_PROXY_CONTAINER=nginx-proxy
    container_name: nginx-proxy-letsencrypt
    depends_on:
      - nginx-proxy
    volumes:
      - certs:/etc/nginx/certs:rw
      - vhost:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - /var/run/docker.sock:/var/run/docker.sock:ro

volumes:
  bot-logs:
  redis-data:
  postgres-data:
  certs:
  vhost:
  html:

nginx-proxynginx-proxy-letsencrypt を追加し、その動作に必要なもろもろを修正しています。設定は環境変数ファイルから読み込まれるため、修正は不要です。

効果

.au new したあとにボットから届く DM のリンクを踏むと、AmongUsCapture が galactus に HTTPS と WSS でつなぐようになります。

パケットの中も暗号化されました。

補足

今回は、Google Cloud Platform(GCP)の Google Computing Engine(GCE)で、f1-micro な Container-Optimized OS インスタンスを作成しました。

をしています。ドメインは手持ちのもので、DNS サーバには Azure DNS を使っています。

一点だけ、この方式の気に入らないところは、Let’s Encrypt の HTTP-01 チャレンジで使うため 80 番ポートを閉じられないところです。

HTTP-01 チャレンジの完了後は、nginx-proxy は 80 番へのアクセスには 301 を返して HTTPS に誘導するようになりはするので、NGINXPROXY_EXTERNAL_PORT を 443 以外にしておけば AutoMuteUs の存在が過度に露出することにはなりませんが、それでもポートスキャンなどで Nginx まではたどり着けてしまうのがちょっと微妙ですね。Let’s Encrypt を使う場合、普段は DNS-01 チャレンジを好んでいますが、letsencrypt-nginx-proxy-companion で対応していないので妥協しています。

なお、80 番ポートは HTTP-01 チャレンジの発生時(≒リバースプロキシの起動直後と期限が近付いてからの更新時)だけ開放されていればよいので、不測の再起動や更新忘れなどへの備えを犠牲にして(つまりサービスレベルを下げて)よければ、普段は閉じておくのも手です(閉じています)。

@kurokobo

くろいです。ギターアンサンブルやら音響やらがフィールドの IT やさんなアルトギター弾き。たまこう 48 期ぎたさん、SFC '07 おんぞう、新日本ギターアンサンブル、Rubinetto。今は野良気味。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です