Zero Static Credentials on Kubernetes — Workload Identity + ESO + 1Password

Apr 3, 2026

Every Kubernetes cluster I’ve worked on had the same problem: static credentials scattered across namespaces. Docker registry secrets copied everywhere. SOPS-encrypted files in git. Service account JSON keys mounted as volumes. It works, but it’s fragile and nobody wants to rotate them.

I recently built a GKE cluster where nothing has a static credential. No gcr-regcred. No SOPS. No key files. Here’s how.


The Old Way

The typical setup:

  1. Create a GCP service account with Artifact Registry reader permissions
  2. Generate a JSON key
  3. Base64 encode it into a kubernetes.io/dockerconfigjson secret
  4. Copy that secret to every namespace that pulls images
  5. Add imagePullSecrets to every deployment
  6. SOPS-encrypt the secret in git so it’s not plaintext
  7. Hope nobody forgets to rotate the key

For 1Password integration, the pattern was:

  1. Store 1Password Connect credentials as SOPS-encrypted secrets in git
  2. External Secrets Operator reads the SOPS-encrypted credentials
  3. ESO connects to 1Password and pulls the actual secrets

SOPS encrypted the thing that reads secrets. Turtles all the way down.

The New Way

Image Pulls — GKE Workload Identity

GKE nodes already have a service account. If that service account has artifactregistry.reader on the project where images live, every pod on the node can pull images natively. No imagePullSecrets needed.

# One IAM binding — covers all pods on all nodes
gcloud projects add-iam-policy-binding image-project \
  --member="serviceAccount:node-sa@cluster-project.iam.gserviceaccount.com" \
  --role="roles/artifactregistry.reader"

Remove imagePullSecrets from every deployment. Done.

Helm Chart Pulls — Dedicated SA + Workload Identity

Flux’s source-controller needs to authenticate to pull Helm charts from private OCI registries. Create a dedicated GCP service account with only artifactregistry.reader, bind it to the Flux source-controller Kubernetes service account via Workload Identity:

# HelmRepository — use provider: gcp instead of secretRef
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: private-registry
spec:
  type: oci
  url: oci://us-docker.pkg.dev/my-project/my-registry
  provider: gcp  # ← Workload Identity, no secret needed

Patch the Flux source-controller service account:

# In flux-system/kustomization.yaml
patches:
  - patch: |
      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: source-controller
        namespace: flux-system
        annotations:
          iam.gke.io/gcp-service-account: flux-source@project.iam.gserviceaccount.com
    target:
      kind: ServiceAccount
      name: source-controller

1Password Bootstrap — GCP Secret Manager

Instead of SOPS-encrypting the 1Password Connect credentials, store them in GCP Secret Manager. ESO reads them via Workload Identity — no static credentials anywhere.

The chain:

GKE Workload Identity
   dedicated ESO service account
     GCP Secret Manager (reads 1Password Connect creds)
       1Password Connect Server starts
         ClusterSecretStore connects to 1Password
           All other secrets pulled via ExternalSecrets

Create a SecretStore pointing to GCP SM:

apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: gcp-secret-manager
spec:
  provider:
    gcpsm:
      projectID: my-project
      auth:
        workloadIdentity:
          clusterLocation: us-central1-a
          clusterName: my-cluster
          clusterProjectID: my-project
          serviceAccountRef:
            name: eso-gcp-sm  # annotated with WI

The Result

What Old way New way
Pod image pulls gcr-regcred secret per namespace GKE node SA (automatic)
Helm chart pulls gcr-regcred in flux-system provider: gcp on HelmRepository
1Password bootstrap SOPS-encrypted in git GCP Secret Manager + Workload Identity
All other secrets 1Password via ESO Same (unchanged)
Static credentials in git ~5 SOPS files Zero
Rotation needed Manual key rotation Automatic (WI tokens rotate)

Least Privilege

Each workload gets its own GCP service account with only the permissions it needs:

No shared service accounts. No over-privileged node SA. One SA = one workload = one set of permissions.

Takeaway

The key insight: GKE Workload Identity replaces static credentials with automatic, short-lived tokens. Combined with ESO for application secrets and GCP Secret Manager for bootstrap credentials, you can run a production Kubernetes cluster with zero static credentials in git, zero SOPS files, and zero manually-managed secrets. Everything authenticates through IAM bindings.