|
| 1 | +# External Secrets |
| 2 | + |
| 3 | +[External Secrets](https://external-secrets.io/latest/) is a CNCF Sandbox |
| 4 | +project, accepted in 2022 under the sponsorship of TAG Security. |
| 5 | + |
| 6 | +## About |
| 7 | + |
| 8 | +The **External Secrets Operator (ESO)** is a Kubernetes operator that enhances |
| 9 | +secret management by decoupling the storage of secrets from Kubernetes itself. |
| 10 | +It enables seamless synchronization between external secret management systems |
| 11 | +and native Kubernetes `Secret` resources. |
| 12 | + |
| 13 | +ESO supports a wide range of backends, including: |
| 14 | + |
| 15 | +- [HashiCorp Vault](https://www.vaultproject.io/) |
| 16 | +- [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) |
| 17 | +- [Google Secret Manager](https://cloud.google.com/secret-manager) |
| 18 | +- [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/) |
| 19 | +- [IBM Cloud Secrets Manager](https://www.ibm.com/cloud/secrets-manager) |
| 20 | + |
| 21 | +…and many more. For a full and up-to-date list of supported providers, refer to |
| 22 | +the [official External Secrets documentation](https://external-secrets.io/latest/). |
| 23 | + |
| 24 | +## Integration with PostgreSQL and CloudNativePG |
| 25 | + |
| 26 | +When it comes to PostgreSQL databases, External Secrets integrates seamlessly |
| 27 | +with [CloudNativePG](https://cloudnative-pg.io/) in two major use cases: |
| 28 | + |
| 29 | +- **Automated password management:** ESO can handle the automatic generation |
| 30 | + and rotation of database user passwords stored in Kubernetes `Secret` |
| 31 | + resources, ensuring that applications running inside the cluster always have |
| 32 | + access to up-to-date credentials. |
| 33 | + |
| 34 | +- **Cross-platform secret access:** It enables transparent synchronization of |
| 35 | + those passwords with an external Key Management Service (KMS) via a |
| 36 | + `SecretStore` resources. This allows applications and developers outside the |
| 37 | + Kubernetes cluster—who may not have access to Kubernetes secrets—to retrieve |
| 38 | + the database credentials directly from the external KMS. |
| 39 | + |
| 40 | +## Example: Automated Password Management with External Secrets |
| 41 | + |
| 42 | +Let’s walk through how to automatically rotate the password of the `app` user |
| 43 | +every 24 hours in the `cluster-example` Postgres cluster from the |
| 44 | +[quickstart guide](../quickstart.md#part-3-deploy-a-postgresql-cluster). |
| 45 | + |
| 46 | +!!! Important |
| 47 | + Before proceeding, ensure that the `cluster-example` Postgres cluster is up |
| 48 | + and running in your environment. |
| 49 | + |
| 50 | +By default, CloudNativePG generates and manages a Kubernetes `Secret` named |
| 51 | +`cluster-example-app`, which contains the credentials for the `app` user in the |
| 52 | +`cluster-example` cluster. You can read more about this in the |
| 53 | +[“Connecting from an application” section](../applications.md#secrets). |
| 54 | + |
| 55 | +With External Secrets, the goal is to: |
| 56 | + |
| 57 | +1. Define a `Password` generator that specifies how to generate the password. |
| 58 | +2. Create an `ExternalSecret` resource that keeps the `cluster-example-app` |
| 59 | + secret in sync by updating only the `password` and `pgpass` fields. |
| 60 | + |
| 61 | +### Creating the Password Generator |
| 62 | + |
| 63 | +The following example creates a |
| 64 | +[`Password` generator](https://external-secrets.io/main/api/generator/password/) |
| 65 | +resource named `pg-password-generator` in the `default` namespace. You can |
| 66 | +customize the name and properties to suit your needs: |
| 67 | + |
| 68 | +```yaml |
| 69 | +apiVersion: generators.external-secrets.io/v1alpha1 |
| 70 | +kind: Password |
| 71 | +metadata: |
| 72 | + name: pg-password-generator |
| 73 | +spec: |
| 74 | + length: 42 |
| 75 | + digits: 5 |
| 76 | + symbols: 5 |
| 77 | + symbolCharacters: "-_$@" |
| 78 | + noUpper: false |
| 79 | + allowRepeat: true |
| 80 | +``` |
| 81 | +
|
| 82 | +This specification defines the characteristics of the generated password, |
| 83 | +including its length and the inclusion of digits, symbols, and uppercase |
| 84 | +letters. |
| 85 | +
|
| 86 | +### Creating the External Secret |
| 87 | +
|
| 88 | +The example below creates an `ExternalSecret` resource named |
| 89 | +`cluster-example-app-secret`, which refreshes the password every 24 hours. It |
| 90 | +uses a `Merge` policy to update only the specified fields (`password`, `pgpass`, |
| 91 | +`jdbc-uri` and `uri`) in the `cluster-example-app` secret. |
| 92 | + |
| 93 | +```yaml |
| 94 | +apiVersion: external-secrets.io/v1beta1 |
| 95 | +kind: ExternalSecret |
| 96 | +metadata: |
| 97 | + name: cluster-example-app-secret |
| 98 | +spec: |
| 99 | + refreshInterval: "24h" |
| 100 | + target: |
| 101 | + name: cluster-example-app |
| 102 | + creationPolicy: Merge |
| 103 | + template: |
| 104 | + metadata: |
| 105 | + labels: |
| 106 | + cnpg.io/reload: "true" |
| 107 | + data: |
| 108 | + password: "{{ .password }}" |
| 109 | + pgpass: "cluster-example-rw:5432:app:app:{{ .password }}" |
| 110 | + jdbc-uri: "jdbc:postgresql://cluster-example-rw.default:5432/app?password={{ .password }}&user=app" |
| 111 | + uri: "postgresql://app:{{ .password }}@cluster-example-rw.default:5432/app" |
| 112 | + dataFrom: |
| 113 | + - sourceRef: |
| 114 | + generatorRef: |
| 115 | + apiVersion: generators.external-secrets.io/v1alpha1 |
| 116 | + kind: Password |
| 117 | + name: pg-password-generator |
| 118 | +``` |
| 119 | + |
| 120 | +The label `cnpg.io/reload: "true"` ensures that CloudNativePG triggers a reload |
| 121 | +of the user password in the database when the secret changes. |
| 122 | +
|
| 123 | +### Verifying the Configuration |
| 124 | +
|
| 125 | +To check that the `ExternalSecret` is correctly synchronizing: |
| 126 | + |
| 127 | +```sh |
| 128 | +kubectl get es cluster-example-app-secret |
| 129 | +``` |
| 130 | + |
| 131 | +To observe the password being refreshed in real time, temporarily reduce the |
| 132 | +`refreshInterval` to `30s` and run the following command repeatedly: |
| 133 | + |
| 134 | +```sh |
| 135 | +kubectl get secret cluster-example-app \ |
| 136 | + -o jsonpath="{.data.password}" | base64 -d |
| 137 | +``` |
| 138 | + |
| 139 | +You should see the password change every 30 seconds, confirming that the |
| 140 | +rotation is working correctly. |
| 141 | + |
| 142 | +### There's More |
| 143 | + |
| 144 | +While the example above focuses on the default `cluster-example-app` secret |
| 145 | +created by CloudNativePG, the same approach can be extended to manage any |
| 146 | +custom secrets or PostgreSQL users you create to regularly rotate their |
| 147 | +password. |
| 148 | + |
| 149 | + |
| 150 | +## Example: Integration with an External KMS |
| 151 | + |
| 152 | +A widely used Key Management Service (KMS) provider in the CNCF ecosystem is |
| 153 | +[HashiCorp Vault](https://www.vaultproject.io/). |
| 154 | + |
| 155 | +In this example, we'll demonstrate how to integrate CloudNativePG, |
| 156 | +External Secrets Operator, and HashiCorp Vault to automatically rotate |
| 157 | +a PostgreSQL password and securely store it in Vault. |
| 158 | + |
| 159 | +!!! Important |
| 160 | + This example assumes that HashiCorp Vault is already installed and properly |
| 161 | + configured in your environment, and that your team has the necessary expertise |
| 162 | + to operate it. There are various ways to deploy Vault, and detailing them is |
| 163 | + outside the scope of CloudNativePG. While it's possible to run Vault inside |
| 164 | + Kubernetes, it is more commonly deployed externally. For detailed instructions, |
| 165 | + consult the [HashiCorp Vault documentation](https://www.vaultproject.io/docs). |
| 166 | + |
| 167 | +Continuing from the previous example, we will now create the necessary |
| 168 | +`SecretStore` and `PushSecret` resources to complete the integration with |
| 169 | +Vault. |
| 170 | + |
| 171 | +### Creating the `SecretStore` |
| 172 | + |
| 173 | +In this example, we assume that HashiCorp Vault is accessible from within the |
| 174 | +namespace at `http://vault.vault.svc:8200`, and that a Kubernetes `Secret` |
| 175 | +named `vault-token` exists in the same namespace, containing the token used to |
| 176 | +authenticate with Vault. |
| 177 | + |
| 178 | +```yaml |
| 179 | +apiVersion: external-secrets.io/v1beta1 |
| 180 | +kind: SecretStore |
| 181 | +metadata: |
| 182 | + name: vault-backend |
| 183 | +spec: |
| 184 | + provider: |
| 185 | + vault: |
| 186 | + server: "http://vault.vault.svc:8200" |
| 187 | + path: "secrets" |
| 188 | + # Specifies the Vault KV secret engine version ("v1" or "v2"). |
| 189 | + # Defaults to "v2" if not set. |
| 190 | + version: "v2" |
| 191 | + auth: |
| 192 | + # References a Kubernetes Secret that contains the Vault token. |
| 193 | + # See: https://www.vaultproject.io/docs/auth/token |
| 194 | + tokenSecretRef: |
| 195 | + name: "vault-token" |
| 196 | + key: "token" |
| 197 | +--- |
| 198 | +apiVersion: v1 |
| 199 | +kind: Secret |
| 200 | +metadata: |
| 201 | + name: vault-token |
| 202 | +data: |
| 203 | + token: aHZzLioqKioqKio= # hvs.******* |
| 204 | +``` |
| 205 | + |
| 206 | +This configuration creates a `SecretStore` resource named `vault-backend`. |
| 207 | + |
| 208 | +!!! Important |
| 209 | + This example uses basic token-based authentication, which is suitable for |
| 210 | + testing API, and CLI use cases. While it is the default method enabled in |
| 211 | + Vault, it is not recommended for production environments. For production, |
| 212 | + consider using more secure authentication methods. |
| 213 | + Refer to the [External Secrets Operator documentation](https://external-secrets.io/latest/provider/hashicorp-vault/) |
| 214 | + for a full list of supported authentication mechanisms. |
| 215 | + |
| 216 | +!!! Info |
| 217 | + HashiCorp Vault must have a KV secrets engine enabled at the `secrets` path |
| 218 | + with version `v2`. If your Vault instance uses a different path or |
| 219 | + version, be sure to update the `path` and `version` fields accordingly. |
| 220 | + |
| 221 | +### Creating the `PushSecret` |
| 222 | + |
| 223 | +The `PushSecret` resource is used to push a Kubernetes `Secret` to HashiCorp |
| 224 | +Vault. In this simplified example, we'll push the credentials for the `app` |
| 225 | +user of the sample cluster `cluster-example`. |
| 226 | + |
| 227 | +For more details on configuring `PushSecret`, refer to the |
| 228 | +[External Secrets Operator documentation](https://external-secrets.io/latest/api/pushsecret/). |
| 229 | + |
| 230 | +```yaml |
| 231 | +apiVersion: external-secrets.io/v1alpha1 |
| 232 | +kind: PushSecret |
| 233 | +metadata: |
| 234 | + name: pushsecret-example |
| 235 | +spec: |
| 236 | + deletionPolicy: Delete |
| 237 | + refreshInterval: 24h |
| 238 | + secretStoreRefs: |
| 239 | + - name: vault-backend |
| 240 | + kind: SecretStore |
| 241 | + selector: |
| 242 | + secret: |
| 243 | + name: cluster-example-app |
| 244 | + data: |
| 245 | + - match: |
| 246 | + remoteRef: |
| 247 | + remoteKey: cluster-example-app |
| 248 | +``` |
| 249 | + |
| 250 | +In this example, the `PushSecret` resource instructs the External Secrets |
| 251 | +Operator to push the Kubernetes `Secret` named `cluster-example-app` to |
| 252 | +HashiCorp Vault (from the previous example). The `remoteKey` defines the name |
| 253 | +under which the secret will be stored in Vault, using the `SecretStore` named |
| 254 | +`vault-backend`. |
| 255 | + |
| 256 | +### Verifying the Configuration |
| 257 | + |
| 258 | +To verify that the `PushSecret` is functioning correctly, navigate to the |
| 259 | +HashiCorp Vault UI. In the `kv` secrets engine at the path `secrets`, you |
| 260 | +should find a secret named `cluster-example-app`, corresponding to the |
| 261 | +`remoteKey` defined above. |
0 commit comments