|
| 1 | +--- |
| 2 | + date: 2024-01-26 |
| 3 | + title: More Secrets Options Now Available with Validated Patterns |
| 4 | + summary: Validated Patterns now supports alternatives to both HashiCorp Vault and the External Secrets Operator |
| 5 | + author: Martin Jackson |
| 6 | + blog_tags: |
| 7 | + - patterns |
| 8 | + - secrets |
| 9 | +--- |
| 10 | + |
| 11 | +# More Secrets Options Now Available with Validated Patterns |
| 12 | + |
| 13 | +## Overview |
| 14 | + |
| 15 | +One of the things about the kubernetes application management experience that |
| 16 | +we wanted to explore and improve as part of the Validated Patterns initiative |
| 17 | +was the secrets handling in GitOps. So we worked out a scheme that stored |
| 18 | +secret material in a dedicated secrets store, using the External Secrets Operator |
| 19 | +to project that secret material into the applications using ESO's powerful and |
| 20 | +convenient abstraction and templating features. At that time, HahsiCorp Vault |
| 21 | +was well supported and popular in the community, as was using ESO to retrieve |
| 22 | +secrets from it. All of the major hyperscaler keystores are supported (AWS, |
| 23 | +Azure, GCP), but multi- or hybrid- cloud solutions that can be "self-contained" |
| 24 | +are either less well supported or the solutions themselves lean towards SaaS |
| 25 | +offerings. |
| 26 | + |
| 27 | +Almost two years later, the secrets landscape has shifted somewhat. As a Red |
| 28 | +Hat project, Validated Patterns initiative gravitates towards Red Hat-supported |
| 29 | +solutions, and neither HashiCorp Vault nor ESO are currently Red Hat supported. |
| 30 | +Meanwhile, the only backend we provided a code path to drive with ESO was Vault. |
| 31 | + |
| 32 | +This nullifies one of the major reasons we wanted to use ESO in the first place - |
| 33 | +namely, the ability to easily swap out secrets backends in case Vault was not |
| 34 | +usable for some reason. Earlier in 2023, one of our engineers did a proof of |
| 35 | +concept of ESO support using the AWS Secrets Manager backend - demonstrating that |
| 36 | +ESO delivered on its promise of multiple secret store support. Adapting ESO was |
| 37 | +the easy part - the hard part is building more abstraction into the VP secrets |
| 38 | +handling code that runs on pattern install. |
| 39 | + |
| 40 | +To this end, we decided we would expand secrets support by introducing at least |
| 41 | +one new backend - and we chose the kubernetes backend, because it is self-contained |
| 42 | +(that is, it can be run on-prem and requires no new products or projects to be |
| 43 | +installed), and is a useful vehicle for introducing an abstraction layer for |
| 44 | +validated patterns secret parsing. In addition, dealing with kubernetes secrets |
| 45 | +objects directly has the side effect of enabling us to provide a mechanism for |
| 46 | +users to inject their secrets directly into their patterns, bypassing the need |
| 47 | +to use any secrets manager or ESO at all. This also provides benefits to installations |
| 48 | +where only commercially supported solutions can be installed, since ESO is |
| 49 | +currently not commercially supported by any entity. |
| 50 | + |
| 51 | +In a nutshell, the new features depend on an abstraction of secret file parsing, so |
| 52 | +that the secrets are held in memory in a datastructure that is then processed and |
| 53 | +loaded by the appropriate backend code. |
| 54 | + |
| 55 | +Users of the pattern framework will be able to change secrets backends as straightforwardly |
| 56 | +as we can make possible. The only other change the user will need to make (to use another |
| 57 | +ESO backend) is to use the backend's mechanism to refer to keys. (For example: in Vault, |
| 58 | +keys have have names like `secret/data/global/config-demo`; in the Kubernetes backend |
| 59 | +it would just be the secret object name that's being used to store the secret material, |
| 60 | +such as `config-demo`). |
| 61 | + |
| 62 | +## Chart changes |
| 63 | + |
| 64 | +The clusterSecretStore related chart elements have moved to |
| 65 | +values-global.yaml, specifically `global.secretStore.backend`. For example, from |
| 66 | +multicloud-gitops: |
| 67 | + |
| 68 | +```yaml |
| 69 | +global: |
| 70 | + pattern: multicloud-gitops |
| 71 | + options: |
| 72 | + useCSV: false |
| 73 | + syncPolicy: Automatic |
| 74 | + installPlanApproval: Automatic |
| 75 | + secretStore: |
| 76 | + backend: kubernetes |
| 77 | +``` |
| 78 | +
|
| 79 | +Previously, the `secretStore` setting was done per-chart; but ordinarily this |
| 80 | +setting will hold for the entire cluster, which may include many charts. Because |
| 81 | +of this, we will move these settings in our charts. (Older configs will not |
| 82 | +break if they still use vault; and it is still an option to override each |
| 83 | +chart if you want to do that.) |
| 84 | + |
| 85 | +Individual secret keys used for ESO lookups will need to be overridden or changed |
| 86 | +to use the kubernetes backend. |
| 87 | + |
| 88 | +This values-global setting is also used by the Ansible workflow to decide which |
| 89 | +backend to inject secrets into. |
| 90 | + |
| 91 | +## The "vault" Backend - Unchanged Interface, New plumbing |
| 92 | + |
| 93 | +The vault support now has a new code path to follow, but supports exactly the same |
| 94 | +features in the same ways it always has. Vault is still the default backend for |
| 95 | +patterns, and all existing patterns should be able to adopt the new code path without |
| 96 | +making any changes. Any other experience is a bug we will fix. |
| 97 | + |
| 98 | +All of the defaults for the new code path are designed to work with existing |
| 99 | +vault-based configurations; the new features are entirely optional and we do not |
| 100 | +expect breakage or regressions to existing vault-based configurations. |
| 101 | + |
| 102 | +## The "kubernetes" Backend *new* |
| 103 | + |
| 104 | +New features have been introduced to the secrets structure to support the use of the |
| 105 | +Kubernetes ESO backend. See the below details for the new options and how they are processed |
| 106 | +by the `kubernetes` parsing strategy. |
| 107 | + |
| 108 | +## The "none" Backend *new* |
| 109 | + |
| 110 | +New features have been introduced to the secrets structure to support not using an ESO |
| 111 | +ESO backend, but rather injecting secrets objects directly into the pattern. This violates |
| 112 | +the spirit of GitOps by not recording the details of the deployment in a versioned way. |
| 113 | +However users might want or need to make this tradeoff for different reasons. See the below |
| 114 | +details for the new options and how they are processed by the `none` parsing strategy. |
| 115 | + |
| 116 | +At present, any external secret objects will need to be deleted from the repository to use |
| 117 | +the `none` backend - since the ArgoCD application will not sync when a non-existing CRD is |
| 118 | +referenced. |
| 119 | + |
| 120 | +## How to Use a non-default Backend |
| 121 | + |
| 122 | +We have provided Makefile targets to switch between backends. These targets edit the pattern |
| 123 | +configuration files in-place; in keeping with the GitOps philosophy they change the files in |
| 124 | +git that control how the application is deployed. |
| 125 | + |
| 126 | +These Makefile targets are: |
| 127 | + |
| 128 | +```text |
| 129 | + secrets-backend-vault Edits values files to use default Vault+ESO secrets config |
| 130 | + secrets-backend-kubernetes Edits values file to use Kubernetes+ESO secrets config |
| 131 | + secrets-backend-none Edits values files to remove secrets manager + ESO |
| 132 | +``` |
| 133 | + |
| 134 | +Run the makefile target in your repo to effect the necessary changes. If the command makes changes, |
| 135 | +it will display them in `git diff` format, and it will be up to you to commit and push the result |
| 136 | +to effect the change. Nothing will change on your cluster until you commit and push. |
| 137 | + |
| 138 | +## Using the old system - The `legacy-load-secrets` Makefile target |
| 139 | + |
| 140 | +The existing vault-utils codepath is available via the `legacy-load-secrets` |
| 141 | +Makefile target. If secrets loading fails, or you just want to use the other |
| 142 | +system, you can run `make legacy-load-secrets` after `make install` and it will |
| 143 | +run those scripts and the Ansible playbooks and roles associated with them. |
| 144 | + |
| 145 | +## Deprecation of v1.0 Secrets |
| 146 | + |
| 147 | +The v1.0 secrets format has not been used in the Validated Patterns framework |
| 148 | +for over a year now. The v2.0 framework is a strict superset of the v1.0 |
| 149 | +framework. Support for the v1.0 framework is still available via the |
| 150 | +`legacy-load-secrets` code path, but this may be removed in the future. |
| 151 | + |
| 152 | +## Updates to the Secrets v2.0 Schema |
| 153 | + |
| 154 | +New features have been added at both the file level and the per-secret level |
| 155 | +to support the new backends: |
| 156 | + |
| 157 | +### Top-level Additions |
| 158 | + |
| 159 | +#### `secretStoreNamespace` |
| 160 | + |
| 161 | +example: |
| 162 | +```yaml |
| 163 | +--- |
| 164 | +version: "2.0" |
| 165 | +secretStoreNamespace: 'validated-patterns-secrets' |
| 166 | +
|
| 167 | +secrets: |
| 168 | +``` |
| 169 | + |
| 170 | +A new top-level key has been introduced in the secrets file: |
| 171 | +`secretStoreNamespace`. This defaults to `validated-patterns-secrets`. This is |
| 172 | +the namespace that ESO uses as its special secret store, which serves the same |
| 173 | +architectural purpose as vault does in the default installation. (Secrets are |
| 174 | +installed into this namespace as Kubernetes secrets objects, and ESO allows for |
| 175 | +them to be copied or templated out using ESO mechanisms). |
| 176 | + |
| 177 | +#### `defaultAnnotations` |
| 178 | + |
| 179 | +example: |
| 180 | + |
| 181 | +```yaml |
| 182 | +defaultAnnotations: |
| 183 | + validatedpatterns.io/pattern: 'myPattern' |
| 184 | +``` |
| 185 | + |
| 186 | +This data structure is a hash, or dictionary. These labels will be applied to all |
| 187 | +secrets objects unless they have per-secret annotations set. Labels are only added to |
| 188 | +kubernetes based secrets objects (using the `kubernetes` or `none`) backends. The |
| 189 | +vault loader ignores these settings. |
| 190 | + |
| 191 | +#### `defaultLabels` |
| 192 | + |
| 193 | +example: |
| 194 | + |
| 195 | +```yaml |
| 196 | +defaultLabels: |
| 197 | + patternType: 'edge' |
| 198 | + patternEnvironment: 'production' |
| 199 | +``` |
| 200 | + |
| 201 | +This data structure is a hash, or dictionary. These labels will be applied to all |
| 202 | +secrets objects unless they have per-secret labels set. Labels are only added to |
| 203 | +kubernetes based secrets objects (using the `kubernetes` or `none`) backends. The |
| 204 | +vault loader ignores these settings. |
| 205 | + |
| 206 | +### Per-secret Additions |
| 207 | + |
| 208 | +#### `targetNamespaces` |
| 209 | + |
| 210 | +example: |
| 211 | + |
| 212 | +```yaml |
| 213 | +secrets: |
| 214 | + - name: config-demo |
| 215 | + targetNamespaces: |
| 216 | + - config-demo |
| 217 | + fields: |
| 218 | +``` |
| 219 | + |
| 220 | +This option is ignored by `vault` and `kubernetes` backends, and only used by the `none` backend. |
| 221 | +Normally, you will only need to add your secret to one namespace at a time. However, if you do need |
| 222 | +to copy a secret that is identical except for the namespace it goes into, you can add multiple |
| 223 | +`targetNamespaces` each namespace specified will get a copy of the secret. |
| 224 | + |
| 225 | +There is not a default target namespace for the none backend, so omitting this field from a config |
| 226 | +parsed for the `none` backend is an error. |
| 227 | + |
| 228 | +#### `labels` |
| 229 | + |
| 230 | +example: |
| 231 | +```yaml |
| 232 | +secrets: |
| 233 | + - name: config-demo |
| 234 | + labels: |
| 235 | + patternType: 'edge' |
| 236 | + patternEnvironment: 'production' |
| 237 | + fields: |
| 238 | +``` |
| 239 | + |
| 240 | +In this case, these labels will only be applied to any `config-demo` secret objects created |
| 241 | +by the framework. This option is only used by the `none` and `kubernetes` backends and |
| 242 | +ignored by the `vault` backend. If `defaultLabels` are specified at the top level of the |
| 243 | +file, per-secrets labels will override them. |
| 244 | + |
| 245 | +#### `annotations` |
| 246 | + |
| 247 | +example: |
| 248 | + |
| 249 | +```yaml |
| 250 | +secrets: |
| 251 | + - name: config-demo |
| 252 | + annotations: |
| 253 | + validatedpatterns.io/pattern: 'myPattern' |
| 254 | + fields: |
| 255 | +``` |
| 256 | + |
| 257 | +In this case, this annotation will only be applied to any `config-demo` secret objects created |
| 258 | +by the framework. This option is only used by the `none` and `kubernetes` backends and |
| 259 | +ignored by the `vault` backend. If `defaultAnnotations` are specified at the top level of the |
| 260 | +file, per-secrets annotations will override them. |
| 261 | + |
| 262 | +## Under the Hood - Python and Ansible Code |
| 263 | + |
| 264 | +The main changes here were to factor out the code that did the file parsing and actual secret |
| 265 | +loading into different modules. The `parse_secrets_info` module now reads all of the file contents |
| 266 | +and renders all of the secrets it can before turning the process over to an appropriate secrets |
| 267 | +loader. |
| 268 | + |
| 269 | +### The process_secrets playbook |
| 270 | + |
| 271 | +The `process_secrets` understands the backend configured for the different backends from values-global, |
| 272 | +and follows the appropriate strategy. |
| 273 | + |
| 274 | +### parse_secrets_info Ansible Module |
| 275 | + |
| 276 | +`parse_secrets_info` understands the different backends, and parses the secrets file into an in-memory |
| 277 | +structure that can then be handed over to a loader specific to the backend. There is an additional |
| 278 | +script, `common/scripts/process_secrets/display-secrets-info.sh <secrets_file> <backend_type>` that |
| 279 | +can be used to view how the secrets are parsed. This will display secrets on the terminal, so use |
| 280 | +with caution. It creates a `parsed_secrets` structure that should be generally useful, as well as |
| 281 | +`vault_policies` (Specifically for Vault support). Additionally, it creates a `kubernetes_secret_objects` |
| 282 | +structure suitable to hand over to the Ansible k8s.core collection directly. |
| 283 | + |
| 284 | +### vault_load_parsed_secrets Ansible Module |
| 285 | + |
| 286 | +`vault_load_parsed_secrets` is responsible for setting up the commands to load the secrets into vault, |
| 287 | +and running them. |
| 288 | + |
| 289 | +### The k8s_secret_utils Ansible Role |
| 290 | + |
| 291 | +`k8s_secret_utils` is used for loading both the `kubernetes` and `none` backends. It |
| 292 | + |
| 293 | +### Changes to to vault_utils Ansible Role |
| 294 | + |
| 295 | +Some code has been factored out of `vault_utils` and now lives in roles called `cluster_pre_check` and |
| 296 | +`find_vp_secrets` roles. A new task file has been added, `push_parsed_secrets.yaml` that knows how to use |
| 297 | +the `parsed_secrets` structure generated by `parse_secrets_info`. The existing code in the other task files |
| 298 | +remains. |
| 299 | + |
| 300 | +## Developing a new backend |
| 301 | + |
| 302 | +To provide support for an additional backend, the framework will need changes to: |
| 303 | + |
| 304 | +- The `golang-external-secrets` chart (to support the new provider) |
| 305 | +- The shell and ansible framework for loading (understanding the new backend name and |
| 306 | + developing behaviors for it). |
| 307 | + |
| 308 | +## Conclusion |
| 309 | + |
| 310 | +The Validated Patterns framework strives to offer solutions to real-world problems, and we hope you will find |
| 311 | +these new features useful. If you run into problems, we will do our best to |
| 312 | +[help](https://github.com/validatedpatterns/common/issues)! |
0 commit comments