用 Owner 權限跑 Terraform 等於用 root 權限跑後端,夜路跑多了遲早遇到鬼
CI/CD 系列
課程內容與代碼會放在 Github 上: https://github.com/chechiachang/terraform-30-days
賽後文章會整理放到個人的部落格上 http://chechia.net/
本章介紹如何建立獨立的 service principal (service account / iam role) 專門給 terraform 執行。
前面的例子,我們執行 Terraform 都是用 azure ad User 的身份執行。
Review: 我們使用 az-cli az login,讓 terraform 使用本地的 credential 檔案。也就是說 terraform 是使用 login user 的權限來運行。我們可以檢查一下目前使用者的 Azure 權限
az role assignment list --include-inherited --assignee 12345678-1234-1234-1234-123456789012
[
{
"principalName": "chechia_chechia.net#EXT#@chechianet.onmicrosoft.com",
"principalType": "User",
"roleDefinitionName": "User Access Administrator",
"scope": "/",
"type": "Microsoft.Authorization/roleAssignments"
}
]
Azure AD 的權限則需要通過 Azure portal 查看
Global administrator 基本上是超級管理員,權限其實蠻大的,Terraform 的操作並不需要這麼大的權限。
Linux 管理常說,沒必要不要使用 root 的權限。
Terraform 也是同樣道理。沒事不要開著權限很大的帳號亂逛。
administrator / owner 權限太大,什麼事都能做很方便。但實務上常常使用 Admin role 其實是有相當風險的,使用 administrator role 操作 Terraform ,違反最小授權原則(POLP: Principle of least privilege)
所有 credential 只要使用,就可能有洩漏或被害的風險,只是管理方式不同,風險高低差異
我們上堂課展示過 CI / CD
通常 iam / RBAC 管理的權限,與其他 resource provision 的權限會切開,特別管理,連工程師的 User 都不應該有 iam 管理權限
許多整合性的角色權限 administrator, owner, editor, collaborater, ... 之類的權限其實都太大,最佳實踐是用到什麼權限,就開什麼權限
NOTE:
在實務上,我們為 terraform 設定專屬的 service principal。
我們會需要產生 terraform 專屬的 service principal
登入認證使用非對稱加密的 rsa key/crt,遠比起傳統密碼更加安全
KEY_NAME=~/.ssh/terraform-30-days
openssl req -newkey rsa:4096 -nodes -subj '/CN=terraform-30-days' -keyout ${KEY_NAME}.key -out ${KEY_NAME}.csr
openssl x509 -signkey ${KEY_NAME}.key -in ${KEY_NAME}.csr -req -days 365 -out ${KEY_NAME}.crt
openssl pkcs12 -export -out ${KEY_NAME}.pfx -inkey ${KEY_NAME}.key -in ${KEY_NAME}.crt
Getting Private key
Enter Export Password:
Verifying - Enter Export Password:
ls ~/.ssh/terraform-30-days*
/Users/che-chia/.ssh/terraform-30-days.crt
/Users/che-chia/.ssh/terraform-30-days.csr
/Users/che-chia/.ssh/terraform-30-days.key
/Users/che-chia/.ssh/terraform-30-days.pfx
產生完後我們使用 terraform 來創建 azure service principal。這裡還是使用有 admin 權限的 User 帳號來操作 terraform,範例在 azure/foundation/servcie_principal
,看一下內容
enable_service_principal_certificate=true
使用 certificate 來認證
# azure/foundation/service_principal
service_principal_name = "terraform-30-days"
enable_service_principal_certificate = true
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_certificate)
certificate_path = "/Users/che-chia/.ssh/terraform-30-days.crt"
password_rotation_in_years = 1
# Adding roles to service principal
# The principle of least privilege
role_definition_names = [
"Contributor"
]
這裡參考 Github kumarvna/terraform-azuread-service-principal 來修改的是 repo 內的 module。module 本身有放上 Terraform Registry 所以可以直接使用,我自己調整內容所以放到本 repo。細節可以看 azure/modules/azuread_service_principal
確定沒問題後 provision resource
terragrunt init
terragrunt plan
terragrunt apply
Azure 上就會產生 service principal,使用 certificate 做登入認證
測試一下 az-cli 是否能夠登入 service principal,Azure 官方文件:Service Principal
cat ~/.ssh/terraform-30-days.key > ~/.ssh/terraform-30-days.keycrt
cat ~/.ssh/terraform-30-days.crt >> ~/.ssh/terraform-30-days.keycrt
APP_NAME=terraform-30-days
az ad sp list --display-name ${APP_NAME}
TENANT_ID=$(az ad sp list --display-name ${APP_NAME} | jq -r '.[0].appOwnerTenantId')
SERVICE_NAME=$(az ad sp list --display-name ${APP_NAME} | jq -r '.[0].servicePrincipalNames[0]')
az login --service-principal \
--username ${SERVICE_NAME} \
--tenant ${TENANT_ID} \
--password ~/.ssh/terraform-30-days.keycrt > /tmp/azure-login-profile
[
{
"cloudName": "AzureCloud",
"homeTenantId": "12345678-1234-1234-1234-123456789012",
"id": "12345678-1234-1234-1234-123456789012",
"isDefault": true,
"managedByTenants": [],
"name": "Microsoft Azure Sponsorship",
"state": "Enabled",
"tenantId": "12345678-1234-1234-1234-123456789012",
"user": {
"name": "12345678-1234-1234-1234-123456789012",
"type": "servicePrincipal"
}
}
]
目前身份是 service principal,檢查一下自身權限 role assignment,看見正確設定的 role: Contributor
az role assignment list
[
{
"principalType": "ServicePrincipal",
...
"roleDefinitionName": "Contributor",
...
}
]
接下來要設定 terraform
有兩個方法
這邊示範使用環境變數,之後夾到 Github Action 上,或是其他 CI 上會比較方便,不用再更改 provider.tf 的原始碼
export ARM_CLIENT_ID="00000000-0000-0000-0000-000000000000"
export ARM_CLIENT_CERTIFICATE_PATH="/Users/che-chia/.ssh/terraform-30-days.pfx"
export ARM_CLIENT_CERTIFICATE_PASSWORD=<password> # change this
export ARM_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000"
export ARM_TENANT_ID="00000000-0000-0000-0000-000000000000"
export ARM_CLIENT_ID=$(cat /tmp/azure-login-profile | jq -r '.[0].user.name')
export ARM_CLIENT_CERTIFICATE_PATH="/Users/che-chia/.ssh/terraform-30-days.pfx"
export ARM_CLIENT_CERTIFICATE_PASSWORD=<password> # change this
export ARM_SUBSCRIPTION_ID=$(az account subscription list | jq -r '.[0].subscriptionId')
export ARM_TENANT_ID=$(cat /tmp/azure-login-profile | jq -r '.[0].tenantId')
env | grep ARM
ARM_CLIENT_CERTIFICATE_PATH=/Users/che-chia/.ssh/terraform-30-days.pfx
ARM_CLIENT_ID=
ARM_SUBSCRIPTION_ID=
ARM_TENANT_ID=
ARM_CLIENT_CERTIFICATE_PASSWORD=
如果參數都正常,
terragrunt init && terragrunt plan
嘗試更改自己 service principal 的 role,看能不能 Privilege escalation,把自己從 Contributor 變成 Owner。如果可以的話,terraform 可以自己提升權限變成 Owner,然後近來 azure 亂改
# azure/foundation/service_principal
role_definition_names = [
"Contributor",
"Owner" # try Privilege escalation
]
然後再次 apply 看看計謀會不會得逞
terragrunt plan
terragrunt apply
│ Error: Could not set Owners
│
│ with azuread_application.main,
│ on application.tf line 4, in resource "azuread_application" "main":
│ 4: owners = [data.azuread_client_config.current.object_id]
│ Error: authorization.RoleAssignmentsClient#Create: Failure responding to request: StatusCode=403 -- Original Error: autorest/azure: Service returned an error. Status=403 Code="AuthorizationFailed" Message="The client '12345678-1234-1234-1234-123456789012' with object id '12345678-1234-1234-1234-123456789012' does not have authorization to perform action 'Microsoft.Authorization/roleAssignments/write' over scope '/subscriptions/ba8eb346-d19e-4f65-96d9-8c783e6eea61' or the scope is invalid. If access was recently granted, please refresh your credentials."
Plan 都正常,表示 read / refresh azure 上的資源時,service principal 的 contributor 全縣市足夠的
apply 則回傳 403 permission denied 以及相關錯誤訊息,azure 表示 terraform 的 service principal 沒有權限可以調整 RBAC。表示 service principal 權限是受到限制的,符合我們的預期。
之後 Terraform 的操作,我們就不再使用 az login 來產生本地的 credential,而是使用 service principal
# azure/provider
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "azurerm" {
features {}
}
EOF
}
但是拜託
密碼跟 certificate .key .pfx 拜託拜託不要 commit 到 git 裏面,務必每次手動輸入
密碼跟 certificate .key .pfx 拜託拜託不要 commit 到 git 裏面,務必每次手動輸入
密碼跟 certificate .key .pfx 拜託拜託不要 commit 到 git 裏面,務必每次手動輸入
很重要所以講三次,但還是會看到有人 git add 就把 password commit 進去