OIDCでのデプロイメモ (Git to AWS)

April 26, 2023

Intro

以前は、GitHub や Bitbucket から個人の AWS へのデプロイ時にアクセスキーとシークレットキーを使用していました。しかし、これを管理するのが煩雑になったため、OIDC に切り替えました。

OIDC (OpenID Connect) は、認証と認可のためのオープンスタンダードです。OIDC を使用することで、AWS へのアクセスを安全に管理し、認証プロバイダを介してシームレスなアクセス体験を提供できます。OIDC を使用する場合、AWS へのアクセスにはアクセストークンが使用されます。

OIDC を導入することで、アクセスキーやシークレットキーを個別に管理する必要がなくなり、セキュリティと利便性が向上します。

やること

  • Terraform で OIDC を設定する方法
    • GitHub
    • Bitbucket
  • OIDC を使用してデプロイする方法
    • シンプルな GitHub Actions と Bitbucket Pipelines の作成

技術要素

  • OIDC
  • AWS
  • Bitbucket
  • GitHub
  • Terraform

What is OIDC?

OIDC(OpenID Connect)は、OAuth 2.0 プロトコルを拡張し、Web アプリケーションやモバイルアプリケーションにおけるユーザー認証の仕組みを提供する認証プロトコルです。

Benefits of OIDC

Git からのデプロイに OIDC を使用することで、いくつかの利点があります。それには、Git を介したデプロイのセキュリティの向上、トークンの有効期限の管理の容易化、MFA が有効化されていないアクセスキーの使用に関連するセキュリティリスクの回避などがあります。ただし、MFA が有効化されたアクセスキーを使用する場合は、デバイス管理などの要素を考慮する必要があるため、複雑な場合があります。OIDC を使用することで、ユーザーは ID プロバイダーの認証資格情報を使用できるため、認証情報の管理が簡素化され、セキュリティが向上します。

構築背景

以前は、AWS へのデプロイは手間がかかるものでした。CLI の代わりに管理コンソールからデプロイすることを考えるほどです。

  • 以前のフローでは、キーの取得に不便さが伴いました:
    • アプリから MFA トークンを確認してコマンドを実行する:
      • $ aws sts get-session-token –serial-number arn:aws:iam::xxxxxx:mfa/xxxxxx –token-code xxxxxx
      • 取得したキーをエクスポートするか、Git に登録する
      • セッションの有効期限が切れるたびに定期的にコマンドを実行する…

Terraform で OIDC を構築する

  • Terraform での構築
    • GitHub Actions を使用するシナリオと Bitbucket Pipelines を使用するシナリオの 2 つを説明します。

GitHub Sample

variable "aws_account_id" {}
variable "github_repo_name" {}

variable "oidc_token_url" {
  default = "https://token.actions.githubusercontent.com"
}

data "tls_certificate" "github_oidc_token" {
  url = var.oidc_token_url
}

resource "aws_iam_openid_connect_provider" "github_oidc_provider" {
  url = var.oidc_token_url
  client_id_list = [
    "sts.amazonaws.com"
  ]
  thumbprint_list = [data.tls_certificate.github_oidc_token.certificates.0.sha1_fingerprint]
}

data "aws_iam_policy_document" "github_oidc_policy" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    effect  = "Allow"
    principals {
      type        = "Federated"
      identifiers = ["arn:aws:iam::${var.aws_account_id}:oidc-provider/oidc-provider/token.actions.githubusercontent.com"]
    }
    condition {
      test     = "StringEquals"
      variable = "token.actions.githubusercontent.com:sub"
      values   = ["repo:${var.github_repo_name}:ref:refs/heads/main"]
    }
  }
}

resource "aws_iam_role" "github_oidc_role" {
  name               = "GithubOIDC-TEST"
  description        = "GithubOIDC-TEST"
  assume_role_policy = data.aws_iam_policy_document.github_oidc_policy.json
}

resource "aws_iam_role_policy_attachment" "github_oidc_administrator_access_attachment" {
  role       = aws_iam_role.github_oidc_role.name
  policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

Bitbucket Sample


variable "bitbucket_oidc_url" {}
variable "bitbucket_oidc_audience" {}
variable "account_id" {}
variable "git_space" {}

data "tls_certificate" "bitbucket" {
  url = var.bitbucket_oidc_url
}

resource "aws_iam_openid_connect_provider" "bitbucket" {
  url = var.bitbucket_oidc_url
  client_id_list = [
    var.bitbucket_oidc_audience,
  ]
  thumbprint_list = [data.tls_certificate.bitbucket.certificates.0.sha1_fingerprint]
}

data "aws_iam_policy_document" "bitbucket_oidc_policy" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    effect  = "Allow"
    principals {
      type        = "Federated"
      identifiers = ["arn:aws:iam::${var.account_id}:oidc-provider/api.bitbucket.org/2.0/workspaces/${var.git_space}/pipelines-config/identity/oidc"]
    }
    condition {
      test     = "StringEquals"
      variable = "api.bitbucket.org/2.0/workspaces/${var.git_space}/pipelines-config/identity/oidc:aud"
      values   = [var.bitbucket_oidc_audience]
    }
  }
}

resource "aws_iam_role" "bitbucket_oidc_role" {
  name               = "BitbucketOIDC-TEST"
  description        = "BitbucketOIDC-TEST"
  assume_role_policy = data.aws_iam_policy_document.bitbucket_oidc_policy.json
}

resource "aws_iam_role_policy_attachment" "administrator_access_attachment" {
  role       = aws_iam_role.bitbucket_oidc_role.name
  policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

Testing OIDC Setup

Github Action

  • The sample code deploys statically built Hugo content to S3 using OIDC.
name: s3-deploy

on:
  push:
    branches:
      - main

jobs:
  s3put:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: '0.111.3'

      - name: Build
        run: hugo --minify

      - uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-region: 'ap-northeast-1' # Specify the region
          role-to-assume: 'arn:aws:iam::xxxxxxxxxxx:role/oidc-role' # ARN of the created IAM role
      - name: Deploy
        run: aws s3 sync --delete public s3://my-s3-bucket/

Bitbucket Pipelines

  • The sample code syncs the “public” folder from the master branch to my-s3-bucket using OIDC.
image: amazon/aws-cli

pipelines:
  default:
    - step: &s3-deploy
        name: Deploy to S3 with OIDC
        oidc: true
        script:
          - export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
          - export AWS_ROLE_ARN='created role'
          - echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token
          - aws s3 sync --delete public s3://my-s3-bucket/
  branches:
    master:
      - step: *s3-deploy

Setting up OIDC with CloudFormation

Parameters:
  AwsAccountId:
    Type: String
    Description: AWS Account ID
  GithubRepoName:
    Type: String
    Description: Name of the GitHub repository
  OidcTokenUrl:
    Type: String
    Default: https://token.actions.githubusercontent.com

Resources:
  GithubOidcTokenCertificate:
    Type: AWS::CloudFormation::CustomResource
    Version: "1.0"
    Properties:
      ServiceToken: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:token-actions-github
      Url: !Ref OidcTokenUrl

  GithubOidcProvider:
    Type: AWS::IAM::OpenIDConnectProvider
    Properties:
      Url: !Ref OidcTokenUrl
      ClientIDList:
        - sts.amazonaws.com
      ThumbprintList:
        - !GetAtt GithubOidcTokenCertificate.Sha1Fingerprint

  GithubOidcPolicyDocument:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: GithubOIDCPolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action: sts:AssumeRoleWithWebIdentity
            Resource: "*"
            Condition:
              StringEquals:
                token.actions.githubusercontent.com:sub:
                  - !Sub "repo:${GithubRepoName}:ref:refs/heads/main"
            Principal:
              Federated: !Sub arn:aws:iam::${AwsAccountId}:oidc-provider/oidc-provider/token.actions.githubusercontent.com

  GithubOidcRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: GithubOIDC-TEST
      AssumeRolePolicyDocument: !Ref GithubOidcPolicyDocument

  GithubOidcAdministratorAccessAttachment:
    Type: AWS::IAM::PolicyAttachment
    Properties:
      PolicyArn: arn:aws:iam::aws:policy/AdministratorAccess
      Roles:
        - !Ref GithubOidcRole
Nifty tech tag lists from Wouter Beeftink