Image Repositories

The GitOps Toolkit Custom Resource Definitions documentation.

The ImageRepository API specifies how to scan OCI image repositories. A repository is a collection of images – e.g., alpine, as opposed to a specific image, e.g., alpine:3.1. ImagePolicy objects can then refer to an ImageRepository in order to select a specific image from those scanned.

Specification

The ImageRepository type holds details for scanning a particular image repository.

// ImageRepositorySpec defines the parameters for scanning an image
// repository, e.g., `fluxcd/flux`.
type ImageRepositorySpec struct {
	// Image is the name of the image repository
	// +required
	Image string `json:"image,omitempty"`
	// Interval is the length of time to wait between
	// scans of the image repository.
	// +required
	Interval metav1.Duration `json:"interval,omitempty"`

	// Timeout for image scanning.
	// Defaults to 'Interval' duration.
	// +optional
	Timeout *metav1.Duration `json:"timeout,omitempty"`

	// SecretRef can be given the name of a secret containing
	// credentials to use for the image registry. The secret should be
	// created with `kubectl create secret docker-registry`, or the
	// equivalent.
	// +optional
	SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`

	// CertSecretRef can be given the name of a secret containing
	// either or both of
	//
	//  - a PEM-encoded client certificate (`certFile`) and private
	//  key (`keyFile`);
	//  - a PEM-encoded CA certificate (`caFile`)
	//
	//  and whichever are supplied, will be used for connecting to the
	//  registry. The client cert and key are useful if you are
	//  authenticating with a certificate; the CA cert is useful if
	//  you are using a self-signed server certificate.
	// +optional
	CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`


	// ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate
	// the image pull if the service account has attached pull secrets.
	// +optional
	ServiceAccountName string `json:"serviceAccountName,omitempty"`

	// This flag tells the controller to suspend subsequent image scans.
	// It does not apply to already started scans. Defaults to false.
	// +optional
	Suspend bool `json:"suspend,omitempty"`
	
	// AccessFrom defines an ACL for allowing cross-namespace references
	// to the ImageRepository object based on the caller's namespace labels.
	// +optional
	AccessFrom *AccessFrom `json:"accessFrom,omitempty"`

	// ExclusionList is a list of regex strings used to exclude certain tags
	// from being stored in the database.
	// +optional
	ExclusionList []string `json:"exclusionList,omitempty"`
}

The Suspend field can be set to true to stop the controller scanning the image repository specified; remove the field value or set to false to resume scanning.

Authentication

The spec.secretRef names a secret in the same namespace that holds credentials for accessing the image repository. This secret is expected to be in the same format as for imagePullSecrets. The usual way to create such a secret is with

kubectl create secret docker-registry ...

For using image pull secrets attached to a service account, you can specify the account name with spec.serviceAccountName.

For a publicly accessible image repository, you will not need to provide a secretRef.

Automatic Authentication

When running on any of the three major cloud providers and using their container registry to store images, you should be able to rely on the controller retrieving credentials automatically. The controllers must be run with the corresponding flag for each provider.

For EKS and ECR, the flag is --aws-autologin-for-ecr. For GKE and GCR, the flag is --gcp-autologin-for-gcr. For AKS and ACR, the flag is --azure-autologin-for-acr.

These flags can be added by including a patch in the kustomization.yaml overlay file in your flux-system, as described in cloud providers authentication guide. If there is no need for a security boundary on your cluster around container registries and you are not using Flux with so-called “soft multi-tenancy”, then you will likely prefer to use the Auto-Login feature for the convenience and improved ease of use.

Alternatively, the advice to use a cron job to refresh a secret token under Other platforms below will also work with ECR, GCR, and ACR environments that require security boundaries and soft multi-tenancy.

Other platforms

If you are running on another platform that links service permissions to service accounts, you will need to create the secret using tooling for that platform, rather than directly with kubectl create secret. There is advice specific to some platforms in the image automation guide.

TLS Certificates

The certSecretRef field names a secret with TLS certificate data. This is for two separate purposes:

  • to provide a client certificate and private key, if you use a certificate to authenticate with the image registry; and,
  • to provide a CA certificate, if the registry uses a self-signed certificate.

These will often go together, if you are hosting an image registry yourself. All the files in the secret are expected to be PEM-encoded. This is an ASCII format for certificates and keys; openssl and such tools will typically give you an option of PEM output.

Assuming you have obtained a certificate file and private key and put them in the files client.crt and client.key respectively, you can create a secret with kubectl like this:

SECRET_NAME=tls-certs
kubectl create secret generic $SECRET_NAME \
  --from-file=certFile=client.crt \
  --from-file=keyFile=client.key

You could also prepare a secret and encrypt it; the important bit is that the data keys in the secret are certFile and keyFile.

If you have a CA certificate for the client to use, the data key for that is caFile. Adapting the previous example, if you have the certificate in the file ca.crt, and the client certificate and key as before, the whole command would be:

SECRET_NAME=tls-certs
kubectl create secret generic $SECRET_NAME \
  --from-file=certFile=client.crt \
  --from-file=keyFile=client.key \
  --from-file=caFile=ca.crt

Allow cross-namespace references

To grant access to an ImageRepository for policies in other namespaces, the owner of the ImageRepository has to specify a list of label selectors that match the namespaces of the ImagePolicy objects.

apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageRepository
metadata:
  name: app1
  namespace: apps
spec:
  interval: 5m
  image: docker.io/org/image
  secretRef:
    name: regcred
  accessFrom:
    namespaceSelectors:
      - matchLabels:
          kubernetes.io/metadata.name: flux-system

Note that the kubernetes.io/metadata.name is a readonly label added by Kubernetes >= 1.21 automatically on namespaces. If you’re using an older version of Kubernetes, please set labels on the namespaces where the ImagePolicy are.

The above definition, allows for ImagePolicy in the flux-system namespace to reference the app1 ImageRepository e.g.:

apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
  name: app1
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: app1
    namespace: apps
  policy:
    semver:
      range: 1.0.x

To grant access to all namespaces, an empty matchLabels must be provided:

  accessFrom:
    namespaceSelectors:
      - matchLabels: {}

Exclude Tags

To exclude certain tags, the spec.exclusionList field can be used to specify a list of regex expressions. Any tags that match any of the regex expressions, will be excluded from the final tag list. If spec.exclusionList is empty, by default the regex "^.*\\.sig$" is used to exclude all tags ending with .sig, since these are Cosign generated objects and not container images which can be deployed on a Kubernetes cluster.

Status

// ImageRepositoryStatus defines the observed state of ImageRepository
type ImageRepositoryStatus struct {
	// +optional
	Conditions []metav1.Condition `json:"conditions,omitempty"`

	// ObservedGeneration is the last reconciled generation.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// CanonicalImageName is the name of the image repository with all the
	// implied bits made explicit; e.g., `docker.io/library/alpine`
	// rather than `alpine`.
	// +optional
	CanonicalImageName string `json:"canonicalImageName,omitempty"`

	// LastScanResult contains the number of fetched tags.
	// +optional
	LastScanResult *ScanResult `json:"lastScanResult,omitempty"`

	meta.ReconcileRequestStatus `json:",inline"`
}

The CanonicalImageName field gives the fully expanded image name, filling in any parts left implicit in the spec. For instance, alpine expands to docker.io/library/alpine.

The LastScanResult field gives a summary of the most recent scan:

type ScanResult struct {
	TagCount int         `json:"tagCount"`
	ScanTime metav1.Time `json:"scanTime,omitempty"`
}

Conditions

There is one condition used: the GitOps toolkit-standard ReadyCondition. This will be marked as true when a scan succeeds, and false when a scan fails.

Examples

Fetch metadata for a public image every ten minutes:

apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageRepository
metadata:
  name: podinfo
  namespace: flux-system
spec:
  interval: 10m
  image: ghcr.io/stefanprodan/podinfo

Fetch metadata for a private image every minute:

apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageRepository
metadata:
  name: podinfo
  namespace: flux-system
spec:
  interval: 1m0s
  image: docker.io/org/image
  secretRef:
    name: regcred

For private images, you can create a Kubernetes secret in the same namespace as the ImageRepository with kubectl create secret docker-registry, and reference it under secretRef.