open
stack
Open source software to build public and private clouds.
About OpenStack Identity (keystone)
Internet Initiative Japan Inc. / Japan OpenStack User Group
さいとうひでき (twitterid: @saito_hideki)
本セッションの概要
OpenStack
が提供する各コンポーネントが利用
する認証基盤OpenStack Identity(keystone)の概
要と認証・認可の仕組み、利用のされ方につい
て紹介する約30分のセッションです。
目次
●自己紹介
●OpenStack Identity(keystone)
について
●keystone
の構造
●認証・認可の仕組み
●普通の利用方法
●ちょっと変わった利用方法
●まとめ
自己紹介
●なまえ:
–
齊藤 秀喜 (さいとう ひでき)
–
TwitterId: @saito_hideki
●しごと:
–
クラウド関連のちょっとした開発
–
クラウド関連のちょっとした運用
–
クラウド関連のちょっとした火消しとかお詫び
●しゅみ:
–
OpenStack(
嗜む程度)
OpenStack Identity(keystone)
について
OpenStack
の各コンポーネントが利用する共
通の認証基盤としてシステム全体に統合認証
機能を提供しています。
OpenStack
を利用する上では嫌でもダダをこ
ねても避けて通ることはできない重要なサー
ビスです。
goo
辞書によると....
1 《建築》(アーチ頂上の)かなめ石,
くさび石.
2 (組織の)中枢, 中心;(学説などの
)根本原理.
OpenStack Identity(keystone)
について
各コンポーネントとの相関図は以下のような
感じ。コレは動かないと結構ヤバそう。
認証・認可の仕組み
実際に認証と認可の仕組みを追ってみる。キーワー
ドは以下の4つ。
●ユーザID/パスワード
–
認証とトークンを取得するのに必要なユーザIDとパスワード
●トークン
–
認証成功時に発行されるAPIアクセスに必須となる許可証
●テナント
–
仮想リソースをグループ化したもの。ユーザは所属するテナントに対
して認可された操作権を持つ。
●エンドポイント
–
指定テナント内でユーザに認可されたサービスとそのAPIのURL情報
認証・認可の仕組み
keystone-all
はクライアントからのリクエスト
(REST
ベース)を処理することにより認証・認可
を行う。POSTする情報はjsonまたはxml形式。
(1)ユーザID/パスワードでトークンを要求
(2)
トークン
(3)権限を持つテナント情報を要求
(4)テナントリスト
(5)テナントに対するエンドポイント情報を要求
(6)認可されているエンドポイントリスト
client
keystone-all
backend
認証・認可の仕組み
Openstack
のクライアントがendpointを取得する
動きをpythonスクリプトで再現してみる。
#!/usr/bin/env python # coding: utf-8 -*-from getpass import getpass
from httplib import HTTPConnection Import json
user = raw_input("user: ") password = getpass("password: ")
session = HTTPConnection("%s:%s" % (HOST, PORT)) auth_result = get_token(user, password, session) token = auth_result['access']['token']['id']
tenant_result = get_tenant(token, session) tenant = tenant_result["tenants"][0]["id"]
endpoint_result = get_endpoint(token, tenant, session) session.close()
print "=" * 70
print json.dumps(auth_result, sort_keys=True, indent=2) print "-" * 70
print json.dumps(tenant_result, sort_keys=True, indent=2)
get_token()
(1)ユーザID/パスワードからトークンを要求
(2)トークン取得
get_tenant()
(3)権限を持つテナント情報を要求
(4)テナントリスト取得
get_endpoint()
(5)テナントに対するエンドポイント情報を要求
(6)認可されているエンドポイントリスト取得
認証・認可の仕組み
(1)
ユーザID/パスワードでトークンを要求
(2)
トークン取得
def get_token(user, password, session): token_path = "/v2.0/tokens"
header = { "Content-Type": "application/json" } request = ''' { "auth": { "passwordCredentials": { "username":"%s", "password":"%s" } } }''' % (user, password)
session.request("POST", token_path, request, header) return json.load(session.getresponse())
接続先URLは
http://<host>:<port>/v2.0/tokens
リクエストヘッダでデータ形式をjson
に指定する。
トークンを取得するAPIリクエストを
生成する。
リクエスト送信!(POST)
keystone-allからトークンが返される。
認証・認可の仕組み
(1)
ユーザID/パスワードでトークンを要求
(2)
トークン取得
{
"access": {
"serviceCatalog": {},
"token": {
"expires": "2012-11-18T05:23:51Z",
"id": "84e9f54aef284bf6a8dc79699045ad99"
},
"user": {
"id": "d60717fe43e94c908bd9248b87d8e045",
"name": "foo",
"roles": [],
"roles_links": [],
"username": "foo"
}
}
}
keystone-all
から返されるトークン
情報。リクエスト時にjson形式を
指定しているので形式は当然なが
らjson形式となっている。
以降のリクエストでは、ヘッダに
X-Auth-Token: トークンID
を付加することにより認証された
リクエストであること証明する。
認証・認可の仕組み
(3)
権限を持つテナント情報を要求
(4)
テナントリスト取得
接続先URLは
http://<host>:<port>/v2.0/tenants
リクエストヘッダでデータ形式をjson
に指定するだけでなく、X-Auth-Token
に取得済みのトークンIDを指定する。
def get_tenant(token, session):
tenant_path = "/v2.0/tenants"
header = {
"Content-Type": "application/json",
"X-Auth-Token": token,
}
session.request("GET", tenant_path, "", header)
return json.load(session.getresponse())
リクエスト送信!(GET)
認証・認可の仕組み
(3)
権限を持つテナント情報を要求
(4)
テナントリスト取得
keystone-allから返されるテナント
情報は、リストとなっているので
注意。1つだけでも当然リストと
なる。
次の段階のエンドポイントリスト
を取得するには、このテナントID
を利用する。
{
"tenants": [
{
"description": "Default Tenant",
"enabled": true,
"id": "7695c8332c1b4450a6be1376b3a1f5c4",
"name": "openstackDemo"
}
],
"tenants_links": []
}
認証・認可の仕組み
(5)
テナントに対するエンドポイント情報を要求
(6)
認可されているエンドポイントリスト取得
def get_endpoint(token, tenant, session): token_path = "/v2.0/tokens" header = { "Content-Type": "application/json", "X-Auth-Token": token, } endpoint_request = ''' { "auth": { "token": { "id":"%s" }, "tenantId": "%s" } }''' % (token, tenant) session.request(
"POST", "/v2.0/tokens" , endpoint_request, header) return json.load(session.getresponse())
接続先URLは
http://<host>:<port>/v2.0/tokens
リクエストヘッダでデータ形式をjson
に指定するだけでなく、X-Auth-Token
に取得済みのトークンIDを指定する。
取得済みのトークンIDとテナントIDで
エンドポイント情報を取得するAPIリク
エストを生成する。
リクエスト送信!(POST)
keystone-allからトークンが返される。
認証・認可の仕組み
(5)
テナントに対するエンドポイント情報を要求
(6)
認可されているエンドポイントリスト取得
{ "access": { <!-- 中略 --> "serviceCatalog": [ { "endpoints": [ { "adminURL": "http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4", "id": "48567a3d0f7a40fc9aa57172b9cc46e8", "internalURL": "http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4", "publicURL": "http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4", "region": "RegionOne" } ], "endpoints_links": [], "name": "nova", "type": "compute" }, <!-- 中略 --> }keystone-all
から返されるエンドポイント
情報の中にnova/glanceなどのリソースを
操作するためのAPIのURLが含まれる
keystone
の構造
●
本体: keystone-all
●
keystone-all
の認証/認可用バックエンド
–
KVS Backend
–
SQL Backend
–
PAM Backend
–
Template Backend
–
LDAP Backend
<keystone.conf
内>
driver=keystone.identity.backends.*
REST API
keystone-all
keystone
の構造
mysql> show tables;
+---+
| Tables_in_keystone |
+---+
| ec2_credential |
| endpoint |
| metadata |
| migrate_version |
| role |
| service |
| tenant |
| token |
| user |
| user_tenant_membership |
+---+
10 rows in set (0.00 sec)
SQL
バックエンドで見るkeystoneのデータ構造
●keystone
のSQLバックエンドデータベ
ースのテーブル数はessex / folsom と
もに10個
●migrate_version
テーブルに記録されて
いるデータベースのバージョン情報は
上がっている。
●essex : 1
●folsom: 4
●データ構造的に見ると変更箇所はtoken
テーブルにvalidフィールドが追加と
なったのみ。
●tokenテーブルに発行されたtokenが延
々と登録され続けていくんだけど...
これ溢れないのかな?
普通の利用方法
それぞれのコンポーネントでkeystoneを利用す
るための設定が必要。
1. keystone
自身の設定
2. nova
からkeystoneを利用するための設定
3. glance
からkeystoneを利用するための設定
ココを参考に設定してください!
http://d.hatena.ne.jp/pyde/20121111/
普通の利用方法
keystone
コマンドを利用して初期情報を登録し
ます。
1.
テナントの登録
2.
ユーザの登録
3.
ロールの登録
4.
テナント・ユーザ・ロールの関連付け
5. ec2
互換APIを利用する場合はec2クレデンシャルを登録
ココを参考にして作ってください!
http://aikotobaha.blogspot.jp/2012/04/openstackessex-configuration-02keystone.html
ちょっと変わった利用方法
$ export OS_USERNAME=foo
$ export OS_PASSWORD=bar
$ export OS_TENANT_NAME=openstackDemo
$ export OS_AUTH_URL="http://172.16.100.14:5000/v2.0"
$ nova flavor-list
+----+---+---+---+---+---+---+---+
| ID | Name | Memory_MB | Disk | Ephemeral | Swap | VCPUs | RXTX_Factor |
+----+---+---+---+---+---+---+---+
| 1 | m1.tiny | 512 | 0 | 0 | | 1 | 1.0 |
| 2 | m1.small | 2048 | 10 | 20 | | 1 | 1.0 |
| 3 | m1.medium | 4096 | 10 | 40 | | 2 | 1.0 |
| 4 | m1.large | 8192 | 10 | 80 | | 4 | 1.0 |
| 5 | m1.xlarge | 16384 | 10 | 160 | | 8 | 1.0 |
| 6 | m1.minimal | 64 | 0 | 0 | | 1 | 1.0 |
+----+---+---+---+---+---+---+---+
keystone
認証させつつ、novaコマンドと同等の
動きをするpythonスクリプトを書いてみる。
例えばflavorのリスト取得。これが...
keystone認証のための情報
を環境変数に設定する。
ちょっと変わった利用方法
keystone
さえマスターすれば、こんな感じに
python
でもワンライナーで簡単に書ける!
渾身のワンライナーでflavorリストを取得する
$ python -c 'from httplib import HTTPConnection as c;from json import load as l;s=c("172.16.100.100:5000");s.request("POST","/v2.0/tokens","{\"auth\":
{\"tenantName\":\"openstackDemo\",\"passwordCredentials\":{\"username\":\"foo\",\"password\":\"bar\"}}}",{"Content-Type":"application/json"});j=l(s.getresponse());s.close();tk=j["access"]["token"]["id"];tn=j["access"]
["serviceCatalog"][0]["endpoints"][0]["publicURL"].split("/")[-
1];s.close();s=c("172.16.100.100:8774");s.request("GET","/v2/%s/flavors"%tn,"",{"X-Auth-Token":tk});j=l(s.getresponse());s.close();print [(x["id"],x["name"],x["links"][0]["href"]) for x in j["flavors"]]'
結果(なんかそれっぽい!)
[(u'1', u'm1.tiny', u'http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4/flavors/1'), (u'2',
u'm1.small', u'http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4/flavors/2'), (u'3', u'm1.medium', u'http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4/flavors/3'), (u'4', u'm1.large',
u'http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4/flavors/4'), (u'5', u'm1.xlarge', u'http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4/flavors/5'), (u'6', u'm1.minimal', u'http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4/flavors/6')]
ちょっと変わった利用方法
続いて、OSイメージのリストも取得してみる。
先ほど既に環境変数としてkeystone認証に必要
となる情報を設定しているのでnovaコマンドで
取得できる。
$ nova image-list
+---+---+---+---+
| ID | Name | Status | Server |
+---+---+---+---+
| c9ef36c7-e6f4-414e-b603-257e36836e76 | tty-linux | ACTIVE | |
| 732ccc6c-093f-4ff3-88ec-d2c97df45014 | tty-linux-kernel | ACTIVE | |
| 1d51d35e-cd1f-4205-9e62-d249e439536b | tty-linux-ramdisk | ACTIVE | |
+---+---+---+---+
ちょっと変わった利用方法
もちろんpythonのワンライナーでも簡単。
渾身のワンライナーでimageリストを取得する
$ python -c 'from httplib import HTTPConnection as c;from json import load as l;s=c("172.16.100.100:5000");s.request("POST","/v2.0/tokens","{\"auth\":
{\"tenantName\":\"openstackDemo\",\"passwordCredentials\":{\"username\":\"foo\",\"password\":\"bar\"}}}",{"Content-Type":"application/json"});j=l(s.getresponse());s.close();tk=j["access"]["token"]["id"];tn=j["access"]
["serviceCatalog"][0]["endpoints"][0]["publicURL"].split("/")[-
1];s.close();s=c("172.16.100.100:8774");s.request("GET","/v2/%s/images"%tn,"",{"X-Auth-Token":tk});j=l(s.getresponse());s.close();print [(x["id"],x["links"][0]["href"]) for x in j["images"]]'
結果(これまたなんとなく取れてる気がする!)
[(u'c9ef36c7-e6f4-414e-b603-257e36836e76', u'http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4/images/c9ef36c7-e6f4-414e-b603-257e36836e76'), (u'1d51d35e-cd1f-4205-9e62-d249e439536b', u'http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4/images/1d51d35e-cd1f-4205-9e62-d249e439536b'), (u'732ccc6c-093f-4ff3-88ec-d2c97df45014', u'http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4/images/732ccc6c-093f-4ff3-88ec-d2c97df45014')]ちょっと変わった利用方法
UI
なしでVMも作れます!
じゃぁじゃぁ...渾身のワンライナーでVMインスタンスを作成!
※このあたりでsessionって1回張ればいいじゃない?って気がつくけど面倒だからそのまま。
$ python -c 'from httplib import HTTPConnection as c;from json import load as l;s=c("172.16.100.100:5000");s.request("POST","/v2.0/tokens","{\"auth\": {\"tenantName\":\"openstackDemo\",\"passwordCredentials\":{\"username\":\"foo\",\"password\":\"bar\"}}}",{"Content-Type":"application/json"});j=l(s.getresponse());s.close();tk=j["access"]["token"]["id"];tn=j["access"] ["serviceCatalog"][0]["endpoints"][0]["publicURL"].split("/")[-1];s.close();s=c("172.16.100.100:8774");s.request("POST","/v2/%s/servers"%tn,"{\"server\": {\"name\":\"josug009\",\"imageRef\":\"c9ef36c7-e6f4-414e-b603- 257e36836e76\",\"flavorRef\":\"6\",\"OS_DCF:diskConfig\":\"MANUAL\"}}",{"Content-Type":"application/json","X-Auth-Token":tk});j=l(s.getresponse());s.close();print j'
結果(おぉ!できた!のか!?)
{u'server': {u'links': [{u'href':
u'http://172.16.100.100:8774/v2/7695c8332c1b4450a6be1376b3a1f5c4/servers/2e81b265-0a33-416d-b60c-afc511395279', u'rel': u'self'}, {u'href': u'http://172.16.100.100:8774/7695c8332c1b4450a6be1376b3a1f5c4/servers/2e81b265-0a33-416d-b60c-afc511395279', u'rel': u'bookmark'}], u'OS-DCF:diskConfig': u'MANUAL', u'id': u'2e81b265-0a33-416d-b60c-afc511395279', u'security_groups': [{u'name': u'default'}], u'adminPass': u'vhcq2X8G4eXz'}}
ちょっと変わった利用方法
VM
インスタンスのリスト取得だってUI不要。
渾身のワンライナーでちょいちょいのドーンよ!
$ python -c 'from httplib import HTTPConnection as c;from json import load as l;s=c("172.16.100.100:5000");s.request("POST","/v2.0/tokens","{\"auth\":
{\"tenantName\":\"openstackDemo\",\"passwordCredentials\":{\"username\":\"foo\",\"password\":\"bar\"}}}",{"Content-Type":"application/json"});j=l(s.getresponse());s.close();tk=j["access"]["token"]["id"];tn=j["access"]
["serviceCatalog"][0]["endpoints"][0]["publicURL"].split("/")[-
1];s.close();s=c("172.16.100.100:8774");s.request("GET","/v2/%s/servers"%tn,"",{"X-Auth-Token":tk});j=l(s.getresponse());s.close();print [(x["id"],x["name"],x["links"][0]["href"]) for x in j["servers"]]'
結果(できてるw)
[(u'2e81b265-0a33-416d-b60c-afc511395279', u'josug009',