🔐 SSHでDockerソケットに安全にアクセスする — ポート2375を開かないでください!

Dockerソケットをインターネットに直接公開することは、サーバーのrootアカウントを公開することと同じです。

>

🎯 この記事で扱うこと

  • Dockerソケット(/var/run/docker.sock)がなぜ危険なのか
  • SSHトンネルでDockerソケットに安全にアクセスする3つの方法
  • DOCKER_HOST環境変数 vs docker contextの比較
  • CI/CDパイプラインでの実践的な活用
  • 見落としがちなセキュリティミスのまとめ

📌 導入 / 背景

リモートサーバーのDockerを管理したいとき、最初に思いつく方法があります。DockerデーモンのTCPポート(2375または2376)を開放し、外部からアクセスすることです。

しかし、ちょっと待ってください、これは本当に危険な選択です。

Dockerデーモンは、デフォルトでローカルクライアントからのリクエストのみを受け付けるUnixソケットでリッスンしています。これをTCPで開放してしまうとどうなるでしょうか?

Dockerデーモンはホストシステムでroot権限で実行されるため、認証なしでそのAPIにアクセスできるということは、サーバーへのrootアクセス権限を世界に公開することと同じです。

実際にインターネットに2375ポートを開放しているサーバーは、数分以内にボットによって発見され、悪意のあるコンテナがデプロイされます。暗号通貨マイニング、ランサムウェア、バックドアのインストールに繋がる事例も少なくありません。

では、リモートからDockerを安全に管理するにはどうすればよいでしょうか?答えはまさにSSHトンネルです。


🔍 DockerソケットとSSHトンネルの基本概念

Dockerソケットとは?

Docker CLI(dockerコマンド)がDockerデーモンと通信するチャネルです。デフォルトのパスは/var/run/docker.sockであり、このUnixソケットファイルを介してすべてのDockerコマンドが処理されます。

[docker CLI] ──── /var/run/docker.sock ──── [dockerd 데몬]

SSHトンネルとは?

SSH接続の上に他のプロトコルのトラフィックを安全に転送する技術です。SSHがすでに暗号化、認証、整合性検証をすべて処理してくれるため、別途TLS設定なしでも安全な通信が可能です。


💻 方法1: SSHローカルフォワーディングでソケットトンネリング(最も基本的な方法)

リモートサーバーのDockerソケットをローカルポートにフォワーディングする方式です。

# ローカルポート2375をリモートサーバーのDockerソケットに転送
ssh -L localhost:2375:/var/run/docker.sock user@REMOTE_HOST -fN

各オプションの説明:

  • -L localhost:2375:/var/run/docker.sock : ローカル2375ポートをリモートソケットに接続
  • -f : バックグラウンドで実行
  • -N : リモートコマンドを実行せずにトンネルのみを維持

トンネルが開かれた後、環境変数を設定してDocker CLIがこのトンネルを使用するようにします:

export DOCKER_HOST=tcp://localhost:2375
docker ps       # リモートサーバーのコンテナリストを表示
docker images   # リモートサーバーのイメージリストを表示

この方式の利点は、インターネットにポートを公開せずに、SSHの検証された認証方式(キー、MFAなど)をそのまま活用することです。

トンネル終了時:

# トンネルプロセスを検索
ps aux | grep ssh

# PIDで終了
kill $PID

# DOCKER_HOSTを解除
unset DOCKER_HOST

💻 方法2: DOCKER_HOSTにssh://スキームを使用(Docker 18.09+)

Docker 18.09バージョンからSSHをネイティブでサポートしています。トンネルを別途作成する必要なく、DOCKER_HOST環境変数に直接SSHアドレスを指定できます。

# 環境変数方式(一時的)
export DOCKER_HOST=ssh://docker-user@host1.example.com
docker ps
docker run -d nginx

# 使用後に元に戻す
unset DOCKER_HOST

この方式は、コンテキストを別途生成する必要がないため、他のエンジンと一時的に接続する際に便利です。

⚠️ 重要: ssh://方式は必ずSSHキー認証が必要です。パスワード認証はDockerではサポートされておらず、DOCKER_HOSTベースの設定では不可能です。

SSHキー登録:

# キーを生成
ssh-keygen -t ed25519 -C "docker-remote-key"

# リモートサーバーに公開鍵を登録
ssh-copy-id docker-user@host1.example.com

# ssh-agentにキーを追加
ssh-add ~/.ssh/id_ed25519

SSH接続の最適化(~/.ssh/config):

Host docker-remote
    HostName host1.example.com
    User docker-user
    IdentityFile ~/.ssh/id_ed25519
    ControlMaster auto
    ControlPath ~/.ssh/control-%C
    ControlPersist yes

ControlMasterとControlPersist設定により、1つのSSH接続を複数のdocker CLI呼び出しで再利用できるため、毎回ハンドシェイクを繰り返す必要がありません。

その後、はるかに簡潔に使用可能です:

export DOCKER_HOST=ssh://docker-remote
docker ps

💻 方法3: docker contextでリモートエンジン管理(運用推奨)

複数のリモートサーバーを頻繁に行き来して管理する場合、docker contextが最もクリーンな方法です。

# リモートコンテキストを作成
docker context create my-remote-engine 
  --docker "host=ssh://docker-user@host1.example.com" 
  --description "Production Server"

# コンテキストリストを確認
docker context ls

# コンテキストを切り替え
docker context use my-remote-engine

# これで全てのdockerコマンドがリモートサーバーで実行されます
docker ps
docker run -d --name web nginx

# ローカルに戻る
docker context use default

リモートエンジンコンテキストを作成した後、docker context useでアクティブ化すると、VS CodeおよびDocker CLIの両方がそのリモートマシンコンテキストを使用するようになります。


💻 方法4: CI/CDパイプラインでの活用

GitHub Actions、GitLab CI、JenkinsなどでリモートDockerホストにデプロイする際に有用なパターンです。

#!/bin/bash
# deploy.sh — CI/CDリモートデプロイスクリプト

SSH_USER="deploy"
SSH_HOST="prod.example.com"

# 環境変数を設定するだけで完了(SSHキーはCIシークレットに登録)
export DOCKER_HOST="ssh://${SSH_USER}@${SSH_HOST}"

# イメージをプルしてコンテナを再デプロイ
docker pull myapp:latest
docker stop myapp || true
docker rm myapp || true
docker run -d 
  --name myapp 
  --restart unless-stopped 
  -p 80:8080 
  myapp:latest

echo "✅ 배포 완료"
unset DOCKER_HOST

GitLab CIの例:

# .gitlab-ci.yml
deploy:
  stage: deploy
  image: docker:latest
  before_script:
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | ssh-add -
    - mkdir -p ~/.ssh
    - ssh-keyscan $PROD_HOST >> ~/.ssh/known_hosts
  script:
    - export DOCKER_HOST="ssh://deploy@$PROD_HOST"
    - docker pull $CI_REGISTRY_IMAGE:latest
    - docker run -d --name app $CI_REGISTRY_IMAGE:latest

⚠️ 注意事項 / よくある間違い

① known_hosts未登録エラー

初回接続時にホストキー検証失敗により接続が拒否されます。

# 最初にknown_hostsに登録する必要があります
ssh-keyscan -H host1.example.com >> ~/.ssh/known_hosts
# または一度直接SSH接続して確認
ssh docker-user@host1.example.com

② SSHキーパスフレーズの問題

キーにパスフレーズが設定されている場合、dockerコマンドごとにパスワードを要求します。ssh-agentに事前にキーを登録する必要があります。

eval $(ssh-agent -s)
ssh-add ~/.ssh/id_ed25519
# 登録確認
ssh-add -l

③ リモートユーザーのdockerグループ未登録

リモートホストでSSH_USERはDockerソケットへのアクセス権限があるユーザーである必要があります(例:dockerグループのメンバー)。

# リモートサーバーで実行
sudo usermod -aG docker docker-user
# グループ変更を適用(ログアウト後、再ログインが必要)

④ TCPポートをインターネットに直接公開しないこと

# ❌ 絶対に禁止 — ポートを全てのIPに公開
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"]

# ✅ ループバックのみ許可(ローカルSSHトンネル用)
"hosts": ["unix:///var/run/docker.sock", "tcp://127.0.0.1:2375"]

✅ まとめ / 締めくくり

方法 利点 適切な状況
ssh -L ポートフォワーディング 最も柔軟、きめ細やかな制御 一時的な接続、レガシー環境
DOCKER_HOST=ssh:// 設定が簡単、追加ソフトウェア不要 迅速な一時接続、CI/CD
docker context マルチサーバー管理に最適化 運用環境、日常的なリモート管理

要点をもう一度まとめると:

  • Dockerソケットは絶対にインターネットに直接公開しない
  • SSHトンネルは追加インフラなしで即座に利用可能な最も安全なリモートアクセス方法
  • Docker 18.09以降ではssh://スキームがネイティブでサポートされ、設定が非常に単純になった
  • 運用環境ではdocker contextでリモートホストを体系的に管理しよう

次のステップとしては、TLS相互認証方式やTailscale/ZeroTierのようなZero-Trustネットワークを活用したDockerリモートアクセス方式も検討する価値があります。


Comments

コメントを残す

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