From 953d58dd680455632eed16f95b7231219574e086 Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Sat, 4 Jun 2022 21:54:32 +0200 Subject: [PATCH 1/9] feat: add vault example --- simple-with-vault-secrets/.kluctl.yml | 16 ++++++++ .../mongodb/simple/mongo-secrets.yml | 20 ++++++++++ simple-with-vault-secrets/README.md | 26 +++++++++++++ simple-with-vault-secrets/deployment.yml | 12 ++++++ .../deployment/deployment.yml | 4 ++ .../deployment/mongodb/deploy.yml | 35 ++++++++++++++++++ .../deployment/mongodb/kustomization.yml | 7 ++++ .../mongodb/mongo-secrets.yml.sealme | 8 ++++ .../deployment/mongodb/namespace.yml | 4 ++ simple-with-vault-secrets/vault-example.png | Bin 0 -> 56122 bytes 10 files changed, 132 insertions(+) create mode 100644 simple-with-vault-secrets/.kluctl.yml create mode 100644 simple-with-vault-secrets/.sealed-secrets/deployment/mongodb/simple/mongo-secrets.yml create mode 100644 simple-with-vault-secrets/README.md create mode 100644 simple-with-vault-secrets/deployment.yml create mode 100644 simple-with-vault-secrets/deployment/deployment.yml create mode 100644 simple-with-vault-secrets/deployment/mongodb/deploy.yml create mode 100644 simple-with-vault-secrets/deployment/mongodb/kustomization.yml create mode 100644 simple-with-vault-secrets/deployment/mongodb/mongo-secrets.yml.sealme create mode 100644 simple-with-vault-secrets/deployment/mongodb/namespace.yml create mode 100644 simple-with-vault-secrets/vault-example.png diff --git a/simple-with-vault-secrets/.kluctl.yml b/simple-with-vault-secrets/.kluctl.yml new file mode 100644 index 0000000..ad4313e --- /dev/null +++ b/simple-with-vault-secrets/.kluctl.yml @@ -0,0 +1,16 @@ +targets: + - name: simple + context: kind-kind + args: + environment: simple + sealingConfig: + secretSets: + - simple + +secretsConfig: + secretSets: + - name: simple + vars: + - vault: + address: http://localhost:8200 + path: secret/data/simple \ No newline at end of file diff --git a/simple-with-vault-secrets/.sealed-secrets/deployment/mongodb/simple/mongo-secrets.yml b/simple-with-vault-secrets/.sealed-secrets/deployment/mongodb/simple/mongo-secrets.yml new file mode 100644 index 0000000..7980035 --- /dev/null +++ b/simple-with-vault-secrets/.sealed-secrets/deployment/mongodb/simple/mongo-secrets.yml @@ -0,0 +1,20 @@ +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + annotations: + kluctl.io/sealedsecret-cluster-id: 6c5b3058a41fbe9d711f51a78b525610a14395ca433fde67f489b28deb6814dd + kluctl.io/sealedsecret-hashes: | + DB_PASSWORD: 3ae68d4694167940c2d803cc46ddfebb3ecfe75e4e8dcfec3da339e6d81b8c921f8d3fe81ef414ee0533048dfac4fb5f4e0bc8d4e64824fea24a1c13de8098f7 + DB_USERNAME: 2f6bf36c7d9ed298a354029fb63a605c112efd9f9f4c1b5c664bc3e2801ccbaa86210c1611671dd6abfcfaed8c3a3ca89bd2a39f4377f4d127e9e67bac809011 + sealedsecrets.bitnami.com/scope: strict + name: db-secrets + namespace: simple +spec: + encryptedData: + DB_PASSWORD: AQACk5hEWQy0U41al47iw3vjlqW50kbLypKL4mwUNG+9ytcvd3WuGgALZFrSubZu1QcZMjC+aJMj9IxWMYVu14GYaJ+FgHVeCP6Csvbam4QYaRii+xCZWJg1c+kS1iLW112t4cBXNEioSGoGN5JaFOfh6cwpULcA/reA5ZquOpSWUyzI2wDl7VxZh/dn1JXOabC0iOVZyW6v/hqNxhfiuF4eKeuqcR7PYAeM0AnVksigJlS9xZYfCTLOozCy7arvtZ/M+OZFod/T/h+0w3l/15hLF3DxOauK51z9V+nI0qJWb/mx8pBjY2XOBcrwomgnEykgON1U0WjJUqJLhVTxU8gAsXGFGEkVwDoin4Ehf9DXpbwlOn5jksQk + DB_USERNAME: AQCjnblHUqbanNVWh3NhjCxginJy3PrFOwcYwEaKZWDwIAiLNA5pw7UBLwZoicvumikRdq1wWpUr//GR5Z3iTqZ84PNU6yFGE4jfKDRh0y3DjNuYsnNSDAVIZNFmCe/FxdjVnqSDaYJYcaIXQyZxYvZIzSkg750qqzX4JzediT5OSChXtXAO4qCmjeotdvQnY++PtLR3aR4q3B9CgAWNlIPdGCRhwjVzpgxfiaD5JQuYI3KUhhgGvQfUakyDcRdc99eAsFtOXeEcWDrQj2MHyFTu/ZcB3a2cV5F49ebcV0NhbVL6Y37/0O/6DVHA6ABGMyd6OGybE29s5ct0z7Z18PbJubUP4lmp11WK3qkWa0z41fh4LMfa + template: + metadata: + name: db-secrets + namespace: simple + type: Opaque diff --git a/simple-with-vault-secrets/README.md b/simple-with-vault-secrets/README.md new file mode 100644 index 0000000..a2fa3b4 --- /dev/null +++ b/simple-with-vault-secrets/README.md @@ -0,0 +1,26 @@ +# start local vault + +For this example, we start a Vault service locally, which we equip with a secret + +```bash +VAULT_TOKEN=admintoken +docker run -p 8200:8200 --cap-add=IPC_LOCK -d -e "VAULT_DEV_ROOT_TOKEN_ID=${VAULT_TOKEN}" --name=dev-vault vault +curl \ + -H "X-Vault-Token: ${VAULT_TOKEN}" \ + -H "X-Vault-Request: true" \ + -H "Content-Type: application/json" \ + -X POST \ + -d '{"data":{"secrets": {"database": {"password": "password","username": "admin"}}}}' \ + http://127.0.0.1:8200/v1/secret/data/simple +``` +![vault-example.png](vault-example.png) +```json +{ + "secrets": { + "database": { + "password": "password", + "username": "admin" + } + } +} +``` \ No newline at end of file diff --git a/simple-with-vault-secrets/deployment.yml b/simple-with-vault-secrets/deployment.yml new file mode 100644 index 0000000..54d2769 --- /dev/null +++ b/simple-with-vault-secrets/deployment.yml @@ -0,0 +1,12 @@ +sealedSecrets: + outputPattern: "{{ target.name }}" + +deployments: + - include: deployment + +commonLabels: + examples.kluctl.io/environment: "{{ args.environment }}" + examples.kluctl.io/deployment-project: k8s-deployment-simple + +args: + - name: environment \ No newline at end of file diff --git a/simple-with-vault-secrets/deployment/deployment.yml b/simple-with-vault-secrets/deployment/deployment.yml new file mode 100644 index 0000000..df86aad --- /dev/null +++ b/simple-with-vault-secrets/deployment/deployment.yml @@ -0,0 +1,4 @@ +deployments: + - path: mongodb + +overrideNamespace: "{{ args.environment }}" diff --git a/simple-with-vault-secrets/deployment/mongodb/deploy.yml b/simple-with-vault-secrets/deployment/mongodb/deploy.yml new file mode 100644 index 0000000..9788341 --- /dev/null +++ b/simple-with-vault-secrets/deployment/mongodb/deploy.yml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mongodb-deployment + labels: + app: mongodb +spec: + replicas: 1 + selector: + matchLabels: + app: mongodb + template: + metadata: + labels: + app: mongodb + spec: + containers: + - name: mongodb + image: mongo:5 + ports: + - containerPort: 27017 + env: + - name: MONGO_INITDB_ROOT_USERNAME + valueFrom: + secretKeyRef: + name: mongodb-secret + key: username + optional: false + - name: MONGO_INITDB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: mongodb-secret + key: password + optional: false + diff --git a/simple-with-vault-secrets/deployment/mongodb/kustomization.yml b/simple-with-vault-secrets/deployment/mongodb/kustomization.yml new file mode 100644 index 0000000..986a140 --- /dev/null +++ b/simple-with-vault-secrets/deployment/mongodb/kustomization.yml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - mongo-secrets.yml + - namespace.yml + - deploy.yml \ No newline at end of file diff --git a/simple-with-vault-secrets/deployment/mongodb/mongo-secrets.yml.sealme b/simple-with-vault-secrets/deployment/mongodb/mongo-secrets.yml.sealme new file mode 100644 index 0000000..8040153 --- /dev/null +++ b/simple-with-vault-secrets/deployment/mongodb/mongo-secrets.yml.sealme @@ -0,0 +1,8 @@ +kind: Secret +apiVersion: v1 +metadata: + name: db-secrets + namespace: {{ args.environment }} +stringData: + DB_USERNAME: {{ secrets.database.username }} + DB_PASSWORD: {{ secrets.database.password }} \ No newline at end of file diff --git a/simple-with-vault-secrets/deployment/mongodb/namespace.yml b/simple-with-vault-secrets/deployment/mongodb/namespace.yml new file mode 100644 index 0000000..1b561a9 --- /dev/null +++ b/simple-with-vault-secrets/deployment/mongodb/namespace.yml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: "{{ args.environment }}" diff --git a/simple-with-vault-secrets/vault-example.png b/simple-with-vault-secrets/vault-example.png new file mode 100644 index 0000000000000000000000000000000000000000..873b9bec56b7e00ad978190ffff4a740d04a689d GIT binary patch literal 56122 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 fJ@mizHte^LY0WK?xY?55z(2*Os()6 Date: Sat, 4 Jun 2022 21:58:54 +0200 Subject: [PATCH 2/9] feat: add sealed secrets operator --- .../deployment/deployment.yml | 2 ++ .../sealed-secrets-operator/helm-chart.yml | 6 +++++ .../sealed-secrets-operator/helm-values.yml | 22 +++++++++++++++++++ .../sealed-secrets-operator/kustomization.yml | 5 +++++ 4 files changed, 35 insertions(+) create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/helm-chart.yml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/helm-values.yml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/kustomization.yml diff --git a/simple-with-vault-secrets/deployment/deployment.yml b/simple-with-vault-secrets/deployment/deployment.yml index df86aad..ce47c85 100644 --- a/simple-with-vault-secrets/deployment/deployment.yml +++ b/simple-with-vault-secrets/deployment/deployment.yml @@ -1,4 +1,6 @@ deployments: + - path: sealed-secrets-operator + - barrier: true - path: mongodb overrideNamespace: "{{ args.environment }}" diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/helm-chart.yml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/helm-chart.yml new file mode 100644 index 0000000..9b92aed --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/helm-chart.yml @@ -0,0 +1,6 @@ +helmChart: + repo: https://bitnami-labs.github.io/sealed-secrets + chartName: sealed-secrets + chartVersion: 2.1.6 + releaseName: sealed-secrets-controller + output: deploy.yml diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/helm-values.yml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/helm-values.yml new file mode 100644 index 0000000..8dfeb32 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/helm-values.yml @@ -0,0 +1,22 @@ +podSecurityContext: + runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + fsGroup: 65534 + seccompProfile: + type: RuntimeDefault + +containerSecurityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - all + +resources: + limits: + cpu: 1 + memory: 256Mi + requests: + cpu: 1 + memory: 256Mi diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/kustomization.yml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/kustomization.yml new file mode 100644 index 0000000..b24bc32 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/kustomization.yml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deploy.yml From b4ee69c4e84884f5c70175c9c094780b06b1f68e Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Sat, 4 Jun 2022 21:59:56 +0200 Subject: [PATCH 3/9] chore: helm-pull for sealed secret operator --- .../charts/sealed-secrets/.helmignore | 21 + .../charts/sealed-secrets/Chart.yaml | 19 + .../charts/sealed-secrets/README.md | 258 +++++++++++++ .../sealed-secrets/crds/sealedsecret-crd.yaml | 27 ++ .../dashboards/sealed-secrets-controller.json | 302 +++++++++++++++ .../charts/sealed-secrets/templates/NOTES.txt | 46 +++ .../sealed-secrets/templates/_helpers.tpl | 202 ++++++++++ .../templates/cluster-role-binding.yaml | 19 + .../templates/cluster-role.yaml | 42 ++ .../templates/configmap-dashboards.yaml | 19 + .../sealed-secrets/templates/deployment.yaml | 105 +++++ .../sealed-secrets/templates/extra-list.yaml | 4 + .../sealed-secrets/templates/ingress.yaml | 53 +++ .../templates/networkpolicy.yaml | 14 + .../templates/psp-clusterrole.yaml | 16 + .../templates/psp-clusterrolebinding.yaml | 19 + .../charts/sealed-secrets/templates/psp.yaml | 29 ++ .../templates/role-binding.yaml | 38 ++ .../charts/sealed-secrets/templates/role.yaml | 55 +++ .../templates/service-account.yaml | 11 + .../sealed-secrets/templates/service.yaml | 23 ++ .../templates/servicemonitor.yaml | 39 ++ .../sealed-secrets/templates/tls-secret.yaml | 32 ++ .../charts/sealed-secrets/values.yaml | 362 ++++++++++++++++++ 24 files changed, 1755 insertions(+) create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/.helmignore create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/Chart.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/README.md create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/crds/sealedsecret-crd.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/dashboards/sealed-secrets-controller.json create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/NOTES.txt create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/_helpers.tpl create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/cluster-role-binding.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/cluster-role.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/configmap-dashboards.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/deployment.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/extra-list.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/ingress.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/networkpolicy.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/psp-clusterrole.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/psp.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/role-binding.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/role.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/service-account.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/service.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/servicemonitor.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/tls-secret.yaml create mode 100644 simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/values.yaml diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/.helmignore b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/.helmignore new file mode 100644 index 0000000..f0c1319 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/Chart.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/Chart.yaml new file mode 100644 index 0000000..e666a41 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/Chart.yaml @@ -0,0 +1,19 @@ +annotations: + category: DeveloperTools +apiVersion: v2 +appVersion: v0.17.5 +description: Helm chart for the sealed-secrets controller. +home: https://github.com/bitnami-labs/sealed-secrets +icon: https://bitnami.com/assets/stacks/sealed-secrets/img/sealed-secrets-stack-220x234.png +keywords: +- secrets +- sealed-secrets +kubeVersion: '>=1.16.0-0' +maintainers: +- email: containers@bitnami.com + name: Bitnami +- email: mmikulicic@gmail.com + name: mkmik +name: sealed-secrets +type: application +version: 2.1.6 diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/README.md b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/README.md new file mode 100644 index 0000000..7443544 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/README.md @@ -0,0 +1,258 @@ +# Sealed Secrets + +Sealed Secrets are "one-way" encrypted K8s Secrets that can be created by anyone, but can only be decrypted by the controller running in the target cluster recovering the original object. + + + + +- [TL;DR](#tldr) +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Installing the Chart](#installing-the-chart) +- [Uninstalling the Chart](#uninstalling-the-chart) +- [Parameters](#parameters) + - [Common parameters](#common-parameters) + - [Sealed Secrets Parameters](#sealed-secrets-parameters) + - [Traffic Exposure Parameters](#traffic-exposure-parameters) + - [Other Parameters](#other-parameters) + - [Metrics parameters](#metrics-parameters) +- [Using kubeseal](#using-kubeseal) +- [Configuration and installation details](#configuration-and-installation-details) +- [Troubleshooting](#troubleshooting) +- [Upgrading](#upgrading) + - [To 2.0.0](#to-200) + + + +## TL;DR + +```console +$ helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets +$ helm install my-release sealed-secrets/sealed-secrets +``` + +## Introduction + +Bitnami charts for Helm are carefully engineered, actively maintained and are the quickest and easiest way to deploy containers on a Kubernetes cluster that are ready to handle production workloads. + +This chart bootstraps a [Sealed Secret Controller](https://github.com/bitnami-labs/sealed-secrets) Deployment in [Kubernetes](http://kubernetes.io) using the [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. + +## Prerequisites + +- Kubernetes 1.16+ +- Helm 3.1.0 + +## Installing the Chart + +To install the chart with the release name `my-release`: + +```console +helm install my-release sealed-secrets/sealed-secrets +``` + +The command deploys the Sealed Secrets controller on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: + +```console +helm delete my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Parameters + +### Common parameters + +| Name | Description | Value | +| ------------------ | ------------------------------------------------------- | ----- | +| `kubeVersion` | Override Kubernetes version | `""` | +| `nameOverride` | String to partially override sealed-secrets.fullname | `""` | +| `fullnameOverride` | String to fully override sealed-secrets.fullname | `""` | +| `namespace` | Namespace where to deploy the Sealed Secrets controller | `""` | +| `extraDeploy` | Array of extra objects to deploy with the release | `[]` | + + +### Sealed Secrets Parameters + +| Name | Description | Value | +| ------------------------------------------------- | ------------------------------------------------------------------------------------ | ----------------------------------- | +| `image.registry` | Sealed Secrets image registry | `docker.io` | +| `image.repository` | Sealed Secrets image repository | `bitnami/sealed-secrets-controller` | +| `image.tag` | Sealed Secrets image tag (immutable tags are recommended) | `v0.17.5` | +| `image.pullPolicy` | Sealed Secrets image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Sealed Secrets image pull secrets | `[]` | +| `createController` | Specifies whether the Sealed Secrets controller should be created | `true` | +| `secretName` | The name of an existing TLS secret containing the key used to encrypt secrets | `sealed-secrets-key` | +| `updateStatus` | Specifies whether the Sealed Secrets controller should update the status subresource | `true` | +| `keyrenewperiod` | Specifies key renewal period. Default 30 days | `""` | +| `command` | Override default container command | `[]` | +| `args` | Override default container args | `[]` | +| `livenessProbe.enabled` | Enable livenessProbe on Sealed Secret containers | `true` | +| `livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `0` | +| `livenessProbe.periodSeconds` | Period seconds for livenessProbe | `10` | +| `livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `1` | +| `livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `3` | +| `livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `readinessProbe.enabled` | Enable readinessProbe on Sealed Secret containers | `true` | +| `readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `0` | +| `readinessProbe.periodSeconds` | Period seconds for readinessProbe | `10` | +| `readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `1` | +| `readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `3` | +| `readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | +| `startupProbe.enabled` | Enable startupProbe on Sealed Secret containers | `false` | +| `startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `0` | +| `startupProbe.periodSeconds` | Period seconds for startupProbe | `10` | +| `startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `1` | +| `startupProbe.failureThreshold` | Failure threshold for startupProbe | `3` | +| `startupProbe.successThreshold` | Success threshold for startupProbe | `1` | +| `customLivenessProbe` | Custom livenessProbe that overrides the default one | `{}` | +| `customReadinessProbe` | Custom readinessProbe that overrides the default one | `{}` | +| `customStartupProbe` | Custom startupProbe that overrides the default one | `{}` | +| `resources.limits` | The resources limits for the Sealed Secret containers | `{}` | +| `resources.requests` | The requested resources for the Sealed Secret containers | `{}` | +| `podSecurityContext.enabled` | Enabled Sealed Secret pods' Security Context | `true` | +| `podSecurityContext.fsGroup` | Set Sealed Secret pod's Security Context fsGroup | `65534` | +| `containerSecurityContext.enabled` | Enabled Sealed Secret containers' Security Context | `true` | +| `containerSecurityContext.readOnlyRootFilesystem` | Whether the Sealed Secret container has a read-only root filesystem | `true` | +| `containerSecurityContext.runAsNonRoot` | Indicates that the Sealed Secret container must run as a non-root user | `true` | +| `containerSecurityContext.runAsUser` | Set Sealed Secret containers' Security Context runAsUser | `1001` | +| `podLabels` | Extra labels for Sealed Secret pods | `{}` | +| `podAnnotations` | Annotations for Sealed Secret pods | `{}` | +| `priorityClassName` | Sealed Secret pods' priorityClassName | `""` | +| `affinity` | Affinity for Sealed Secret pods assignment | `{}` | +| `nodeSelector` | Node labels for Sealed Secret pods assignment | `{}` | +| `tolerations` | Tolerations for Sealed Secret pods assignment | `[]` | + + +### Traffic Exposure Parameters + +| Name | Description | Value | +| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `service.type` | Sealed Secret service type | `ClusterIP` | +| `service.port` | Sealed Secret service HTTP port | `8080` | +| `service.nodePort` | Node port for HTTP | `""` | +| `service.annotations` | Additional custom annotations for Sealed Secret service | `{}` | +| `ingress.enabled` | Enable ingress record generation for Sealed Secret | `false` | +| `ingress.pathType` | Ingress path type | `ImplementationSpecific` | +| `ingress.apiVersion` | Force Ingress API version (automatically detected if not set) | `""` | +| `ingress.ingressClassName` | IngressClass that will be be used to implement the Ingress | `""` | +| `ingress.hostname` | Default host for the ingress record | `sealed-secrets.local` | +| `ingress.path` | Default path for the ingress record | `/v1/cert.pem` | +| `ingress.annotations` | Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations. | `{}` | +| `ingress.tls` | Enable TLS configuration for the host defined at `ingress.hostname` parameter | `false` | +| `ingress.selfSigned` | Create a TLS secret for this ingress record using self-signed certificates generated by Helm | `false` | +| `ingress.extraHosts` | An array with additional hostname(s) to be covered with the ingress record | `[]` | +| `ingress.extraPaths` | An array with additional arbitrary paths that may need to be added to the ingress under the main host | `[]` | +| `ingress.extraTls` | TLS configuration for additional hostname(s) to be covered with this ingress record | `[]` | +| `ingress.secrets` | Custom TLS certificates as secrets | `[]` | +| `networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `false` | + + +### Other Parameters + +| Name | Description | Value | +| ----------------------- | ---------------------------------------------------- | ------- | +| `serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` | +| `serviceAccount.labels` | Extra labels to be added to the ServiceAccount | `{}` | +| `serviceAccount.name` | The name of the ServiceAccount to use. | `""` | +| `rbac.create` | Specifies whether RBAC resources should be created | `true` | +| `rbac.labels` | Extra labels to be added to RBAC resources | `{}` | +| `rbac.pspEnabled` | PodSecurityPolicy | `false` | + + +### Metrics parameters + +| Name | Description | Value | +| ------------------------------------------ | -------------------------------------------------------------------------------------- | ------- | +| `metrics.serviceMonitor.enabled` | Specify if a ServiceMonitor will be deployed for Prometheus Operator | `false` | +| `metrics.serviceMonitor.namespace` | Namespace where Prometheus Operator is running in | `""` | +| `metrics.serviceMonitor.labels` | Extra labels for the ServiceMonitor | `{}` | +| `metrics.serviceMonitor.annotations` | Extra annotations for the ServiceMonitor | `{}` | +| `metrics.serviceMonitor.interval` | How frequently to scrape metrics | `""` | +| `metrics.serviceMonitor.scrapeTimeout` | Timeout after which the scrape is ended | `""` | +| `metrics.serviceMonitor.metricRelabelings` | Specify additional relabeling of metrics | `[]` | +| `metrics.serviceMonitor.relabelings` | Specify general relabeling | `[]` | +| `metrics.dashboards.create` | Specifies whether a ConfigMap with a Grafana dashboard configuration should be created | `false` | +| `metrics.dashboards.labels` | Extra labels to be added to the Grafana dashboard ConfigMap | `{}` | +| `metrics.dashboards.namespace` | Namespace where Grafana dashboard ConfigMap is deployed | `""` | + + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```console +$ helm install my-release \ + --set resources.requests.cpu=25m \ + sealed-secrets/sealed-secrets +``` + +The above command sets the `resources.requests.cpu` parameter to `25m`. + +Alternatively, a YAML file that specifies the values for the above parameters can be provided while installing the chart. For example, + +```console +helm install my-release -f values.yaml sealed-secrets/sealed-secrets +``` + +## Using kubeseal + +Install the kubeseal CLI by downloading the binary from [sealed-secrets/releases](https://github.com/bitnami-labs/sealed-secrets/releases). + +Fetch the public key by passing the release name and namespace: + +```bash +kubeseal --fetch-cert \ +--controller-name=my-release \ +--controller-namespace=my-release-namespace \ +> pub-cert.pem +``` + +Read about kubeseal usage on [sealed-secrets docs](https://github.com/bitnami-labs/sealed-secrets#usage). + +NOTE: the helm chart by default installs the controller with the name `sealed-secrets`, while the `kubeseal` command line interface (CLI) tries to access the controller with the name `sealed-secrets-controller`. You can explicitly pass `--controller-name` to the CLI: + +```bash +kubeseal --controller-name sealed-secrets +``` + +Alternatively, you can override `fullnameOverride` on the helm chart install. + +## Configuration and installation details + +- In the case that **serviceAccount.create** is `false` and **rbac.create** is `true` it is expected for a ServiceAccount with the name **serviceAccount.name** to exist _in the same namespace as this chart_ before the installation. +- If **serviceAccount.create** is `true` there cannot be an existing service account with the name **serviceAccount.name**. +- If a secret with name **secretName** does not exist _in the same namespace as this chart_, then on install one will be created. If a secret already exists with this name the keys inside will be used. +- OpenShift: unset the runAsUser and fsGroup like this when installing in a custom namespace: + +```yaml +podSecurityContext: + fsGroup: + +containerSecurityContext: + runAsUser: +``` + +## Troubleshooting + +Find more information about how to deal with common errors related to Bitnami's Helm charts in [this troubleshooting guide](https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues). + +## Upgrading + +### To 2.0.0 + +A major refactoring of the chart has been performed to adopt several common practices for Helm charts. Upgrades from previous chart versions should work, however, the values structure suffered several changes and you'll have to adapt your custom values/parameters so they're aligned with the new structure. For instance, these are a couple of examples: + +- `controller.create` renamed as `createController`. +- `securityContext.*` parameters are deprecated in favor of `podSecurityContext.*`, and `containerSecurityContext.*` ones. +- `image.repository` changed to `image.registry`/`image.repository`. +- `ingress.hosts[0]` changed to `ingress.hostname`. + +Consult the [Parameters](#parameters) section to obtain more info about the available parameters. + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this new major version is no longer compatible with Helm v2. diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/crds/sealedsecret-crd.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/crds/sealedsecret-crd.yaml new file mode 100644 index 0000000..062729e --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/crds/sealedsecret-crd.yaml @@ -0,0 +1,27 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: sealedsecrets.bitnami.com +spec: + group: bitnami.com + names: + kind: SealedSecret + listKind: SealedSecretList + plural: sealedsecrets + singular: sealedsecret + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/dashboards/sealed-secrets-controller.json b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/dashboards/sealed-secrets-controller.json new file mode 100644 index 0000000..a01a7e1 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/dashboards/sealed-secrets-controller.json @@ -0,0 +1,302 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Sealed Secrets Controller", + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 3, + "iteration": 1585599163503, + "links": [ + { + "icon": "external link", + "tags": [], + "title": "GitHub", + "tooltip": "View Project on GitHub", + "type": "link", + "url": "https://github.com/bitnami-labs/sealed-secrets" + } + ], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Rate of requests to unseal a SealedSecret.\n\nThis can include non-obvious operations such as deleting a SealedSecret.", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": true, + "current": false, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(sealed_secrets_controller_unseal_requests_total{}[1m]))", + "format": "time_series", + "instant": false, + "intervalFactor": 1, + "legendFormat": "rps", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Unseal Request Rate/s", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Rate of errors when unsealing a SealedSecret. \n\nReason for error included as label value, eg:\n- unseal = cryptography issue (key/namespace) or RBAC\n- unmanaged = destination Secret wasn't created by SealedSecrets\n- update = potentially RBAC\n- status = potentially RBAC\n- fetch = potentially RBAC\n", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 3, + "legend": { + "avg": false, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(sealed_secrets_controller_unseal_errors_total{pod=~\"$pod\"}[1m])) by (reason)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{ reason }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Unseal Error Rate/s", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 22, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": { + "text": "prometheus", + "value": "prometheus" + }, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allValue": null, + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "$datasource", + "definition": "label_values(kube_pod_info, pod)", + "hide": 0, + "includeAll": true, + "label": null, + "multi": false, + "name": "pod", + "options": [], + "query": "label_values(kube_pod_info, pod)", + "refresh": 1, + "regex": "/^sealed-secrets-controller.*$/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Sealed Secrets Controller", + "uid": "UuEtZCVWz", + "version": 2 +} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/NOTES.txt b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/NOTES.txt new file mode 100644 index 0000000..269265f --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/NOTES.txt @@ -0,0 +1,46 @@ +{{ if .Values.createController -}} + +** Please be patient while the chart is being deployed ** + +You should now be able to create sealed secrets. + +1. Install the client-side tool (kubeseal) as explained in the docs below: + + https://github.com/bitnami-labs/sealed-secrets#installation-from-source + +2. Create a sealed secret file running the command below: + + kubectl create secret generic secret-name --dry-run=client --from-literal=foo=bar -o [json|yaml] | \ + kubeseal \ + --controller-name={{ include "sealed-secrets.fullname" . }} \ + --controller-namespace={{ include "sealed-secrets.namespace" . }} \ + --format yaml > mysealedsecret.[json|yaml] + +The file mysealedsecret.[json|yaml] is a commitable file. + +If you would rather not need access to the cluster to generate the sealed secret you can run: + + kubeseal \ + --controller-name={{ include "sealed-secrets.fullname" . }} \ + --controller-namespace={{ include "sealed-secrets.namespace" . }} \ + --fetch-cert > mycert.pem + +to retrieve the public cert used for encryption and store it locally. You can then run 'kubeseal --cert mycert.pem' instead to use the local cert e.g. + + kubectl create secret generic secret-name --dry-run=client --from-literal=foo=bar -o [json|yaml] | \ + kubeseal \ + --controller-name={{ include "sealed-secrets.fullname" . }} \ + --controller-namespace={{ include "sealed-secrets.namespace" . }} \ + --format [json|yaml] --cert mycert.pem > mysealedsecret.[json|yaml] + +3. Apply the sealed secret + + kubectl create -f mysealedsecret.[json|yaml] + +Running 'kubectl get secret secret-name -o [json|yaml]' will show the decrypted secret that was generated from the sealed secret. + +Both the SealedSecret and generated Secret must have the same name and namespace. +{{- else }} +Sealed Secrets controller not installed, You need to install controller before +sealed secrets can be created. +{{- end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/_helpers.tpl b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/_helpers.tpl new file mode 100644 index 0000000..3835d8b --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/_helpers.tpl @@ -0,0 +1,202 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "sealed-secrets.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "sealed-secrets.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Expand to the namespace sealed-secrets installs into. +*/}} +{{- define "sealed-secrets.namespace" -}} +{{- default .Release.Namespace .Values.namespace -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "sealed-secrets.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "sealed-secrets.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "sealed-secrets.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Kubernetes standard labels +*/}} +{{- define "sealed-secrets.labels" -}} +app.kubernetes.io/name: {{ include "sealed-secrets.name" . }} +helm.sh/chart: {{ include "sealed-secrets.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/version: {{ .Chart.AppVersion }} +{{- end -}} + +{{/* +Labels to use on deploy.spec.selector.matchLabels and svc.spec.selector +*/}} +{{- define "sealed-secrets.matchLabels" -}} +app.kubernetes.io/name: {{ include "sealed-secrets.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* +Return true if cert-manager required annotations for TLS signed certificates are set in the Ingress annotations +Ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations +*/}} +{{- define "sealed-secrets.ingress.certManagerRequest" -}} +{{ if or (hasKey . "cert-manager.io/cluster-issuer") (hasKey . "cert-manager.io/issuer") }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "sealed-secrets.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} +{{- define "sealed-secrets.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{/* +Return the target Kubernetes version +*/}} +{{- define "sealed-secrets.kubeVersion" -}} +{{- if .Values.global }} + {{- if .Values.global.kubeVersion }} + {{- .Values.global.kubeVersion -}} + {{- else }} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} + {{- end -}} +{{- else }} +{{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for deployment. +*/}} +{{- define "sealed-secrets.deployment.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "sealed-secrets.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for ingress. +*/}} +{{- define "sealed-secrets.ingress.apiVersion" -}} +{{- if .Values.ingress -}} +{{- if .Values.ingress.apiVersion -}} +{{- .Values.ingress.apiVersion -}} +{{- else if semverCompare "<1.14-0" (include "sealed-secrets.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "sealed-secrets.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end }} +{{- else if semverCompare "<1.14-0" (include "sealed-secrets.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "sealed-secrets.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for networkpolicy. +*/}} +{{- define "sealed-secrets.networkPolicy.apiVersion" -}} +{{- if semverCompare "<1.7-0" (include "sealed-secrets.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +Usage: +{{ include "sealed-secrets.backend" (dict "serviceName" "backendName" "servicePort" "backendPort" "context" $) }} + +Params: + - serviceName - String. Name of an existing service backend + - servicePort - String/Int. Port name (or number) of the service. It will be translated to different yaml depending if it is a string or an integer. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "sealed-secrets.backend" -}} +{{- $apiVersion := (include "sealed-secrets.ingress.apiVersion" .context) -}} +{{- if or (eq $apiVersion "extensions/v1beta1") (eq $apiVersion "networking.k8s.io/v1beta1") -}} +serviceName: {{ .serviceName }} +servicePort: {{ .servicePort }} +{{- else -}} +service: + name: {{ .serviceName }} + port: + {{- if typeIs "string" .servicePort }} + name: {{ .servicePort }} + {{- else if or (typeIs "int" .servicePort) (typeIs "float64" .servicePort) }} + number: {{ .servicePort | int }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +Print "true" if the API pathType field is supported +Usage: +{{ include "sealed-secrets.supportsPathType" . }} +*/}} +{{- define "sealed-secrets.supportsPathType" -}} +{{- if (semverCompare "<1.18-0" (include "sealed-secrets.kubeVersion" .)) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the ingressClassname field is supported +Usage: +{{ include "sealed-secrets.supportsIngressClassname" . }} +*/}} +{{- define "sealed-secrets.supportsIngressClassname" -}} +{{- if semverCompare "<1.18-0" (include "sealed-secrets.kubeVersion" .) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/cluster-role-binding.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/cluster-role-binding.yaml new file mode 100644 index 0000000..1fd193e --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/cluster-role-binding.yaml @@ -0,0 +1,19 @@ +{{ if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "sealed-secrets.fullname" . }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} + {{- if .Values.rbac.labels }} + {{- include "sealed-secrets.render" ( dict "value" .Values.rbac.labels "context" $) | nindent 4 }} + {{- end }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: secrets-unsealer +subjects: + - apiGroup: "" + kind: ServiceAccount + name: {{ include "sealed-secrets.serviceAccountName" . }} + namespace: {{ include "sealed-secrets.namespace" . }} +{{ end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/cluster-role.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/cluster-role.yaml new file mode 100644 index 0000000..5fc3875 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/cluster-role.yaml @@ -0,0 +1,42 @@ +{{ if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: secrets-unsealer + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} + {{- if .Values.rbac.labels }} + {{- include "sealed-secrets.render" ( dict "value" .Values.rbac.labels "context" $) | nindent 4 }} + {{- end }} +rules: + - apiGroups: + - bitnami.com + resources: + - sealedsecrets + verbs: + - get + - list + - watch + - apiGroups: + - bitnami.com + resources: + - sealedsecrets/status + verbs: + - update + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - create + - update + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +{{ end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/configmap-dashboards.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/configmap-dashboards.yaml new file mode 100644 index 0000000..7d0572c --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/configmap-dashboards.yaml @@ -0,0 +1,19 @@ +{{- if .Values.metrics.dashboards.create }} +{{- $namespace := .Values.metrics.dashboards.namespace | default $.Release.Namespace }} +{{- range $path, $_ := .Files.Glob "dashboards/*.json" }} +{{- $filename := trimSuffix (ext $path) (base $path) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-%s" (include "sealed-secrets.fullname" $) $filename }} + namespace: {{ $namespace }} + labels: {{- include "sealed-secrets.labels" $ | nindent 4 }} + {{- if $.Values.metrics.dashboards.labels }} + {{- include "sealed-secrets.render" ( dict "value" $.Values.metrics.dashboards.labels "context" $) | nindent 4 }} + {{- end }} +data: + {{ base $path }}: |- +{{ $.Files.Get $path | indent 4 }} +--- +{{- end }} +{{- end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/deployment.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/deployment.yaml new file mode 100644 index 0000000..47f3804 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/deployment.yaml @@ -0,0 +1,105 @@ +{{- if .Values.createController }} +apiVersion: {{ include "sealed-secrets.deployment.apiVersion" . }} +kind: Deployment +metadata: + name: {{ include "sealed-secrets.fullname" . }} + namespace: {{ include "sealed-secrets.namespace" . }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} +spec: + selector: + matchLabels: {{- include "sealed-secrets.matchLabels" . | nindent 6 }} + template: + metadata: + {{- if .Values.podAnnotations }} + annotations: {{- toYaml .Values.podAnnotations | nindent 8 }} + {{- end }} + labels: {{- include "sealed-secrets.matchLabels" . | nindent 8 }} + {{- if .Values.podLabels }} + {{- toYaml .Values.podLabels | nindent 8 }} + {{- end }} + spec: + {{- if .Values.image.pullSecrets }} + imagePullSecrets: + {{- range .Values.image.pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} + {{- if .Values.affinity }} + affinity: {{- toYaml .Values.affinity | nindent 8 }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: {{- toYaml .Values.nodeSelector | nindent 8 }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: {{- toYaml .Values.tolerations | nindent 8 }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName | quote }} + {{- end }} + {{- if .Values.podSecurityContext.enabled }} + securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "sealed-secrets.serviceAccountName" . }} + containers: + - name: controller + command: + {{- if .Values.command }} + {{- include "sealed-secrets.render" (dict "value" .Values.command "context" $) | nindent 12 }} + {{- else }} + - controller + {{- end }} + args: + {{- if .Values.args }} + {{- include "sealed-secrets.render" (dict "value" .Values.args "context" $) | nindent 12 }} + {{- else }} + {{- if .Values.updateStatus }} + - --update-status + {{- end }} + {{- if .Values.keyrenewperiod }} + - --key-renew-period + - {{ .Values.keyrenewperiod | quote }} + {{- end }} + - --key-prefix + - {{ .Values.secretName | quote }} + {{- end }} + image: {{ printf "%s/%s:%s" .Values.image.registry .Values.image.repository .Values.image.tag }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: 8080 + name: http + {{- if .Values.startupProbe.enabled }} + startupProbe: {{- include "sealed-secrets.render" (dict "value" (omit .Values.startupProbe "enabled") "context" $) | nindent 12 }} + tcpSocket: + port: http + {{- else if .Values.customStartupProbe }} + startupProbe: {{- include "sealed-secrets.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: {{- include "sealed-secrets.render" (dict "value" (omit .Values.livenessProbe "enabled") "context" $) | nindent 12 }} + httpGet: + path: /healthz + port: http + {{- else if .Values.customLivenessProbe }} + livenessProbe: {{- include "sealed-secrets.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: {{- include "sealed-secrets.render" (dict "value" (omit .Values.readinessProbe "enabled") "context" $) | nindent 12 }} + httpGet: + path: /healthz + port: http + {{- else if .Values.customReadinessProbe }} + readinessProbe: {{- include "sealed-secrets.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + volumeMounts: + - mountPath: /tmp + name: tmp + volumes: + - name: tmp + emptyDir: {} +{{- end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/extra-list.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/extra-list.yaml new file mode 100644 index 0000000..dee3ab6 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/extra-list.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraDeploy }} +--- +{{ include "sealed-secrets.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/ingress.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/ingress.yaml new file mode 100644 index 0000000..966a37e --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/ingress.yaml @@ -0,0 +1,53 @@ +{{- if and .Values.createController .Values.ingress.enabled }} +apiVersion: {{ include "sealed-secrets.ingress.apiVersion" . }} +kind: Ingress +metadata: + name: {{ include "sealed-secrets.fullname" . }} + namespace: {{ include "sealed-secrets.namespace" . }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} + {{- if .Values.ingress.annotations }} + annotations: + {{- if .Values.ingress.annotations }} + {{- include "sealed-secrets.render" ( dict "value" .Values.ingress.annotations "context" $) | nindent 4 }} + {{- end }} + {{- end }} +spec: + {{- if and .Values.ingress.ingressClassName (eq "true" (include "sealed-secrets.supportsIngressClassname" .)) }} + ingressClassName: {{ .Values.ingress.ingressClassName | quote }} + {{- end }} + rules: + {{- if .Values.ingress.hostname }} + - host: {{ .Values.ingress.hostname }} + http: + paths: + {{- if .Values.ingress.extraPaths }} + {{- toYaml .Values.ingress.extraPaths | nindent 10 }} + {{- end }} + - path: {{ .Values.ingress.path }} + {{- if eq "true" (include "sealed-secrets.supportsPathType" .) }} + pathType: {{ .Values.ingress.pathType }} + {{- end }} + backend: {{- include "sealed-secrets.backend" (dict "serviceName" (include "sealed-secrets.fullname" .) "servicePort" "http" "context" $) | nindent 14 }} + {{- end }} + {{- range .Values.ingress.extraHosts }} + - host: {{ .name | quote }} + http: + paths: + - path: {{ default "/" .path }} + {{- if eq "true" (include "sealed-secrets.supportsPathType" $) }} + pathType: {{ default "ImplementationSpecific" .pathType }} + {{- end }} + backend: {{- include "sealed-secrets.backend" (dict "serviceName" (include "sealed-secrets.fullname" $) "servicePort" "http" "context" $) | nindent 14 }} + {{- end }} + {{- if or (and .Values.ingress.tls (or (include "sealed-secrets.ingress.certManagerRequest" .Values.ingress.annotations) .Values.ingress.selfSigned)) .Values.ingress.extraTls }} + tls: + {{- if and .Values.ingress.tls (or (include "sealed-secrets.ingress.certManagerRequest" .Values.ingress.annotations) .Values.ingress.selfSigned) }} + - hosts: + - {{ .Values.ingress.hostname | quote }} + secretName: {{ printf "%s-tls" .Values.ingress.hostname }} + {{- end }} + {{- if .Values.ingress.extraTls }} + {{- include "sealed-secrets.render" (dict "value" .Values.ingress.extraTls "context" $) | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/networkpolicy.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/networkpolicy.yaml new file mode 100644 index 0000000..4c31c1e --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/networkpolicy.yaml @@ -0,0 +1,14 @@ +{{- if .Values.networkPolicy.enabled }} +apiVersion: {{ include "sealed-secrets.networkPolicy.apiVersion" . }} +kind: NetworkPolicy +metadata: + name: {{ include "sealed-secrets.fullname" . }} + namespace: {{ include "sealed-secrets.namespace" . }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: {{- include "sealed-secrets.matchLabels" . | nindent 6 }} + ingress: + - ports: + - port: 8080 +{{- end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/psp-clusterrole.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/psp-clusterrole.yaml new file mode 100644 index 0000000..65b5555 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/psp-clusterrole.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.pspEnabled }} +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ printf "%s-psp" (include "sealed-secrets.fullname" .) }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} + {{- if .Values.rbac.labels }} + {{- include "sealed-secrets.render" ( dict "value" .Values.rbac.labels "context" $) | nindent 4 }} + {{- end }} +rules: + - apiGroups: ['extensions'] + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: + - {{ include "sealed-secrets.fullname" . }} +{{- end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml new file mode 100644 index 0000000..4cd5188 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/psp-clusterrolebinding.yaml @@ -0,0 +1,19 @@ +{{- if .Values.rbac.pspEnabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ printf "%s-psp" (include "sealed-secrets.fullname" .) }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} + {{- if .Values.rbac.labels }} + {{- include "sealed-secrets.render" ( dict "value" .Values.rbac.labels "context" $) | nindent 4 }} + {{- end }} +rules: +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ printf "%s-psp" (include "sealed-secrets.fullname" .) }} +subjects: + - kind: ServiceAccount + name: {{ include "sealed-secrets.serviceAccountName" . }} + namespace: {{ include "sealed-secrets.namespace" . }} +{{- end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/psp.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/psp.yaml new file mode 100644 index 0000000..d9cda30 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/psp.yaml @@ -0,0 +1,29 @@ +{{- if .Values.rbac.pspEnabled }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ include "sealed-secrets.fullname" . }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} +spec: + privileged: false + allowPrivilegeEscalation: false + allowedCapabilities: [] + volumes: + - 'configMap' + - 'emptyDir' + - 'projected' + - 'secret' + - 'downwardAPI' + - 'persistentVolumeClaim' + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'RunAsAny' + fsGroup: + rule: 'RunAsAny' +{{- end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/role-binding.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/role-binding.yaml new file mode 100644 index 0000000..e73d30e --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/role-binding.yaml @@ -0,0 +1,38 @@ +{{ if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ printf "%s-key-admin" (include "sealed-secrets.fullname" .) }} + namespace: {{ include "sealed-secrets.namespace" . }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} + {{- if .Values.rbac.labels }} + {{- include "sealed-secrets.render" ( dict "value" .Values.rbac.labels "context" $) | nindent 4 }} + {{- end }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ printf "%s-key-admin" (include "sealed-secrets.fullname" .) }} +subjects: + - apiGroup: "" + kind: ServiceAccount + name: {{ include "sealed-secrets.serviceAccountName" . }} + namespace: {{ include "sealed-secrets.namespace" . }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ printf "%s-service-proxier" (include "sealed-secrets.fullname" .) }} + namespace: {{ include "sealed-secrets.namespace" . }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} + {{- if .Values.rbac.labels }} + {{- include "sealed-secrets.render" ( dict "value" .Values.rbac.labels "context" $) | nindent 4 }} + {{- end }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ printf "%s-service-proxier" (include "sealed-secrets.fullname" .) }} +subjects: +- apiGroup: rbac.authorization.k8s.io + kind: Group + name: system:authenticated +{{ end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/role.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/role.yaml new file mode 100644 index 0000000..73fd8a7 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/role.yaml @@ -0,0 +1,55 @@ +{{ if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ printf "%s-key-admin" (include "sealed-secrets.fullname" .) }} + namespace: {{ include "sealed-secrets.namespace" . }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} + {{- if .Values.rbac.labels }} + {{- include "sealed-secrets.render" ( dict "value" .Values.rbac.labels "context" $) | nindent 4 }} + {{- end }} +rules: + - apiGroups: + - "" + resourceNames: + - {{ .Values.secretName }} + resources: + - secrets + verbs: + - get + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ printf "%s-service-proxier" (include "sealed-secrets.fullname" .) }} + namespace: {{ include "sealed-secrets.namespace" . }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} + {{- if .Values.rbac.labels }} + {{- include "sealed-secrets.render" ( dict "value" .Values.rbac.labels "context" $) | nindent 4 }} + {{- end }} +rules: + - apiGroups: + - "" + resources: + - services + verbs: + - get + - apiGroups: + - "" + resourceNames: + - 'http:{{ include "sealed-secrets.fullname" . }}:' + - 'http:{{ include "sealed-secrets.fullname" . }}:http' + - {{ include "sealed-secrets.fullname" . }} + resources: + - services/proxy + verbs: + - create + - get +{{ end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/service-account.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/service-account.yaml new file mode 100644 index 0000000..ba09fb4 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/service-account.yaml @@ -0,0 +1,11 @@ +{{ if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "sealed-secrets.serviceAccountName" . }} + namespace: {{ include "sealed-secrets.namespace" . }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} + {{- if .Values.serviceAccount.labels }} + {{- include "sealed-secrets.render" ( dict "value" .Values.serviceAccount.labels "context" $) | nindent 4 }} + {{- end }} +{{ end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/service.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/service.yaml new file mode 100644 index 0000000..e388cf1 --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/service.yaml @@ -0,0 +1,23 @@ +{{- if .Values.createController -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "sealed-secrets.fullname" . }} + namespace: {{ include "sealed-secrets.namespace" . }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} + {{- if .Values.service.labels }} + {{- include "sealed-secrets.render" ( dict "value" .Values.service.labels "context" $) | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - name: http + port: {{ .Values.service.port }} + targetPort: http + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePort)) }} + nodePort: {{ .Values.service.nodePort }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + selector: {{- include "sealed-secrets.matchLabels" . | nindent 4 }} +{{- end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/servicemonitor.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/servicemonitor.yaml new file mode 100644 index 0000000..25410dd --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/servicemonitor.yaml @@ -0,0 +1,39 @@ +{{- if .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "sealed-secrets.fullname" . }} + {{- if .Values.metrics.serviceMonitor.namespace }} + namespace: {{ .Values.metrics.serviceMonitor.namespace }} + {{- else }} + namespace: {{ include "sealed-secrets.namespace" . }} + {{- end }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} + {{- if .Values.metrics.serviceMonitor.labels }} + {{- include "sealed-secrets.render" ( dict "value" .Values.metrics.serviceMonitor.labels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.annotations }} + annotations: {{- include "sealed-secrets.render" (dict "value" .Values.metrics.serviceMonitor.annotations "context" $) | nindent 4 }} + {{- end }} +spec: + endpoints: + - honorLabels: true + port: http + {{- if .Values.metrics.serviceMonitor.interval }} + interval: {{ .Values.metrics.serviceMonitor.interval }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.metricRelabelings }} + metricRelabelings: {{ toYaml .Values.metrics.serviceMonitor.metricRelabelings | nindent 8 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.relabelings }} + relabelings: {{ toYaml .Values.metrics.serviceMonitor.relabelings | nindent 8 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ include "sealed-secrets.namespace" . }} + selector: + matchLabels: {{- include "sealed-secrets.matchLabels" . | nindent 6 }} +{{- end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/tls-secret.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/tls-secret.yaml new file mode 100644 index 0000000..79bc5bd --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/templates/tls-secret.yaml @@ -0,0 +1,32 @@ +{{- if and .Values.createController .Values.ingress.enabled }} +{{- if .Values.ingress.secrets }} +{{- range .Values.ingress.secrets }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .name }} + namespace: {{ include "sealed-secrets.namespace" $ | quote }} + labels: {{- include "sealed-secrets.labels" $ | nindent 4 }} +type: kubernetes.io/tls +data: + tls.crt: {{ .certificate | b64enc }} + tls.key: {{ .key | b64enc }} +--- +{{- end }} +{{- end }} +{{- if and .Values.ingress.tls .Values.ingress.selfSigned }} +{{- $ca := genCA "sealed-secrets-ca" 365 }} +{{- $cert := genSignedCert .Values.ingress.hostname nil (list .Values.ingress.hostname) 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-tls" .Values.ingress.hostname }} + namespace: {{ include "sealed-secrets.namespace" . }} + labels: {{- include "sealed-secrets.labels" . | nindent 4 }} +type: kubernetes.io/tls +data: + tls.crt: {{ $cert.Cert | b64enc | quote }} + tls.key: {{ $cert.Key | b64enc | quote }} + ca.crt: {{ $ca.Cert | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/values.yaml b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/values.yaml new file mode 100644 index 0000000..624c52d --- /dev/null +++ b/simple-with-vault-secrets/deployment/sealed-secrets-operator/charts/sealed-secrets/values.yaml @@ -0,0 +1,362 @@ +## @section Common parameters + +## @param kubeVersion Override Kubernetes version +## +kubeVersion: "" +## @param nameOverride String to partially override sealed-secrets.fullname +## +nameOverride: "" +## @param fullnameOverride String to fully override sealed-secrets.fullname +## +fullnameOverride: "" +## @param namespace Namespace where to deploy the Sealed Secrets controller +## +namespace: "" +## @param extraDeploy [array] Array of extra objects to deploy with the release +## +extraDeploy: [] + +## @section Sealed Secrets Parameters + +## Sealed Secrets image +## ref: https://hub.docker.com/r/bitnami/sealed-secrets-controller/tags +## @param image.registry Sealed Secrets image registry +## @param image.repository Sealed Secrets image repository +## @param image.tag Sealed Secrets image tag (immutable tags are recommended) +## @param image.pullPolicy Sealed Secrets image pull policy +## @param image.pullSecrets [array] Sealed Secrets image pull secrets +## +image: + registry: docker.io + repository: bitnami/sealed-secrets-controller + tag: v0.17.5 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] +## @param createController Specifies whether the Sealed Secrets controller should be created +## +createController: true +## @param secretName The name of an existing TLS secret containing the key used to encrypt secrets +## +secretName: "sealed-secrets-key" +## @param updateStatus Specifies whether the Sealed Secrets controller should update the status subresource +## +updateStatus: true +## @param keyrenewperiod Specifies key renewal period. Default 30 days +## e.g +## keyrenewperiod: "720h30m" +## +keyrenewperiod: "" +## @param command Override default container command +## +command: [] +## @param args Override default container args +## +args: [] +## Configure extra options for Sealed Secret containers' liveness, readiness and startup probes +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes +## @param livenessProbe.enabled Enable livenessProbe on Sealed Secret containers +## @param livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe +## @param livenessProbe.periodSeconds Period seconds for livenessProbe +## @param livenessProbe.timeoutSeconds Timeout seconds for livenessProbe +## @param livenessProbe.failureThreshold Failure threshold for livenessProbe +## @param livenessProbe.successThreshold Success threshold for livenessProbe +## +livenessProbe: + enabled: true + initialDelaySeconds: 0 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + successThreshold: 1 +## @param readinessProbe.enabled Enable readinessProbe on Sealed Secret containers +## @param readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe +## @param readinessProbe.periodSeconds Period seconds for readinessProbe +## @param readinessProbe.timeoutSeconds Timeout seconds for readinessProbe +## @param readinessProbe.failureThreshold Failure threshold for readinessProbe +## @param readinessProbe.successThreshold Success threshold for readinessProbe +## +readinessProbe: + enabled: true + initialDelaySeconds: 0 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + successThreshold: 1 +## @param startupProbe.enabled Enable startupProbe on Sealed Secret containers +## @param startupProbe.initialDelaySeconds Initial delay seconds for startupProbe +## @param startupProbe.periodSeconds Period seconds for startupProbe +## @param startupProbe.timeoutSeconds Timeout seconds for startupProbe +## @param startupProbe.failureThreshold Failure threshold for startupProbe +## @param startupProbe.successThreshold Success threshold for startupProbe +## +startupProbe: + enabled: false + initialDelaySeconds: 0 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + successThreshold: 1 +## @param customLivenessProbe Custom livenessProbe that overrides the default one +## +customLivenessProbe: {} +## @param customReadinessProbe Custom readinessProbe that overrides the default one +## +customReadinessProbe: {} +## @param customStartupProbe Custom startupProbe that overrides the default one +## +customStartupProbe: {} +## Sealed Secret resource requests and limits +## ref: http://kubernetes.io/docs/user-guide/compute-resources/ +## @param resources.limits [object] The resources limits for the Sealed Secret containers +## @param resources.requests [object] The requested resources for the Sealed Secret containers +## +resources: + limits: {} + requests: {} +## Configure Pods Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod +## @param podSecurityContext.enabled Enabled Sealed Secret pods' Security Context +## @param podSecurityContext.fsGroup Set Sealed Secret pod's Security Context fsGroup +## +podSecurityContext: + enabled: true + fsGroup: 65534 +## Configure Container Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod +## @param containerSecurityContext.enabled Enabled Sealed Secret containers' Security Context +## @param containerSecurityContext.readOnlyRootFilesystem Whether the Sealed Secret container has a read-only root filesystem +## @param containerSecurityContext.runAsNonRoot Indicates that the Sealed Secret container must run as a non-root user +## @param containerSecurityContext.runAsUser Set Sealed Secret containers' Security Context runAsUser +## +containerSecurityContext: + enabled: true + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1001 +## @param podLabels [object] Extra labels for Sealed Secret pods +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +## +podLabels: {} +## @param podAnnotations [object] Annotations for Sealed Secret pods +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +## +podAnnotations: {} +## @param priorityClassName Sealed Secret pods' priorityClassName +## +priorityClassName: "" +## @param affinity [object] Affinity for Sealed Secret pods assignment +## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +## +affinity: {} +## @param nodeSelector [object] Node labels for Sealed Secret pods assignment +## ref: https://kubernetes.io/docs/user-guide/node-selection/ +## +nodeSelector: {} +## @param tolerations [array] Tolerations for Sealed Secret pods assignment +## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] + +## @section Traffic Exposure Parameters + +## Sealed Secret service parameters +## +service: + ## @param service.type Sealed Secret service type + ## + type: ClusterIP + ## @param service.port Sealed Secret service HTTP port + ## + port: 8080 + ## @param service.nodePort Node port for HTTP + ## Specify the nodePort value for the LoadBalancer and NodePort service types + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## NOTE: choose port between <30000-32767> + ## + nodePort: "" + ## @param service.annotations [object] Additional custom annotations for Sealed Secret service + ## + annotations: {} +## Sealed Secret ingress parameters +## ref: http://kubernetes.io/docs/user-guide/ingress/ +## +ingress: + ## @param ingress.enabled Enable ingress record generation for Sealed Secret + ## + enabled: false + ## @param ingress.pathType Ingress path type + ## + pathType: ImplementationSpecific + ## @param ingress.apiVersion Force Ingress API version (automatically detected if not set) + ## + apiVersion: "" + ## @param ingress.ingressClassName IngressClass that will be be used to implement the Ingress + ## This is supported in Kubernetes 1.18+ and required if you have more than one IngressClass marked as the default for your cluster. + ## ref: https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/ + ## + ingressClassName: "" + ## @param ingress.hostname Default host for the ingress record + ## + hostname: sealed-secrets.local + ## @param ingress.path Default path for the ingress record + ## + path: /v1/cert.pem + ## @param ingress.annotations [object] Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations. + ## Use this parameter to set the required annotations for cert-manager, see + ## ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations + ## e.g: + ## annotations: + ## kubernetes.io/ingress.class: nginx + ## cert-manager.io/cluster-issuer: cluster-issuer-name + ## + annotations: + ## @param ingress.tls Enable TLS configuration for the host defined at `ingress.hostname` parameter + ## TLS certificates will be retrieved from a TLS secret with name: `{{- printf "%s-tls" .Values.ingress.hostname }}` + ## You can: + ## - Use the `ingress.secrets` parameter to create this TLS secret + ## - Relay on cert-manager to create it by setting the corresponding annotations + ## - Relay on Helm to create self-signed certificates by setting `ingress.selfSigned=true` + ## + tls: false + ## @param ingress.selfSigned Create a TLS secret for this ingress record using self-signed certificates generated by Helm + ## + selfSigned: false + ## @param ingress.extraHosts [array] An array with additional hostname(s) to be covered with the ingress record + ## e.g: + ## extraHosts: + ## - name: sealed-secrets.local + ## path: / + ## + extraHosts: [] + ## @param ingress.extraPaths [array] An array with additional arbitrary paths that may need to be added to the ingress under the main host + ## e.g: + ## extraPaths: + ## - path: /* + ## backend: + ## serviceName: ssl-redirect + ## servicePort: use-annotation + ## + extraPaths: [] + ## @param ingress.extraTls [array] TLS configuration for additional hostname(s) to be covered with this ingress record + ## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls + ## e.g: + ## extraTls: + ## - hosts: + ## - sealed-secrets.local + ## secretName: sealed-secrets.local-tls + ## + extraTls: [] + ## @param ingress.secrets [array] Custom TLS certificates as secrets + ## NOTE: 'key' and 'certificate' are expected in PEM format + ## NOTE: 'name' should line up with a 'secretName' set further up + ## If it is not set and you're using cert-manager, this is unneeded, as it will create a secret for you with valid certificates + ## If it is not set and you're NOT using cert-manager either, self-signed certificates will be created valid for 365 days + ## It is also possible to create and manage the certificates outside of this helm chart + ## Please see README.md for more information + ## e.g: + ## secrets: + ## - name: sealed-secrets.local-tls + ## key: |- + ## -----BEGIN RSA PRIVATE KEY----- + ## ... + ## -----END RSA PRIVATE KEY----- + ## certificate: |- + ## -----BEGIN CERTIFICATE----- + ## ... + ## -----END CERTIFICATE----- + ## + secrets: [] +## Network policies +## Ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +## +networkPolicy: + ## @param networkPolicy.enabled Specifies whether a NetworkPolicy should be created + ## + enabled: false + +## @section Other Parameters + +## ServiceAccount configuration +## +serviceAccount: + ## @param serviceAccount.create Specifies whether a ServiceAccount should be created + ## + create: true + ## @param serviceAccount.labels Extra labels to be added to the ServiceAccount + ## + labels: {} + ## @param serviceAccount.name The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the sealed-secrets.fullname template + ## + name: "" +## RBAC configuration +## +rbac: + ## @param rbac.create Specifies whether RBAC resources should be created + ## + create: true + ## @param rbac.labels Extra labels to be added to RBAC resources + ## + labels: {} + ## @param rbac.pspEnabled PodSecurityPolicy + ## + pspEnabled: false + +## @section Metrics parameters + +metrics: + ## Prometheus Operator ServiceMonitor configuration + ## + serviceMonitor: + ## @param metrics.serviceMonitor.enabled Specify if a ServiceMonitor will be deployed for Prometheus Operator + ## + enabled: false + ## @param metrics.serviceMonitor.namespace Namespace where Prometheus Operator is running in + ## + namespace: "" + ## @param metrics.serviceMonitor.labels Extra labels for the ServiceMonitor + ## + labels: {} + ## @param metrics.serviceMonitor.annotations Extra annotations for the ServiceMonitor + ## + annotations: {} + ## @param metrics.serviceMonitor.interval How frequently to scrape metrics + ## e.g: + ## interval: 10s + ## + interval: "" + ## @param metrics.serviceMonitor.scrapeTimeout Timeout after which the scrape is ended + ## e.g: + ## scrapeTimeout: 10s + ## + scrapeTimeout: "" + ## @param metrics.serviceMonitor.metricRelabelings [array] Specify additional relabeling of metrics + ## + metricRelabelings: [] + ## @param metrics.serviceMonitor.relabelings [array] Specify general relabeling + ## + relabelings: [] + ## Grafana dashboards configuration + ## + dashboards: + ## @param metrics.dashboards.create Specifies whether a ConfigMap with a Grafana dashboard configuration should be created + ## ref https://github.com/helm/charts/tree/master/stable/grafana#configuration + ## + create: false + ## @param metrics.dashboards.labels Extra labels to be added to the Grafana dashboard ConfigMap + ## + labels: {} + ## @param metrics.dashboards.namespace Namespace where Grafana dashboard ConfigMap is deployed + ## + namespace: "" From 47345abdce7aa8cd37862ad915ce9be7708c3a3b Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Sat, 4 Jun 2022 22:16:24 +0200 Subject: [PATCH 4/9] fix: namespace config for sealedSecrets operator --- simple-with-vault-secrets/.kluctl.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/simple-with-vault-secrets/.kluctl.yml b/simple-with-vault-secrets/.kluctl.yml index ad4313e..0044052 100644 --- a/simple-with-vault-secrets/.kluctl.yml +++ b/simple-with-vault-secrets/.kluctl.yml @@ -8,6 +8,9 @@ targets: - simple secretsConfig: + sealedSecrets: + namespace: simple + controllerName: sealed-secrets-controller secretSets: - name: simple vars: From 8c0a5a7a5a95af349de2dba5f61fab5f5c6a799d Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Sat, 4 Jun 2022 22:17:26 +0200 Subject: [PATCH 5/9] fix: EOL --- simple-with-vault-secrets/.kluctl.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simple-with-vault-secrets/.kluctl.yml b/simple-with-vault-secrets/.kluctl.yml index 0044052..517515a 100644 --- a/simple-with-vault-secrets/.kluctl.yml +++ b/simple-with-vault-secrets/.kluctl.yml @@ -16,4 +16,4 @@ secretsConfig: vars: - vault: address: http://localhost:8200 - path: secret/data/simple \ No newline at end of file + path: secret/data/simple From f41532e1a4a51d01e9412d99b921ef13529a74c4 Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Sat, 4 Jun 2022 22:22:24 +0200 Subject: [PATCH 6/9] fix: eof --- simple-with-vault-secrets/deployment.yml | 2 +- simple-with-vault-secrets/deployment/mongodb/kustomization.yml | 2 +- .../deployment/mongodb/mongo-secrets.yml.sealme | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/simple-with-vault-secrets/deployment.yml b/simple-with-vault-secrets/deployment.yml index 54d2769..e7a9c29 100644 --- a/simple-with-vault-secrets/deployment.yml +++ b/simple-with-vault-secrets/deployment.yml @@ -9,4 +9,4 @@ commonLabels: examples.kluctl.io/deployment-project: k8s-deployment-simple args: - - name: environment \ No newline at end of file + - name: environment diff --git a/simple-with-vault-secrets/deployment/mongodb/kustomization.yml b/simple-with-vault-secrets/deployment/mongodb/kustomization.yml index 986a140..463cff1 100644 --- a/simple-with-vault-secrets/deployment/mongodb/kustomization.yml +++ b/simple-with-vault-secrets/deployment/mongodb/kustomization.yml @@ -4,4 +4,4 @@ kind: Kustomization resources: - mongo-secrets.yml - namespace.yml - - deploy.yml \ No newline at end of file + - deploy.yml diff --git a/simple-with-vault-secrets/deployment/mongodb/mongo-secrets.yml.sealme b/simple-with-vault-secrets/deployment/mongodb/mongo-secrets.yml.sealme index 8040153..540d7a5 100644 --- a/simple-with-vault-secrets/deployment/mongodb/mongo-secrets.yml.sealme +++ b/simple-with-vault-secrets/deployment/mongodb/mongo-secrets.yml.sealme @@ -5,4 +5,4 @@ metadata: namespace: {{ args.environment }} stringData: DB_USERNAME: {{ secrets.database.username }} - DB_PASSWORD: {{ secrets.database.password }} \ No newline at end of file + DB_PASSWORD: {{ secrets.database.password }} From 38af4a938ac853c6ab3a53714a6dff3071e15515 Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Sat, 4 Jun 2022 22:24:35 +0200 Subject: [PATCH 7/9] fix: remove local sealed secret --- .../mongodb/simple/mongo-secrets.yml | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 simple-with-vault-secrets/.sealed-secrets/deployment/mongodb/simple/mongo-secrets.yml diff --git a/simple-with-vault-secrets/.sealed-secrets/deployment/mongodb/simple/mongo-secrets.yml b/simple-with-vault-secrets/.sealed-secrets/deployment/mongodb/simple/mongo-secrets.yml deleted file mode 100644 index 7980035..0000000 --- a/simple-with-vault-secrets/.sealed-secrets/deployment/mongodb/simple/mongo-secrets.yml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: bitnami.com/v1alpha1 -kind: SealedSecret -metadata: - annotations: - kluctl.io/sealedsecret-cluster-id: 6c5b3058a41fbe9d711f51a78b525610a14395ca433fde67f489b28deb6814dd - kluctl.io/sealedsecret-hashes: | - DB_PASSWORD: 3ae68d4694167940c2d803cc46ddfebb3ecfe75e4e8dcfec3da339e6d81b8c921f8d3fe81ef414ee0533048dfac4fb5f4e0bc8d4e64824fea24a1c13de8098f7 - DB_USERNAME: 2f6bf36c7d9ed298a354029fb63a605c112efd9f9f4c1b5c664bc3e2801ccbaa86210c1611671dd6abfcfaed8c3a3ca89bd2a39f4377f4d127e9e67bac809011 - sealedsecrets.bitnami.com/scope: strict - name: db-secrets - namespace: simple -spec: - encryptedData: - DB_PASSWORD: AQACk5hEWQy0U41al47iw3vjlqW50kbLypKL4mwUNG+9ytcvd3WuGgALZFrSubZu1QcZMjC+aJMj9IxWMYVu14GYaJ+FgHVeCP6Csvbam4QYaRii+xCZWJg1c+kS1iLW112t4cBXNEioSGoGN5JaFOfh6cwpULcA/reA5ZquOpSWUyzI2wDl7VxZh/dn1JXOabC0iOVZyW6v/hqNxhfiuF4eKeuqcR7PYAeM0AnVksigJlS9xZYfCTLOozCy7arvtZ/M+OZFod/T/h+0w3l/15hLF3DxOauK51z9V+nI0qJWb/mx8pBjY2XOBcrwomgnEykgON1U0WjJUqJLhVTxU8gAsXGFGEkVwDoin4Ehf9DXpbwlOn5jksQk - DB_USERNAME: AQCjnblHUqbanNVWh3NhjCxginJy3PrFOwcYwEaKZWDwIAiLNA5pw7UBLwZoicvumikRdq1wWpUr//GR5Z3iTqZ84PNU6yFGE4jfKDRh0y3DjNuYsnNSDAVIZNFmCe/FxdjVnqSDaYJYcaIXQyZxYvZIzSkg750qqzX4JzediT5OSChXtXAO4qCmjeotdvQnY++PtLR3aR4q3B9CgAWNlIPdGCRhwjVzpgxfiaD5JQuYI3KUhhgGvQfUakyDcRdc99eAsFtOXeEcWDrQj2MHyFTu/ZcB3a2cV5F49ebcV0NhbVL6Y37/0O/6DVHA6ABGMyd6OGybE29s5ct0z7Z18PbJubUP4lmp11WK3qkWa0z41fh4LMfa - template: - metadata: - name: db-secrets - namespace: simple - type: Opaque From d8b96fbc158fda18f8af076cbc70523fb92ec998 Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Sat, 4 Jun 2022 22:34:43 +0200 Subject: [PATCH 8/9] fix: name secretKeyRef --- simple-with-vault-secrets/deployment/mongodb/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simple-with-vault-secrets/deployment/mongodb/deploy.yml b/simple-with-vault-secrets/deployment/mongodb/deploy.yml index 9788341..f41bdda 100644 --- a/simple-with-vault-secrets/deployment/mongodb/deploy.yml +++ b/simple-with-vault-secrets/deployment/mongodb/deploy.yml @@ -23,13 +23,13 @@ spec: - name: MONGO_INITDB_ROOT_USERNAME valueFrom: secretKeyRef: - name: mongodb-secret + name: db-secrets key: username optional: false - name: MONGO_INITDB_ROOT_PASSWORD valueFrom: secretKeyRef: - name: mongodb-secret + name: db-secrets key: password optional: false From 6ec1d649870a1f85b5756137ffcc552024110d22 Mon Sep 17 00:00:00 2001 From: Mathias Gebbe Date: Sat, 4 Jun 2022 22:37:49 +0200 Subject: [PATCH 9/9] fix: secretKeyRef key --- simple-with-vault-secrets/deployment/mongodb/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simple-with-vault-secrets/deployment/mongodb/deploy.yml b/simple-with-vault-secrets/deployment/mongodb/deploy.yml index f41bdda..f388626 100644 --- a/simple-with-vault-secrets/deployment/mongodb/deploy.yml +++ b/simple-with-vault-secrets/deployment/mongodb/deploy.yml @@ -24,12 +24,12 @@ spec: valueFrom: secretKeyRef: name: db-secrets - key: username + key: DB_USERNAME optional: false - name: MONGO_INITDB_ROOT_PASSWORD valueFrom: secretKeyRef: name: db-secrets - key: password + key: DB_PASSWORD optional: false