Skip to content

🌟 Implement WIF for Container Registry Scanning#1471

Open
slntopp wants to merge 4 commits intomainfrom
mik/wif-registry-scanning
Open

🌟 Implement WIF for Container Registry Scanning#1471
slntopp wants to merge 4 commits intomainfrom
mik/wif-registry-scanning

Conversation

@slntopp
Copy link
Copy Markdown
Member

@slntopp slntopp commented Apr 13, 2026

Summary

Adds Workload Identity Federation (WIF) support for container image scanning. The operator can now authenticate to private container registries (GCR, Artifact Registry, ECR, ACR) using cloud-native WIF credentials instead of static pull secrets.

How it works

When containers.workloadIdentity is configured, the operator:

  1. Creates a Kubernetes ServiceAccount annotated for the cloud provider's WIF (GKE, EKS, or AKS)
  2. Adds an init container to the container-scan CronJob that obtains short-lived registry credentials using the workload identity and writes a Docker config to a shared emptyDir volume
  3. The main cnspec container uses that Docker config to pull and scan private images

Cloud-specific init containers

  • GKE: Uses gcloud auth print-access-token to generate auth for all Artifact Registry regional endpoints and GCR hosts
  • EKS: Uses aws ecr get-login-password to authenticate against ECR
  • AKS: Uses az acr login --expose-token to authenticate against ACR

RBAC

The WIF ServiceAccount needs the same cluster read permissions as the default scanner SA. The Helm chart accepts k8SResourcesScanning.extraClusterRoleBindingSubjects to bind additional SAs to the scanning ClusterRole.

E2E

  • Terraform modules for GKE, EKS, and AKS extended with WIF IAM resources for container registry access
  • Private test workload deployment script pushes nginx to private registries and deploys it
  • Verification script checks that private images appear in scan results

Config example

spec:
  containers:
    enable: true
    workloadIdentity:
      provider: gke
      gke:
        projectId: my-project
        googleServiceAccount: scanner@my-project.iam.gserviceaccount.com

Test plan

  • GKE e2e: run-wif-external-cluster.sh gke
  • EKS e2e: run-wif-external-cluster.sh eks
  • AKS e2e: run-wif-external-cluster.sh aks
  • Unit tests: go test ./controllers/container_image/...
  • Verify private images appear in Mondoo platform after scan
  • Verify disabling WIF cleans up the ServiceAccount

slntopp added 2 commits April 13, 2026 21:11
- Add tests for GKE, EKS, and AKS Workload Identity configurations in `resources_test.go`.
- Update AKS and EKS manifests to include Workload Identity configurations.
- Modify Terraform scripts to grant necessary permissions for Workload Identity.
- Create new manifests for private workload deployments and associated scripts.
- Implement verification scripts for WIF container registry scanning.
- Enhance existing scripts to support private image deployments and verifications.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

Test Results

  7 files  +  5   44 suites  +42   1h 36m 11s ⏱️ + 1h 31m 50s
462 tests +454  462 ✅ +456  0 💤  - 1  0 ❌  - 1 
528 runs  +512  522 ✅ +510  6 💤 +4  0 ❌  - 2 

Results for commit 7787b7d. ± Comparison against base commit d0c9879.

♻️ This comment has been updated with latest results.

@slntopp slntopp marked this pull request as ready for review April 15, 2026 16:41
@github-actions
Copy link
Copy Markdown
Contributor

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, or 📝 job summary for details.

Unrecognized words (3)

acr
artifactregistry
myregistry

These words are not needed and should be removed ACR

To accept these unrecognized words as correct and remove the previously acknowledged and now absent words, you could run the following commands

... in a clone of the git@github.com:mondoohq/mondoo-operator.git repository
on the mik/wif-registry-scanning branch (ℹ️ how do I use this?):

curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/c635c2f3f714eec2fcf27b643a1919b9a811ef2e/apply.pl' |
perl - 'https://github.com/mondoohq/mondoo-operator/actions/runs/24466961745/attempts/1' &&
git commit -m 'Update check-spelling metadata'
Available 📚 dictionaries could cover words (expected and unrecognized) not in the 📘 dictionary

This includes both expected items (35) from .github/actions/spelling/expect.txt and unrecognized words (3)

Dictionary Entries Covers Uniquely
cspell:python/src/python/python-lib.txt 2417 3 1
cspell:elixir/dict/elixir.txt 95 2 1
cspell:node/dict/node.txt 891 2
cspell:php/dict/php.txt 1689 2
cspell:java/src/java.txt 2464 2

Consider adding them (in .github/workflows/spell-check.yaml) in jobs:/spelling: for uses: check-spelling/check-spelling@c635c2f3f714eec2fcf27b643a1919b9a811ef2e in its with:

      with:
        extra_dictionaries: |
          cspell:python/src/python/python-lib.txt
          cspell:elixir/dict/elixir.txt
          cspell:node/dict/node.txt
          cspell:php/dict/php.txt
          cspell:java/src/java.txt

To stop checking additional dictionaries, add (in .github/workflows/spell-check.yaml) for uses: check-spelling/check-spelling@c635c2f3f714eec2fcf27b643a1919b9a811ef2e in its with:

check_extra_dictionaries: ""
Warnings ⚠️ (1)

See the 📂 files view, the 📜action log, or 📝 job summary for details.

⚠️ Warnings Count
⚠️ ignored-expect-variant 1

See ⚠️ Event descriptions for more information.

If the flagged items are false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

Copy link
Copy Markdown

@mondoo-code-review mondoo-code-review bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Container registry WIF scanning forces users to provide unused cluster-specific fields and may fail at runtime due to missing field validation.

Additional findings (file/line not in diff):

  • 🟡 api/v1alpha2/mondooauditconfig_types.go:298 — Reusing WorkloadIdentityConfig (and its sub-structs like AKSWorkloadIdentity) for container registry WIF forces users to provide CRD-required fields that are irrelevant for registry auth (clusterName, resourceGroup, subscriptionId for AKS; clusterName, clusterLocation for GKE; clusterName for EKS). The CRD validation will reject a CR that omits those fields even though they're unused.

Consider either:

  1. Making the cluster-specific fields optional (changing +kubebuilder:validation:Required to +optional and adding omitempty) — this is the minimal change but weakens validation for the k8s-scan WIF path, or
  2. Creating separate, slimmer config types for container registry WIF that only contain the fields actually needed (e.g., ContainerRegistryAKSConfig with only clientId, tenantId, loginServer).

Option 2 is cleaner long-term. At minimum, the docs/user-manual examples should show the required dummy values so users aren't surprised by validation errors.

Comment on lines +343 to +364
func validateContainerRegistryWIF(wif *v1alpha2.WorkloadIdentityConfig) error {
if wif == nil {
return nil
}

switch wif.Provider {
case v1alpha2.CloudProviderGKE:
if wif.GKE == nil {
return fmt.Errorf("containers.workloadIdentity: gke config required when provider is gke")
}
case v1alpha2.CloudProviderEKS:
if wif.EKS == nil {
return fmt.Errorf("containers.workloadIdentity: eks config required when provider is eks")
}
case v1alpha2.CloudProviderAKS:
if wif.AKS == nil {
return fmt.Errorf("containers.workloadIdentity: aks config required when provider is aks")
}
}

return nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 warningvalidateContainerRegistryWIF only checks that the provider-specific struct is non-nil, but does not validate the fields actually needed for registry auth. For example, an AKS config with an empty LoginServer, or an EKS config with an empty Region, will pass validation but the init container will fail at runtime with a confusing error.

Add field-level checks for registry-specific fields:

case v1alpha2.CloudProviderAKS:
    if wif.AKS == nil {
        return fmt.Errorf("...")
    }
    if wif.AKS.LoginServer == "" {
        return fmt.Errorf("containers.workloadIdentity: aks.loginServer is required for container registry WIF")
    }
case v1alpha2.CloudProviderEKS:
    if wif.EKS == nil {
        return fmt.Errorf("...")
    }
    if wif.EKS.Region == "" {
        return fmt.Errorf("containers.workloadIdentity: eks.region is required for container registry WIF")
    }
case v1alpha2.CloudProviderGKE:
    if wif.GKE == nil {
        return fmt.Errorf("...")
    }
    if wif.GKE.GoogleServiceAccount == "" {
        return fmt.Errorf("containers.workloadIdentity: gke.googleServiceAccount is required for container registry WIF")
    }

Comment on lines +193 to +196

// Add init container for registry credential generation
podSpec.InitContainers = append(podSpec.InitContainers, registryWIFInitContainer(wif))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 warning — For AKS, the azure.workload.identity/use label is added to ls (the label map), but ls was defined earlier and is used for both the CronJob metadata labels and the pod template labels. Mutating it here also changes the CronJob's own labels, not just the pod template labels. This is likely harmless but unintentional — the AKS WIF webhook only needs this label on the pod, not the CronJob object itself.

Consider setting it directly on the pod template labels instead:

podSpec.Labels["azure.workload.identity/use"] = "true"

Or make a copy of ls for the pod template before mutating.

Comment thread docs/user-manual.md
Comment on lines +575 to +581
workloadIdentity:
provider: gke
gke:
projectId: my-gcp-project
googleServiceAccount: scanner@my-gcp-project.iam.gserviceaccount.com
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 suggestion — The GKE example omits the clusterName and clusterLocation fields that are marked as required in the CRD schema. Users following this example will get a validation error when applying the CR. Either add placeholder values with a comment explaining they're unused, or fix the CRD to make those fields optional (see other finding).

Comment thread docs/user-manual.md
Comment on lines +591 to +597
containers:
enable: true
workloadIdentity:
provider: eks
eks:
region: us-west-2
roleArn: arn:aws:iam::123456789012:role/MondooRegistryReader
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 suggestion — Same issue as the GKE example: the EKS example omits the required clusterName field. Users will get a CRD validation error.

Comment thread docs/user-manual.md
Comment on lines +607 to +614
```yaml
spec:
containers:
enable: true
workloadIdentity:
provider: aks
aks:
clientId: abcdef12-3456-7890-abcd-ef1234567890
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 suggestion — Same issue: the AKS example omits required fields clusterName, resourceGroup, and subscriptionId. Users will get a CRD validation error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant