🐙

TerraformでGitHub Actions OIDC with AWSを定義し、EKSに安全にアクセスする

2021/12/02に公開

LAPRAS株式会社でエンジニアをしてます@yktakaha4ともうします🐠
この記事は、LAPRAS Advent Calendar 2021 2日目の記事です

https://qiita.com/advent-calendar/2021/lapras

先日、GitHub ActionsがOIDCに対応しましたが、ネットを探すとベータとして公開されていた時期の情報が残っていて若干動かしづらい部分があったため、備忘メモとして残すこととしました✍

https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect

なににアクセスするかについては丁度いいネタが思いつかなかったので、
直近でTerraformでサッと使える検証用EKS環境を作ったという記事を書いていたため、
この時作ったEKSクラスタにGitHub Actionsからアクセスし、マニフェストを適用するワークフローを書いてみました

KubernetesのデプロイについてはGitOpsでおこなっている方も多いかもしれませんが、
kubectl に限らず、CI/CDの過程でなにがしかのコマンドをクラスタに対して打ちたい時に使えるはず…

作ったもの

GitHubにて公開しています
試し方は上記リンクのUsageに記載していますのでご確認ください🍨

うまく動かせると、Sock Shop をEKS上で立ち上げられます👢

ポイント

肝心のOIDC関連の設定を抜粋します
2021/11/21時点ではこの設定で動きました

まずはOIDCプロバイダーを定義します

resource "aws_iam_openid_connect_provider" "github_actions" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["a031c46782e6e6c662c2c87c76da9aa62ccabd8e"]
}

これを参照するロールを定義します
github_repository_name については、 yktakaha4/study-eks-github-oidc などの、GitHubのリポジトリ名が入るようにしてください

resource "aws_iam_role" "github_actions" {
  name = "${var.resource_prefix}-github-actions"
  path = "/"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = "sts:AssumeRoleWithWebIdentity"
      Principal = {
        Federated = aws_iam_openid_connect_provider.github_actions.arn
      }
      Condition = {
        StringLike = {
          "token.actions.githubusercontent.com:sub" = [
            "repo:${var.github_repository_name}:*"
          ]
        }
      }
    }]
  })
}

このロールに対して、適切な権限を付与します
今回はEKSクラスタに対するアクセスするために必要な update-kubeconfigが実行できれば充分なので、
作成したEKSクラスタに対して eks:DescribeCluster が許可されるようにします

resource "aws_iam_policy" "github_actions" {
  name = "${var.resource_prefix}-github-actions"
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = "eks:DescribeCluster"
      Resource = module.eks.cluster_arn
    }]
  })
}

resource "aws_iam_policy_attachment" "github_actions" {
  name = "${var.resource_prefix}-github-actions"
  roles = [
    aws_iam_role.github_actions.name,
  ]

  policy_arn = aws_iam_policy.github_actions.arn
}

ここからはEKS側の設定になります
aws-auth ConfigMapに対して作成したIAMロールを紐付けます
今回はAWS EKS moduleを使ってEKSクラスタを作成していますが、同等のConfigMapを適用できれば別の方法でもよいものとおもいます

https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest

admin-group については、K8s側のロールに対応させる必要があるので覚えておきます

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "17.0.0"

  cluster_name    = var.resource_prefix
  cluster_version = "1.21"

  map_roles = [{
    rolearn  = aws_iam_role.github_actions.arn
    username = "github-actions"
    groups   = ["admin-group"]
  }]

  # 略

}

マニフェスト側では、RoleとRoleBindingを作成します
今回は、 sock-shop ネームスペース上でのみ任意の操作を実行できるようにしました
Deploymentのrolloutのみ許可したい…など、ケースに応じて権限を限定できるとよいものとおもいます

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: sock-shop
  name: sock-shop-admin
rules:
  - apiGroups:
      - "*"
    resources:
      - "*"
    verbs:
      - "*"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: sock-shop
  name: admin-group
subjects:
  - kind: Group
    name: admin-group
    namespace: sock-shop
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: sock-shop-admin

最後に、GitHub Actions側の設定になります
まずは、PR作成時に kubectl diff の実施&結果のコメントを投稿するようにしました

注意点としては、configure-aws-credentialsが、確認時だとv1タグでは動かなかったので、
差し当たって記載時点の最新のリビジョン fcd8bb1 で固定することとしました

https://github.com/aws-actions/configure-aws-credentials

記事の本旨からはそれますが、コマンドの実行結果をいい感じに投稿するためには色々おまじないが必要かつちょいちょいdeprecatedになってるので、今回書いている内容はそこそこ新しい書き方になってるはず…

必要な環境変数はIAMロールのARNとEKSクラスタ名のみです
AWSのアクセスキーやGitHubのPATは不要です

on: pull_request

name: Check

permissions:
  id-token: write
  contents: read
  pull-requests: write

jobs:
  check:
    runs-on: ubuntu-20.04
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v2

      - uses: aws-actions/configure-aws-credentials@fcd8bb1e0a3c9d2a0687615ee31d34d8aea18a96
        with:
          role-to-assume: ${{ secrets.ROLE_TO_ASSUME }}
          aws-region: ap-northeast-1

      - run: aws eks update-kubeconfig --name ${{ secrets.CLUSTER_NAME }}

      - run: |
          curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.22.0/bin/linux/amd64/kubectl
          chmod +x ./kubectl
          ./kubectl version

      - run: ./kubectl diff -f ./manifests/sock-shop.yml 2>&1 | tee diff.log

      - run: |
          details="$(cat diff.log)"
          details="${details//'%'/'%25'}"
          details="${details//$'\n'/'%0A'}"
          details="${details//$'\r'/'%0D'}"
          echo "::set-output name=details::$details"
        id: diff_details

      - uses: actions/github-script@v5
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `### kubectl diff result\n\n<details>\n\n\`\`\`\n${process.env.DETAILS}\n\`\`\`\n\n</details>`
            })
        env:
          DETAILS: "${{ steps.diff_details.outputs.details }}"

また、mainへのマージ時には kubectl apply します

こちらはPR作成時とほとんど同じなので、あまり言うことはないです

on:
  push:
    branches:
      - main
  workflow_dispatch:

name: Release

permissions:
  id-token: write
  contents: read

jobs:
  release:
    runs-on: ubuntu-20.04
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v2

      - uses: aws-actions/configure-aws-credentials@fcd8bb1e0a3c9d2a0687615ee31d34d8aea18a96
        with:
          role-to-assume: ${{ secrets.ROLE_TO_ASSUME }}
          aws-region: ap-northeast-1

      - run: aws eks update-kubeconfig --name ${{ secrets.CLUSTER_NAME }}

      - run: |
          curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.22.0/bin/linux/amd64/kubectl
          chmod +x ./kubectl
          ./kubectl version

      - run: ./kubectl apply -f ./manifests/sock-shop.yml

一応、権限が適切に効いているか確認してみます

例えば別のnamespaceに何かを作ろうとすると、権限がないので失敗します

リポジトリ名を変更すると、AWSへの接続自体が拒否されます

よさそう👞
ほっとくとお金がかかるので、確認し終わったら確実に削除しましょう

おわりに

明日は同じくエンジニアの@chanmoroさんです
お楽しみに!

https://qiita.com/advent-calendar/2021/lapras

Discussion