Terraformを使ってAWS SSOのユーザにEKSクラスターのフルアクセス権限を与える
やりたいこと
AWS SSOのユーザーに、特定のEKSクラスターのフルアクセス権限を付与する設定を、Terraformで行ないたいと思います。
詳細
厳密に言うと、SSOのユーザーは、アクセス権限セットで作成されるIAM Roleにスイッチロールして当該アカウントにログインしますので、フルアクセス権限を与えるエンティティもIAM Roleになります。このあたりの仕組みをよくご存じない方は以下のエントリを御覧ください。
また、「フルアクセス権限を付与する」というのも、厳密には当該IAM Roleをk8sクラスター内のsystem:masters
グループに追加するだけです。system:masters
グループがフルアクセス権限を持っているcluster-admin
ClusterRoleと紐付いています。
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: annotations: rbac.authorization.kubernetes.io/autoupdate: "true" creationTimestamp: "2021-03-12T06:33:31Z" labels: kubernetes.io/bootstrapping: rbac-defaults managedFields: - apiVersion: rbac.authorization.k8s.io/v1 fieldsType: FieldsV1 fieldsV1: f:metadata: f:annotations: .: {} f:rbac.authorization.kubernetes.io/autoupdate: {} f:labels: .: {} f:kubernetes.io/bootstrapping: {} f:rules: {} manager: kube-apiserver operation: Update time: "2021-03-12T06:33:31Z" name: cluster-admin resourceVersion: "46" selfLink: /apis/rbac.authorization.k8s.io/v1/clusterroles/cluster-admin uid: eaf54afc-0b97-4607-bb60-79c2cec01f8f rules: - apiGroups: - '*' resources: - '*' verbs: - '*' - nonResourceURLs: - '*' verbs: - '*'
rules
欄がすべて*
(全許可)になっています。
ゴール
具体的な対応内容は、kube-system
namespaceに存在する aws-auth
というConfigMapに前述のSSOで作成されるIAM Roleに関する設定を書くというものになります。
以下公式ドキュメントに書かれているように、mapRoles
セクション内にrolearn
、username
、groups
という項目値を追記します。
やってみた
前提条件
AWS SSO側での作業が終わっていることとします。つまり以下データが作成済であるということです。
- ユーザー
- アクセス権限セット
- 割り当て(ユーザー/アカウント/アクセス権限セットの紐付け)
今回は、AdministratorAccess AWS管理ポリシーをアタッチしたAdministratorAccessアクセス権限セットを作っています。
EKSクラスターの作成
以下の方法で作成します。
IAM RoleのARNを取得
さて、次はSSOで作成されるIAM RoleのARNをTerraform内で参照出来るようにしましょう。最終的にそのARNをaws-auth
ConfigMap内のmapRoles
セクション内のrolearn
に書きます。
Data Sourceではできなかった
TerraformにはData Sourceという、既存リソースの情報を参照するための方法があり、IAM Roleについても用意されているのでこいつを使えば楽勝だろうと思っていたのですが、無理でした。以下のように、参照したいIAM Roleの名前を完全一致で指定する必要があるためです。
data "aws_iam_role" "example" { name = "an_example_role_name" }
SSOによって作成されるIAM Roleの名前は、AWSReservedSSO_(アクセス権限セット名)_(英字小文字と数字の組み合わせのランダムな文字列)
となります。最後にランダムな文字列が付くので、名前完全一致を実現するのは難しいです。
ダメもとで以下のような部分一致のコードを実行(terraform apply)してみましたが、
data "aws_iam_role" "sso_admin_role" { name = "AWSReservedSSO_AdministratorAccess_*" }
エラーになりました。
Error: error reading IAM Role (AWSReservedSSO_AdministratorAccess_*): ValidationError: The specified value for roleName is invalid. It must contain only alphanumeric characters and/or the following: +=,.@_-
仕方が無いので別のアプローチで攻めます。色々試行錯誤した結果、以下の構成になりました。
- AWS CLIでIAM Roleの情報をざっくり取得
- jqで絞り込み
- external providerを使ってterraform内で利用する
AWS CLI
IAM RoleのARNを取得するには、aws iam list-roles
を使って一覧をざっくり取得する必要があります。aws iam get-role
もありますが、これは必須引数でロール名があるので今回の要件には合いませんでした。
$ aws iam list-roles --query 'Roles[*].{RoleName:RoleName,Arn:Arn}' --path-prefix /aws-reserved/sso.amazonaws.com/ [ { "RoleName": "AWSReservedSSO_AdministratorAccess_51de502f7e28cecb", "Arn": "arn:aws:iam::123456789012:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_AdministratorAccess_51de502f7e28cecb" }, { "RoleName": "AWSReservedSSO_ReadOnlyAccess_13403f71ae721770", "Arn": "arn:aws:iam::123456789012:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_ReadOnlyAccess_13403f71ae721770" }, { "RoleName": "AWSReservedSSO_ViewOnlyAccess_c2ac1decdda72cbc", "Arn": "arn:aws:iam::123456789012:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_ViewOnlyAccess_c2ac1decdda72cbc" } ]
--query
で必要な項目だけ取得するようにしています。--path-prefix
でAWS SSO経由で作成されたIAM Roleだけを取得するように絞り込んでいます。
これだけだとSSOで作成された他のIAM Roleもヒットしてしまっています。jqを使って絞り込んでいきます。
jq
$ aws iam list-roles --query 'Roles[*].{RoleName:RoleName,Arn:Arn}' --path-prefix /aws-reserved/sso.amazonaws.com/ \ | jq '.[] | select(.RoleName |test("AWSReservedSSO_AdministratorAccess_*"))' { "RoleName": "AWSReservedSSO_AdministratorAccess_51de502f7e28cecb", "Arn": "arn:aws:iam::123456789012:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_AdministratorAccess_51de502f7e28cecb" }
先程のAWS CLIの結果をjqに渡しています。test()
で正規表現が使えます。select((検索対象文字列)| test((正規表現)))
とすることで、検索対象文字列が正規表現にマッチする配列要素だけを取得できます。
Arnの取得方法については目処が付きました。
external provider
external providerはコマンド結果を参照できるようになるTerraformのproviderです。これを使って先程のコマンドの結果をTerraformの世界に取り込みます。
まず先程のコマンドをbashスクリプト化します。
aws iam list-roles --query 'Roles[*].{RoleName:RoleName,Arn:Arn}' --path-prefix /aws-reserved/sso.amazonaws.com/ | jq '.[] | select(.RoleName |test("AWSReservedSSO_AdministratorAccess_*"))'
そしてexternal data sourceで参照します。
data "external" "get_sso_admin_role" { program = ["bash", "./bash/get-sso-admin-role.sh"] }
required_providers
の設定も忘れずに。
terraform { required_providers { external = { source = "hashicorp/external" version = "2.1.0" } } }
この状態で terraform init
し直してterraform apply
をすることで、コマンド結果をTerraform内で扱うことが出来るようになります。
$ terraform state show module.blue.data.external.get_sso_admin_role # module.blue.data.external.get_sso_admin_role: data "external" "get_sso_admin_role" { id = "-" program = [ "bash", "./bash/get-sso-admin-role.sh", ] result = { "Arn" = "arn:aws:iam::123456789012:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_AdministratorAccess_51de502f7e28cecb" "RoleName" = "AWSReservedSSO_AdministratorAccess_51de502f7e28cecb" } }
aws-auth ConfigMapの更新
EKS published moduleを使っている場合、今回更新が必要なaws-auth
ConfigMapのmapRoles
セクションの設定はmoduleの引数から可能です。map_roles
引数を使います。
module "main" { source = "terraform-aws-modules/eks/aws" version = "14.0.0" cluster_name = var.cluster_name cluster_version = var.k8s_version subnets = var.subnet_ids vpc_id = var.vpc_id node_groups = { example = { target_group_arns = [aws_alb_target_group.tg.arn] subnets = var.private_subnet_ids } } + map_roles = local.map_roles } +locals { + map_roles = [ + { + rolearn = data.external.get_sso_admin_role.result.Arn + username = "${data.external.get_sso_admin_role.result.RoleName}:{{SessionName}}" + groups = ["system:masters"] + }] +} data "external" "get_sso_admin_role" { program = ["bash", "./modules/k8s_cluster/bash/get-sso-admin-role.sh"] }
{{SessionName}}
は動的にassume roleのセッション名に変換されます。これにより、スイッチロール元のユーザーを識別することができます。詳細は以下GREEさんのブログに記載されています。
接続確認
以上で設定完了なので、SSOユーザーで接続確認してみましょう。
AWSでの認証
まずAWS側の認証が必要です。CLIをSSOユーザー(がスイッチするIAM Role)の権限で利用できるように設定します。aws configure sso
コマンドを使います。
$ aws configure sso SSO start URL [None]: https://d-xxxxxxxxxx.awsapps.com/start SSO Region [None]: ap-northeast-1 Attempting to automatically open the SSO authorization page in your default browser. If the browser does not open or you wish to use a different device to authorize this request, open the following URL: https://device.sso.ap-northeast-1.amazonaws.com/ Then enter the code: NLXV-FDJG
自動でブラウザでSSOポータルのログイン画面が開くのでログインします。 この画面まで進めたらCLIに戻ります。
どのアカウントかや、どのIAMロールを使うのかなど色々訊かれるので答えていきます。
There are 3 AWS accounts available to you. Using the account ID 123456789012 There are 2 roles available to you. Using the role name "AdministratorAccess" CLI default client Region [ap-northeast-1]: CLI default output format : CLI profile name [AdministratorAccess-123456789012]: eks-access-test To use this profile, specify the profile name using --profile, as shown: aws s3 ls --profile eks-access-test
これでAWSの認証ができました。数時間(デフォルト一時間)で認証が切れますが、その場合は $ aws sso login --profile eks-access-test
コマンドを実行してください。
kubeconfigファイルの作成
$ aws eks update-kubeconfig --name blue-with-module --profile eks-access-test
これで~/.kube/config
に設定が追記されます。ちゃんとeks-access-testプロファイルを使うように書かれていますね。
- name: arn:aws:eks:ap-northeast-1:123456789012:cluster/blue-with-module user: exec: apiVersion: client.authentication.k8s.io/v1alpha1 args: - --region - ap-northeast-1 - eks - get-token - --cluster-name - blue-with-module command: aws env: - name: AWS_PROFILE value: eks-access-test
エラー
いよいよ接続です。が、Unauthorizedエラーになります。
$ kubectl config current-context arn:aws:eks:ap-northeast-1:123456789012:cluster/blue-with-module $ kubectl get all -A error: You must be logged in to the server (Unauthorized)
他のユーザーで接続してConfigMapを確認してみましたが、正しく設定されているように見えます。
$ kubectl get configmap aws-auth -o yaml -n kube-system apiVersion: v1 data: mapAccounts: | [] mapRoles: | - "groups": - "system:bootstrappers" - "system:nodes" "rolearn": "arn:aws:iam::123456789012:role/blue-with-module20210321061439521900000009" "username": "system:node:{{EC2PrivateDNSName}}" - "groups": - "system:masters" "rolearn": "arn:aws:iam::123456789012:role/aws-reserved/sso.amazonaws.com/ap-northeast-1/AWSReservedSSO_AdministratorAccess_51de502f7e28cecb" "username": "AWSReservedSSO_AdministratorAccess_51de502f7e28cecb:{{SessionName}}" mapUsers: | [] kind: ConfigMap metadata: creationTimestamp: "2021-03-21T06:17:32Z" labels: app.kubernetes.io/managed-by: Terraform terraform.io/module: terraform-aws-modules.eks.aws managedFields: - apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:data: .: {} f:mapAccounts: {} f:mapRoles: {} f:mapUsers: {} f:metadata: f:labels: .: {} f:app.kubernetes.io/managed-by: {} f:terraform.io/module: {} manager: HashiCorp operation: Update time: "2021-03-21T06:17:32Z" name: aws-auth namespace: kube-system resourceVersion: "34150" selfLink: /api/v1/namespaces/kube-system/configmaps/aws-auth uid: 66cc3516-49aa-4abe-9b26-064377325da1
path-prefixの除去が必要
以下のQiita記事で言及されていたのですが、rolearn
として記述するIAM RoleのARNから/aws-reserved/sso.amazonaws.com/
というpath-prefixを削除する必要があるようです。
今回の場合、東京リージョンでSSOを有効化したので、path-prefixは/aws-reserved/sso.amazonaws.com/ap-northeast-1/
となっています。
というわけで、tfファイルを編集します。
locals { map_roles = [ { - rolearn = data.external.get_sso_admin_role.result.Arn + rolearn = replace(data.external.get_sso_admin_role.result.Arn, "aws-reserved/sso.amazonaws.com/ap-northeast-1/", "") username = "${data.external.get_sso_admin_role.result.RoleName}:{{SessionName}}" groups = ["system:masters"] }] }
terraform apply
をして再度確認すると、無事接続成功しました。?
$ kubectl get all -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system pod/aws-node-sqpvb 1/1 Running 0 5h14m kube-system pod/coredns-59847d77c8-mp8t2 1/1 Running 0 5h20m kube-system pod/coredns-59847d77c8-twzdr 1/1 Running 0 5h20m kube-system pod/kube-proxy-cb65z 1/1 Running 0 5h14m NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default service/kubernetes ClusterIP 172.20.0.1 <none> 443/TCP 5h20m kube-system service/kube-dns ClusterIP 172.20.0.10 <none> 53/UDP,53/TCP 5h20m NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE kube-system daemonset.apps/aws-node 1 1 1 1 1 <none> 5h20m kube-system daemonset.apps/kube-proxy 1 1 1 1 1 <none> 5h20m NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE kube-system deployment.apps/coredns 2/2 2 2 5h20m NAMESPACE NAME DESIRED CURRENT READY AGE kube-system replicaset.apps/coredns-59847d77c8 2 2 2 5h20m
ログ確認
aws-auth
ConfigMap内に書いた{{SessionName}}
が正しく変換されているかどうかも確認しておきます。まずは監査ログを吐くようにクラスターの設定を変更します。
module "main" { source = "terraform-aws-modules/eks/aws" version = "14.0.0" cluster_name = var.cluster_name cluster_version = var.k8s_version subnets = var.subnet_ids vpc_id = var.vpc_id node_groups = { example = { target_group_arns = [aws_alb_target_group.tg.arn] subnets = var.private_subnet_ids } } map_roles = local.map_roles + cluster_enabled_log_types = ["audit"] }
しばらくするとCloudWatch Logsにログが生成されだします。
CloudWatch Logsコンソールにて、変換されていることを確認できました。
環境情報
最後に今回の実行環境の情報です。
- Terraform 0.14.7
- AWS provider 3.30.0
- kubernetes provider 2.0.2
- local provider 2.1.0
- null provider 3.1.0
- random provider 3.1.0
- template provider 2.2.0
- external provider 2.1.0
- awscli aws-cli/2.1.26 Python/3.7.4 Darwin/19.6.0 exe/x86_64 prompt/off
- jq jq-1.6