Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 238 additions & 0 deletions content/blog/2026-05-12-introducing-bootstrap-secrets.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
---
date: 2026-05-12
title: Introducing "Bootstrap" Secrets
summary: Validated Patterns now includes the idea of "bootstrap" secrets that will be injected directly into your cluster during the secrets loading process.
author: Martin Jackson
blog_tags:
- patterns
- argocd
- gitops
- ansible
- secrets
---
:toc:
:imagesdir: /images

== 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:

[yaml,source]
----
---
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:

[yaml,source]
----
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

[yaml,source]
----
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:

[yaml,source]
----
---
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):

[yaml,source]
----
---
version: "2.0"

bootstrap_secrets:
- name: private-repo
targetNamespaces:
- patterns-operator
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.
5 changes: 3 additions & 2 deletions content/learn/getting-started-secret-management.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ ESO

[NOTE]
====
As of November 15, 2024, ESO is not officially supported by Red Hat as a product.
As of November 11, 2025, ESO is officially supported by Red Hat as the External Secrets Operator for OpenShift.
The Validated Patterns initiative provides a link:https://charts.validatedpatterns.io/charts/openshift-external-secrets[chart] to configure the supported version, which is available via the Red Hat Ecosystem Catalog.
====

ESO's custom file format and utilities streamlines secret management by allowing file references and supporting encrypted secret storage. The design prioritizes security through multi-layer encryption and simplifies key management. In particular the ini key type is especially helpful for handling AWS credentials, where mismanagement could lead to unauthorized use and potential financial or operational issues.
Expand Down Expand Up @@ -162,7 +163,7 @@ $ vi mysecret-external-secret.yaml
[source,yaml]
----
---
apiVersion: "external-secrets.io/v1beta1"
apiVersion: "external-secrets.io/v1"
kind: ExternalSecret
metadata:
name: config-demo-mysecret <1>
Expand Down
47 changes: 47 additions & 0 deletions content/learn/private-repos.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,30 @@ stringData:
-----END OPENSSH PRIVATE KEY-----
----

This secret can now be created with the `bootstrap_secrets` feature like so:

[source,yaml]
----
version: "2.0"

bootstrap_secrets:
- name: private-repo
targetNamespaces:
- openshift-operators
fields:
- name: type
value: git
- name: sshPrivateKey
path: |
-----BEGIN OPENSSH PRIVATE KEY-----
a3...
...
...
-----END OPENSSH PRIVATE KEY-----
- name: url
value: git@github.com:mbaldessari/mcg-private.git
----

=== Deploy the pattern with the secret

Reference the secret you created by passing `TOKEN_SECRET` and `TOKEN_NAMESPACE` to the install command:
Expand Down Expand Up @@ -95,6 +119,29 @@ stringData:
password: glpat-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
----

Using the `bootstrap_secrets` feature, this can be created as follows:

[source,yaml]
----
version: "2.0"

bootstrap_secrets:
- name: private-repo
targetNamespaces:
- openshift-operators
labels:
argocd.argoproj.io/secret-type: repository
fields:
- name: type
value: git
- name: url
value: https://gitlab.com/dminnear-rh/mcg-private.git
- name: username
value: oauth2
- name: password
value: glpat-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
----

NOTE: The username must be `oauth2`, not your GitLab handle.

Then reference the secret in the install:
Expand Down