OIDC GitHub Actions ke AWS memungkinkan workflow memperoleh kredensial AWS sementara tanpa menyimpan AWS_ACCESS_KEY_ID dan AWS_SECRET_ACCESS_KEY sebagai GitHub Secrets. GitHub menerbitkan JSON Web Token (JWT) untuk workflow, lalu AWS Security Token Service (STS) memvalidasi token tersebut dan mengembalikan kredensial sementara untuk IAM role yang telah dibatasi.

Keamanan pendekatan ini bergantung pada dua lapisan: trust policy menentukan workflow GitHub mana yang boleh mengambil role, sedangkan permissions policy menentukan tindakan AWS yang boleh dilakukan setelah role diperoleh. Keduanya harus dibuat sesempit mungkin.

Alur autentikasi OIDC GitHub Actions ke AWS

  1. Workflow memiliki permission id-token: write.
  2. GitHub menerbitkan OIDC token berumur pendek yang memuat claim seperti aud, sub, repository, ref, dan nama Environment.
  3. aws-actions/configure-aws-credentials mengirim token tersebut ke operasi STS AssumeRoleWithWebIdentity.
  4. AWS memvalidasi tanda tangan token melalui IAM OIDC provider, lalu mengevaluasi claim terhadap trust policy role.
  5. Jika cocok, STS memberikan access key, secret key, dan session token sementara kepada job.
  6. AWS CLI atau SDK dalam job menggunakan kredensial sementara tersebut sampai sesi berakhir.

Permission id-token: write tidak langsung memberikan akses ke AWS. Permission itu hanya mengizinkan job meminta token OIDC. Akses AWS tetap ditentukan oleh provider, trust policy, permissions policy, resource policy, permission boundary, dan Service Control Policy jika digunakan.

Menyiapkan IAM OIDC provider dan deployment role

Konfigurasi provider

Buat IAM OIDC provider dengan nilai berikut untuk partisi AWS standar:

  • Provider URL: https://token.actions.githubusercontent.com
  • Audience: sts.amazonaws.com

Satu provider dapat dipakai oleh beberapa role dalam akun AWS yang sama. Jangan membuat provider duplikat jika provider GitHub sudah tersedia. Contoh CloudFormation berikut membuat provider sekaligus role untuk deployment ke satu bucket S3:

AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  GitHubOrganization:
    Type: String
  GitHubRepository:
    Type: String
  DeploymentBucket:
    Type: String

Resources:
  GitHubOidcProvider:
    Type: AWS::IAM::OIDCProvider
    Properties:
      Url: https://token.actions.githubusercontent.com
      ClientIdList:
        - sts.amazonaws.com

  DeploymentRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Federated: !Ref GitHubOidcProvider
            Action: sts:AssumeRoleWithWebIdentity
            Condition:
              StringEquals:
                'token.actions.githubusercontent.com:aud': sts.amazonaws.com
                'token.actions.githubusercontent.com:sub':
                  !Sub 'repo:${GitHubOrganization}/${GitHubRepository}:environment:production'
      Policies:
        - PolicyName: DeployStaticSite
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Sid: ListDeploymentBucket
                Effect: Allow
                Action:
                  - s3:ListBucket
                  - s3:GetBucketLocation
                Resource: !Sub 'arn:aws:s3:::${DeploymentBucket}'
              - Sid: WriteDeploymentObjects
                Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:DeleteObject
                Resource: !Sub 'arn:aws:s3:::${DeploymentBucket}/*'

Outputs:
  DeploymentRoleArn:
    Value: !GetAtt DeploymentRole.Arn

Isi parameter organisasi, repository, dan bucket dengan nilai milik Anda. Jika provider sudah ada, hapus resource GitHubOidcProvider dari template dan referensikan ARN provider yang sudah tersedia.

Mengapa aud dan sub harus dibatasi?

Claim aud memastikan token ditujukan untuk AWS STS. Claim sub mengidentifikasi konteks workflow yang meminta token. Memeriksa keduanya mencegah role menerima sembarang token yang diterbitkan GitHub.

Hindari trust policy yang hanya memeriksa aud atau memakai pola repo:ORGANIZATION/* tanpa alasan yang jelas. Pola terlalu luas dapat membuat repository atau workflow lain dalam organisasi ikut memenuhi trust policy.

Menerapkan least privilege pada deployment role

Contoh di atas hanya mengizinkan sinkronisasi objek ke satu bucket. Sesuaikan policy dengan mekanisme deployment sebenarnya. Misalnya, deployment ECS membutuhkan izin terhadap service dan task definition tertentu; deployment Lambda membutuhkan izin pembaruan fungsi tertentu; penggunaan KMS juga memerlukan izin key policy dan IAM yang sesuai.

Jangan memberikan AdministratorAccess hanya untuk mengatasi kegagalan awal. Mulailah dari resource dan operasi yang diketahui, uji deployment, lalu tambahkan izin yang memang terlihat diperlukan. Pisahkan role produksi dan nonproduksi agar kompromi workflow staging tidak otomatis membuka akses produksi.

Membatasi repository, branch, tag, dan GitHub Environment

Format claim sub berubah sesuai konteks workflow. Trust policy harus memakai format yang sama dengan token aktual.

Branch tertentu

Untuk mengizinkan hanya branch main dari satu repository:

"StringEquals": {
  "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
  "token.actions.githubusercontent.com:sub": "repo:ORGANIZATION/REPOSITORY:ref:refs/heads/main"
}

Tag rilis

Untuk mengizinkan tag dengan awalan v, gunakan StringLike secara hati-hati:

"StringEquals": {
  "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
  "token.actions.githubusercontent.com:sub": "repo:ORGANIZATION/REPOSITORY:ref:refs/tags/v*"
}

Pola ini cocok untuk tag seperti v1.4.0, tetapi siapa pun yang dapat membuat tag yang cocok dapat mencoba menjalankan deployment. Lindungi pembuatan tag dan workflow rilis melalui aturan repository yang sesuai.

GitHub Environment

Jika job menggunakan GitHub Environment bernama production, subject menjadi:

repo:ORGANIZATION/REPOSITORY:environment:production

Dalam kondisi ini, subject tidak lagi memuat branch atau tag. Pembatasan branch, tag, dan persetujuan manual harus dikonfigurasi melalui deployment protection rules pada Environment tersebut. Kombinasi yang umum adalah:

  • Trust policy IAM hanya menerima subject Environment production.
  • Environment production hanya menerima deployment dari branch atau tag yang disetujui.
  • Required reviewers digunakan jika deployment produksi memerlukan persetujuan.
  • Workflow tetap membatasi event melalui on.push.branches atau on.push.tags.

Jangan memakai subject branch di IAM sementara job mendeklarasikan environment: production. Perbedaan format tersebut akan menghasilkan kegagalan AssumeRoleWithWebIdentity.

Workflow GitHub Actions tanpa access key statis

Workflow berikut berjalan ketika ada push ke main atau ketika dijalankan manual. Job menggunakan Environment production, sehingga trust policy CloudFormation sebelumnya harus menerima subject Environment tersebut.

name: Deploy to AWS

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Configure temporary AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}
          aws-region: ${{ vars.AWS_REGION }}
          role-session-name: gha-${{ github.run_id }}-${{ github.run_attempt }}

      - name: Verify AWS identity
        run: aws sts get-caller-identity

      - name: Deploy static files
        run: aws s3 sync ./dist "s3://${{ vars.DEPLOYMENT_BUCKET }}" --delete

AWS_DEPLOY_ROLE_ARN, AWS_REGION, dan DEPLOYMENT_BUCKET bukan kredensial sehingga dapat disimpan sebagai GitHub Actions Variables. Tidak ada access key yang perlu disimpan sebagai secret.

Contoh menggunakan tag versi mayor agar mudah dibaca. Untuk mengurangi risiko supply-chain pada workflow produksi, pertimbangkan mematok action pihak ketiga ke commit SHA penuh dan gunakan proses pembaruan dependensi yang terkontrol.

Risiko pull request dari fork

Kode dari pull request eksternal harus dianggap tidak tepercaya. Jika trust policy menggunakan pola luas seperti repo:ORGANIZATION/REPOSITORY:*, subject untuk konteks pull request dapat ikut cocok dan membuka kemungkinan job meminta role AWS.

Gunakan langkah pengamanan berikut:

  • Jangan izinkan subject pull_request pada role deployment.
  • Batasi trust policy ke branch, tag, atau Environment tertentu.
  • Jangan menjalankan deployment pada event pull_request.
  • Gunakan Environment dengan required reviewers untuk produksi.
  • Hindari mengeksekusi kode pull request yang tidak tepercaya dalam job yang memiliki akses OIDC ke AWS.
  • Berhati-hatilah dengan pull_request_target. Event ini berjalan dalam konteks repository dasar dan dapat berbahaya jika workflow mengambil lalu menjalankan kode dari fork.
  • Pisahkan job build kode tidak tepercaya dari job deployment. Artefak yang diteruskan ke deployment juga perlu divalidasi.

Pembatasan event dalam YAML saja bukan pengganti trust policy. Orang yang dapat mengubah workflow mungkin dapat menambah pemicu baru, sedangkan pemeriksaan sub di IAM tetap diberlakukan oleh AWS.

Verifikasi akses melalui AWS dan CloudTrail

Langkah aws sts get-caller-identity menunjukkan account, user ID sesi, dan ARN role yang sedang digunakan. Pastikan hasilnya menunjuk ke deployment role, bukan IAM user lama.

AWS CloudTrail mencatat panggilan STS AssumeRoleWithWebIdentity. Untuk mencari event melalui event history:

aws cloudtrail lookup-events \
  --lookup-attributes \
    AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \
  --max-results 20

Periksa waktu event, role ARN, identity provider, nama sesi, akun tujuan, dan event deployment berikutnya. Nama sesi yang menyertakan github.run_id memudahkan korelasi dengan eksekusi GitHub Actions. Event history dapat memerlukan waktu sebelum muncul; untuk pencarian jangka panjang dan lintas akun, gunakan CloudTrail trail atau event data store yang dikirim ke penyimpanan terpusat.

CloudTrail juga membantu membedakan dua kelas kegagalan:

  • Gagal mengambil role: periksa event AssumeRoleWithWebIdentity, provider, audience, subject, dan trust policy.
  • Role berhasil diperoleh tetapi deployment ditolak: periksa event API layanan tujuan, permissions policy, resource policy, KMS key policy, permission boundary, atau SCP.

Masalah umum dan cara memperbaikinya

AccessDenied saat AssumeRoleWithWebIdentity

  • Pastikan ARN pada role-to-assume menunjuk ke akun dan role yang benar.
  • Pastikan provider token.actions.githubusercontent.com ada di akun tempat role berada.
  • Periksa bahwa audience provider dan condition aud bernilai sts.amazonaws.com.
  • Bandingkan claim sub aktual dengan trust policy secara persis.
  • Pastikan principal federated pada trust policy menunjuk ke ARN OIDC provider yang benar.
  • Periksa apakah permission boundary atau SCP membatasi penggunaan STS.

Subject tidak cocok

Penyebab paling umum adalah workflow menambahkan environment, sedangkan trust policy masih mengharapkan subject branch. Penyebab lain meliputi nama organisasi atau repository yang berbeda, branch selain main, tag yang tidak cocok dengan pola, atau perubahan nama Environment.

Jika perlu memeriksa token saat debugging, dekode hanya payload JWT di lingkungan yang terkendali dan jangan pernah mencetak token lengkap ke log. OIDC token adalah bearer token berumur pendek. Hapus langkah debugging setelah claim terverifikasi.

Unable to get ACTIONS_ID_TOKEN_REQUEST_URL

Biasanya job belum memiliki permissions: id-token: write. Pastikan permission ditempatkan pada workflow atau job yang menjalankan configure-aws-credentials. Jika permissions didefinisikan ulang pada level job, sertakan kembali id-token: write di level tersebut.

Role berhasil diperoleh, tetapi operasi AWS tetap ditolak

Ini bukan masalah OIDC. Baca nama action dan resource pada pesan AccessDenied, lalu periksa policy deployment role. Untuk S3, misalnya, ListBucket berlaku pada ARN bucket, sedangkan PutObject dan DeleteObject berlaku pada ARN objek dengan akhiran /*. Bucket policy atau KMS key policy juga dapat menolak operasi meskipun IAM role mengizinkannya.

Langkah migrasi dan pencabutan access key lama

  1. Inventarisasi kredensial lama. Catat IAM user, access key, repository, Environment, dan workflow yang masih menggunakannya.
  2. Buat OIDC provider dan role nonproduksi. Terapkan trust policy ketat dan permissions policy sesuai deployment nyata.
  3. Ubah workflow. Tambahkan id-token: write, gunakan configure-aws-credentials, dan hapus referensi ke access key dari YAML.
  4. Uji identitas dan deployment. Jalankan get-caller-identity, verifikasi hasil deployment, lalu cocokkan sesi dengan CloudTrail.
  5. Terapkan ke produksi. Gunakan role terpisah dan Environment yang dilindungi untuk deployment produksi.
  6. Hapus GitHub Secrets lama. Hapus access key dari level repository, Environment, dan organization jika tidak lagi digunakan workflow lain.
  7. Nonaktifkan access key di IAM. Nonaktifkan lebih dahulu untuk mendeteksi dependensi tersembunyi tanpa langsung menghilangkan opsi pemulihan.
  8. Pantau kegagalan. Periksa workflow, CloudTrail, dan sistem lain yang mungkin masih memakai key tersebut.
  9. Hapus access key dan IAM user. Setelah dipastikan tidak digunakan, hapus key, policy, dan IAM user khusus CI yang sudah tidak diperlukan.

Jangan mempertahankan access key lama sebagai cadangan tanpa batas waktu. Kredensial yang tetap aktif menghilangkan manfaat utama migrasi OIDC. Jika prosedur darurat diperlukan, dokumentasikan mekanisme akses sementara yang diaudit dan memiliki proses kedaluwarsa yang jelas.