Authenticate to GCP's Artifact Registry Repository with argocd-image-updater

· 3 min read

With argocd-image-updater we can automate deployments in our Kubernetes cluster, after our continous integration (CI) pipelines have built and pushed the docker image into Artifact Registry or our preferred artifact repository. Once correctly configured, argocd-image-updater will continuously check our Artifact Registry and check if any newer images exist, if they exist it can create a git commit to update the image in a kubernetes Deployment, Pod, CronJob , etc. This way we achieve automated deployments in ArgoCD .

In this post we will check how to Authenticate to Artifact Registry with argocd-image-updater.

Imagine we have a highly available Artifact Registry docker repository in multiregion us. Where we store a docker image called my-image which has 3 version tags: 0.0.1 , 0.0.2 and 0.0.3. The full path of the image is:

us-docker.pkg.dev/my-gcp-project/my-artifact-repository/my-image

demo Artifact Registry

Install the helm chart for argocd-image-updater, I like pulling and untaring it locally for testing and getting familiar with it's templates:

helm pull argo/argocd-image-updater --untar

Proceed to customize the values.yaml file of the argocd-image-updater with the following values:

config:
  registries: 
    - name: GCP Artifact Registry
      api_url: https://us-docker.pkg.dev
      prefix: us-docker.pkg.dev
      credentials: ext:/scripts/gcp-auth.sh # defined in authScripts below
      credsexpire: 30m
      default: true

authScripts:
  enabled: true
  scripts:
    gcp-auth.sh: |
      #!/bin/sh
      ACCESS_TOKEN=$(wget --header 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token -q -O - | grep -Eo '"access_token":.*?[^\\]",' | cut -d '"' -f 4)
      echo "oauth2accesstoken:$ACCESS_TOKEN"      

serviceAccount:
  create: true
  annotations: {
    # the GCP service account to bind the present k8s service account
    iam.gke.io/gcp-service-account: sa-argocd-image-updater@my-gcp-project.iam.gserviceaccount.com
  }      

Install the helm chart of argocd-image-updater with the custom values defined above, using the following helm command:

helm install argocd-image-updater \
  charts/argocd-image-updater \ 
  -n argocd \
  -f ./path/to/values.yaml

Now you need to create a GCP service account assign the role roles/artifactregistry.admin to it and finally bind it to the argocd-image-updater service account inside your kubernetes cluster. I created a helper function in Pulumi to achieve (you can do it via gcloud commands or terraform if you prefer):

  type BinderOptions = {
  tag: string; // tag for easily identifying pulumi urns
  env: Environments;
  role: string; // Example: "roles/dns.admin"
  gcpServiceAccountName: string;
  gcpServiceAccountProject?: string; // GCP project where the service account is created
  // Note: k8s service account and namespace, is usually created by helm or custom resource in argocd
  k8sServiceAccountName: string;
  k8sNamespace: string; // k8s namespace where service account is located
};

const gcpServiceAccountToKubernetesServiceAccountBinder = async ({
  tag,
  gcpServiceAccountName,
  k8sServiceAccountName,
  k8sNamespace,
  env,
  role,
  gcpServiceAccountProject,
}: BinderOptions) => {
  // Create GCP service account
  const gcpServiceAccount = new gcp.serviceaccount.Account(
    `${env}-gcp-sa-${tag}`,
    {
      accountId: gcpServiceAccountName,
      project: gcpServiceAccountProject || gcpProject,
    }
  );

  // bind the GCP sa to a role
  const iamPolicyBinding = new gcp.projects.IAMBinding(
    `${env}-gcp-sa-policy-${tag}-binding`,
    {
      role,
      members: [
        pulumi.interpolate`serviceAccount:${gcpServiceAccount.email}`,
      ],
      project: gcpServiceAccountProject || gcpProject,
    }
  );

  // Allow k8s serv account to use roles from the GCP service account
  const workloadIdentityBinding = new gcp.serviceaccount.IAMBinding(
    `${env}-gcp-sa-${tag}-workload-identity-binding`,
    {
      serviceAccountId: gcpServiceAccount.name,
      role: "roles/iam.workloadIdentityUser",
      members: [
        `serviceAccount:${gcpProject}.svc.id.goog[${k8sNamespace}/${k8sServiceAccountName}]`,
      ],
    }
  );

You can call the function in this way:

  const argoCdImageUpdaterAppSaBinderOptions: BinderOptions = {
    tag: "argocd-image-updater",
    gcpServiceAccountName: "sa-argocd-image-updater",
    k8sServiceAccountName: "argocd-image-updater",
    k8sNamespace: "argocd",
    env,
    role: "roles/artifactregistry.admin",
  };

  gcpServiceAccountToKubernetesServiceAccountBinder(
    argoCdImageUpdaterAppSaBinderOptions
  );

Deploy above changes in your infrastructure.

For debugging you can ssh or attach a debug container into your running argocd-image-updater pod and run this commands to check everything is as expected:

cat /app/config/registries.conf

# Output
registries:
  - api_url: https://us-docker.pkg.dev
    credentials: ext:/scripts/gcp-auth.sh
    credsexpire: 30m
    default: true
    name: GCP Artifact Registry
    prefix: us-docker.pkg.dev

Let's also check the gcp-auth script works as expected:

./scripts/gcp-auth.sh

# Output
oauth2accesstoken:my-secret-token

You can use argocd-image-updater test to test the updater is configured correctly:

argocd-image-updater test us-docker.pkg.dev/my-gcp-project/my-artifact-repository/my-image \
  --credentials ext:/scripts/gcp-auth.sh \ 
  --registries-conf-path="/app/config/registries.conf" \
  --semver-constraint 0.0.* 
  --update-strategy semver

The above command will give you at the end of the output the following:

...
INFO[0000] Found 3 tags in registry                      application=test image_alias= image_name=us-docker.pkg.dev/my-gcp-project/my-artifact-repository/my-image registry_url=us-docker.pkg.dev
DEBU[0000] found 3 from 3 tags eligible for consideration  image=us-docker.pkg.dev/my-gcp-project/my-artifact-repository/my-image
INFO[0000] latest image according to constraint is us-docker.pkg.dev/my-gcp-project/my-artifact-repository/my-image:0.0.3  
...