curlを使ってOpenStack Identity API v3を扱う方法


はじめに

curlコマンドを使ってOpenStack Identity API v3からトークンを取得しOpenStack Swiftを操作しようとしたところ、認証周りで手こずったので、操作手順をまとめます。Openstack.orgに掲載されている手順に従って、"Default"ドメイン、"admin"プロジェクト、"admin"ユーザを作成し、"admin"ユーザがOpenStack KeystoneやSwiftを操作する、という前提です。

OpenStack Identity API v3には「ドメイン」や「プロジェクト」という用語があります。プロジェクトはAPI v2のテナントに相当するもののようで、同じプロジェクトに属するユーザの間で例えばSwiftのコンテナやオブジェクトを共有することができます。ドメインはプロジェクトやユーザを包含するもののようで、ドメインに対してプロジェクトやユーザは1対Nの関係になります。

失敗例

OpenStackでは、まずIdentity APIにユーザ名やパスワードなどを与えてトークンを取得し、取得したトークンを使ってSwiftなどを操作する、と言う流れになります。では、さっそくOpenStack Identity API v3のドキュメントに従ってトークンを取得してみましょう。

ここを見ると。。。

トークンを取得するAPIが5個もある!
ちなみに、API v2ではトークンを取得するAPIは1個だけでした。今までAPI v2を使っていた人にはscopedとかunscopedとか良くわからないよね。。。きっと一番上に掲載されているAPIが良く使われるから一番上にあるんでしょ!と判断して「Password authentication with unscoped authorization」を選んで実行してみます。

$ curl -i -X POST -H "Content-Type: application/json" -d '
> {
>   "auth": {
>     "identity": {
>       "methods": [
>         "password"
>       ],
>       "password": {
>         "user": {
>           "name": "admin",
>           "domain": {
>             "name": "Default"
>           },
>           "password": "<password>"
>         }
>       }
>     }
>   }
> }' http://<ip address>:<port>/v3/auth/tokens

HTTP/1.1 201 Created
Date: Fri, 24 Feb 2017 10:23:35 GMT
Server: Apache/2.4.6 (Red Hat Enterprise Linux) mod_wsgi/3.4 Python/2.7.5
X-Subject-Token: gAAAAABYsAmnslpnZeWjRJLyJ0PlSt0uFvgZq-WKmL05qg53xAfaDu6dW_5GozmjbjDcSWcQ-fzrp8kMl5ZwVabnKQQxdkNlrcQCv8T2vCAxtfC-huiVN6sKNvhYrGyScg2qLsz6G1pNMBAh4RRejlrBDiV0GctmKA
Vary: X-Auth-Token
x-openstack-request-id: req-4e24ac10-9261-4e60-ba88-c1148191efa3
Content-Length: 283
Content-Type: application/json

{"token": {"issued_at": "2017-02-24T10:23:35.000000Z", "audit_ids": ["qoo3nym5TJq62znNSQKKpw"], "methods": ["password"], "expires_at": "2017-02-24T11:23:35.000000Z", "user": {"domain": {"id": "default", "name": "Default"}, "id": "870b06aa864647a9b78510e3ecbf2d6e", "name": "admin"}}}

サーバーからレスポンスが返ってきました!Openstack Identity API v3のドキュメントによると、「X-Subject-Token:」の欄の文字列がトークンのようです。では、このトークンを使って、Swiftを操作してみましょう。こちらのドキュメントに従って、adminユーザのコンテナ一覧を表示しようとすると。。。

$ curl -i http://<ip address>:<port>/v1/AUTH_<account id>?format=json -X GET -H "X-Auth-Token: gAAAAABYsAmnslpnZeWjRJLyJ0PlSt0uFvgZq-WKmL05qg53xAfaDu6dW_5GozmjbjDcSWcQ-fzrp8kMl5ZwVabnKQQxdkNlrcQCv8T2vCAxtfC-huiVN6sKNvhYrGyScg2qLsz6G1pNMBAh4RRejlrBDiV0GctmKA"

HTTP/1.1 403 Forbidden
Content-Length: 73
Content-Type: text/html; charset=UTF-8
X-Trans-Id: txc73e8e954fe24a62ac0c7-0058b00cfe
Date: Fri, 24 Feb 2017 10:37:50 GMT

<html><h1>Forbidden</h1><p>Access was denied to this resource.</p></html>

ぎゃー!「403 Forbidden」が返ってきたー!
何か間違えているようです。

「Password authentication with scoped authorization」を使えば良い

どうやら、APIのリストの上から二番目の「Password authentication with scoped authorization」を使えば良いようです。openstack.orgに掲載されている例ではuserやprojectをidで指定していますが、この記事ではnameで指定してみます。nameで指定する場合は、さらにdomain属性の指定が必要になるようです。

$ curl -i -X POST -H "Content-Type: application/json" -d '
> {
>   "auth": {
>     "identity": {
>       "methods": [
>         "password"
>       ],
>       "password": {
>         "user": {
>           "name": "admin",
>           "password": "<password>",
>           "domain": {
>             "name": "Default"
>           }
>         }
>       }
>     },
>     "scope": {
>       "project": {
>         "name": "admin",
>         "domain": {
>           "name": "Default"
>         }
>       }
>     }
>   }
> }' http://<ip address>:<port>/v3/auth/tokens

HTTP/1.1 201 Created
Date: Mon, 27 Feb 2017 02:31:58 GMT
Server: Apache/2.4.6 (Red Hat Enterprise Linux) mod_wsgi/3.4 Python/2.7.5
X-Subject-Token: gAAAAABYs4-gE3h9OlPCRUpD2gFvto8J5xx35DGRqEBvUyi52h0WJw0aHUiZOuwDUjo-TrxDftsxLC8kSXyTxwDsYS58NNNdaiW1pC3W_0QNahIUEDM3hicIY7irwA7GMC8iDHC3f8ZqxyGwzVt3dedUk4Q_j5lQj1qLElm_lUTkGmvmMu-Lbxk
Vary: X-Auth-Token
x-openstack-request-id: req-e99bd1fe-a70f-4b07-8d79-17a2abd0d3bb
Content-Length: 1708
Content-Type: application/json

{"token": {"is_domain": false, "methods": ["password"], "roles": [{"id": "42695c4c18d542a4a24e79fed79b44ca", "name": "admin"}], "expires_at": "2017-02-27T03:31:59.000000Z", "project": {"domain": {"id": "default", "name": "Default"}, "id": "64ad2508c56a4b43b5e9692e36ea6690", "name": "admin"}, "catalog": [{"endpoints": [{"region_id": "RegionOne", "url": "http://9.68.109.77:35357/v3/", "region": "RegionOne", "interface": "admin", "id": "2e75d68dd299473f825062bd6fa8ad01"}, {"region_id": "RegionOne", "url": "http://9.68.109.77:5000/v3/", "region": "RegionOne", "interface": "public", "id": "768599903b3d463e92a73f57ed66a8d0"}, {"region_id": "RegionOne", "url": "http://9.68.109.77:35357/v3/", "region": "RegionOne", "interface": "internal", "id": "848e524718a9401284e074c7e4b109c8"}], "type": "identity", "id": "b50e1c2d6a2241a7a233d46be1bf2b93", "name": "keystone"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://9.68.109.77:8080/v1/AUTH_64ad2508c56a4b43b5e9692e36ea6690", "region": "RegionOne", "interface": "public", "id": "8bedc457deb144acab7b48b07b75562c"}, {"region_id": "RegionOne", "url": "http://9.68.109.77:8080/v1/AUTH_64ad2508c56a4b43b5e9692e36ea6690", "region": "RegionOne", "interface": "internal", "id": "969b6ed80b1549398b6bb9a42030ea8d"}, {"region_id": "RegionOne", "url": "http://9.68.109.77:8080/v1", "region": "RegionOne", "interface": "admin", "id": "f3c989e8b5e84d3da8204050533239a4"}], "type": "object-store", "id": "ea83d73eb58e4a158768ae5fe12db377", "name": "swift"}], "user": {"domain": {"id": "default", "name": "Default"}, "id": "870b06aa864647a9b78510e3ecbf2d6e", "name": "admin"}, "audit_ids": ["5-antUn4SZy2Ju9ZVnmjlg"], "issued_at": "2017-02-27T02:32:00.000000Z"}}

トークンが返ってきました!最初に返ってきたトークンよりも文字列の長さが長いです。では、このトークンを使ってadminユーザのコンテナ一覧を取得してみましょう。

$ curl -i http://<ip address>:<port>/v1/AUTH_<account id>?format=json -X GET -H "X-Auth-Token: gAAAAABYs4-gE3h9OlPCRUpD2gFvto8J5xx35DGRqEBvUyi52h0WJw0aHUiZOuwDUjo-TrxDftsxLC8kSXyTxwDsYS58NNNdaiW1pC3W_0QNahIUEDM3hicIY7irwA7GMC8iDHC3f8ZqxyGwzVt3dedUk4Q_j5lQj1qLElm_lUTkGmvmMu-Lbxk"

HTTP/1.1 200 OK
Content-Length: 155
X-Account-Object-Count: 2
X-Account-Storage-Policy-Policy-0-Bytes-Used: 1949
X-Account-Storage-Policy-Policy-0-Container-Count: 3
X-Timestamp: 1487579754.75443
X-Account-Storage-Policy-Policy-0-Object-Count: 2
X-Account-Bytes-Used: 1949
X-Account-Container-Count: 3
Content-Type: application/json; charset=utf-8
Accept-Ranges: bytes
x-account-project-domain-id: default
X-Trans-Id: tx75855b131d604846bac9d-0058b391d0
Date: Mon, 27 Feb 2017 02:41:21 GMT

[{"count": 0, "bytes": 0, "name": "openstacksdk"}, {"count": 0, "bytes": 0, "name": "openstacksdk2"}, {"count": 2, "bytes": 1949, "name": "testcontainer"}]

コンテナ一覧がJSON形式で返ってきました!上の例では、「openstacksdk」、「openstacksdk2」、「testcontainer」という3つのコンテナが返ってきています。また、最初の二つのコンテナは空ですが、「testcontainer」にはオブジェクトが2つ保存されているようです。

「Scoped / Unscoped authorization」とは何なのか

ここにトークンの種類について説明が書いてありました。トークンには「Unscoped token」、「Project-scoped token」、「Domain-scoped token」の3種類のトークンがあるようです。

「Unscoped authorization」では「Unscoped token」を取得できます。このトークンは、

Their primary use case is simply to prove your identity to keystone at a later time (usually to generate scoped tokens), without repeatedly presenting your original credentials.

とあるように、あとで「Scoped token」を取得するために使うようです。「Unscoped token」を指定して「Scoped token」を取得すれば、ユーザー名やパスワードをcurlコマンドに渡す必要が無く安全性が高いということかもしれませんね。上のリンク先にも、

Tokens can express your authorization in different scopes. You likely have different sets of roles, in different projects, and in different domains. While tokens always express your identity, they may only ever express one set of roles in one authorization scope at a time.

のように書かれていますので、異なるプロジェクトやドメインに対して連続して操作を行うようなシナリオでは、一度「Unscoped token」を取得し、それを使ってプロジェクトごとの「Scoped token」を取得するのが良いのかもしれません。

「Project-scoped token」はあるテナントの操作に必要なトークンのようです。実際に、このトークンを取らないとSwiftの"admin"プロジェクトの操作ができませんでした。

「Domain-scoped token」も「Unscoped token」と同様に、これを取得しただけではSwiftなどを操作できないようです。ただ、Identity API v3の一覧には、「Domain-scoped token」を取得するためのAPIについては掲載されてないのですよね。。。ひょっとすると、「Unscoped authorization」でドメインを指定して取得したものが「Domain-scoped token」なのかも知れません。

まとめ

OpenStack Swiftなどを扱う場合は、「Scoped authorization」を使いましょう。