Skip to content
10 changes: 5 additions & 5 deletions charts/registry-scanner/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
apiVersion: v2
name: registry-scanner
appVersion: 0.12.1
description: Sysdig Registry Scanner
type: application
home: https://www.sysdig.com/
icon: https://avatars.githubusercontent.com/u/5068817?s=200&v=4
version: 1.11.1
appVersion: 0.12.1
maintainers:
- name: sysdiglabs
- name: sysdiglabs
name: registry-scanner
type: application
version: 1.12.0
1 change: 1 addition & 0 deletions charts/registry-scanner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ The following table lists the configurable parameters of the Sysdig Registry Sca
| config.registryUser | The username for registry authentication. | <code>""</code> |
| config.registryPassword | The password for registry authentication. | <code>""</code> |
| config.registryType | **required**<br/>The registry Type. Supported types: artifactory, ecr, icr, acr, quay, harbor, gar, gcr, nexus, ocp and dockerv2. | <code>""</code> |
| config.acr_workloadidentity | Use an Azure AD access token from the init container (IMDS) instead of `registryPassword` for ACR authentication. Applies only when `registryType` is `acr`. `registryUser` is still required and `registryPassword` is ignored. | <code>false</code> |
| config.registryAccountId | The account ID. Applicable only for ICR registry type. | <code>""</code> |
| config.icrIamApi | The ICR IAM API. Applicable only for ICR registry type. | <code>""</code> |
| config.icrIamApiSkipTLS | Ignore TLS certificate for IAM API. Applicable only for ICR registry type. | <code>false</code> |
Expand Down
10 changes: 10 additions & 0 deletions charts/registry-scanner/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ Allow overriding registry and repository for air-gapped environments
{{- end -}}
{{- end -}}

{{/*
Init container image for AAD token fetcher (ACR workload identity)
*/}}
{{- define "registry-scanner.initContainerImage" -}}
{{- $initRegistry := .Values.image.initContainerRegistry | default .Values.image.registry | default "quay.io" -}}
{{- $initRepository := .Values.image.initContainerRepository | default "library/curl" -}}
{{- $initTag := .Values.image.initContainerTag | default "7.85.0" -}}
{{- printf "%s/%s:%s" $initRegistry $initRepository $initTag -}}
{{- end -}}

{{- define "registry-scanner.rawPullSecretList" -}}
{{- range .Values.imagePullSecrets }}{{- if . }}{{- .name}},{{- end}}{{- end}}
{{- end -}}
Expand Down
101 changes: 101 additions & 0 deletions charts/registry-scanner/templates/_job.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
labels:
{{- include "registry-scanner.labels" . | nindent 12 }}
{{- include "registry-scanner.customLabels" . | nindent 12 }}
{{- if and (eq .Values.config.registryType "acr") .Values.config.acr_workloadidentity }}
azure.workload.identity/use: "true"
{{- end }}
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 12 }}
Expand All @@ -21,19 +24,105 @@
serviceAccountName: {{ include "registry-scanner.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 12 }}
{{- if and (eq .Values.config.registryType "acr") .Values.config.acr_workloadidentity }}
initContainers:
- name: aad-token-fetcher
image: {{ include "registry-scanner.initContainerImage" . }}
imagePullPolicy: {{ .Values.image.initContainerPullPolicy | default .Values.image.pullPolicy }}
command:
- /bin/sh
- -c
- |
# Obtain an ACR refresh token as the *federated* managed identity (the UAMI in the
# service account's azure.workload.identity/client-id annotation), NOT the node/kubelet
# identity. The azure-workload-identity webhook injects AZURE_CLIENT_ID / AZURE_TENANT_ID /
# AZURE_FEDERATED_TOKEN_FILE because the pod carries azure.workload.identity/use=true.
REGISTRY_URL="{{ .Values.config.registryURL }}"
: "${AZURE_CLIENT_ID:?workload identity not injected (AZURE_CLIENT_ID unset) - check the SA azure.workload.identity/client-id annotation and the azure.workload.identity/use pod label}"
: "${AZURE_TENANT_ID:?workload identity not injected (AZURE_TENANT_ID unset)}"
: "${AZURE_FEDERATED_TOKEN_FILE:?workload identity not injected (AZURE_FEDERATED_TOKEN_FILE unset)}"
AUTHORITY_HOST="${AZURE_AUTHORITY_HOST:-https://login.microsoftonline.com/}"

fetch_token() {
FED_TOKEN="$(cat "$AZURE_FEDERATED_TOKEN_FILE")"
# 1) Federated client-assertion exchange -> AAD access token. ACR with
# azureAdAuthenticationAsArmPolicy enabled expects the ARM audience; a token for
# the ACR-specific audience yields a refresh token that lacks registry:catalog:*.
AAD_TOKEN="$(curl -s -X POST \
"${AUTHORITY_HOST}${AZURE_TENANT_ID}/oauth2/v2.0/token" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "client_id=${AZURE_CLIENT_ID}" \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode "client_assertion=${FED_TOKEN}" \
--data-urlencode 'scope=https://management.azure.com/.default' \
| grep -o '"access_token":"[^"]*' | cut -d'"' -f4)"
[ -n "$AAD_TOKEN" ] || return 1
# 2) Exchange the AAD token for an ACR refresh token (the docker password used with the
# null-GUID username the chart sets as registryUser).
ACR_TOKEN="$(curl -s -X POST \
"https://${REGISTRY_URL}/oauth2/exchange" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=access_token' \
--data-urlencode "service=${REGISTRY_URL}" \
--data-urlencode "tenant=${AZURE_TENANT_ID}" \
--data-urlencode "access_token=${AAD_TOKEN}" \
| grep -o '"refresh_token":"[^"]*' | cut -d'"' -f4)"
[ -n "$ACR_TOKEN" ] || return 1
printf '%s' "$ACR_TOKEN" > /aad-token/token
}

MAX_RETRIES=5
RETRY_COUNT=0
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
if fetch_token; then
echo "ACR refresh token obtained via workload identity"
exit 0
fi
RETRY_COUNT=$((RETRY_COUNT + 1))
[ $RETRY_COUNT -lt $MAX_RETRIES ] && { echo "Retrying ACR token fetch ($RETRY_COUNT/$MAX_RETRIES)..."; sleep 2; }
done
echo "Failed to obtain ACR token via workload identity after $MAX_RETRIES attempts" >&2
exit 1
volumeMounts:
- name: aad-token
mountPath: /aad-token
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
seccompProfile:
type: RuntimeDefault
{{- end }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 14 }}
image: {{ include "registry-scanner.image" . }}
{{- if and (eq .Values.config.registryType "acr") .Values.config.acr_workloadidentity }}
# The scanner binary ignores REGISTRYSCANNER_REGISTRY_PASSWORD_FILE; load the WIF-minted ACR
# token that the init container wrote into the password env, then exec the scanner.
command: ["/bin/sh", "-c", "export REGISTRYSCANNER_REGISTRY_PASSWORD=\"$(cat /aad-token/token)\"; exec /registry-scanner \"$@\"", "registry-scanner"]
args: ["--scan_runner=new-vm-scanner-k8s-job"]
{{- else }}
args: [ "--scan_runner=new-vm-scanner-k8s-job"]
{{- end }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
resources:
{{- toYaml .Values.resources | nindent 14 }}
volumeMounts:
- name: config-volume
mountPath: /config.yaml
subPath: config.yaml
{{- if and (eq .Values.config.registryType "acr") .Values.config.acr_workloadidentity }}
- name: aad-token
mountPath: /aad-token
{{- end }}
{{- if .Values.reportToPersistentVolumeClaim }}
- name: report-storage
mountPath: "/output"
Expand Down Expand Up @@ -120,6 +209,9 @@
key: registryUser
{{- if ne .Values.config.registryType "ocp" }}
- name: REGISTRYSCANNER_REGISTRY_PASSWORD
{{- if and (eq .Values.config.registryType "acr") .Values.config.acr_workloadidentity }}
value: ""
{{- else }}
valueFrom:
secretKeyRef:
{{- if not .Values.existingSecretName }}
Expand All @@ -128,6 +220,11 @@
name: {{ .Values.existingSecretName }}
{{- end }}
key: registryPassword
{{- end }}
{{- end }}
{{- if and (eq .Values.config.registryType "acr") .Values.config.acr_workloadidentity }}
- name: REGISTRYSCANNER_REGISTRY_PASSWORD_FILE
value: /aad-token/token
{{- end }}
{{- end }}
{{ if .Values.config.parallelGoRoutines }}
Expand Down Expand Up @@ -160,6 +257,10 @@
- name: config-volume
configMap:
name: {{ include "registry-scanner.fullname" . }}
{{- if and (eq .Values.config.registryType "acr") .Values.config.acr_workloadidentity }}
- name: aad-token
emptyDir: {}
{{- end }}
{{- if .Values.ssl.ca.certs }}
- name: ca-certs
projected:
Expand Down
3 changes: 2 additions & 1 deletion charts/registry-scanner/templates/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ data:
registryPassword: {{ required "A valid .Values.config.registryPassword is required" .Values.config.registryPassword | b64enc | quote }}
{{- else if eq .Values.config.registryType "ocp" }}
registryUser: {{ "serviceaccount"| b64enc | quote }}
{{- else if not (and .Values.config.acr_workloadidentity (or (eq .Values.config.registryType "acr") (eq .Values.config.registryType ""))) }}
registryPassword: {{ required "A valid .Values.config.registryPassword is required" .Values.config.registryPassword | b64enc | quote }}
{{- else }}
registryUser: {{ required "A valid .Values.config.registryUser is required" .Values.config.registryUser | b64enc | quote }}
registryPassword: {{ required "A valid .Values.config.registryPassword is required" .Values.config.registryPassword | b64enc | quote }}
{{- end }}
{{- end }}
{{- range $index, $cert := .Values.ssl.ca.certs }}
Expand Down
74 changes: 74 additions & 0 deletions charts/registry-scanner/tests/job_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,77 @@ tests:
secretKeyRef:
name: RELEASE-NAME-registry-scanner
key: aws_region

- it: wires ACR workload identity token fetcher and password file env vars
set:
scanOnStart.enabled: true
config.registryType: "acr"
config.registryUser: "dummy"
config.acr_workloadidentity: true
asserts:
- contains:
path: spec.template.spec.initContainers
content:
name: aad-token-fetcher
- contains:
path: spec.template.spec.volumes
content:
name: aad-token
emptyDir: {}
- contains:
path: spec.template.spec.containers[0].volumeMounts
content:
name: aad-token
mountPath: /aad-token
- contains:
path: spec.template.spec.containers[0].env
content:
name: REGISTRYSCANNER_REGISTRY_PASSWORD
value: ""
- contains:
path: spec.template.spec.containers[0].env
content:
name: REGISTRYSCANNER_REGISTRY_PASSWORD_FILE
value: /aad-token/token

- it: does not wire ACR workload identity token fetcher when flag is disabled
set:
scanOnStart.enabled: true
config.registryType: "acr"
config.registryUser: "dummy"
config.registryPassword: "dummy"
config.acr_workloadidentity: false
asserts:
- isNull:
path: spec.template.spec.initContainers
- notContains:
path: spec.template.spec.containers[0].env
content:
name: REGISTRYSCANNER_REGISTRY_PASSWORD_FILE
value: /aad-token/token

- it: does not wire ACR workload identity token fetcher for non-ACR registries
set:
scanOnStart.enabled: true
config.registryType: "quay"
config.registryUser: "dummy"
config.registryPassword: "dummy"
config.acr_workloadidentity: true
asserts:
- isNull:
path: spec.template.spec.initContainers
- notContains:
path: spec.template.spec.volumes
content:
name: aad-token
emptyDir: {}
- notContains:
path: spec.template.spec.containers[0].volumeMounts
content:
name: aad-token
mountPath: /aad-token
- notContains:
path: spec.template.spec.containers[0].env
content:
name: REGISTRYSCANNER_REGISTRY_PASSWORD_FILE
value: /aad-token/token
42 changes: 42 additions & 0 deletions charts/registry-scanner/tests/secret_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
suite: Registry Scanner - Secret
templates:
- templates/secret.yaml
tests:
- it: should set registryPassword for ACR when workload identity is disabled
set:
config.secureAPIToken: dummy
config.registryType: acr
config.registryPassword: dummy
config.acr_workloadidentity: false
asserts:
- equal:
path: data.registryPassword
value: ZHVtbXk=
- isNull:
path: data.registryUser

- it: should set registryUser and omit registryPassword for ACR when workload identity is enabled
set:
config.secureAPIToken: dummy
config.registryType: acr
config.registryUser: dummy
config.acr_workloadidentity: true
asserts:
- equal:
path: data.registryUser
value: ZHVtbXk=
- isNull:
path: data.registryPassword

- it: should still set registryPassword for non-ACR registries even when workload identity is enabled
set:
config.secureAPIToken: dummy
config.registryType: quay
config.registryPassword: dummy
config.acr_workloadidentity: true
asserts:
- equal:
path: data.registryPassword
value: ZHVtbXk=
- isNull:
path: data.registryUser
14 changes: 14 additions & 0 deletions charts/registry-scanner/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ config:
registryPassword: ""
# **required**<br/>The registry Type. Supported types: artifactory, ecr, icr, acr, quay, harbor, gar, gcr, nexus, ocp and dockerv2.
registryType: ""
# Authenticate to ACR as the federated managed identity (Azure Workload Identity) via an init
# container, instead of registryPassword. The init container exchanges the projected federated
# token for an ACR refresh token. Requires the service account to be annotated with
# azure.workload.identity/client-id; the chart adds the azure.workload.identity/use pod label.
# Applies only when registryType is 'acr'. registryUser is still required (null GUID); registryPassword is ignored.
acr_workloadidentity: false
# The account ID. Applicable only for ICR registry type.
registryAccountId: ""
# The ICR IAM API. Applicable only for ICR registry type.
Expand Down Expand Up @@ -180,6 +186,14 @@ image:
pullPolicy: Always
# Whether or not to use a Federal Information Processing Standard (FIPS) compliant image.
fips: false
# Init container image registry for AAD token fetcher (when registryType is acr). Defaults to the main image registry.
initContainerRegistry: ""
# Init container image repository for AAD token fetcher. Defaults to curel/curl on the main registry.
initContainerRepository: "curl/curl"
# Init container image tag. Defaults to 8.21.0
initContainerTag: "8.21.0"
# Init container pull policy. Defaults to the main pullPolicy.
initContainerPullPolicy: ""

serviceAccount:
# Specifies whether a service account should be created.
Expand Down
Loading