IAM Role for Service Account(IRSA)自体はEKS以外でも利用可能ということだったので,自前のkopsクラスタで構築してみた. ほぼ,こちらのお世話になっている.
ほぼ同じことをしているのだが,いかんせん複雑なので自分用の記録として残しておく.
OIDC Providerを作成
IssuerをS3上に作る
こちらのガイドでは,OIDCのissuerを公開するために,S3のバケットを作っている. もちろんこれはissuerとして振る舞って,必要なファイルを配信しれくれればS3でなくても問題ない.
大事なことは,次で説明する鍵を作成し,これを元にした discovery.json
と,keys.json
を,
https://${ISSUER_HOSTPATH}/.well-known/openid-confugration
https://${ISSUER_HOSTPATH}/keys.json
というURLで配信できていることである.
S3で行う場合,このBucketはus-east-1
リージョンに作ると良い.
S3のBucketはus-east-1
だけ特別扱いされており, bucket-name.s3.amazonaws.com
というようなドメインでアクセスすることができるのはus-east-1
だけである.
これをTokyoに作ったりすると, s3-ap-northeast-1.amazonaws.com/bucket-name
という形になってしまう.まぁどうしてもTokyoに作る場合は,この形式でも問題ないのかもしれないが……あとは,Static Website HostingするとかCloudFrontを通すとか,やり方はいろいろある.いずれにしろ,自分が指定したドメインで,↑のファイルが配信できていれば良い.
resource "random_uuid" "oidc_s3" { } resource "aws_s3_bucket" "oidc" { bucket = "oidc-${random_uuid.oidc_s3.result}" acl = "private" versioning { enabled = true } } output "oidc_website_endpoint" { value = "${aws_s3_bucket.oidc.bucket_domain_name}" }
こんな定義を書いてus-east-1にapplyした.なお,ファイルを公開する必要があるので,当然BucketはObjects can be publicである必要がある.
鍵を作る
$ PRIV_KEY="sa-signer.key" $ PUB_KEY="sa-signer.key.pub" $ PKCS_KEY="sa-signer-pkcs8.pub" $ ssh-keygen -t rsa -b 2048 -f $PRIV_KEY -m pem $ ssh-keygen -e -m PKCS8 -f $PUB_KEY > $PKCS_KEY
この鍵は,discovery.json
と keys.json
の生成以外でも使うので,以下の作業が終わっても捨てないこと.
cat <<EOF > discovery.json { "issuer": "https://$ISSUER_HOSTPATH/", "jwks_uri": "https://$ISSUER_HOSTPATH/keys.json", "authorization_endpoint": "urn:kubernetes:programmatic_authorization", "response_types_supported": [ "id_token" ], "subject_types_supported": [ "public" ], "id_token_signing_alg_values_supported": [ "RS256" ], "claims_supported": [ "sub", "iss" ] } EOF
$ git clone https://github.com/aws/amazon-eks-pod-identity-webhook $ cd amazon-eks-pod-identity-webhook $ go run ./hack/self-hosted/main.go -key $PKCS_KEY | jq '.keys += [.keys[0]] | .keys[1].kid = ""' > keys.json
この2つのファイルは,先程作成したS3 Bucketにアップロードしておく.なお, discovery.json
は /.well-known/openid-configuration
としてアップロードする必要があるので注意.
鍵をKubernetesのmasterインスタンスに配置
これは参考記事の通り.
kopsの定義として,
fileAssets: - content: | LS0tL... isBase64: true name: service-account-signing-key-file path: /srv/kubernetes/assets/service-account-signing-key - content: | LS0tL... isBase64: true name: service-account-key-file path: /srv/kubernetes/assets/service-account-key kubeAPIServer: apiAudiences: - oidc-xxx-yyy-zzz-111-222.s3.amazonaws.com serviceAccountIssuer: https://oidc-xxx-yyy-zzz-111-222.s3.amazonaws.com serviceAccountKeyFile: - /srv/kubernetes/server.key - /srv/kubernetes/assets/service-account-key serviceAccountSigningKeyFile: /srv/kubernetes/assets/service-account-signing-key
こんな感じになる.2020年4月10日 kops 1.16.0現在,
このIssueはすでに解決済みになっており,すでにserviceAccountKeyFileは複数指定できるので,このままで問題ない.
また,これらをmasterのノードに配置する必要があるということは,当然updateした後にrolling-updateまでして,masterノードを更新してやる必要がある.
$ kops update cluster --name your_cluster_name --yes $ kops rolling-update cluster your_cluster_name --instance-group-role=Master --yes
OIDC Providerの作成
ようやくProviderを作成する段階.この作業はkopsに限らずEKSでも必要なので,まったく同じことをやる.EKSの場合は上記の鍵やIssuerはEKSクラスタ起動時に用意されている.
resource "aws_iam_openid_connect_provider" "iam_role_sa" { # issuerのURL # ここでは先程作成したus-east-1のS3 Bucketを参照している url = "https://${data.terraform_remote_state.us.outputs.oidc_website_endpoint}" client_id_list = [ data.terraform_remote_state.us.outputs.oidc_website_endpoint ] thumbprint_list = [ data.external.thumb.result.thumbprint ] } data "external" "thumb" { program = ["kubergrunt", "eks", "oidc-thumbprint", "--issuer-url", "https://${data.terraform_remote_state.us.outputs.oidc_website_endpoint}"] }
なお,thumbprintの生成には,kubergruntを使っている.
これでOIDC Providerの作成まではきた.
Webhookを作成
このWebhookをクラスタにインストールすることで,立ち上がったPodにIRSA用のJTWや環境変数を差し込んでくれる. EKSの場合は,このWebhook自体もEKSクラスタ起動時に勝手にインストールされているものになる.
なお,公式でこのWebhook用のDocker Imageは提供されていない.そのため自前ビルドする必要がある.
https://github.com/aws/amazon-eks-pod-identity-webhook/blob/master/Makefile#L27
docker buildしてpushしているだけなので,ECRでもhub.docker.comでも,好きなところにDocker Imageをpushしたらいいと思う.kopsのクラスタからDocker pullできればどこでも良い.
make push
あとはインストールするだけなのだが,これはMakefileにラップされたkustomizeで行う. この前に,
https://github.com/aws/amazon-eks-pod-identity-webhook/blob/master/deploy/deployment-base.yaml#L28
ここを sts.amazonaws.com
ではなく,oidc-xxx-yyy-zzz-111-222.s3.amazonaws.com
のようなIssuerのホストにしておく必要がある.
そして
$ make cluster-up IMAGE=1234567890.dkr.ecr.ap-northeast-1.amazonaws.com/eks/pod-identity-webhook
https://github.com/aws/amazon-eks-pod-identity-webhook/blob/master/Makefile#L63
としてやれば,クラスタにWebhookがインストールされ,Podが起動する.
ここまでくればEKSでセットアップしたときと同様の状態になるので,あとはEKSのときとまったく同じ手順になる.
IAM Roleを作成
resource "aws_iam_role" "call_s3_role" { name = "call-s3-role" path = "/" assume_role_policy = data.template_file.irsa_assume_role_policy.rendered }
data "template_file" "irsa_assume_role_policy" { template = file( "${path.module}/aws_iam_role_policies/irsa_assume_role_policy.json.tpl", ) vars = { provider_arn = "arn:aws:iam::1234567890:oidc-provider/oidc-xxx-yyy-zzz-111-222.s3.amazonaws.com" issue_hostpath = "oidc-xxx-yyy-zzz-111-222.s3.amazonaws.com" prefix = "my-role" } }
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "${provider_arn}" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringLike": { "${issue_hostpath}:sub": "system:serviceaccount:*:${prefix}*" } } } ] }
こんなIAM Roleを作って,このRoleに必要な権限をattachしておく.
resource "aws_iam_policy_attachment" "s3_all" { name = "s3-all" roles = [ aws_iam_role.call_s3_role.name, ] policy_arn = aws_iam_policy.s3_get.arn }
ServiceAccountの作成
作ったRoleをServiceAccountのannotationsに追加する.
apiVersion: v1 kind: ServiceAccount metadata: name: my-role-s3 annotations: eks.amazonaws.com/role-arn: arn:aws:iam::1234567890:role/call-s3-role
で,これをPodのServiceAccountNameに指定する.
apiVersion: apps/v1 kind: Deployment metadata: name: sample spec: replicas: 1 selector: matchLabels: app: sample template: metadata: labels: app: sample spec: serviceAccountName: my-role-s3
これでPodを起動すれば,
Environment: AWS_ROLE_ARN: arn:aws:iam::1234567890:role/s3-call-role AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token Mounts: /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro) /var/run/secrets/kubernetes.io/serviceaccount from sample-manager-token-9t72l (ro)
というようにIRSAに必要なVolumeMountと環境変数が差し込まれる.
こうなれば,あとは新しいaws-sdkを使っていれば,これらの情報をつかって認証してくれる.
運用してわかったこと
EKS公式のWebhookと違って,自分でWebhookをインストールしてPodを動かしているので,例えばScaleIn等でWebhookのPodがevictされていたり,ノードごと死んでいたりした場合には,Webhookが働かない. そのタイミングで運悪くPodが起動したりすると(実はScaleInやノードの死では複数Podが同時にevictされscheduleされ直すので,こういうのはよくある),そのPodにはWebhookにより必要な情報が差し込まれなかったりする.
EKSではこういうことはないのだが,自前運用であるから仕方ない.防護策としては,WebhookのDeploymentのreplicasを上げて,Podを複数個,それも複数ノードに分散させて起動したりしておくと,多少安全性は増す.流石にScaleInが激しくて複数ノードが全部一度に死んだりすると,Webhookを受け取れないタイミングが存在するかもしれないが.
まとめ
自分でもWebhookをいくつか作ったことがあるのでわかるのだが,Webhookはインストールが結構めんどくさい.これは,Webhookを送るときに必ずTLSで送る必要があり,このときに使う証明書を
- MutatingWebhookConfigurationのclientConfig
- Webhookを受けるPod
の両方で同じものを利用する必要があり,それをhelm chart等に落とすのが難しいという事情がある.証明書を固定にして公開しても良ければ,それこそSecretとして証明書を保存してしまえばいいのだが,あまりよくないので,Makefileを使って生成し,それを元にkustomizeでインストールしたりしている.
kustomizeを使っても,証明書生成の部分はどうしてもユーザが何かしらの方法で行う必要があるため,Makefileが必要になったりしている.
という事情は非常によくわかる.
ただ,めんどくさい.本当はhelm chart一発でインストールできたらどれほど楽かと思う. kube2iamとかkiamは,IAM Roleを用意するのはもちろん必要になるが,クラスタへのインストール自体はhelmコマンド一つでできるからね…….
インストールさえしてしまえば,IRSAの方が安定していて良いと思う.kube2iamのように,AWSへのリクエストを毎回中継して認証してやる必要はない. Webhookが,Pod起動時に必要な情報を差し込んでさえしまえば,あとはaws-sdkとOIDC ProviderとAWS側とのやりとりだけの話になるからだ.