本keystone源碼分析系列基于Juno版Keystone,于2014年10月16日隨Juno版OpenStack發(fā)布。
Keystone作為OpenStack中的身份管理與授權(quán)模塊,主要實(shí)現(xiàn)系統(tǒng)用戶的身份認(rèn)證、基于角色的授權(quán)管理、其他OpenStack服務(wù)的地址發(fā)現(xiàn)和安全策略管理等功能。Keystone作為開源云系統(tǒng)OpenStack中至關(guān)重要的組成部分,與OpenStack中幾乎所有的其他服務(wù)(如Nova, Glance, Neutron等)都有著密切的聯(lián)系。同時(shí),Keystone作為開源的身份管理和授權(quán)系統(tǒng),對(duì)于其當(dāng)前實(shí)現(xiàn)機(jī)制的探討已經(jīng)成為許多信息安全領(lǐng)域研究人員的一個(gè)重要方向,基于其提出的安全模型與擴(kuò)展實(shí)現(xiàn)已經(jīng)很多,這里我們并不贅述這些學(xué)術(shù)成果。
由于作者精力和能力有限,希望得到讀者的反饋與指正,轉(zhuǎn)載請(qǐng)注明出處。
在分析任何一個(gè)系統(tǒng)前安裝并了解該系統(tǒng)的使用方法都是有益的,Keystone也不例外。關(guān)于如何安裝和配置Keystone請(qǐng)參考本博客的其他隨筆,這里我們假設(shè)讀者已經(jīng)熟悉OpenStack中每個(gè)服務(wù)的.conf和paste.ini配置文件的大體作用。
首先介紹我的系統(tǒng)環(huán)境,由于重點(diǎn)關(guān)注Keystone的相關(guān)機(jī)理,我在團(tuán)隊(duì)已有10臺(tái)服務(wù)器組成的小型云外搭建了一個(gè)Keystone的開發(fā)調(diào)試環(huán)境。當(dāng)前使用的是Ubuntu 14.04 LTS Desktop,系統(tǒng)中的Keystone采用Ubuntu的apt工具安裝,源碼采用git下載,并且都是使用的Juno版本的分支,配置文件集中于/etc/keystone目錄下。
首先我們查看/etc/keystone/keystone-paste.ini文件,這里簡(jiǎn)要的介紹這個(gè)文件的大體結(jié)構(gòu)與含義。
# /etc/keystone/keystone-paste.ini # filters [ filter :token_auth] paste.filter_factory = keystone.middleware:TokenAuthMiddleware.factory
[ filter :admin_token_auth] paste.filter_factory = keystone.middleware:AdminTokenAuthMiddleware.factory ... # applications [ app :service_v3] paste.app_factory = keystone.service:v3_app_factory ...
# pipelines [ pipeline :api_v3] pipeline = sizelimit url_normalize build_auth_context token_auth admin_token_auth xml_body_v3 json_body ec2_extension_v3 s3_extension simple_cert_extension revoke_extension service_v3 ...
# composites [ composite :main] use = egg:Paste # urlmap /v2.0 = public_api /v3 = api_v3 / = public_version_api [ composite :admin] use = egg:Paste # urlmap /v2.0 = admin_api /v3 = api_v3 / = admin_version_api
可以發(fā)現(xiàn)這個(gè)文件事實(shí)上由若干filter, pipeline, application和composite的定義組成,在定義時(shí)并非必須嚴(yán)格按照這樣的順序進(jìn)行隔離,這里為了分析的方便我們將其總結(jié)為這些段落。對(duì)于這些內(nèi)容的具體含義,可以參考Paste Deploy和Python WSGI相關(guān)的介紹。
? Keystone的代碼生態(tài)圈包括keystone, python-keystoneclient和keystonemiddleware。其中keystone以WSGI服務(wù)器的方式實(shí)現(xiàn),其監(jiān)聽python-keystoneclient和keystonemiddleware發(fā)送來的HTTP請(qǐng)求并作出相應(yīng)響應(yīng)。
keystone-paste.ini文件事實(shí)上是一個(gè)paste-deploy配置文件,該文件由application, filter, pipeline, composite等定義段落組成。composite是第一層調(diào)度者,它粗略地根據(jù)不同的url類型將請(qǐng)求分配到不同的pipeline上。每一個(gè)pipeline由若干filter和一個(gè)application組成。正如其名,filter按照其在pipeline中的先后順序依次對(duì)http請(qǐng)求進(jìn)行過濾,包括對(duì)參數(shù)進(jìn)行格式化處理等操作。application位于pipeline的末尾,每一個(gè)pipeline只有一個(gè)application。最終通過所有filter的請(qǐng)求被application進(jìn)一步調(diào)度到系統(tǒng)實(shí)現(xiàn)上每一個(gè)模塊的路由層(routers),路由層根據(jù)HTTP請(qǐng)求方法和具體的請(qǐng)求路徑,將HTTP請(qǐng)求分發(fā)給對(duì)應(yīng)的控制層(controllers),controllers集中實(shí)現(xiàn)業(yè)務(wù)邏輯,并通過調(diào)用更低的驅(qū)動(dòng)層完成底層的工作,如數(shù)據(jù)庫(kù)的讀寫等等。這些構(gòu)成了composite, pipeline, filter, application和keystone實(shí)現(xiàn)間的邏輯關(guān)系。
keystone-paste.ini文件從一個(gè)高層次定義了keystone所需的composite會(huì)將哪些類型的url交由由哪些pipeline, 每一個(gè)pipeline由哪些filter和app組成,以及具體到每一個(gè)filter和app是由系統(tǒng)源碼的哪一個(gè)部分實(shí)現(xiàn)的,事實(shí)上,從下文的分析可以看出,每一個(gè)filter在實(shí)現(xiàn)上都對(duì)應(yīng)著一個(gè)特定的類,而每一個(gè)application在實(shí)現(xiàn)上則對(duì)應(yīng)著一個(gè)具體的方法。
以前文節(jié)選的部分keystone-paste.ini文件為例,我們以一個(gè)OpenStack系統(tǒng)要求的REST風(fēng)格的HTTP請(qǐng)求視角看一下系統(tǒng)是如何實(shí)現(xiàn)的:
當(dāng)用戶以HTTP POST方式請(qǐng)求http://localhost:5000/v3/auth/tokens這個(gè)url時(shí),意味著用戶希望進(jìn)行身份認(rèn)證,同時(shí)獲得keystone簽發(fā)的Token。當(dāng)然,用戶需要在自己的HTTP請(qǐng)求中給出一些自己的身份信息,這樣keystone才能據(jù)以判斷該用戶是否是系統(tǒng)合法的用戶,并根據(jù)其擁有的角色和權(quán)限為其簽發(fā)token。
下面是一個(gè)這樣的HTTP請(qǐng)求例子,采用cURL命令行工具模擬,-d 代表了POST方法,-i參數(shù)說明我們要求系統(tǒng)顯示未來獲得的響應(yīng)(response)的header,-H參數(shù)指明了我們的請(qǐng)求攜帶的header,這里向keystone指出我們接受的網(wǎng)絡(luò)數(shù)據(jù)傳輸類型。
curl - i \ -H " Content-Type: application/json " \ -d ' { " auth " : { " identity " : { " methods " : [ " password " ], " password " : { " user " : { " domain " :{ " name " : " Default " }, " name " : " USER_NAME " , " password " : " PASSWORD " } } }, " scope " : { " project " : { " domain " : { " name " : " Default " }, " name " : " PROJECT " } } } } ' \ http: //127 .0.0.1:5000/v3/auth/tokens
系統(tǒng)首先根據(jù)paste-deploy配置文件結(jié)合請(qǐng)求的url類型(/v3)來判斷處理該請(qǐng)求的pipeline即api_v3,接著系統(tǒng)將該請(qǐng)求交由該pipeline來處理。pipeline api_v3中的所有filter會(huì)依次對(duì)該請(qǐng)求的header以及data等內(nèi)容進(jìn)行處理,比如過濾器sizelimit在進(jìn)行一定的操作后,將其傳給下一個(gè)過濾器url_normalize,依次類推,直到給請(qǐng)求被傳遞給管道盡頭的application。
我們以兩個(gè)filter為例進(jìn)行探討,分別是token_auth和admin_token_auth,在該paste-deploy文件的filter定義字段,我們能夠找到這兩個(gè)filter的實(shí)現(xiàn)位置,事實(shí)上這也是系統(tǒng)搜尋每一個(gè)過濾器具體實(shí)現(xiàn)的依據(jù)。可以看到token_auth是由keystone.middleware:TokenAuthMiddleware.factory實(shí)現(xiàn)的,那么我們到keystone/middleware/core.py中找到TokenAuthMiddleware這個(gè)類:
#keystone/middleware/core.py
class TokenAuthMiddleware(wsgi.Middleware): def process_request(self, request): token = request.headers.get(AUTH_TOKEN_HEADER) context = request.environ.get(CONTEXT_ENV, {}) context[ ' token_id ' ] = token if SUBJECT_TOKEN_HEADER in request.headers: context[ ' subject_token_id ' ] = ( request.headers.get(SUBJECT_TOKEN_HEADER)) request.environ[CONTEXT_ENV] = context
發(fā)現(xiàn)該類繼承了wsgi.Middleware,說明這個(gè)類實(shí)質(zhì)上是一個(gè)WSGI中間件。該中間件提取了http請(qǐng)求中的X-Auth-Token字段和(可選的)X-Subject-Token字段的值,將context中相應(yīng)的字段填充為這兩個(gè)值,然后將修改后的上下文寫回到請(qǐng)求攜帶的環(huán)境信息中,傳遞給下一個(gè)filter即中間件admin_token_auth。再來看filter admin_token_auth是如何實(shí)現(xiàn)的,首先定位其實(shí)現(xiàn)的位置:keystone.middleware:AdminTokenAuthMiddleware.factory,接著到源碼keystone/middleware/core.py模塊下找到相應(yīng)的AdminTokenAuthMiddleware類,
#keystone/middleware/core.py
class AdminTokenAuthMiddleware(wsgi.Middleware): """ A trivial filter that checks for a pre-defined admin token. Sets 'is_admin' to true in the context, expected to be checked by methods that are admin-only. """ def process_request(self, request): token = request.headers.get(AUTH_TOKEN_HEADER) context = request.environ.get(CONTEXT_ENV, {}) context[ ' is_admin ' ] = (token == CONF.admin_token) request.environ[CONTEXT_ENV] = context、
可見這個(gè)過濾器的功能也非常簡(jiǎn)單,就是從http請(qǐng)求header中的X-Auth-Token字段提取附帶的token,同時(shí)解析keystone主配置文件(keystone.conf)中[DEFAULT]字段下的admin_token選項(xiàng),二者進(jìn)行比對(duì),如果結(jié)果相同,說明這個(gè)請(qǐng)求的發(fā)送者是我們系統(tǒng)默認(rèn)的admin,在context字典的is_admin字段設(shè)1后寫回到請(qǐng)求的環(huán)境信息,否則在context字典的is_admin字段置0。當(dāng)然,熟悉keystone官方文檔的用戶會(huì)發(fā)現(xiàn),在keystone的官方文檔中強(qiáng)烈建議生產(chǎn)環(huán)境中刪除該中間件,同時(shí)不設(shè)置keystone.conf文件中[DEFAULT]字段下的admin_token選項(xiàng),因?yàn)樵搕oken的出示者將會(huì)獲得系統(tǒng)的最高權(quán)限,因此禁用該賬戶能夠避免一些不必要的攻擊。
從上面的兩個(gè)例子可以看到,每一個(gè)filter進(jìn)行一個(gè)具體的操作,這些操作比較簡(jiǎn)單和獨(dú)立,彼此按先后順序串聯(lián)起來,如本例中的過濾器token_auth放置在過濾器admin_token_auth之前,這就使得系統(tǒng)在對(duì)context的is_admin字段進(jìn)行填充以前,會(huì)對(duì)token_id和subject_token_id字段進(jìn)行填充。
最后我們看一下application是如何實(shí)現(xiàn)的。在pipeline api_v3的末端對(duì)應(yīng)著最終的服務(wù)器應(yīng)用service_v3,我們根據(jù)keystone-paste.ini文件中給出的位置paste.app_factory = keystone.service:v3_app_factory找到該app的具體實(shí)現(xiàn):keystone/service.py,
# keystone/service.py ...
@fail_gracefully def v3_app_factory(global_conf, ** local_conf): controllers.register_version( ' v3 ' ) mapper = routes.Mapper() sub_routers = [] _routers = [] router_modules = [assignment, auth, catalog, credential, identity, policy] if CONF.trust.enabled: router_modules.append(trust) for module in router_modules: routers_instance = module.routers.Routers() _routers.append(routers_instance) routers_instance.append_v3_routers(mapper, sub_routers) # Add in the v3 version api sub_routers.append(routers.VersionV3( ' admin ' , _routers)) sub_routers.append(routers.VersionV3( ' public ' , _routers)) return wsgi.ComposingRouter(mapper, sub_routers)
...
可以看到,該application將會(huì)實(shí)現(xiàn)第二層路由(第一層由keystone-paste.ini文件中的composite字段實(shí)現(xiàn)),此次路由將具體的請(qǐng)求處理工作進(jìn)一步分發(fā)到系統(tǒng)的各個(gè)模塊上,比如代碼中的assignment,auth, catalog等等。由具體的模塊根據(jù)請(qǐng)求的具體路徑和內(nèi)容完成具體功能。
裝飾器@fail_gracefully在keystone/service.py中也有定義,主要功能是包裹住前文中的函數(shù)v3_app_factory,正如名稱所示,讓被裝飾的v3_app_factory()函數(shù)能夠“優(yōu)雅”地執(zhí)行,而由fail_gracefully()來處理可能的日志記錄和異常處理等善后工作。
# keystone/service.py ... def fail_gracefully(f): """ Logs exceptions and aborts. """ @functools.wraps(f) def wrapper(*args, ** kw): try : return f(*args, ** kw) except Exception as e: LOG.debug(e, exc_info = True) # exception message is printed to all logs LOG.critical(e) sys.exit( 1 ) return wrapper ...
至此,我們從WSGI和paste-deploy的角度邁出了深入了解keystone實(shí)現(xiàn)的第一步,知道了keystone服務(wù)器是如何將一個(gè)HTTP請(qǐng)求粗略地歸類分發(fā)到pipeline,再通過filter到達(dá)相應(yīng)的app。下一步,我們將會(huì)看到每一個(gè)pipeline末端的app如何針對(duì)具體的HTTP請(qǐng)求方法和地址將其分發(fā)到對(duì)應(yīng)的router,router再將其交給相應(yīng)的controller,由controller承上啟下完成最終的工作的。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
