Skip to content

Commit 7c04403

Browse files
authored
Merge pull request #682 from mhjacks/bootstrap_blog_post
Add bootstrap blog post
2 parents 5a0ead8 + 76992dc commit 7c04403

3 files changed

Lines changed: 292 additions & 2 deletions

File tree

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
---
2+
date: 2026-05-12
3+
title: Introducing "Bootstrap" Secrets
4+
summary: Validated Patterns now includes the idea of "bootstrap" secrets that will be injected directly into your cluster during the secrets loading process.
5+
author: Martin Jackson
6+
blog_tags:
7+
- patterns
8+
- argocd
9+
- gitops
10+
- ansible
11+
- secrets
12+
---
13+
:toc:
14+
:imagesdir: /images
15+
16+
== Preamble
17+
18+
We have recognized secrets as one of the challenges of GitOps since the beginning
19+
of the Validated Patterns initiative. As a consequence, we have put thought and
20+
effort into making the secrets management process with Validated Patterns as easy
21+
as we can, while still modeling good security practices.
22+
23+
One challenge that we have not fully addressed, though, is the occasional need to
24+
inject secrets into a cluster in order to enable basic cluster functionality. One
25+
example of when this might happen is when a cluster is attached to an enterprise
26+
storage array which requires secrets to be present to access the storage. Another
27+
good example is when the pattern itself is in a credential-protected repository,
28+
such that ArgoCD, the OpenShift GitOps Operator, needs credentials to be able to
29+
render the pattern. Previously, users had to use their own mechanisms to inject
30+
secrets into the cluster, and the Validated Patterns framework did not help with
31+
this.
32+
33+
With this blog post, we are happy to announce that the Validated Patterns framework
34+
now has a mechanism to help with this problem, which requires minimal effort to
35+
use and offers some convenience mechanisms, including all of the convenience
36+
functions (like path and ini_file parsing) that the Patterns secret loading system
37+
already uses. Additionally, it works within the existing Validated Patterns workflow.
38+
39+
== What are "bootstrap" secrets?
40+
41+
The "bootstrap" secrets process is somewhat exceptional. The vast majority of secrets in
42+
Validated Patterns usage are ordinary. But what if you need to do authentication to start
43+
installing your pattern? Specific examples are when you are using an authenticated private
44+
repository to hold the pattern itself, as well as any secrets you might need to create a
45+
default storageclass. In such a case, these secrets must be injected before the pattern
46+
object itself is created, and since it contains the list of namespaces to create, the early
47+
phase secrets have a "chicken-and-egg" problem. Previous instructions have provided for the
48+
manual creation of these objects, but this can be tedious and error prone.
49+
50+
Bootstrap secrets are injected immediately after the initial cluster validations in the
51+
ordinary installation flow. They are allowed to create their own namespaces, but to help with
52+
idempotence, they check to see if the namespace exists first, and only create if it is missing.
53+
54+
== How to use bootstrap secrets
55+
56+
The Validated Patterns secrets v2.0 file format adds a new section, `boostrap_secrets`. These
57+
secrets, and these only, will be considered in the "early" secrets installation phase. The
58+
early secrets installation phase happens just prior to the Patterns Operator and CR creation.
59+
This is because the pattern CR needs to be able to clone the repository in order to work. A
60+
secret that is linked to the Pattern CR will automatically be copied to Patterns-managed
61+
Argo instances.
62+
63+
Bootstrap secrets have some special properties:
64+
65+
1. They are always injected using the "none" injector.
66+
2. They are allowed to create `targetNamespaces` if they do not already exist. (They check
67+
before creating them, to avoid overwriting existing namespaces with annotations.)
68+
3. Bootstrap secrets loading is always attempted if the section is present, even if the global
69+
secrets loading feature is disabled via `global.secretsLoader.disabled` being set to `true`.
70+
71+
Additionally, bootstrap secrets are checked and loaded during a `make load-secrets` run.
72+
73+
== Kubernetes-specific secret injector features
74+
75+
While it has long been possible, it has not been well documented (or advertized) that
76+
Validated Patterns can inject secrets directly into kubernetes. The currently available injectors
77+
that use kubernetes secret injection are `kubernetes` and `none`. (They were named based on the
78+
External Secrets backend they are intended to support.) Some additional conveniences
79+
are available in values-secret files for kubernetes secret injection, including:
80+
81+
* `targetNamespaces`: The same secret can be injected into multiple namespaces. This is useful,
82+
for example, when you have to inject the same ArgoCD secret into two or more different namespaces. The
83+
loader treats each secret/namespace as a separate item for injection. Of course a list containing
84+
one item is always acceptable as well. The loader will attempt to create a minimal namespace if it does
85+
not already exist, but only during the early (boostrap) secrets loading phase.
86+
* `Labels and annotations`: You can add arbitrary labels and/or annotations to your secret objects.
87+
* `type`: Some secret types benefit from having type associated with them. The secret loader
88+
defaults to `Opaque` when no type is specified, but will honor a type that is specified.
89+
90+
== Example Bootstrap Secret file
91+
92+
Given following file, ~/values-secret.yaml:
93+
94+
[yaml,source]
95+
----
96+
---
97+
version: "2.0"
98+
99+
bootstrap_secrets:
100+
- name: test-secret
101+
annotations:
102+
validatedpatterns.io/example: foo
103+
labels:
104+
validatedpatterns.io/injected-secrets: "true"
105+
targetNamespaces:
106+
- default
107+
- vp-gitops
108+
type: kubernetes.io/basic
109+
fields:
110+
- name: username
111+
value: user
112+
- name: password
113+
value: password
114+
115+
secrets:
116+
# ...
117+
# As before
118+
----
119+
120+
The loader would generate and inject the following two objects:
121+
122+
[yaml,source]
123+
----
124+
apiVersion: v1
125+
data:
126+
password: cGFzc3dvcmQ=
127+
username: dXNlcg==
128+
kind: Secret
129+
metadata:
130+
annotations:
131+
validatedpatterns.io/example: foo
132+
creationTimestamp: "2026-05-12T16:47:15Z"
133+
labels:
134+
validatedpatterns.io/injected-secrets: "true"
135+
name: test-secret
136+
namespace: default
137+
resourceVersion: "158877"
138+
uid: be74939e-6acc-41e8-96f3-0439f9e2e0af
139+
type: kubernetes.io/basic
140+
----
141+
142+
and
143+
144+
[yaml,source]
145+
----
146+
apiVersion: v1
147+
data:
148+
password: cGFzc3dvcmQ=
149+
username: dXNlcg==
150+
kind: Secret
151+
metadata:
152+
annotations:
153+
validatedpatterns.io/example: foo
154+
creationTimestamp: "2026-05-12T16:48:02Z"
155+
labels:
156+
validatedpatterns.io/injected-secrets: "true"
157+
name: test-secret
158+
namespace: vp-gitops
159+
resourceVersion: "158893"
160+
uid: 311ba451-da6d-427b-8e0f-1293ef7b18a4
161+
type: kubernetes.io/basic
162+
----
163+
164+
== Private Git Repositories - a Practical Use of this feature
165+
166+
The primary use of this feature is to enable the use of private repositories to hold Validated Patterns.
167+
These repositories need authentication. The patterns framework has included support for private repositories
168+
for some time, but this feature introduces the ability to inject the secret necessary to run those patterns
169+
without extra manual steps.
170+
171+
Given the following repository definition in values-global.yaml:
172+
173+
[yaml,source]
174+
----
175+
---
176+
global:
177+
pattern: private-pattern-test
178+
options:
179+
useCSV: false
180+
syncPolicy: Automatic
181+
installPlanApproval: Automatic
182+
main:
183+
git:
184+
repoURL: git@github.com:mhjacks/private-pattern-test.git
185+
revision: main
186+
tokenSecret: private-repo
187+
tokenSecretNamespace: patterns-operator
188+
clusterGroupName: hub
189+
multiSourceConfig:
190+
enabled: true
191+
clusterGroupChartVersion: "0.9.*"
192+
----
193+
194+
The following secret file configuration will inject the private-repo secret needed into the
195+
patterns-operator namespace (creating it if it does not already exist, which at the time it
196+
is normally installed, it will not):
197+
198+
[yaml,source]
199+
----
200+
---
201+
version: "2.0"
202+
203+
bootstrap_secrets:
204+
- name: private-repo
205+
targetNamespaces:
206+
- patterns-operator
207+
labels:
208+
argocd.argoproj.io/secret-type: repository
209+
fields:
210+
- name: type
211+
value: git
212+
- name: sshPrivateKey
213+
path: ~/.ssh/id_ed25519
214+
- name: url
215+
value: git@github.com:mhjacks/private-pattern-test.git
216+
----
217+
218+
== Technical Details of the change
219+
220+
The "bootstrap" loading process ignores the values-global secrets backend setting during the bootstrap
221+
phase, forcing the use of the "none" provider instead, which will use the kubernetes secrets injection
222+
roles with special flags enabled. It searches the discovered secrets file for the bootstrap secrets
223+
section, loading them as described above. This step runs between cluster validation and pattern operator and object
224+
creation. The regular secrets injection happens after pattern creation, as before. It is also possible
225+
to force bootstrap secret loading via `./pattern.sh ansible-playbook rhvp.cluster_utils.load_bootstrap_secrets`,
226+
but this is normally not necessary as it is integrated into the typical `make install` process, as
227+
well as the `make load-secrets` process.
228+
229+
The benefit to this approach is that all existing patterns can make use of this feature without
230+
changing their Makefiles. Patterns without the bootstrap secrets section will skip bootstrap secrets
231+
loading as they will have no bootstrap secrets to load.
232+
233+
For each kubernetes secret that is generated, the loader will check for the namespace, and create a
234+
minimal namespace if it does not already exist, and then create the secret.
235+
236+
== Summary
237+
238+
This change improves the Validated Patterns experience with secrets, streamlining the process of
239+
installing private patterns. Additional cases, like injecting secrets for CSI drivers, is also
240+
possible using this new mechanism.

content/learn/getting-started-secret-management.adoc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ ESO
4040

4141
[NOTE]
4242
====
43-
As of November 15, 2024, ESO is not officially supported by Red Hat as a product.
43+
As of November 11, 2025, ESO is officially supported by Red Hat as the External Secrets Operator for OpenShift.
44+
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.
4445
====
4546

4647
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.
@@ -162,7 +163,7 @@ $ vi mysecret-external-secret.yaml
162163
[source,yaml]
163164
----
164165
---
165-
apiVersion: "external-secrets.io/v1beta1"
166+
apiVersion: "external-secrets.io/v1"
166167
kind: ExternalSecret
167168
metadata:
168169
name: config-demo-mysecret <1>

content/learn/private-repos.adoc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,32 @@ stringData:
4444
-----END OPENSSH PRIVATE KEY-----
4545
----
4646

47+
This secret can now be created with the `bootstrap_secrets` feature like so:
48+
49+
[source,yaml]
50+
----
51+
version: "2.0"
52+
53+
bootstrap_secrets:
54+
- name: private-repo
55+
targetNamespaces:
56+
- openshift-operators
57+
labels:
58+
argocd.argoproj.io/secret-type: repository
59+
fields:
60+
- name: type
61+
value: git
62+
- name: sshPrivateKey
63+
path: |
64+
-----BEGIN OPENSSH PRIVATE KEY-----
65+
a3...
66+
...
67+
...
68+
-----END OPENSSH PRIVATE KEY-----
69+
- name: url
70+
value: git@github.com:mbaldessari/mcg-private.git
71+
----
72+
4773
=== Deploy the pattern with the secret
4874

4975
Reference the secret you created by passing `TOKEN_SECRET` and `TOKEN_NAMESPACE` to the install command:
@@ -95,6 +121,29 @@ stringData:
95121
password: glpat-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
96122
----
97123

124+
Using the `bootstrap_secrets` feature, this can be created as follows:
125+
126+
[source,yaml]
127+
----
128+
version: "2.0"
129+
130+
bootstrap_secrets:
131+
- name: private-repo
132+
targetNamespaces:
133+
- openshift-operators
134+
labels:
135+
argocd.argoproj.io/secret-type: repository
136+
fields:
137+
- name: type
138+
value: git
139+
- name: url
140+
value: https://gitlab.com/dminnear-rh/mcg-private.git
141+
- name: username
142+
value: oauth2
143+
- name: password
144+
value: glpat-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
145+
----
146+
98147
NOTE: The username must be `oauth2`, not your GitLab handle.
99148

100149
Then reference the secret in the install:

0 commit comments

Comments
 (0)