kube2iamをTerraformとHelmfileでデプロイする
Kubernetes workerをEC2インスタンスで実行する場合,何も設定しないとPodはEC2インスタンスのIAMロールを利用します.このままでは,攻撃者が悪意のあるイメージを利用して情報漏洩や破壊を行うリスクがあります.kube2iamを利用すると,Podに適切なIAMロールを割り当てることが可能です.
本稿では,Amazon EKSで以下を利用する方法を説明します.
ここでは例として,S3バケットの読み取りが必要なアプリケーションをPodで実行するケースを考えます.
workerインスタンスへのIAMポリシーの割り当て
kube2iamはAssume Roleという仕組みを利用してPodにIAMロールを割り当てます.Assume Roleに必要なリソースは別のモジュール(kube2iam
)で定義します.
# kube2iam/main.tf variable "worker_iam_role_name" { description = "Name of the IAM role of the Kubernetes worker" } data "aws_iam_role" "worker" { name = var.worker_iam_role_name } resource "aws_iam_role_policy" "this" { role = data.aws_iam_role.worker.id name_prefix = "kube2iam" policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": [ "sts:AssumeRole" ], "Effect": "Allow", "Resource": "*" } ] } EOF } data "aws_iam_policy_document" "assume_role_policy" { statement { actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = ["ec2.amazonaws.com"] } principals { type = "AWS" identifiers = [data.aws_iam_role.worker.arn] } } } output "assume_role_policy" { description = "JSON string of IAM policy for Assume Role" value = data.aws_iam_policy_document.assume_role_policy.json }
上記のkube2iam
モジュールを利用して,Assume Roleを許可するIAMポリシーをworkerインスタンスに割り当てます.terraform-aws-eks module を利用している場合は以下のように定義します.
module "kube2iam" { source = "./kube2iam" worker_iam_role_name = module.eks.worker_iam_role_name }
S3アクセスのIAMロールの作成
S3の読み取りアクセスを許可するIAMロールを作成します.ここでは簡単のため,AWSであらかじめ定義されているポリシーを利用します.
resource "aws_iam_role" "s3_readonly" { name = "s3_readonly" assume_role_policy = module.kube2iam.assume_role_policy } resource "aws_iam_role_policy_attachment" "s3_readonly" { role = aws_iam_role.s3_readonly.id policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" }
実際には,IAMロールに利用目的を表す名前を付けた方がよいでしょう.例えば,GitLab Runnerであれば gitlab_runner_build_app
だったり,アプリケーションであれば app_frontend
といった感じです.
Helm chartのデプロイ
stable/kube2iamをデプロイします.
releases: # https://github.com/helm/charts/tree/master/stable/kube2iam - name: kube2iam namespace: kube-system chart: stable/kube2iam values: - host: iptables: true # https://github.com/jtblin/kube2iam#iptables interface: eni+ extraArgs: # https://github.com/jtblin/kube2iam#base-arn-auto-discovery auto-discover-base-arn: "" rbac: create: true
kube2iamはiptablesを利用してPodの通信を横取りします.amazon-vpc-cniを利用している場合は,上記のようにインタフェース名に eni+
を指定します.Calicoを利用している場合は cali+
になります.詳しくはkube2iamのiptablesセクションを参照してください.
Pod annotationによるIAMロール割り当ての確認
kube2iamを利用すると,Podに iam.amazonaws.com/role
アノテーションを付与するとIAMロールが割り当てられるようになります.以下の例ではPodに s3_readonly
というIAMロールを割り当てています.
apiVersion: v1 kind: Pod metadata: annotations: iam.amazonaws.com/role: s3_readonly spec:
ここでは,以下のコマンドを実行して動作確認を行います.
kubectl run s3 -i --rm --image fstab/aws-cli --restart=Never --overrides '{"metadata":{"annotations":{"iam.amazonaws.com/role":"s3_readonly"}}}' -- /home/aws/aws/env/bin/aws s3 ls
S3バケットの一覧が表示されれば成功です.
kube2iamが適切に設定されていない場合,PodはworkerインスタンスのIAMロールを利用してしまうため,以下のエラーがでます.
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
アノテーションに指定したIAMロールが存在しない場合,以下のエラーが表示されます.
Unable to locate credentials. You can configure credentials by running "aws configure".
アノテーションを指定しない場合は何も割り当てられません.
最後に,動作確認用に作成した以下のIAMロールを削除しておきましょう.
resource "aws_iam_role" "s3_readonly" { name = "s3_readonly" assume_role_policy = module.kube2iam.assume_role_policy } resource "aws_iam_role_policy_attachment" "s3_readonly" { role = aws_iam_role.s3_readonly.id policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" }
TerraformでNATインスタンスを管理する
個人のAWS環境でプライベートサブネット構成を検証したいけどNATゲートウェイに毎月3,500円も払えない*1ので,NATインスタンスのTerraformモジュール int128/nat-instance/aws を作りました.主な特徴はこちらです.
- Auto Scaling GroupによるAuto Healingに対応.インスタンスが落ちても自動復旧します.
- スポットインスタンスで低コストを実現.t3a.nanoなら月100円程度で運用できます.
- ENIの付け替えによる固定ソースIPを実現.
使い方
VPCとサブネットを作成するには terraform-aws-modules/vpc/aws モジュールが便利です.VPCとサブネットに加えてNATインスタンスを作成するには下記のように定義します.
module "vpc" { source = "terraform-aws-modules/vpc/aws" name = "main" cidr = "172.18.0.0/16" azs = ["us-west-2a", "us-west-2b", "us-west-2c"] private_subnets = ["172.18.64.0/20", "172.18.80.0/20", "172.18.96.0/20"] public_subnets = ["172.18.128.0/20", "172.18.144.0/20", "172.18.160.0/20"] enable_dns_hostnames = true } module "nat" { source = "int128/nat-instance/aws" name = "main" vpc_id = module.vpc.vpc_id public_subnet = module.vpc.public_subnets[0] private_subnets_cidr_blocks = module.vpc.private_subnets_cidr_blocks private_route_table_ids = module.vpc.private_route_table_ids }
Terraformを実行すると,下図のようなリソースが作成されます.(プライベートサブネットの各EC2インスタンスは除く)
動作確認のため,AWSコンソールでプライベートサブネットにEC2インスタンスを作成しましょう.作成したEC2インスタンスにSSMセッションマネージャでログインして,外部にアクセスできるか確認してみましょう.
仕組み
Terraformを実行すると,Auto Scaling Groupとそれに必要なリソース群(セキュリティグループ,ENI,EIPなど)が作成されます.また,プライベートサブネットのデフォルトルートがENIに設定されます.これにより,プライベートサブネットから外部への通信はENIを通るようになります.
Auto Scaling GroupがNATインスタンスを起動すると,User Dataにある以下のスクリプトが実行されます.
- あらかじめ確保しておいたENIをeth1にアタッチする.
- IP forwardingのカーネルパラメータを有効にする.
- iptablesでIP masqueradeを有効にする.
- デフォルトゲートウェイをeth1に変更する.
これにより,プライベートサブネット内のEC2インスタンスはENIとNATインスタンスを経由して通信できるようになります.
もしNATインスタンスが停止した場合は,Auto Scaling Groupによって新しいNATインスタンスが作成されます.既存のENIとEIPをアタッチするため,外部通信のソースIPを固定できます.
User DataでENIのアタッチとデフォルトゲートウェイを変更する部分は試行錯誤が必要でかなり苦労しました.
おまけ
マネージドサービスとは逆行する使い方になりますが,iptablesでDNATを有効にするとNATインスタンスのポートをプライベートサブネットに転送できます.例えば,以下のようなスクリプトを追加すれば,NATインスタンスの443ポートをプライベートサブネットにあるEC2インスタンスに転送できます.NLBに課金する代わりにどうぞ.
# Look up the target instance tag_name="TARGET_TAG" target_private_ip="$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$tag_name" | jq -r .Reservations[0].Instances[0].PrivateIpAddress)" # Expose the port of the NAT instance. iptables -t nat -A PREROUTING -m tcp -p tcp --dst "${eni_private_ip}" --dport 8080 -j DNAT --to-destination "$target_private_ip:8080"
Terraformモジュールの詳細については下記を参照してください.
Argo CDでGitLab SSOを利用する
Argo CDは自前でユーザ管理の仕組みを持たず*1,外部のIdentity Providerに認証を移譲するという設計思想になっています.Argo CDのHelm chartにはDexがバンドルされており,様々なIdentity Providerと連携させることが可能です.本稿では,Argo CDでGitLab SSOを利用する方法を紹介します.
Argo CDはOpenID Connectに対応しています.また,GitLabは自身がOpenID Connect Identity Providerになることが可能です.しかし,Argo CDが必要とする groups
claimをGitLabがサポートしていないため,直接連携させることはできません.Argo CDのoidc configでGitLabを指定すると,GitLabで以下のエラーが表示されます(argo-cd#1195).
The requested scope is invalid, unknown, or malformed
代わりに,DexのGitLab connectorを利用することで,Argo CD→Dex→GitLabの流れで連携が可能です.
GitLabの設定
admin area→Applicationsもしくはユーザの設定→Applicationsから,新しいアプリケーションを作成します.以下のように設定します.
- Name: 任意
- Redirect URI:
https://argocd.example.com/api/dex/callback
- Scopes:
read_user
,openid
Dex GitLab connectorではOAuth2を利用するとの記述がありますが,実際はOpenID Connectが利用されるようです.openid
スコープを許可しないとエラーになります.
Argo CDの設定
Helmfileを利用する場合は以下のように設定します.
repositories: - name: argo url: https://argoproj.github.io/argo-helm releases: - name: argocd namespace: argocd chart: argo/argo-cd values: - config: url: https://argocd.example.com dexConfig: connectors: # GitLab SSO - type: gitlab id: gitlab name: GitLab config: baseURL: https://gitlab.com clientID: xxxxxxxx clientSecret: xxxxxxxx rbac: # assign the admin role to everyone policyDefault: role:admin
上記ではすべてのユーザにadminロールを割り当てていますが,GitLabグループに応じてアクセス制御を行うとよいでしょう.
動作確認
下図のように LOGIN VIA GITLAB ボタンが表示されるはずです.ボタンをクリックし,GitLabの同意画面が表示された後,ログインできれば成功です.
See Also
- Argo CD and Dex
- GitLab SSO
*1:ローカルでは組み込みのadminユーザのみ利用できます.