|
| 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. |
0 commit comments