Dockerシークレットとは?
Docker シークレットとは、コンテナ化されたアプリケーションやシステムで使用される機密データ、値、または構成情報のことを指します。これらは表示されるべきではなく、アクセスが必要なコンテナのみに利用可能であるべきものです。
この記事では、Docker シークレットの包括的なガイドを提供します。具体的には、シークレットの定義、シークレットとして扱われるデータの種類、Docker CLI や Docker Compose を使ったシークレットの設定方法、そしてそれに関連するセキュリティのベストプラクティスについて解説します。
機密情報の定義
機密情報とは、不正アクセスを受けた場合に組織に重大な情報漏洩リスクや金銭的損失をもたらす可能性のあるデータのことです。シークレット(Secrets)はこの機密情報に分類され、公開されることを前提としておらず、容易にアクセスや閲覧ができない形で保護されるべきものです。
どの情報を機密情報とみなすかは、利用する文脈によって異なります。たとえば Docker コンテナの文脈では、アプリケーションのシークレット、データベース認証情報、SSH 鍵などは、漏えいした従業員のメールアドレスよりもはるかに機密性が高い情報です。安全でない運用によって攻撃者がシステムへアクセスできてしまう場合、直接的で深刻な被害を与えるおそれがあります。
一方、従業員のメールアドレスは推測可能であり、必ずしも非公開である必要はありません。確かに、フィッシングやソーシャルエンジニアリング攻撃に悪用される可能性はありますが、それ自体の流出が即座に重大なリスクを引き起こすわけではありません。
したがって Docker Secrets を使用する際は、まずどの情報が文脈上「機密」にあたるのかを明確に分類することが重要です。そのうえで、安全に取り扱うためのベストプラクティスに従う必要があります。
以下では、認識しておくべきさまざまな種類のシークレットについて説明します。
Docker シークレットの種類
シークレットとは、特権アカウント・アプリケーション・サービスへのアクセスを許可するための認証情報です。言い換えれば、これはシステムの「鍵」にあたるものであり、信頼できない第三者に渡すことは重大なリスクを伴います。以下に代表的なシークレットの例を挙げます。
パスワード
パスワードは、本人と第三者サービス提供者のみが知っているテキスト文字列またはトークンです。パスワードやアクセストークンは典型的なシークレットの例であり、これらが外部に漏れると攻撃者がバックエンドに不正ログインし、アカウントをなりすまして悪意ある行為を行う可能性があります。
SSHキー
SSHキー、特にその**秘密鍵(private key)**はシークレットに該当します。SSH プロトコルはアクセス制御の仕組みであり、主な目的はユーザーやシステムの認証です。このプロトコルを利用することで、コンピューター間で安全にリモートログインが可能になります。公開鍵は秘密鍵から導き出すことができるため、秘密鍵が漏洩すると攻撃者はそれを使ってシステムにログインできてしまいます。
SSL(TLS) 証明書
SSL は、クライアントとサーバー間で安全な通信を確立するためのプロトコルです。たとえばウェブサイトを閲覧しているとき、ログインフォームの入力やオンラインバンキングでのやり取りを安全に保ち、第三者が通信内容を盗み見ることを防ぎます。
また、SSL(または TLS)証明書は mTLS(双方向 TLS)通信 にも利用され、アプリケーション間通信を保護し、さまざまな攻撃を防止します。
その他の機密データ
その他、以下のような情報も重要なシークレットに該当します。
- データベース認証情報(Database credentials) ユーザー名やデータベース名はデータベース接続に使用されます。 攻撃者がパスワードを入手した場合でも、残りの認証情報を推測されることで不正アクセスにつながるおそれがあります。
- ハードコードされた認証情報(Hard-coded credentials) 一部のアプリケーションには、特定の認証情報でアクセスできる隠しバックドアが組み込まれていることがあります。 これらはコードに埋め込まれていても、外部に漏洩すれば重大なセキュリティリスクとなります。
Docker CLI でシークレットを管理
ここでは、ローカルの Docker デーモンを起動し、CLI(コマンドラインインターフェース)を使って Docker シークレットを管理する方法を紹介します。
docker secret –help コマンドを実行すると、シークレットを管理するために利用できるオプション一覧が表示されます。secrets:
$ docker secret --help
Usage: docker secret COMMAND
Manage Docker secrets
Commands:
create Create a secret from a file or STDIN as content
inspect Display detailed information on one or more secrets
ls List secrets
rm Remove one or more secrets
Code language: JavaScript (javascript)
ひとつずつ見ていきましょう。
作成
新しいシークレットを作成するには、create コマンドを使用します。このとき、ファイルを指定するか、コマンドラインからシークレットの内容を直接入力して登録します。
$ cat secret.json
{
"username": "theo",
"password": "password"
}%
Code language: JavaScript (javascript)
技術的およびセキュリティ上の理由から、dockerd サービスが Swarm モード で動作していない場合は、シークレットを作成することはできません。Swarm モードが有効になっていない状態でシークレットを作成しようとすると、次のようなメッセージが表示されます。
$ docker secret create my_credentials secret.json
Error response from daemon: This node is not a swarm manager. Use "docker swarm init" or "docker swarm join" to connect this node to swarm and try again.
Code language: JavaScript (javascript)
docker swarm init を実行すると Swarm モードが有効になり、CLI を使用してシークレットを作成できるようになります。
$ docker info |grep Swarm
Swarm: inactive
$ docker swarm init
Swarm initialized: current node (dfqc8qfvy8992lmmx737d0p4e) is now a manager.
$ docker info |grep Swarm
Swarm: active
$ docker secret create my_credentials secret.json
szv7anyechly226td0gmdtvxk
このコマンドを実行した後に表示される文字列は、参照用の一意の ID フィールドです。
以下は、STDIN(標準入力)を使用する例です。
$ echo -n "password" | docker secret create a_password -Aixt00c7zvhr1b1n9w673lz22
Code language: PHP (php)
docker secret ls
docker secret ls コマンドを使用すると、作成されたシークレットの一覧を確認できます。
$ docker secret ls
ID NAME DRIVER CREATED UPDATED
aixt00c7zvhr1b1n9w673lz22 a_password 3 minutes ago 3 minutes ago
szv7anyechly226td0gmdtvxk my_credentials 5 minutes ago 5 minutes ago
この出力には、先ほど説明した ID フィールド、シークレット名、および作成・最終更新からの経過時間が表示されます。また、DRIVER は外部のシークレットストア(たとえば Vault など)からシークレットの値を取得する際に使用されるプロバイダー名を示しています。
docker secret inspect
特定のシークレットを確認するには、docker secret inspect コマンドを使用します。このとき、シークレットの ID または 名前 を指定する必要があります。
$ docker secret inspect my_credentials
[
{
"ID": "szv7anyechly226td0gmdtvxk",
"Version": {
"Index": 11
},
"CreatedAt": "2023-02-06T14:32:41.4086062Z",
"UpdatedAt": "2023-02-06T14:32:41.4086062Z",
"Spec": {
"Name": "my_credentials",
"Labels": {}
}
}
]
Code language: JavaScript (javascript)
もちろん、ここでシークレットの実際の値(ペイロード)を確認することはできません。表示されるのは、シークレットに関する メタデータ のみです。
docker secret rm
ocker secret rm コマンドを使用すると、シークレットを削除できます。削除したいシークレットの ID または 名前 を指定して実行します。
$ docker secret rm a_password
a_password
シークレットがサービスやコンテナで使用されている場合、そのシークレットを削除することはできません。これを実際に確認するには、次のようにサービスを作成してシークレットを使用し、その後で削除を試みてみます。
$ docker service create --name memcached --secret my_credentials memcached:alpine
$ docker exec `docker ps -f name=memcached -q` ls -l /run/secrets
total 4
-r--r--r-- 1 root root 48 Feb 7 10:54 my_credentials
$ docker secret rm my_credentials
Error response from daemon: rpc error: code = InvalidArgument desc = secret 'my_credentials' is in use by the following service: memcached
Code language: JavaScript (javascript)
サービスからシークレットをアンマウントするには、docker service update コマンドに –secret-rm パラメータを指定して実行します。
$ docker service update --secret-rm my_credentials memcached
Memcached
$ docker exec `docker ps -f name=memcached -q` ls -l /run/secrets
ls: /run/secrets: No such file or directory
$ docker secret rm my_credentials
My_credentials
Code language: JavaScript (javascript)
新しいシークレットを作成した後は、docker service update コマンドに –secret-add パラメータを指定して実行することで、再びシークレットを追加できます。
$ docker service update --secret-add my_credentials memcached
シークレットの実際の内容自体は暗号化されていない点に注意してください。ただし、シークレット作成時と同様に、その内容は安全に渡されます。
$ docker exec `docker ps -f name=memcached -q` cat /run/secrets/my_credentials
{
"username": "theo",
"password": "password"
}
Code language: JavaScript (javascript)
Docker Compose でシークレットを管理
Docker Compose を使用してシークレットを指定することもできます。docker-compose.yml ファイルを定義する際、コンテナサービス内でシークレットを作成・マウントするための特定のフィールドを追加する必要があります。たとえば、次の Redis サービスの例を見てみましょう。
version: "3.9"
services:
redis:
image: redis:latest
container_name: redis
command: [
"bash", "-c",
'
docker-entrypoint.sh
--requirepass "$(cat $REDIS_PASS_FILE)"
'
]
volumes:
- .:/var/lib/redis/data
- ./redis.conf:/usr/local/etc/redis/redis.conf
environment:
REDIS_PASS_FILE: /run/secrets/redis_password
secrets:
- redis_password
ports:
- "6379"
secrets:
redis_password:
file: redis_password.txt
Code language: PHP (php)
Compose では、グローバルな secrets フィールドでシークレットの供給元を指定できます。ここでは、redis_password.txt というローカルファイル内の値を参照し、Redis サービス定義ではシークレット名として redis_password を使用しています。
最後に、Redis サーバーが /run/secrets/redis_password からその値を読み取れるようにする必要があります。現在のイメージはファイルからの直接読取に対応していないため、環境変数と cat コマンドを用いて値を docker-entrypoint.sh にパイプで渡します。
イメージによってはカスタムコマンドを使わずにパスワードを読み取れるものもあります。たとえば公式の MySQL イメージでは、MYSQL_ROOT_PASSWORD_FILE や MYSQL_PASSWORD_FILE などの環境変数で、マウントしたシークレットファイルを参照できます。
なお、事前に docker secret create を実行する必要はありません。docker-compose がシークレットの作成と管理を行います。
Docker がシークレットを保存するしくみ
技術的およびセキュリティ的な観点から少し踏み込むと、Docker がコンテナのシークレットを処理する方法は次のとおりです。
Docker CLI は、シークレットを管理するために HTTP を使用して dockerd サービスとやり取りします。たとえば、このコードは docker secret create コマンドをデーモンに送信する処理を扱います。
dockerd サービスは docker secret コマンドの一覧を処理し、コンテナのバックエンドサービスとやり取りします。
コンテナのバックエンドでは、コンテナが作成される際に、認証情報やシークレットを環境変数内に保存することを避けます。その代わりに、一時ファイルシステム(tmpfs)上のマウントの下に /run/secrets/<secret_name>(Windows の場合は C:\ProgramData\Docker\secrets<secret_name>)というファイルを提供します。
このコードのセクションは、コンテナ内にシークレットをマウントする処理を担当しています。これは createSpec 関数の実行中に呼び出され、イメージに基づいてコンテナを作成する際の基本呼び出しです。最後に、シークレットタイプの定義はここにあります。
Dockerセキュリティベストプラクティス
コンテナのセキュリティを確保するためには、シークレットを扱う際に特定のセキュリティ対策を守る必要があります。以下は、これらの環境内で保存される機密情報を保護するための Dockerfile セキュリティのベストプラクティスと推奨事項 です。
シークレットを環境変数として渡さない
シークレットを環境変数(env)として渡すのは便利ですが、安全ではありません。docker inspect コマンドを使用すると、ノードに適切なアクセス権を持つ人であれば、その値を平文で確認できてしまいます。
$ docker run --env PASSWORD=mypassword --name memcached -d memcached:alpine
555741257afe8e1e03b9728c1a49bba9883daad9b4795c818a06f6534b7fc347
$ docker exec `docker ps -f name=memcached -q` env | grep PASSWORD
PASSWORD=mypassword
Code language: JavaScript (javascript)
シークレット管理サービスを利用する
Docker はコンテナ内でシークレットを安全にマウントしますが、実際のシークレットの保管は IT チームによって管理される必要があります。シークレットのローテーション(定期的な更新)、アクセス制御、暗号化などの処理は、Vault のような専用のシークレット管理サービスに委ねられます。これらは、たとえば自動化スクリプトの一部として動作させることができます。
シークレット管理サービスは、組織の運用に必要なすべてのシークレットを安全に保管します。CI/CD パイプラインを通じてコンテナやサービスを構築する際、シークレットは取得され、Docker サービスまたは docker-compose にインライン変数として渡されます。
シークレットをローテーションする必要がある場合、ワークフローはすべてのコンテナを順に処理し、そのシークレットを更新してアプリケーションの再読み込みをトリガーします。これにより、アプリケーションコンテナ内でシークレットを安全に保管・取得することが可能になります。
Dockerfile にシークレットを記載しない
Dockerfile にシークレットを記述してはいけません。これは当然のことですが、バージョン管理システム内にシークレットをハードコーディングすることも避けるべきです(それ自体が非常に危険です)。
まとめ
これで Docker シークレットについての解説は終了です。シークレットは、コードと設定情報を分離し、アプリケーションの認証情報が外部に漏れないようにするために不可欠です。
このガイドでは、Docker シークレットとは何か、なぜセキュリティやコンプライアンスの観点で重要なのかを説明しました。さらに、Docker CLI や Docker Compose を使用してシークレットを管理する方法を紹介し、シークレットを安全に扱うためのいくつかの Docker セキュリティ・ベストプラクティスについても説明しました。
Docker シークレットの運用において、脅威や不審な挙動をスケーラブルに検知するには、より高度なツールが必要です。
たとえば Falco を使用すれば、「ランタイムセキュリティ」層を構築し、環境変数や特定のシステムコールを監視したり、誤ってシークレットがバージョン管理にコミットされるのを防いだりして、暗号化された状態からシークレットが漏れるのを積極的に検知・防止することができます。