google-cloud-platformjenkinsterraform

Integration between Jenkins and GCP service account, 403 forbidden


2025/4/12 update:

...

        stage('Configure GCP Authentication') {
            steps {
                withCredentials([file(credentialsId: 'gcp-sa-dev', variable: 'GOOGLE_APPLICATION_CREDENTIALS')]) { // Ensure 'gcp-sa-dev' is your Credential ID in Jenkins
                    sh 'gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS'
                    sh 'gcloud config set project $GCP_PROJECT_ID'
                    echo "GCP Authentication Configured as: ${SERVICE_ACCOUNT_EMAIL}"

                    sh 'terraform init -backend-config="bucket=${TF_STATE_BUCKET}" -migrate-state'
                    sh 'terraform validate'
                    sh 'terraform plan -out=tfplan'
                    archiveArtifacts artifacts: 'tfplan'
                    input message: 'Approve Terraform Apply to Production?', ok: 'Proceed with Apply'
                    sh 'terraform apply tfplan'
                }
            }
        }
...

2025/4/11 update:

+ gcloud config set account jenkins-cicd-dev@open-data-v2-cicd.iam.gserviceaccount.com
Updated property [core/account].
+ terraform init -backend-config=bucket=terraform-state-bucket-project-data-sharing -migrate-state
[0m[1mInitializing the backend...[0m
[31m╷[0m[0m
[31m│[0m [0m[1m[31mError: [0m[0m[1mError loading state: writing "gs://terraform-state-bucket-project-data-sharing/terraform/state/default.tflock" failed: googleapi: Error 403: Access denied., forbidden
[31m│[0m [0mstorage: object doesn't exist[0m
[31m│[0m [0m
[31m│[0m [0m[0m
[31m╵[0m[0m
Your Cloud Platform project in this session is set to open-data-v2-cicd.
Use `gcloud config set project [PROJECT_ID]` to change to a different project.
shihwenwutw@cloudshell:~ (open-data-v2-cicd)$ gcloud auth activate-service-account jenkins-cicd-dev@open-data-v2-cicd.iam.gserviceaccount.com \
  --key-file=sa-key-3.json
Activated service account credentials for: [jenkins-cicd-dev@open-data-v2-cicd.iam.gserviceaccount.com]
shihwenwutw@cloudshell:~ (open-data-v2-cicd)$ gcloud auth list
Credentialed Accounts

ACTIVE: *
ACCOUNT: jenkins-cicd-dev@open-data-v2-cicd.iam.gserviceaccount.com

ACTIVE: 
ACCOUNT: xxx@gmail.com

To set the active account, run:
    $ gcloud config set account `ACCOUNT`

shihwenwutw@cloudshell:~ (open-data-v2-cicd)$ gsutil ls gs://terraform-state-bucket-project-data-sharing/
shihwenwutw@cloudshell:~ (open-data-v2-cicd)$ echo "This is a test file from Jenkins SA." > dummy.txt
shihwenwutw@cloudshell:~ (open-data-v2-cicd)$ gcloud storage cp dummy.txt gs://terraform-state-bucket-project-data-sharing/terraform/state/test-upload.txt
Copying file://dummy.txt to gs://terraform-state-bucket-project-data-sharing/terraform/state/test-upload.txt
  Completed files 1/1 | 37.0B/37.0B            

uploaded-text-file

Original post
I am trying to set up a datapipline which:

What I have done:

Issue I encountered:

+ terraform init -backend-config=bucket=terraform-state-bucket--data-sharing
[0m[1mInitializing the backend...[0m
[31m╷[0m[0m
[31m│[0m [0m[1m[31mError: [0m[0m[1mError loading state: writing "gs://terraform-state-bucket--data-sharing/terraform/state/default.tflock" failed: googleapi: Error 403: Access denied., forbidden
[31m│[0m [0mstorage: object doesn't exist[0m
[31m│[0m [0m
[31m│[0m [0m[0m
[31m╵[0m[0m
+ gcloud auth list
                            Credentialed Accounts
ACTIVE  ACCOUNT
        443305306906-compute@developer.gserviceaccount.com
*       data-sharing-jenkins-tf-dev@open-data-v2-cicd.iam.gserviceaccount.com
        jenkins-cicd-dev@open-data-v2-cicd.iam.gserviceaccount.com

To set the active account, run:
    $ gcloud config set account `ACCOUNT`

[Pipeline] sh
+ gcloud config list
[core]
account = data-sharing-jenkins-tf-dev@open-data-v2-cicd.iam.gserviceaccount.com
disable_usage_reporting = True
project = open-data-v2-cicd

Your active configuration is: [default]
    terraform {
      backend "gcs" {
        bucket = "terraform-state-bucket--data-sharing"  
        prefix = "terraform/state"                       
        region = "asia-east1"  
      }
    }
    
    resource "google_storage_bucket" "my-bucket" {
      name                     = "test-githubdemo-bucket-001"
      project                  = "open-data-v2-cicd"
      location                 = "asia-east1"
      force_destroy            = true
      public_access_prevention = "enforced"
    }
pipeline {
    agent any
    tools {
        terraform 'Terraform-v1.11.3'
    }
    environment {
        GCP_PROJECT_ID = 'open-data-v2-cicd'
        TF_STATE_BUCKET = 'terraform-state-bucket--data-sharing'
        GCP_REGION = 'asia-east1'
        SERVICE_ACCOUNT_EMAIL = 'jenkins-cicd-dev@open-data-v2-cicd.iam.gserviceaccount.com'
    }
    stages {
        stage('Checkout Code') {
            steps {
                checkout scm
            }
        }
        stage('Configure GCP Authentication') {
            steps {
                withCredentials([file(credentialsId: 'gcp-sa-dev', variable: 'GOOGLE_APPLICATION_CREDENTIALS')]) {
                    sh 'gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS'
                    sh 'gcloud config set project $GCP_PROJECT_ID'
                    sh 'gcloud auth list'
                    sh 'gcloud config list'
                }
            }
        }
        stage('Terraform Init') {
            steps {
                sh 'terraform init -backend-config="bucket=${TF_STATE_BUCKET}"'
            }
        }
        stage('Terraform Validate') {
            steps {
                sh 'terraform validate'
            }
        }
        stage('Terraform Plan') {
            steps {
                sh 'terraform plan -out=tfplan'
                // Optionally, archive the plan file as an artifact for review
                archiveArtifacts artifacts: 'tfplan'
            }
        }
        stage('Terraform Apply') {
            steps {
                // For automated apply (e.g., for dev/staging)
                // sh 'terraform apply tfplan'

                // For main/prod pipeline, consider manual approval step before apply
                input message: 'Approve Terraform Apply to Production?', ok: 'Proceed with Apply'
                // Check if Jenkins is using correct SA
                sh 'gcloud auth list'
                sh 'gcloud config list'
                sh 'terraform apply tfplan'
            }
        }
    }
    post {
        failure {
            script {
                echo "Terraform Pipeline Failed!"
                // Add notifications (e.g., email, Slack) here if needed
            }
        }
        success {
            script {
                echo "Terraform Pipeline Succeeded!"
                // Add notifications here if needed
            }
        }
    }
}

Solution

  • The issue solved after adding following in the environment, the credential can be used in all stages automatically

    GOOGLE_APPLICATION_CREDENTIALS = credentials('gcp-sa-dev')

    pipeline {
        agent any
        tools {
            terraform 'Terraform-v1.11.3'
        }
        environment {
            GCP_PROJECT_ID = 'open-data-v2-cicd'
            TF_STATE_BUCKET = 'terraform-state-bucket-project-data-sharing'
            GCP_REGION = 'asia-east1'
            SERVICE_ACCOUNT_EMAIL = 'jenkins-tf-dev@open-data-v2-cicd.iam.gserviceaccount.com'
            GOOGLE_APPLICATION_CREDENTIALS = credentials('gcp-sa-dev') //<-------
       }
    ...