Keyless GitOps Delivery Platform on GCP
A production CI/CD reference implementation — Spring Boot to GKE via SonarQube and Trivy gates, Helm, and ArgoCD, with Workload Identity Federation removing every long-lived credential.
The constraint
Most CI/CD pipelines authenticate to their cloud with a long-lived JSON service account key pasted into a secrets store. That key does not expire, is rarely rotated, and grants its permissions to anyone who can read the secret or trick the pipeline into printing it.
It is the single most common serious misconfiguration in cloud CI/CD, and it is entirely avoidable.
Approach
Workload Identity Federation. GitHub Actions mints a short-lived OIDC token; GCP exchanges it for a scoped access token valid for minutes. No stored credential exists.
The detail that matters and is frequently missed: the WIF provider needs an
attribute-condition pinning the specific repository.
--attribute-condition="assertion.repository=='org/repo'"
Without it, any repository on GitHub can mint tokens for your service account. A federation setup missing this line is arguably worse than the JSON key it replaced, because it looks secure.
Architecture
Push to main triggers: checkout, Java build, SonarQube scan with a blocking quality gate, then WIF authentication to GCP. Jib builds the container image without a Docker daemon — faster and one less privileged component in the runner.
Trivy scans the pushed image and fails on critical or high findings. Both gates are blocking; a warning nobody acts on is not a gate.
Then the GitOps step: the pipeline bumps the image tag in the Helm values.yaml and
commits. ArgoCD detects the change and syncs to GKE. The pipeline never holds cluster
credentials — it only writes to Git, and Git is the source of truth. Rollback is
git revert.
What broke
Initially the GitOps commit went to the same repository, which created a loop: the tag-bump
commit triggered the workflow, which bumped the tag again. A [skip ci] marker patched it,
but that is a workaround, not a design.
The correct answer is a separate manifests repository. Application code and deployed state have genuinely different lifecycles and different reviewers. Keeping them in one repo is convenient at first and consistently regretted later.
Outcome
Zero long-lived credentials in CI. Two blocking gates before anything reaches a cluster. Deployed state visible and revertible in Git. The implementation is public — see the repository link above.
Stack
- GitHub Actions
- ArgoCD
- Helm
- GKE
- Trivy
- SonarQube
- Jib