After a pile of troubleshooting, I managed to get my gitlab CICD pipeline to connect to GCP without requiring my service account to use a JSON key. However, I'm unable to do anything with Terraform in my pipeline using a remote statefile because of the following error:
Error: Failed to get existing workspaces: querying Cloud Storage failed: googleapi: Error 403: Insufficient Permission, insufficientPermissions
My gitlab-ci.yml file is defined as follows:
stages:
- auth
- validate
gcp-auth:
stage: auth
image: google/cloud-sdk:slim
script:
- echo ${CI_JOB_JWT_V2} > .ci_job_jwt_file
- gcloud iam workload-identity-pools create-cred-config ${GCP_WORKLOAD_IDENTITY_PROVIDER}
--service-account="${GCP_SERVICE_ACCOUNT}"
--output-file=.gcp_temp_cred.json
--credential-source-file=.ci_job_jwt_file
- gcloud auth login --cred-file=`pwd`/.gcp_temp_cred.json
- gcloud auth list
tf-stuff:
stage: validate
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
before_script:
- export TF_LOG=DEBUG
- cd terraform
- rm -rf .terraform
- terraform --version
- terraform init
script:
- terraform validate
My gcp-auth job is running successfully from what I can see:
Authenticated with external account credentials for: [[MASKED]].
I've also went as far as adding in a gsutil cp command inside the gcp-auth job to make sure I can access the desired bucket as expected, which I can. I can successfully edit the contents of the bucket where my terraform statefile is stored.
I'm fairly new to gitlab CICD pipelines. Is there something I need to do to have the gcp-auth job tied to the tf-stuff job? It's like that job does not know the pipeline was previously authenticated using the service account.
Thanks!
Like mentioned by other posters, gitlab jobs run independently and dont share env variables or filesystem. So to preserve login state betwen jobs you have to preserve the state somehow.
I wrote a blog with a working example: https://ael-computas.medium.com/gcp-workload-identity-federation-on-gitlab-passing-authentication-between-jobs-ffaa2d51be2c
I have done it like github actions is doing it, by storing (tmp) credentials as artifacts. By setting correct env variables you should be able to "keep" the logged in state (gcp will implicitly refresh your token) without you having to create a base image containing everything. All jobs must run the gcp_auth_before method, or extend the auth job for this to work. and also have _auth/ artifacts preserved between jobs
In the sample below you can see that login state is preserved over two jobs, but only actuallt signing in on the first one. I have used this together with terraform images for further steps and it works like a charm so far.
This is very early so there might be hardening required for production.
Hope this example gives you some ideas on how to solve this!
.gcp_auth_before: &gcp_auth_before
- export GOOGLE_APPLICATION_CREDENTIALS=$CI_PROJECT_DIR/_auth/.gcp_temp_cred.json
- export CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE=$CI_PROJECT_DIR/_auth/.gcp_temp_cred.json
- export GOOGLE_GHA_CREDS_PATH=$CI_PROJECT_DIR/_auth/.gcp_temp_cred.json
- export GOOGLE_CLOUD_PROJECT=$(cat $CI_PROJECT_DIR/_auth/.GOOGLE_CLOUD_PROJECT)
- export CLOUDSDK_PROJECT=$(cat $CI_PROJECT_DIR/_auth/.GOOGLE_CLOUD_PROJECT)
- export CLOUDSDK_CORE_PROJECT=$(cat $CI_PROJECT_DIR/_auth/.GOOGLE_CLOUD_PROJECT)
- export GCP_PROJECT=$(cat $CI_PROJECT_DIR/_auth/.GOOGLE_CLOUD_PROJECT)
- export GCLOUD_PROJECT=$(cat $CI_PROJECT_DIR/_auth/.GOOGLE_CLOUD_PROJECT)
.gcp-auth:
artifacts:
paths:
- _auth/
before_script:
*gcp_auth_before
stages:
- auth
- debug
auth:
stage: auth
image: "google/cloud-sdk:slim"
variables:
SERVICE_ACCOUNT_EMAIL: "... service account email ..."
WORKLOAD_IDENTITY_PROVIDER: "projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL/providers/PROVIDER"
GOOGLE_CLOUD_PROJECT: "... project id ...."
artifacts:
paths:
- _auth/
script:
- |
mkdir -p _auth
echo "$CI_JOB_JWT_V2" > $CI_PROJECT_DIR/_auth/.ci_job_jwt_file
echo "$GOOGLE_CLOUD_PROJECT" > $CI_PROJECT_DIR/_auth/.GOOGLE_CLOUD_PROJECT
gcloud iam workload-identity-pools create-cred-config \
$WORKLOAD_IDENTITY_PROVIDER \
--service-account=$SERVICE_ACCOUNT_EMAIL \
--service-account-token-lifetime-seconds=600 \
--output-file=$CI_PROJECT_DIR/_auth/.gcp_temp_cred.json \
--credential-source-file=$CI_PROJECT_DIR/_auth/.ci_job_jwt_file
gcloud config set project $GOOGLE_CLOUD_PROJECT
- "export GOOGLE_APPLICATION_CREDENTIALS=$CI_PROJECT_DIR/_auth/.gcp_temp_cred.json"
- "gcloud auth login --cred-file=$GOOGLE_APPLICATION_CREDENTIALS"
- gcloud auth list # DEBUG!!
debug:
extends: .gcp-auth
stage: debug
image: "google/cloud-sdk:slim"
script:
- env
- gcloud auth list
- gcloud storage ls
Your two Gitlab job run on a separated pod for the Kubernetes runner.
The tf-stuff job doesn't see the authentication done in the job gcp-auth.
To solve this issue, you can add the authentication code logic in a separated Shell script, then reuse this script in the two Gitlab jobs, example :
Authentication Shell script gcp_authentication.sh :
echo ${CI_JOB_JWT_V2} > .ci_job_jwt_file
gcloud iam workload-identity-pools create-cred-config ${GCP_WORKLOAD_IDENTITY_PROVIDER}
--service-account="${GCP_SERVICE_ACCOUNT}"
--output-file=.gcp_temp_cred.json
--credential-source-file=.ci_job_jwt_file
gcloud auth login --cred-file=`pwd`/.gcp_temp_cred.json
gcloud auth list
# Check if you need to set GOOGLE_APPLICATION_CREDENTIALS env var on `pwd`/.gcp_temp_cred.json
For the tf-stuff, you can create a custom Docker image containing gcloud and Terraform because the image hashicorp/terraform doesn't contains gcloud cli natively.
Your Docker image can be added in Gitlab registry
Your Gitlab yml file :
stages:
- auth
- validate
gcp-auth:
stage: auth
image: google/cloud-sdk:slim
script:
- . ./gcp_authentication.sh
tf-stuff:
stage: validate
image:
name: yourgitlabregistry/your-custom-image:1.0.0
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
before_script:
- . ./gcp_authentication.sh
- export TF_LOG=DEBUG
- cd terraform
- rm -rf .terraform
- terraform --version
- terraform init
script:
- terraform validate
Some explanations :
The same Shell script has been used in the 2 Gitlab jobs : gcp_authentication.sh
A custom Docker image has been created with Terraform and gcloud cli in the job concerning the Terraform part. This image can be added to the Gitlab registry
In the authentication Shell script, check if you need to set GOOGLE_APPLICATION_CREDENTIALS env var on pwd/.gcp_temp_cred.json
You have to give the needed permission to your Service Account to use Gitlab with Workload Identity :
roles/iam.workloadIdentityUser
You can check this example project and the documentation
Related
We have two services running on Google App Engine.
We would like to restrict deployment to only specific users to the default (prod) target, but allow any devs to deploy to dev target.
Can't figure out the IAM conditions for it.
App engine doesn't seem to be an official resource type here https://cloud.google.com/iam/docs/conditions-resource-attributes#resource-name
and it's consistent with the service filter dropdown
I've tried using the name i get from gcloud app services describe dev:
resource.name == 'apps/my-project/services/dev'
Bt that doesn't seem to work either, it just gives access denied so guessing that's not the right resource name filter.
Is there a way to limit this as above?
App Engine permissions are granted at the project level and cannot be filtered for each different service of the application.
There is an open feature request https://issuetracker.google.com/115904598 to allow specific deployments of versions that I recommend you to star and follow.
Separating your prod and dev environments (I understand that this can be inconvenient sometimes) in different GCP projects could be the only viable alternative for the time being.
AFAIK, you can't restrict permissions to deploy to specific service because users can create custom services per GCP account.
Two options that I can suggest:
Create a different GCP project for prod. If you're using CLI, the prod devs can simply change the GCP project and deploy.
Use CICD with Cloud Build, and only grant merge access to prod branch to prod devs. No dev in this case would need access to your GCP projects.
For anyone still looking for options, it is possible to accomplish deployment isolation via gcloud config. It allows the creation of named configuration for each environment, for example.
Configuration governs the behavior of gcloud CLI.
Usage example:
Deploy to DEV (default) environment:
deploy:
image: google/cloud-sdk:alpine
stage: deploy
environment: Development
script:
- cp $GAE_ENV_VARIABLES ./env_variables.yaml
- echo $GAE_SERVICE_ACCOUNT > /tmp/$CI_PIPELINE_ID.json
- gcloud auth activate-service-account --key-file /tmp/$CI_PIPELINE_ID.json
- gcloud app deploy app.yaml --project $GCP_PROJECT_ID --version $CI_COMMIT_SHORT_SHA --image-url=us.gcr.io/$GCP_PROJECT_ID/$IMAGE_NAME:$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA
Deploy to TEST environment :
you can make it a requirement to have a specific service account to create/activate this environment and even a dedicated app.yaml like app_test.yaml with different service name.
deploy_test:
image: google/cloud-sdk:alpine
stage: deploy
environment: Test
only:
- master
script:
- cp $GAE_ENV_VARIABLES ./env_variables.yaml
- echo $GAE_SERVICE_ACCOUNT > /tmp/$CI_PIPELINE_ID.json
- gcloud auth activate-service-account --key-file /tmp/$CI_PIPELINE_ID.json
- gcloud config configurations create test --quiet --project $GCP_PROJECT_ID
- gcloud config configurations activate test --quiet --project $GCP_PROJECT_ID
- gcloud auth activate-service-account --key-file /tmp/$CI_PIPELINE_ID.json #activate service account under the new configuration
- gcloud app deploy app_test.yaml --project $GCP_PROJECT_ID --version $CI_COMMIT_SHORT_SHA --promote --image-url=us.gcr.io/$GCP_PROJECT_ID/$IMAGE_NAME:$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA
I've a bitbucket pipeline that must have multiple aws credentials for different duties.
In the first lines, I have custom ECR image. To pull it, I created an AWS user for only ECR read only permissions. access-key and secret-key parameters are the keys of that user.
And in this ECR image, I embedded another AWS user's credentials to do the rest of the work (image push etc). But somehow, the credentials that I used for pulling base image running in steps too. Because of this situation, image push is being denied.
I tried to use export AWS_PROFILE=deployment but it doesn't help.
Is the credentials for base image pull being applied pipeline-wide?
And how can I overcome with this situation?
Thank you.
image:
name: <ECR Image>
aws:
access-key: $AWS_ACCESS_KEY_ID
secret-key: $AWS_SECRET_ACCESS_KEY
pipelines:
- step:
name: "Image Build & Push"
services:
-docker
script:
- export AWS_PROFILE=deployment
- export ENVIRONMENT=beta
- echo "Environment is ${ENVIRONMENT}"
- export DOCKER_IMAGE_BUILDER="${BITBUCKET_REPO_SLUG}:builder"
- make clean
- make build BUILD_VER=${BITBUCKET_TAG}.${BITBUCKET_BUILD_NUMBER} \ APP_NAME=${BITBUCKET_REPO_SLUG} \
DOCKER_IMAGE_BUILDER=${DOCKER_IMAGE_BUILDER}
- make test
- docker tag ....
- docker push .....
What I would do here instead of baking credentials inside the images:
Use one credential for pulling/pushing/taggin the image, why not use the same for pushing the image.
If that is something you don't wanna do:
Create an IAM role and give that permission to tag/push the images and assume this role from the earlier credentials being exported, No need to bake credentials in the images.
I found the following example in the documentation
script:
# build the image
- docker build -t my-docker-image .
# use the pipe to push to AWS ECR
- pipe: atlassian/aws-ecr-push-image:1.2.2
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION
IMAGE_NAME: my-docker-image
TAGS: '${BITBUCKET_TAG} latest'G
The OpenID Connect is nice feature https://support.atlassian.com/bitbucket-cloud/docs/deploy-on-aws-using-bitbucket-pipelines-openid-connect/
When trying to gcloud builds submit --tag gcr.io/********/*** in order to build a container image, I get a:
ERROR: (gcloud.builds.submit) HTTPError 403: Insufficient Permission
I am trying this from a compute VM instance, where gcloud is set up with the service account.
The service account has the following roles:
Cloud Build Service Account,
Cloud Build Editor,
Cloud Scheduler Job Runner,
Cloud SQL Admin,
Editor,
Organization Administrator,
Cloud Run Admin,
Cloud Run Invoker,
Cloud Run Service Agent,
If anyone has any idea why I am getting denied, help woul be greatly appreciated.
If your using gcloud cli.
Please Verify Your Gcloud auth is Using Services Account.
Then try again gcloud builds submit --tag gcr.io/********/***
If you use Google Cloud Build :
Add google cloud steps to your cloudbuild.yml
steps:
- name: 'gcr.io/cloud-builders/docker'
entrypoint: 'bash'
args:
- '-c'
- 'docker pull gcr.io/$PROJECT_ID/$_APP_NAME:latest || exit 0'
- name: gcr.io/cloud-builders/docker
args:
- 'build'
- '-t'
- 'gcr.io/$PROJECT_ID/$_APP_NAME:latest'
- '.'
- name: gcr.io/cloud-builders/docker
args:
- 'push'
- 'gcr.io/$PROJECT_ID/$_APP_NAME:latest'
images:
- 'gcr.io/$PROJECT_ID/$_APP_NAME'
timeout: 1200s
substitutions:
_APP_NAME: 'app_examples'
Reference : https://cloud.google.com/cloud-build/docs/running-builds/start-build-manually
I am trying to set up a pipeline that builds my react application and deploys it to my AWS S3 bucket. It is building fine, but fails on the deploy.
My .gitlab-ci.yml is :
image: node:latest
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
S3_BUCKET_NAME: $S3_BUCKET_NAME
stages:
- build
- deploy
build:
stage: build
script:
- npm install --progress=false
- npm run build
deploy:
stage: deploy
script:
- aws s3 cp --recursive ./build s3://MYBUCKETNAME
It is failing with the error:
sh: 1: aws: not found
#jellycsc is spot on.
Otherwise, if you want to just use the node image, then you can try something like Thomas Lackemann details (here), which is to use a shell script to install; python, aws cli, zip and use those tools to do the deployment. You'll need AWS credentials stored as environment variables in your gitlab project.
I've successfully used both approaches.
The error is telling you AWS CLI is not installed in the CI environment. You probably need to use GitLab’s AWS Docker image. Please read the Cloud deployment documentation.
I am trying to create a Gitlab CI CD pipeline to build my java spring project and deploy it to amazon eks.
I have followed instruction as in this article.
This is the gitlab-ci-cd.yml file to apply the deployment script.
k8s-deploy-dev:
image: docker.io/sulemanhasib43/eks:latest
stage: k8-deploy
tags:
- kubernetes
before_script: *kubectl_config
script:
- sed -i "s#$CONTAINER_IMAGE#$CONTAINER_IMAGE:dev$CI_PIPELINE_IID#g" deployment.yaml
- kubectl apply -f deployment.yaml -n dev
only:
- master
But I got an issue when applying my deployment.yml file.As following image I got an error as
system:node:"user" cannot create resource ...
But when I am adding the eks cluster to the gitlab, I have created a user with cluster-admin role.
I have also tried adding roles to the system:node ClusterRole.