Nginx を認証プロキシとして使う

使用例

皆さんの中には、サービスを使うユーザの認証のために Apache のプロキシ機能を利用しているケースもあるでしょう。そして、そうなら更に活用したいと思うかもしれません。Registry で同じ処理系(パイプライン)を通して通信させることができます。

エンタープライズの環境であれば、通常はバックエンドに LDAP やアクティブ・ディレクトリを使うか、内部の http ポータルで SSO (シングル・サインオン)の仕組みを使うでしょう。

あるいは

自分の Registry に認証が必要になったときや、ユーザのアクセスを分けてメンテナンスを楽にしたい場合は、内部実装されている ベーシック認証レジストリ機能 の利用を検討するでしょう。

解決策

この課題に対応するため、ここでは Docker Engine にリバース・プロキシを通したベーシック認証機能を実装します。これには Registry の前にリバース・プロキシをおきます。

今回のサンプルでは簡単な htpasswd ファイルを使います。もしも他の Apache 認証バックエンドの実装になれているのであれば、今回のサンプル同様、簡単に実装できるでしょう。

また、push を制限(ユーザやグループの制限)する実装も可能です。そのような場合も、このサンプルを目的に合わせて書き換えるだけで実現できるでしょう。

捕捉

このモデルは、自分が使いたい何らかのバックエンド機構をプロキシとして使えることをも意味します。また、その場合には自分自身で Registry をプロキシの TLS 末端に移動する必要があります。

それだけではありません。何らかの http 通信パイプラインを導入することで、更に複雑なデプロイ、メンテナンス、デバッグなど、様々な問題に対処できるでしょう。そのためには、外部との互換性が必要になります。

例えば、Amazon Elastic Load Balancer (ELB) の HTTPS 状態では、既に以下のヘッダが追加されています。

X-Real-IP
X-Forwarded-For
X-Forwarded-Proto

そのため、この背後に Nginx を設定する場合は、サンプル設定から以下の設定を削除する必要があるでしょう。

X-Real-IP         $remote_addr; # pass on real client's IP
X-Forwarded-For   $proxy_add_x_forwarded_for;
X-Forwarded-Proto $scheme;

Nginx が ELB の設定をリセットすると、正常に動作しなくなる場合があります。詳しい情報は #970 をご覧ください。

セットアップ

動作条件 をもう一度お読みください。

準備はできましたか?

必要なディレクトリを作成します。

mkdir -p auth
mkdir -p data

メインで使う Nginx 設定ファイルを作成します。

cat <<EOF > auth/nginx.conf
events {
    worker_connections  1024;
}

http {

  upstream docker-registry {
    server registry:5000;
  }

  ## Set a variable to help us decide if we need to add the
  ## 'Docker-Distribution-Api-Version' header.
  ## The registry always sets this header.
  ## In the case of nginx performing auth, the header will be unset
  ## since nginx is auth-ing before proxying.
  map \$upstream_http_docker_distribution_api_version \$docker_distribution_api_version {
    'registry/2.0' '';
    default registry/2.0;
  }

  server {
    listen 443 ssl;
    server_name myregistrydomain.com;

    # SSL
    ssl_certificate /etc/nginx/conf.d/domain.crt;
    ssl_certificate_key /etc/nginx/conf.d/domain.key;

    # Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
    ssl_protocols TLSv1.1 TLSv1.2;
    ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;

    # disable any limits to avoid HTTP 413 for large image uploads
    client_max_body_size 0;

    # required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486)
    chunked_transfer_encoding on;

    location /v2/ {
      # Do not allow connections from docker 1.5 and earlier
      # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
      if (\$http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*\$" ) {
        return 404;
      }

      # To add basic authentication to v2 use auth_basic setting.
      auth_basic "Registry realm";
      auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd;

      ## If $docker_distribution_api_version is empty, the header will not be added.
      ## See the map directive above where this variable is defined.
      add_header 'Docker-Distribution-Api-Version' \$docker_distribution_api_version always;

      proxy_pass                          http://docker-registry;
      proxy_set_header  Host              \$http_host;   # required for docker client's sake
      proxy_set_header  X-Real-IP         \$remote_addr; # pass on real client's IP
      proxy_set_header  X-Forwarded-For   \$proxy_add_x_forwarded_for;
      proxy_set_header  X-Forwarded-Proto \$scheme;
      proxy_read_timeout                  900;
    }
  }
}
EOF

次は「testuser」と「testpassword」を使うパスワード・ファイルを作成します。

docker run --rm --entrypoint htpasswd registry:2 -bn testuser testpassword > auth/nginx.htpasswd

証明書用のファイルをコピーします。

cp domain.crt auth
cp domain.key auth

Compose ファイルを新規作成します。

cat <<EOF > docker-compose.yml
nginx:
  image: "nginx:1.9"
  ports:
    - 5043:443
  links:
    - registry:registry
  volumes:
    - ./auth:/etc/nginx/conf.d
    - ./auth/nginx.conf:/etc/nginx/nginx.conf:ro

registry:
  image: registry:2
  ports:
    - 127.0.0.1:5000:5000
  volumes:
    - `pwd`./data:/var/lib/registry
EOF

開始と停止

それでは、スタックを起動しましょう。

docker-compose up -d

「push」するために認証されたユーザ( testuserpushtestpasswordpush を使います)でログインします。それから皆さんのイメージをタグ付けして push します。

docker login -p=testuser -u=testpassword -e=root@example.ch myregistrydomain.com:5043
docker tag ubuntu myregistrydomain.com:5043/test
docker push myregistrydomain.com:5043/test
docker pull myregistrydomain.com:5043/test

参考

Authenticating proxy with nginx
https://docs.docker.com/registry/nginx/