Secret Copier

The SecretCopier custom resource sets rules for the copying of secrets between namespaces. The SecretCopier is a cluster scoped resource.

The raw custom resource definition for the SecretCopier custom resource can be viewed by running:

kubectl get crd/secretcopiers.secrets.educates.dev -o yaml

Specifying the source secret

To copy a secret from the specified namespace into all other namespaces use the following.

apiVersion: secrets.educates.dev/v1beta1
kind: SecretCopier
metadata:
  name: registry-credentials
spec:
  rules:
  - sourceSecret:
      name: registry-credentials
      namespace: registry

Secrets will never be copied into the same namespace they originated from.

Changing the target secret name

To change the name of the secret when copied to the target namespace, set targetSecret.name.

apiVersion: secrets.educates.dev/v1beta1
kind: SecretCopier
metadata:
  name: registry-credentials
spec:
  rules:
  - sourceSecret
      name: registry-credentials
      namespace: registry
    targetSecret:
      name: copy-of-registry-credentials

The secret will still never be copied into the same namespace even though the name is different.

Adding labels to the target secret

Labels which may exist on the source secret are not copied to the target secret as doing so may cause issues with software which is triggered based on the presence of any labels.

To specify labels that should be applied to the secret when copied to the target namespace set targetSecret.labels.

apiVersion: secrets.educates.dev/v1beta1
kind: SecretCopier
metadata:
  name: registry-credentials
spec:
  rules:
  - sourceSecret:
      name: registry-credentials
      namespace: registry
    targetSecret:
      labels:
        registry-pull-secret: ""

Limiting set of target namespaces

To limit the set of target namespaces based on name, set targetNamespaces.nameSelector.

apiVersion: secrets.educates.dev/v1beta1
kind: SecretCopier
metadata:
  name: registry-credentials
spec:
  rules:
  - sourceSecret:
      name: registry-credentials
      namespace: registry
    targetNamespaces:
      nameSelector:
        matchNames:
        - example-1
        - example-2

The list of names can be the exact value, or you can also use a shell style glob expression to match a set of names.

apiVersion: secrets.educates.dev/v1beta1
kind: SecretCopier
metadata:
  name: registry-credentials
spec:
  rules:
  - sourceSecret:
      name: registry-credentials
      namespace: registry
    targetNamespaces:
      nameSelector:
        matchNames:
        - example-*

Alternatively, you can match on labels on the namespace by setting a targetNamespaces.labelSelector.matchLabels.

apiVersion: secrets.educates.dev/v1beta1
kind: SecretCopier
metadata:
  name: registry-credentials
spec:
  rules:
  - sourceSecret:
      name: registry-credentials
      namespace: registry
    targetNamespaces:
      labelSelector:
        matchLabels:
          developer-namespace: ""

Or if more flexible matching of labels is required, you can supply a list of expressions by setting targetNamespaces.labelSelector.matchExpressions.

apiVersion: secrets.educates.dev/v1beta1
kind: SecretCopier
metadata:
  name: registry-credentials
spec:
  rules:
  - sourceSecret:
      name: registry-credentials
      namespace: registry
    targetNamespaces:
      labelSelector:
        matchExpressions:
        - key: developer-namespace
          operator: In
          values:
          - ""

For an expression the list of operators is In, NotIn, Exists and DoesNotExist. For In and NotIn a non empty list of values must be supplied. The Exists and DoesNotExist only pertain to the existance of the label key.

When targeting a specific namespace and the namespace is deleted, then recreated, the rule will still match the new instance of the namespace if selectors match the new instance of the namespace, even though it may notionally be used for a different purpose and the secret shouldn’t be copied there.

If you need to ensure that a secret is only copied to that specific instance of the namespace, and not a future version of the namespace created subsequent to its deletion, you can require the namespace have a specific uid by setting targetNamespaces.uidSelector.

apiVersion: secrets.educates.dev/v1beta1
kind: SecretCopier
metadata:
  name: registry-credentials
spec:
  rules:
  - sourceSecret:
      name: registry-credentials
      namespace: registry
    targetNamespaces:
      uidSelector:
        matchUIDs:
        - 9b030c48-b773-4ffb-9d8f-7eacf7dbcfbe

The uid should be that set by Kubernetes in the metadata section of the namespace resource.

Alternatively, you can restrict whether the secret should be copied to a specific instance of a namespace based on whether that resource is owned by another. This can be done by providing the details of the owner by setting targetNamespaces.ownerSelector.

apiVersion: secrets.educates.dev/v1beta1
kind: SecretCopier
metadata:
  name: registry-credentials
spec:
  rules:
  - sourceSecret:
      name: registry-credentials
      namespace: registry
    targetNamespaces:
      ownerSelector:
        matchOwners:
        - apiVersion: training.educates.dev/v1beta1
          kind: WorkshopSession
          name: educates-cli-w01-s001
          uid: dcc4cf0f-6774-4eb4-bbf7-77a4ecaefa42

Kubernetes reserved namespaces

Although it was originally said that where no selectors are specified which target specific namespaces, that all namespaces are targeted, this isn’t actually the case. Instead if no nameSelector is specified, it defaults to the equivalent of:

apiVersion: secrets.educates.dev/v1beta1
kind: SecretCopier
metadata:
  name: registry-credentials
spec:
  rules:
  - sourceSecret:
      name: registry-credentials
      namespace: registry
    targetNamespaces:
      nameSelector:
        matchNames:
        - !kube-*

That is, neither kube-public, kube-system or other namespaces starting with kube- will be targeted, they being generally reserved by Kubernetes itself. The ! before the names indicates the namespace will be excluded.

If you need to target these namespaces, you will need to list them explicitly using matchNames, which will negate the default. When using labelSelector and you need to exclude certain namespaces even though it may have matching labels, you can list those namespaces in matchNames with the leading ! to have them excluded. When overriding matchNames to list excluded namespaces, you will need to manually include the exclusion for kube-public and kube-system if necessary.

Authorizing copying of a secret

For extra control over whether a secret should be copied to a namespace, even when selectors match, a shared secret can be defined.

apiVersion: secrets.educates.dev/v1beta1
kind: SecretCopier
metadata:
  name: registry-credentials
spec:
  rules:
  - sourceSecret:
      name: registry-credentials
      namespace: registry
    copyAuthorization:
      sharedSecret: my-shared-secret

In this case the target namespace must contain an instance of the namespaced custom resource SecretImporter, with name the same as that which would be used for the secret were the secret copied to that namespace. The shared secret must match that set by the rule in the SecretCopier instance.

apiVersion: secrets.educates.dev/v1beta1
kind: SecretImporter
metadata:
  name: registry-credentials
  namespace: target-namespace
spec:
  copyAuthorization:
    sharedSecret: my-shared-secret

Multiple target namespace selectors

More than one type of selector can be listed under targetNamespaces, in which case all selectors must match else the secret will not be copied to the namespace.

Multiple rules for copying secrets

The rules property is a list, so rules related to more than one secret or set of target namespaces can be specified in the one custom resource.

Overlapping rules for target secret

If multiple rules within different SecretCopier custom resource instances target the same secret, the first SecretCopier to create the target secret will win. Only the same SecretCopier as created a target secret will be able to update the secret if the source secret changes.

Deletion of the secret copier

The operator does not by default delete secrets previously copied to a namespace if the original secret is deleted or the rules change such that it wouldn’t have been copied in the first place.

It is possible however to have any secrets which were copied into a namespace automatically deleted if the SecretCopier containing the rules which allowed it to be copied is deleted. This is enabled by setting a reclaimPolicy of Delete instead of the default which is Retain.

apiVersion: secrets.educates.dev/v1beta1
kind: SecretCopier
metadata:
  name: registry-credentials
spec:
  rules:
  - sourceSecret:
      name: registry-credentials
      namespace: registry
    reclaimPolicy: Delete

Only secrets copied after reclaimPolicy was set to Delete will be deleted. Updating the value from Retain to Delete will not retrospectively result in secrets previously copied pertaining to that rule being deleted when the SecretCopier is deleted, even if the source secret were subsequently updated.

Tracking the source of a secret

When secrets are copied, the copy of a secret will have the following annotation added so that it can be determined where the secret was copied from:

secrets.educates.dev/secret-name: namespace/name

The SecretCopier instance containing the rules which resulted in the secret being copied is recorded using the annotation:

secrets.educates.dev/copier-rule: secretcopier/name