Tubi 的基礎(chǔ)設(shè)施團(tuán)隊(duì)旨在鼓勵大家使用最佳實(shí)踐,不過我們不是通過宣揚(yáng)和說教,而是通過工具、策略和流程來實(shí)現(xiàn)這一目標(biāo)。我們期望幫助開發(fā)同事更容易地作出正確決策,尤其是在安全方面。
決策之一是關(guān)于如何管理用于訪問 AWS 的密鑰。雖然使用靜態(tài) IAM 用戶憑據(jù)是個(gè)好的開端,但它也為開發(fā)和運(yùn)維同學(xué)埋下了隱患:這些密鑰的的傳輸和存儲都非常困難,并且它們本身沒有過期時(shí)間,必須定期手動更新。
我們認(rèn)為可以通過使用我們的 SSO 廠商 Okta 提供的認(rèn)證功能來優(yōu)化這一過程。這樣不僅避免了入職/離職時(shí)需要自動化增減 IAM 用戶, 同時(shí)也實(shí)現(xiàn)了具有時(shí)效性、基于角色的、可以綁定到 Okta 支持的多重認(rèn)證(MFA)的用戶訪問控制。它避免了直接使用 IAM 用戶登錄,而是使用 AWS STS 生成基于角色的憑據(jù),這樣服務(wù)和用戶都使用相同的方式來訪問資源。(同時(shí)也有利于將來過渡到基于角色的跨賬號關(guān)聯(lián)方式 [1])。
Okta & AWS 集成配置指南
我們構(gòu)建了一些自定義工具來集成 Okta 和 AWS。這些工具跟我們的內(nèi)部系統(tǒng)緊密耦合。
下面我們會一步步來說明如何使用簡單的 bash 腳本完成這個(gè)工作。完成之后就可以避免使用靜態(tài) AWS 密鑰,轉(zhuǎn)而讓用戶使用 Okta 來認(rèn)證(支持 MFA),同時(shí)也不再需要通過 IAM 手動管理入職/離職用戶。
在本指南的最后,我們應(yīng)該可以做到:
·通過 Okta 登錄
·使 Okta 通過 SAML 登錄到 IAM
·訪問 AWS 安全令牌服務(wù)(STS)生成一系列會話憑據(jù)
·使用這些憑據(jù)訪問 AWS 資源
本指南需要讀者熟悉 Okta,AWS IAM,并且具備一些 bash 腳本知識。
集成配置
開始之前需要先登錄你的 AWS 和 Okta 賬號。你可以參考 Okta 的官方文檔[2],我們在此總結(jié)了以下步驟以供參考。
準(zhǔn)備工作
假定你具備 Admin 身份訪問 Okta 和 AWS IAM 資源,并且有創(chuàng)建 AWS 資源及權(quán)限的相關(guān)知識,接下來可以參考以下步驟。
配置 Okta
首先,你需要為 Okta 賬號創(chuàng)建一個(gè) Amazon Web Service 應(yīng)用。(這里的操作之后可能會發(fā)生變化,所以如果遇到問題請參考上面的官方文檔鏈接。
1.在你的 Okta 管理員頁面,訪問 Applications → Applications → Add Application → Amazon Web Services → Add。
2.這里可以根據(jù)需要自行配置,默認(rèn)的配置也可以正常工作。在 Sign-on 菜單下,選擇 SAML 2.0。這可以讓 Okta 生成一個(gè) SAML XML 文檔發(fā)給 AWS,用來將 Okta 會話轉(zhuǎn)換為 AWS 會話。這里也可能有其他可用的登錄方案,但是在本文寫作的時(shí)候 SAML 似乎是 Okta 文檔里提到的最好的方案。
3.頁面上應(yīng)該還有一個(gè)叫做 Identity Provider metadata 的鏈接,指向一個(gè) XML 文檔。現(xiàn)在下載該文檔,并保留當(dāng)前窗口,后面配置 AWS 的時(shí)候我們還需要從這個(gè)頁面獲取一些信息。
配置AWS
接下來,我們需要在 AWS 上創(chuàng)建一些資源,以使 AWS 信任 Okta 作為授權(quán)認(rèn)證提供商生成會話 token。我們還需要為 Okta 授權(quán)以使其瀏覽 AWS IAM 環(huán)境中的預(yù)設(shè)角色。
1.在 AWS IAM 控制臺導(dǎo)航至 Services → IAM → Identity Providers 創(chuàng)建一個(gè)Provider 并上傳我們之前下載的的元文檔命名為 Okta。獲取該資源的 ARN 信息以供后面使用。
2.在 Services → IAM → Users 選項(xiàng)下,創(chuàng)建一個(gè)新用戶并賦予這些權(quán)限。該用戶將被用于為 Okta 獲取可分配角色的元數(shù)據(jù)。我們需要通過編程訪問,所以下載 Access key 和 Secret key 供后面使用。同時(shí)記錄下改用戶對象的 ARN 以供后面使用。
{
"Statement": [
{
"Action": [
"iam:ListRoles",
"iam:ListAccountAliases"
],
"Effect": "Allow",
"Resource": "*"
}
],
"Version": "2012-10-17"
}
3.在 IAM Roles 頁面,使用此信任關(guān)系創(chuàng)建 一個(gè)新角色。一旦最終用戶使用 Okta 用戶憑據(jù)登錄到 AWS 就會被分配該角色。請確保你為該角色分配了一些策略以便后面測試,例如 AWS 提供的 ReadOnly 策略。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<your-aws-account-id>:saml-provider/okta"
},
"Action": "sts:AssumeRoleWithSAML",
"Condition": {
"StringEquals": {
"SAML:aud": "https://signin.aws.amazon.com/saml"
}
}
}
]
}
繼續(xù)設(shè)置 Okta
回到之前的 Okta 應(yīng)用程序創(chuàng)建頁面。現(xiàn)在我們已經(jīng)創(chuàng)建了 AWS 資源(并記下他們的 ARNs ),可以繼續(xù)配置了。
1.回到應(yīng)用程序創(chuàng)建窗口,填寫以下表單然后點(diǎn)擊 Done
Identity Provider ARN:我們創(chuàng)建的授權(quán)認(rèn)證提供商的 ARN。
Session Duration:默認(rèn)為1小時(shí),不過設(shè)置為一天比較實(shí)用。
2.訪問 Provisioning → Configure API Integration → Enable API integration
將之前創(chuàng)建的 IAM 用戶的憑據(jù)信息填入 Access Key 和 Secret Key 輸入框
在 Provisioning → To App → Edit → Update User Attributes頁面,勾選Enable 然后點(diǎn)擊Save
3.訪問 Assignments → Assign → Assign to People → Your account → Assign
在 Role 下拉列表中選擇之前所創(chuàng)建的角色,選中 SAML User Roles 選項(xiàng),保存。
會看到類似 app/amazon_aws/instance/<application-id> 這樣的 URL,記錄下來,我們后面將會用到那個(gè) Application ID。
認(rèn)證
當(dāng) Okta 和 AWS 賬號都設(shè)置好后,你就可以使用 Okta API 生成 AWS 憑據(jù)登錄了。當(dāng)前已經(jīng)有 一些可用工具 [3],但是目前支持 MFA 認(rèn)證的還不是很多,因?yàn)槟壳霸?Okta Python SDK 中還沒支持。必須要做的請求可以參考下面的腳本。
準(zhǔn)備工作
需要先為后面的要寫的腳本準(zhǔn)備一些工具:
·awscli:用于測試你的憑據(jù)并分配角色。如果要寫程序?qū)崿F(xiàn),那可以替換為 boto 或 AWS SDK for Java 等 AWS SDK。
·jq:用于命令行 JSON 解析。它自帶一種查詢語言可以從 JSON 響應(yīng)中獲取特定字段。
·xq:用于 XML 的 jq(SAML文檔中使用的是 XML 格式)
·curl 、recode 和 sed:linux 自帶的一些工具
使用 Okta 獲取會話
先為登錄做一些設(shè)置。
OKTA_ORG=tubi
OKTA_USERNAME=james
OKTA_PASSWORD=correcthorsebatterystaple
首先,向 Okta 請求狀態(tài) token
RESPONSE=$(
curl -X POST \\
"https://${OKTA_ORG}.okta.com/api/v1/authn" \\
-H "Content-Type: application/json" \\
-d "{\\"username\\": \\"${OKTA_USERNAME}\\", \\"password\\": \\"${OKTA_PASSWORD}\\"}"
)
echo ${RESPONSE} | jq -r .status # MFA_REQUIRED
Tubi 有個(gè)組織級策略會強(qiáng)制啟用 MFA[4],所以這里我從響應(yīng)信息中獲取了 MFA_REQUIRED 狀態(tài)信息。如果你沒有開啟 MFA(為什么不呢?),你可以跳過下一個(gè)用來提交 MFA 認(rèn)證的 curl 請求。
提交 MFA 認(rèn)證
_embedded 字段列出的是我的 MFA 設(shè)備列表。在這里我使用的例子是我的 TOTP 雙重認(rèn)證。
{...
"_embedded":{...
"factors":[...
{...
"factorType":"token:software:totp",
"id":"<factor-id>",
"provider":"GOOGLE"
}
]
}
}
為了提交 MFA 認(rèn)證,我們需要先設(shè)置一些字段。首先,我們需要保留請求中的 stateToken 以保持登錄會話。其次,我們需要認(rèn)證類型的 id 字段。最后需要的是 MFA app 生成的驗(yàn)證碼,在這個(gè)例子中,我從我的 Authenticator 應(yīng)用中獲取驗(yàn)證碼。
STATE_TOKEN=$(echo ${RESPONSE} | jq -r .stateToken)
FACTOR_ID=$(echo ${RESPONSE} | jq -r '._embedded.factors[] | select(.provider == "GOOGLE") | .id')
MFA_CODE=206847
RESPONSE=$(
curl -X POST \\
"https://${OKTA_ORG}.okta.com/api/v1/authn/factors/${FACTOR_ID}/verify" \\
-H "Content-Type: application/json" \\
-d "{\\"stateToken\\": \\"${STATE_TOKEN}\\", \\"passCode\\": \\"${MFA_CODE}\\"}"
)
echo ${RESPONSE} | jq -r .status # SUCCESS
注意: 狀態(tài) token 只在響應(yīng)有效期內(nèi)有效。如果你的 token 過期,請使用上面的 POST 命令再生成一個(gè)。
獲取 SAML XML 文檔
還記得我們當(dāng)時(shí)決定使用 SAML 來集成 Okta 和 AWS 嗎?這里我們需要一個(gè)中間資源來轉(zhuǎn)換已有的 Okta 會話為 AWS 會話。我們先轉(zhuǎn)換該會話為一個(gè) session id,它允許我們生成一個(gè)可以通過 Okta 傳遞給 AWS 的 SAML XML 會話文檔。
SESSION_TOKEN=$(echo ${RESPONSE} | jq -r .sessionToken)
RESPONSE=$(
curl -X POST \\
"https://${OKTA_ORG}.okta.com/api/v1/sessions" \\
-H "Content-Type: application/json" \\
-d "{\\"sessionToken\\": \\"${SESSION_TOKEN}\\"}"
)
echo ${RESPONSE} | jq -r .id # session id
最終,我們可以使用這個(gè) session ID 重新找回我們的 SAML 文檔。在這里你需要 Okta app id,你可以在上面提到的 admin 頁面的 url 中獲取到。
SESSION_ID=$(echo ${RESPONSE} | jq -r .id)
OKTA_APP_ID=T0tUQV9PUkc9dHViaQpP
RESPONSE=$(
curl -L \\
"https://${OKTA_ORG}.okta.com/app/amazon_aws/${OKTA_APP_ID}/login" \\
-H "Cookie: sid=${SESSION_ID}"
)
SAML_B64=$(echo ${RESPONSE} | sed 's/^.*name="SAMLResponse" type="hidden" value="\\([^"]*\\).*$/\\1/g' | recode html)
echo ${SAML_B64} | base64 --decode | xq -r .
響應(yīng)是 HTML 編碼,所以首先我們需要使用 recode 將它解碼。然后通過 base-decode,配合 xq 來解析 XML 文檔。
你可以在 SAML 文檔分配一個(gè)角色,當(dāng)然你需要有一個(gè)角色才行。可用角色列表在文檔中有。
ROLES=$(echo ${SAML_B64} | base64 --decode | xq -r '.["saml2p:Response"]["saml2:Assertion"]["saml2:AttributeStatement"]["saml2:Attribute"][] | select(.["@Name"] == "https://aws.amazon.com/SAML/Attributes/Role") | .["saml2:AttributeValue"][] | .["#text"]')
使用 SAML 文檔獲取 AWS 關(guān)聯(lián)角色
現(xiàn)在我們有了 SAML XML 文檔,它作為 Okta 和 AWS 認(rèn)證憑據(jù)的中間媒介,最終我們可以用它轉(zhuǎn)換為一個(gè)真實(shí)的 AWS 會話。
當(dāng)?shù)卿浀?AWS 時(shí),可能有一個(gè)或者多個(gè)角色,這里,我們只使用第一個(gè)角色?,F(xiàn)在我們有了運(yùn)行 assume-role 命令的所需的一切。
ROLE=$(echo ${ROLES} | awk '{print $1}')
PRINCIPAL_ARN=$(echo ${ROLE} | jq -r -R 'split(",")[0]')
ROLE_ARN=$(echo ${ROLE} | jq -r -R 'split(",")[1]')
CREDENTIALS=$(
aws sts assume-role-with-saml \\
--role-arn=${ROLE_ARN} --principal-arn=${PRINCIPAL_ARN} \\
--saml-assertion=${SAML_B64} --duration=3600
)
echo ${CREDENTIALS} | jq -r .
現(xiàn)在我們有了可以成功運(yùn)行 awscli 的憑據(jù)。
export AWS_ACCESS_KEY_ID=$(echo ${CREDENTIALS} | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo ${CREDENTIALS} | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo ${CREDENTIALS} | jq -r .Credentials.SessionToken)
aws sts get-caller-identity | jq -r .UserId # AROAQGUD4GZ4NDOKIDOKI:james
完成
從這里開始,我們可以用 awscli 或者其他 AWS SDK 執(zhí)行一些其他請求。
因?yàn)槲覀兪褂玫氖怯袝r(shí)間限制的 STS 憑據(jù),所以需要設(shè)置那個(gè) SessionToken 變量。
記住這些憑據(jù)將在你 duration 指定的期限內(nèi)將持續(xù)有效,它本身跟由 AWS 定義角色中指定的 max_session_duration 變量綁定。這兩個(gè)期限也將受限于我們在 Okta 指定的會話生命周期。
下一步?
你可以使用上面的列出的腳本來完成這個(gè)工作,但是在 Tubi 我們更傾向于圍繞我們的工作流程構(gòu)建額外的自動化工具。生成的憑據(jù)會被安裝到 ~/.aws/credentials,當(dāng)開發(fā)同學(xué)使用 awscli 時(shí)會用到他們。他們也可以通過環(huán)境變量的方式用于 Kubernetes pod 的本地調(diào)試。最后,我們也會通過 AWS 認(rèn)證方法 [5]使用這些憑據(jù)認(rèn)證我們的 Hashicorp Vault [6]部署,因此我們也可以使用 AWS 會話生成動態(tài)的、有時(shí)間期限的數(shù)據(jù)庫憑據(jù)。AWS [7]和 Vault[8] 都提供了非常細(xì)粒度的訪問控制策略,所以我們不僅可以非常明確的指定哪些資源可以被訪問,還能將其與 Okta 和 AWS 會話關(guān)聯(lián)起來。
以下是 Tubi 一個(gè)內(nèi)部工具的架構(gòu)圖,使用我們多賬號、基于角色的 Okta 集成配置以訪問資源。盡管并非所有工具都需要如此高的復(fù)雜性,但是使用代碼構(gòu)建最佳實(shí)踐的靈活性將保證滿足組織在未來幾年內(nèi)在規(guī)模和復(fù)雜性上進(jìn)行擴(kuò)展。
原文:James Wu, Lead Production Infra Engineer
譯者:Youqing Han, Senior SRE
[1]https://www.google.com/search?q=aws+multiple+account&rlz=1C5CHFA_enUS840US840&oq=aws+multiple+account&aqs=chrome..69i57j69i60.1734j0j4&sourceid=chrome&ie=UTF-8
[2]https://saml-doc.okta.com/SAML_Docs/How-to-Configure-SAML-2.0-for-Amazon-Web-Service#scenarioA
[3]https://github.com/jmhale/okta-awscli
[4]https://developer.okta.com/blog/2018/02/08/set-up-and-enforce-multi-factor-auth-with-okta
[5]https://www.vaultproject.io/docs/auth/aws/
[6]https://www.vaultproject.io/
[7]https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html
[8]https://www.vaultproject.io/docs/concepts/policies/