Posted at

docker_authを使ってプライベートレジストリの権限管理をする

More than 1 year has passed since last update.


前提

TLS通信をするプライベートレジストリを構築済みです。

構築の詳細は別記事で記載しました。

privateなdockerレジストリを構築する

そちらの内容を前提にしています。


  • dockerレジストリ → docker-host

  • dockerimageをpullしたりpushするサーバー → docker-client

と呼んでいます。


前回までのあらすじ

プライベートレジストリを構築したは良いのですが、urlを知っている誰もがレジストリにあるリポジトリを操作できる状態になります。

それをどうにかする方法を調べました。


今回やること

docker_authという仕組みを使って、ユーザー毎の権限管理をしたいと思います。

イメージとしては以下のようなものです。

ユーザー
できること

master
全て

developer
pullのみ

何者でも無い人
何もできない


tokenでの認証の仕組み

https://docs.docker.com/registry/spec/auth/token/

docker registryの認証の仕組みです。

こちらを使って、ユーザー毎の認証をしていこうということです。


docker_auth

https://github.com/cesanta/docker_auth

docker registry v2の認証サーバーを簡単に立てることができます。

こちらもdockerコンテナを作成します。

プライベートレジストリを動かしているサーバー(docker-host)でセットアップをします。


docker-hostのディレクトリ構成

前回の記事で下記のようなディレクトリ構成になっています。

/root/docker/registry

            /certs/ ←証明書を配置する
            /data/ ←dockerコンテナのデータをマウントする

今回はこちらを以下のようにします。

/root/docker/registry

            /certs/ ←証明書を配置する
            /data/ ←dockerコンテナのデータをマウントする
            /docker_auth ←今回作成したディレクトリ
                   /config/ ←同上


設定ファイル

/root/docker/registry/docker_auth/config/ 以下に auth_config.yml を作成します。docker_authの設定ファイルです。

ファイルはgithubのexampleを参考にします。

https://github.com/cesanta/docker_auth/blob/master/examples/simple.yml

今回は、simple.ymlのserverプロパティのファイルパスのみ編集しています。このファイルパスは、追い追いdockerコンテナを立ち上げたときの、コンテナ内部のファイルパスです。

下記のような感じにしました。

# A simple example. See reference.yml for explanation for explanation of all options.

#
# auth:
# token:
# realm: "https://127.0.0.1:5001/auth"
# service: "Docker registry"
# issuer: "Acme auth server"
# rootcertbundle: "/path/to/server.pem"

server:
addr: ":5001"
certificate: "/certs/domain.crt"
key: "/certs/domain.key"

token:
issuer: "Acme auth server" # Must match issuer in the Registry config.
expiration: 900

users:
# Password is specified as a BCrypt hash. Use `htpasswd -nB USERNAME` to generate.
"admin":
password: "$2y$05$LO.vzwpWC5LZGqThvEfznu8qhb5SGqvBSWY1J3yZ4AxtMRZ3kN5jC" # badmin
"test":
password: "$2y$05$WuwBasGDAgr.QCbGIjKJaep4dhxeai9gNZdmBnQXqpKly57oNutya" # 123

acl:
- match: {account: "admin"}
actions: ["*"]
comment: "Admin has full access to everything."
- match: {account: "user"}
actions: ["pull"]
comment: "User \"user\" can pull stuff."
# Access is denied by default.

それ以外は全く変えていません。まずは。後ほどこちらにユーザー毎の権限を設定するのですが、後回しします。


コンテナ起動

gitHubのREADMEにあるように、dockerコンテナを起動させます。

cd /root/docker/registry しておいてください。

下記のようにしました。

docker run \

-itd --name docker_auth -p 5001:5001 \
-v /root/docker/registry/docker_auth/config:/config:ro \ ←auth_config.ymlがあるディレクトリをマウントします
-v /var/log/docker_auth:/logs \
-v /root/docker/registry/certs:/certs \ ←証明書があるディレクトリをマウントします
cesanta/docker_auth:1 /config/auth_config.yml

証明書があるディレクトリを /certs にマウントしているので、コンテナ内部ではdomain.crtとdomain.keyが/certs/domain.crt等にあるということです。なので、先程の設定ファイルのパスに合った場所にマウントしてください。


レジストリの修正

このままでは、docker_authサーバーを立ち上げただけなので、レジストリ側でtokenを使うように変更します。

一旦レジストリのコンテナを止めましょう。

docker stop registry

docker rm registry

またレジストリを立ち上げます。

docker run -itd  \

--restart=always \
--name registry \
-v `pwd`/certs:/certs \
-v `pwd`/data:/var/lib/registry \
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
-e REGISTRY_AUTH=token \ ←authの方式はtokenです
-e REGISTRY_AUTH_TOKEN_REALM=https://{docker-hostのdomain}:5001/auth \ ←先程立ち上げたdocker_authです。ポートを5001から変えていたらポートも変えておいてください。
-e REGISTRY_AUTH_TOKEN_SERVICE="Docker registry" \ ←serviceを記載しますが、今回は固定文字列で良いです。
-e REGISTRY_AUTH_TOKEN_ISSUER="Acme auth server" \ ←issuerを記載します。後述
-e REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE=/certs/domain.crt \
-p 443:443 \
registry:2

issuerについて

isuuerもこの流れでは固定文字列で良いのですが、それは、docker_authのauth_config.ymlによります。

token:

issuer: "Acme auth server" # Must match issuer in the Registry config.
expiration: 900

というブロックがあります。

レジストリの設定とissuerがマッチしている必要があります。ここを変えていたらレジストリの起動のときの設定も変更してください。


確認

さて、ここまでで、レジストリを使うのはdocker_authで認証されてからという状態が作れたはずです。

全く別のサーバー(docker-client)でレジストリ操作をしつつ、確認をします。

前回の記事の続きのサーバーなので、以下の条件を満たしています。


  • centos7

  • dockerインストール済み

  • プライベートレジストリの自己証明書を信頼済み

まずは、細かいことを飛ばしてまずは動くことだけを確認します。


admin

docker login https://{docker-hostのdomain}

Username: admin
Password: badmin

usernameとpasswordの詳細については後述しますが、docker_auth.configをsimple.ymlから作成している上記手順の場合、これでログインが成功すると思います。

docker loginに成功したら、pullやpushを試してみてください。問題無く操作できると思います。


test

つぎはtestというユーザーでloginします。

docker login https://{docker-hostのdomain}

Username: test
Password: 123

simple.ymlから変更がなければ、test/123でログインできます。

pullやpushを試してみて下さい。

これでできなければ成功です。

pullの場合は、そのようなリポジトリは存在しないというエラーのようです。

pushの場合は、認証エラーとなると思います。


ユーザーとアクセス制限の設定


設定ファイル

設定ファイルは、auth_config.ymlです。

上記の手順で、simple.ymlからコピーしてきた中に、ユーザーやアクセス制限の設定が記載されていました。

該当箇所は以下です。

ユーザー

users:

# Password is specified as a BCrypt hash. Use `htpasswd -nB USERNAME` to generate.
"admin":
password: "$2y$05$LO.vzwpWC5LZGqThvEfznu8qhb5SGqvBSWY1J3yZ4AxtMRZ3kN5jC" # badmin
"test":
password: "$2y$05$WuwBasGDAgr.QCbGIjKJaep4dhxeai9gNZdmBnQXqpKly57oNutya" # 123

アクセス制限


acl:
- match: {account: "admin"}
actions: ["*"]
comment: "Admin has full access to everything."
- match: {account: "user"}
actions: ["pull"]
comment: "User \"user\" can pull stuff."
# Access is denied by default.

この設定ファイルの概要や、設定変更をしてみたいと思います。


ユーザー

さて、ここまでで、レジストリを使うのはdocker_authで認証されてからという状態が作れたはずです。

実はこの段階で、docker_authに認証されるべく2つのユーザーが設定されています。

docker_auth.ymlではそのままsampleをコピーしていてあまり意識していなかった部分ですが、以下がそれに当たります。

users:

# Password is specified as a BCrypt hash. Use `htpasswd -nB USERNAME` to generate.
"admin":
password: "$2y$05$LO.vzwpWC5LZGqThvEfznu8qhb5SGqvBSWY1J3yZ4AxtMRZ3kN5jC" # badmin
"test":
password: "$2y$05$WuwBasGDAgr.QCbGIjKJaep4dhxeai9gNZdmBnQXqpKly57oNutya" # 123

つまり、

users:

{user_name}:
password: ハッシュしたパスワード

という具合の設定方法です。

上記の設定ですと、下記2ユーザーが既に設定されています。


  • admin

  • test

また、親切にコメントに、それぞれの本当のパスワードが記載されています。先程確認のときに、細けぇこたぁいいんだよ!!という具合にパスワードを指定しましたのは、こちらの内容でした。

もちろん本当に使うときはパスワードは再作成してください。


新しい設定

ではこちらに設定を新しく追加していきましょう。

下記2ユーザーを追加します。


  • user

  • test2

コメントにある通りhtpasswd -nB USERNAMEコマンドでパスワードを生成します。

もしもhtpasswdが入っていない場合は、 yum install httpd-tools でapache等はインストールせずにこのコマンドを使うことができます。

入力したパスワードはちゃんとメモしておきましょう。

他のユーザーと同じように、auth_config.ymlに情報を記載しましょう。

users:

# Password is specified as a BCrypt hash. Use `htpasswd -nB USERNAME` to generate.
"admin":
password: "$2y$05$LO.vzwpWC5LZGqThvEfznu8qhb5SGqvBSWY1J3yZ4AxtMRZ3kN5jC" # badmin
"test":
password: "$2y$05$WuwBasGDAgr.QCbGIjKJaep4dhxeai9gNZdmBnQXqpKly57oNutya" # 123
"user":
password: 生成したハッシュ値
 "test2":
password: 生成したハッシュ値


アクション

それぞれのユーザーにどのようなアクションが許可されているかを定義する必要があります。

アクションの権限については、下記ブロックで定義されています。

acl:

- match: {account: "admin"}
actions: ["*"]
comment: "Admin has full access to everything."
- match: {account: "user"}
actions: ["pull"]
comment: "User \"user\" can pull stuff."
# Access is denied by default.

上記の意味するところは以下です。


  • adminは全てにアクセスができます

  • userはpullのみ許されています

それ以外のユーザーについては、何も許可されていません。

つまり、ユーザーとしては設定されていた「test」は何もアクセス権限が無いので、前述の段でtestでログインしてもpullすらできなかったのです。

uesrとtest2について新しくユーザーを作成したので、彼らの権限も適当に設定してみます。

acl:

- match: {account: "admin"}
actions: ["*"]
comment: "Admin has full access to everything."
- match: {account: "user"}
actions: ["pull"]
comment: "User \"user\" or \"test\" can pull stuff."
 - match: {account: "test2"}
  actions: ["pull", "push"]
  comment: "User \"test2\" can pull and push."
# Access is denied by default.

testは相変わらず何もできません。

test2はpullとpushができるようにしてみました。


確認

ちなみに上記設定をauth_config.ymlに追加したら、docker_authコンテナを停止→削除→起動します。

ここからは、再度、docker-client側のサーバーを使って試します。(もちろんdocker-hostの方でやっても同じことですが…)


user

#login

[root@docker-client docker]# docker login https://{docker-hostのdomain}
Username : user
Password:
Login Succeeded

#pull
[root@docker-client docker]# docker pull {docker-hostのdomain}/test/centos:6
Trying to pull repository {docker-hostのdomain}/test/centos ...
6: Pulling from {docker-hostのdomain}/test/centos
Digest: sha256:

#tagつけてpush
[root@docker-client docker]# docker tag docker.io/centos:6 {docker-hostのdomain}/test/centos:6.5
[root@docker-client docker]# docker push {docker-hostのdomain}/test/centos:6.5
The push refers to a repository [{docker-hostのdomain}/test/centos]
f6fe4b6dd5e6: Layer already exists
unauthorized: authentication required

pullはできるがpushはできないことが確認できました。


test

#login

[root@docker-client docker]# docker login https://{docker-hostのdomain}
Username (user): test
Password:
Login Succeeded

#pull
[root@docker-client docker]# docker pull {docker-hostのdomain}/test/centos:6
Trying to pull repository {docker-hostのdomain}/test/centos ...
Pulling repository {docker-hostのdomain}/test/centos
Error: image test/centos:6 not found

#push
[root@docker-client docker]# docker tag docker.io/centos:6 {docker-hostのdomain}/test/centos:6.6
[root@docker-client docker]# docker push {docker-hostのdomain}/test/centos:6.6
The push refers to a repository [{docker-hostのdomain}/test/centos]
f6fe4b6dd5e6: Preparing
unauthorized: authentication required

pullもpushもできません。


test2

#login

[root@docker-client docker]# docker login https://{docker-hostのdomain}
Username (test): test2
Password:
Login Succeeded

#pull
[root@docker-client docker]# docker pull {docker-hostのdomain}/test/centos:6
Trying to pull repository {docker-hostのdomain}/test/centos ...
6: Pulling from {docker-hostのdomain}/test/centos
Digest: sha256:cb359951a2aec569a130e66460835ea71718ceecf17e7522c87ef2dd418803f8

#push
[root@docker-client docker]# docker tag docker.io/centos:6 {docker-hostのdomain}/test/centos:6.7
[root@docker-client docker]# docker push {docker-hostのdomain}/test/centos:6.7
The push refers to a repository [{docker-hostのdomain}/test/centos]
f6fe4b6dd5e6: Layer already exists
6.7: digest: sha256:

pullもpushもできます。

以上のことから、docker_authを利用して、ユーザーとそのユーザーのアクション権限を設定できることができました。

たとえば、開発者やテスターはpullのみを許可し、pushできるのは一部のユーザーのみ、という状態にすることができます。


UI

ここで一つの問題?があります。

前回の記事で、docker_registry_frontendというコンテナでレジストリ内のリポジトリをブラウザ上で確認できるようにしました。

しかし、docker_authを導入したことで、これがブラウザ上から確認できない状態になっていると思います。

調べた限りでは、docker_registry_frontendはtokenによる認証に対応していないため、レジストリの情報等が取得できないようになってしまたみたいです。


APIを利用してレジストリを操作する

UIは確認できなくなってしまったので、最低限API利用をして、レジストリを操作する方法を持っておきます。

今まで通りにリポジトリ情報を取得しようとすると、UIの方と同様の問題に当たります。

[root@docker-client docker]# curl https://{docker-hostのdomain}/v2/_catalog

{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"registry","Class":"","Name":"catalog","Action":"*"}]}]}

header情報を出します。

[root@docker-client docker]# curl -k -IL https://{docker-hostのdomain}/v2/_catalog

HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Docker-Distribution-Api-Version: registry/2.0
Www-Authenticate: Bearer realm="https://{docker-hostのdomain}:5001/auth",service="Docker registry",scope="registry:catalog:*"
X-Content-Type-Options: nosniff
Date: Tue, 23 Jan 2018 02:42:48 GMT
Content-Length: 145


tokenを生成して認証する

tokenを生成し、それを使って通信をすることにします。

面倒ですけれど、tokenは各user、scope毎に生成する必要がある。

つまり、やりたいこと毎(リポジトリの一覧取得や、タグ取得など)にtokenを生成することになります。

例えば以下は、adminユーザーがリポジトリ一覧の取得するためのtoken

curl -s -u admin:badmin -d "service=Docker registry" -d "scope=registry:catalog:*" -d "account=admin" https://{docker-hostのdomain}:5001/auth

-uオプションには、{ユーザー名}:{パスワード}を指定します。

serviceとscopeについては、出力したheader情報の「Www-Authenticate」内の値と揃えておきます。accountにはユーザーと同名を指定すれば良いです。

account=の部分は、-uのusernameと一致するように。

すると、tokenが出力されます(json形式)

[root@docker-client ~]# curl -s -u admin:badmin -d "service=Docker registry" -d "scope=registry:catalog:*" -d "account=admin" https://{docker-hostのdomain}:5001/auth

{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlU3Qlg6RkxZWDpLTldMOlRJRUc6VjRXRjo3VjM2OlNRUlU6Nk42UjpaTFBZOkJJSVk6S05LVTozNVdHIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiYWRtaW4iLCJhdWQiOiJEb2NrZXIgcmVnaXN0cnkiLCJleHAiOjE1MTU1NzkyNzUsIm5iZiI6MTUxNTU3ODM2NSwiaWF0IjoxNTE1NTc4Mzc1LCJqdGkiOiI0OTE4Nzc0NzQ2NjEyOTg0MDYwIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVnaXN0cnkiLCJuYW1lIjoiY2F0YWxvZyIsImFjdGlvbnMiOlsiKiJdfV19.ibWk4kCeM9ab39pYnUXl1Ndppheon1Yz8SCgd6xNa85m8Kfy6qB85FlnfQJvc0AmGnuj0y8LPJ_hDIS_FxKg8OHnkjDA5FWrF0LtXrhZtXvm0yWFmhA5H-S-XxWwFKfD8kIUGhUtHPsfMj10J7C8TQdjERKcYzu3vIB9kIWaajs1kF-9LBkeDrp_GVByK9EBmYfiH6_HO3vsGhJ089eaO8AW7HQpyHzrRKXTqlG2nK49x_kxp90cxu8Wdz2acN1fEQW9nau12oW3aNPcmzyGyNB9jiVVeBu8heoAht_ODxmVC8dMpEYBfeqZ6jczKQWOqLbHkWJ8cJ_NjUzVup_HZaob3-W9Idb7X88r7Qf-gDRg7LOsQvVcQCqXRX3qWc-VmiwgEyk6WhtfjXXBcOQlGApZ_tOPqaagzq8SnQG_0FCxqLipQHHUEiDMydCkJ9Jg57If2Qojddo5Z83sHUlK0N8Wq3PE5UIr4WETHZsB9oCbOBEEjqXBqT2R1EkoKSGSRmXIvf4t8pmcKRMMoDfLt-Gw-SkukscbtPKiFjdvEDy3-WAdaNf6ZClQE733m05SXRQ8dH27wK8aUh93lScAisX2u8x0JPNMWaoMOtt-MqwfnGlAxRaZLznKPw4rzvuSCD3zAUJ-U2rA_j0Z6pwMlo1UGMd7_jXS2KkFSq30r_w"}


tokenで認証してAPIを利用する

上記で出力したtokenをコピーしておきます。

リポジトリ一覧を出力する場合、以下のようにヘッダ情報をつけて、リクエストしてあげます。

curl -H "Authorization: Bearer {コピーしたtoken文字列}" https://{docker-hostのdomain}/v2/_catalog

下記具体例

[root@docker-host01 ~]# curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlU3Qlg6RkxZWDpLTldMOlRJRUc6VjRXRjo3VjM2OlNRUlU6Nk42UjpaTFBZOkJJSVk6S05LVTozNVdHIn0.eyJpc3MiOiJBY21lIGF1dGggc2VydmVyIiwic3ViIjoiYWRtaW4iLCJhdWQiOiJEb2NrZXIgcmVnaXN0cnkiLCJleHAiOjE1MTU1NzkyNzUsIm5iZiI6MTUxNTU3ODM2NSwiaWF0IjoxNTE1NTc4Mzc1LCJqdGkiOiI0OTE4Nzc0NzQ2NjEyOTg0MDYwIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVnaXN0cnkiLCJuYW1lIjoiY2F0YWxvZyIsImFjdGlvbnMiOlsiKiJdfV19.ibWk4kCeM9ab39pYnUXl1Ndppheon1Yz8SCgd6xNa85m8Kfy6qB85FlnfQJvc0AmGnuj0y8LPJ_hDIS_FxKg8OHnkjDA5FWrF0LtXrhZtXvm0yWFmhA5H-S-XxWwFKfD8kIUGhUtHPsfMj10J7C8TQdjERKcYzu3vIB9kIWaajs1kF-9LBkeDrp_GVByK9EBmYfiH6_HO3vsGhJ089eaO8AW7HQpyHzrRKXTqlG2nK49x_kxp90cxu8Wdz2acN1fEQW9nau12oW3aNPcmzyGyNB9jiVVeBu8heoAht_ODxmVC8dMpEYBfeqZ6jczKQWOqLbHkWJ8cJ_NjUzVup_HZaob3-W9Idb7X88r7Qf-gDRg7LOsQvVcQCqXRX3qWc-VmiwgEyk6WhtfjXXBcOQlGApZ_tOPqaagzq8SnQG_0FCxqLipQHHUEiDMydCkJ9Jg57If2Qojddo5Z83sHUlK0N8Wq3PE5UIr4WETHZsB9oCbOBEEjqXBqT2R1EkoKSGSRmXIvf4t8pmcKRMMoDfLt-Gw-SkukscbtPKiFjdvEDy3-WAdaNf6ZClQE733m05SXRQ8dH27wK8aUh93lScAisX2u8x0JPNMWaoMOtt-MqwfnGlAxRaZLznKPw4rzvuSCD3zAUJ-U2rA_j0Z6pwMlo1UGMd7_jXS2KkFSq30r_w" https://{docker-hostのdomain}/v2/_catalog?n=100

認証が正しくされると、jsonデータでリポジトリ情報が出力されます。

{"repositories":["test/centos"]}

他のAPIについても、基本的には同じ要領で、scopeを変更してtokenを生成して情報を取得します。

以上の手順を踏むことで、一応APIを使っての操作ができました。

ただ、さすがに面倒です。

どうやら、ユーザーの認証や権限管理とUIを提供することのできる「Portus」というUIがあるそうです。

今度はこちらを使って、うまいことできないだろうか、考えてみる予定ですが、まだ全然手付かずです。