Validated Patterns

Introducing "Bootstrap" Secrets

by Martin Jackson
May 12, 2026
patterns argocd gitops ansible secrets

Preamble

We have recognized secrets as one of the challenges of GitOps since the beginning of the Validated Patterns initiative. As a consequence, we have put thought and effort into making the secrets management process with Validated Patterns as easy as we can, while still modeling good security practices.

One challenge that we have not fully addressed, though, is the occasional need to inject secrets into a cluster in order to enable basic cluster functionality. One example of when this might happen is when a cluster is attached to an enterprise storage array which requires secrets to be present to access the storage. Another good example is when the pattern itself is in a credential-protected repository, such that ArgoCD, the OpenShift GitOps Operator, needs credentials to be able to render the pattern. Previously, users had to use their own mechanisms to inject secrets into the cluster, and the Validated Patterns framework did not help with this.

With this blog post, we are happy to announce that the Validated Patterns framework now has a mechanism to help with this problem, which requires minimal effort to use and offers some convenience mechanisms, including all of the convenience functions (like path and ini_file parsing) that the Patterns secret loading system already uses. Additionally, it works within the existing Validated Patterns workflow.

What are "bootstrap" secrets?

The "bootstrap" secrets process is somewhat exceptional. The vast majority of secrets in Validated Patterns usage are ordinary. But what if you need to do authentication to start installing your pattern? Specific examples are when you are using an authenticated private repository to hold the pattern itself, as well as any secrets you might need to create a default storageclass. In such a case, these secrets must be injected before the pattern object itself is created, and since it contains the list of namespaces to create, the early phase secrets have a "chicken-and-egg" problem. Previous instructions have provided for the manual creation of these objects, but this can be tedious and error prone.

Bootstrap secrets are injected immediately after the initial cluster validations in the ordinary installation flow. They are allowed to create their own namespaces, but to help with idempotence, they check to see if the namespace exists first, and only create if it is missing.

How to use bootstrap secrets

The Validated Patterns secrets v2.0 file format adds a new section, boostrap_secrets. These secrets, and these only, will be considered in the "early" secrets installation phase. The early secrets installation phase happens just prior to the Patterns Operator and CR creation. This is because the pattern CR needs to be able to clone the repository in order to work. A secret that is linked to the Pattern CR will automatically be copied to Patterns-managed Argo instances.

Bootstrap secrets have some special properties:

  1. They are always injected using the "none" injector.

  2. They are allowed to create targetNamespaces if they do not already exist. (They check before creating them, to avoid overwriting existing namespaces with annotations.)

  3. Bootstrap secrets loading is always attempted if the section is present, even if the global secrets loading feature is disabled via global.secretsLoader.disabled being set to true.

Additionally, bootstrap secrets are checked and loaded during a make load-secrets run.

Kubernetes-specific secret injector features

While it has long been possible, it has not been well documented (or advertized) that Validated Patterns can inject secrets directly into kubernetes. The currently available injectors that use kubernetes secret injection are kubernetes and none. (They were named based on the External Secrets backend they are intended to support.) Some additional conveniences are available in values-secret files for kubernetes secret injection, including:

  • targetNamespaces: The same secret can be injected into multiple namespaces. This is useful, for example, when you have to inject the same ArgoCD secret into two or more different namespaces. The loader treats each secret/namespace as a separate item for injection. Of course a list containing one item is always acceptable as well. The loader will attempt to create a minimal namespace if it does not already exist, but only during the early (boostrap) secrets loading phase.

  • Labels and annotations: You can add arbitrary labels and/or annotations to your secret objects.

  • type: Some secret types benefit from having type associated with them. The secret loader defaults to Opaque when no type is specified, but will honor a type that is specified.

Example Bootstrap Secret file

Given following file, ~/values-secret.yaml:

---
version: "2.0"

bootstrap_secrets:
  - name: test-secret
    annotations:
      validatedpatterns.io/example: foo
    labels:
      validatedpatterns.io/injected-secrets: "true"
    targetNamespaces:
      - default
      - vp-gitops
    type: kubernetes.io/basic
    fields:
      - name: username
        value: user
      - name: password
        value: password

secrets:
# ...
# As before

The loader would generate and inject the following two objects:

apiVersion: v1
data:
  password: cGFzc3dvcmQ=
  username: dXNlcg==
kind: Secret
metadata:
  annotations:
    validatedpatterns.io/example: foo
  creationTimestamp: "2026-05-12T16:47:15Z"
  labels:
    validatedpatterns.io/injected-secrets: "true"
  name: test-secret
  namespace: default
  resourceVersion: "158877"
  uid: be74939e-6acc-41e8-96f3-0439f9e2e0af
type: kubernetes.io/basic

and

apiVersion: v1
data:
  password: cGFzc3dvcmQ=
  username: dXNlcg==
kind: Secret
metadata:
  annotations:
    validatedpatterns.io/example: foo
  creationTimestamp: "2026-05-12T16:48:02Z"
  labels:
    validatedpatterns.io/injected-secrets: "true"
  name: test-secret
  namespace: vp-gitops
  resourceVersion: "158893"
  uid: 311ba451-da6d-427b-8e0f-1293ef7b18a4
type: kubernetes.io/basic

Private Git Repositories - a Practical Use of this feature

The primary use of this feature is to enable the use of private repositories to hold Validated Patterns. These repositories need authentication. The patterns framework has included support for private repositories for some time, but this feature introduces the ability to inject the secret necessary to run those patterns without extra manual steps.

Given the following repository definition in values-global.yaml:

---
global:
  pattern: private-pattern-test
  options:
    useCSV: false
    syncPolicy: Automatic
    installPlanApproval: Automatic
main:
  git:
    repoURL: git@github.com:mhjacks/private-pattern-test.git
    revision: main
  tokenSecret: private-repo
  tokenSecretNamespace: patterns-operator
  clusterGroupName: hub
  multiSourceConfig:
    enabled: true
    clusterGroupChartVersion: "0.9.*"

The following secret file configuration will inject the private-repo secret needed into the patterns-operator namespace (creating it if it does not already exist, which at the time it is normally installed, it will not):

---
version: "2.0"

bootstrap_secrets:
  - name: private-repo
    targetNamespaces:
      - patterns-operator
    labels:
      argocd.argoproj.io/secret-type: repository
    fields:
      - name: type
        value: git
      - name: sshPrivateKey
        path: ~/.ssh/id_ed25519
      - name: url
        value: git@github.com:mhjacks/private-pattern-test.git

Technical Details of the change

The "bootstrap" loading process ignores the values-global secrets backend setting during the bootstrap phase, forcing the use of the "none" provider instead, which will use the kubernetes secrets injection roles with special flags enabled. It searches the discovered secrets file for the bootstrap secrets section, loading them as described above. This step runs between cluster validation and pattern operator and object creation. The regular secrets injection happens after pattern creation, as before. It is also possible to force bootstrap secret loading via ./pattern.sh ansible-playbook rhvp.cluster_utils.load_bootstrap_secrets, but this is normally not necessary as it is integrated into the typical make install process, as well as the make load-secrets process.

The benefit to this approach is that all existing patterns can make use of this feature without changing their Makefiles. Patterns without the bootstrap secrets section will skip bootstrap secrets loading as they will have no bootstrap secrets to load.

For each kubernetes secret that is generated, the loader will check for the namespace, and create a minimal namespace if it does not already exist, and then create the secret.

Summary

This change improves the Validated Patterns experience with secrets, streamlining the process of installing private patterns. Additional cases, like injecting secrets for CSI drivers, is also possible using this new mechanism.