From 9e31ff4e0f6e89b3d34e98687622f3380f0d2aa6 Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Tue, 1 Sep 2020 21:07:30 +0200 Subject: [PATCH 01/16] docs(preview): draft crd spec Signed-off-by: Moritz Johner --- keps/20200901-standard-crd.md | 163 ++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 keps/20200901-standard-crd.md diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md new file mode 100644 index 00000000..f52e226d --- /dev/null +++ b/keps/20200901-standard-crd.md @@ -0,0 +1,163 @@ +```yaml +--- +title: Standardize ExternalSecret CRD +authors: all of us +creation-date: 2020-09-01 +status: draft +--- +``` + +# Standardize ExternalSecret CRD + +## Table of Contents + + +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Terminology](#terminology) +- [Use-Cases](#use-cases) +- [Proposal](#proposal) + - [API](#api) +- [Alternatives](#alternatives) + + +## Summary + +This is a proposal to standardize the `ExternalSecret` CRD in an combined effort through all projects that deal with syncing external secrets. This proposal aims to do find the _common denominator_ for all users of `ExternalSecrets`. + +## Motivation + +There are a lot of different projects in the wild that essentially do the same thing: sync secrets with Kubernetes. The idea is to unify efforts into a single project that serves the needs of all users in this problem space. + +As a starting point i would like to define a **common denominator** for a CustomResourceDefinition that serves all known use-cases. This CRD should follow the standard alpha -> beta -> GA feature process. + +Once the CRD API is defined we can move on with more delicate discussions about technology, organization and processes. + +### Goals + +- Define a alpha CRD +- Fully document the Spec and use-cases + +### Non-Goals + +This KEP proposes the CRD Spec and documents the use-cases, not the choice of technology or migration path towards implementing the CRD. + +## Terminology + +* Kubernetes External Secrets `KES`: A Application that runs a control loop which syncs secrets +* KES `instance`: A single entity that runs a control loop. +* ExternalSecret `ES`: A CustomResource that declares which secrets should be synced +* Backend: A **source** for secrets. The Backend is external to the cluster. E.g.: Alibabacloud SecretsManager, AWS SystemsManager, Azure KeyVault... +* Frontend: A **sink** for the synced secrets. Usually a `ConfigMap` or `Secret` +* Secret: credentials that act as a key to sensitive information + +## Use-Cases + +The following use cases are taken from + +1. AS a KES user i want to run multiple KES instances per cluster (e.g. one KES instance per DEV/PROD) +2. AS a KES user i want to integrate **multiple backends** from a **single KES instance** (e.g. dev namespace has access only to dev secrets) +3. AS a KES user i want to control the sink for the secrets (aka frontend: store secret as `kind=ConfigMap` or `kind=Secret`) +4. AS a KES user i want to fetch **from multiple** Backends and store the secrets **in a single** Frontend +5. AS a KES operator i want to limit the access to certain backends or subresources (e.g. having one central KES instance that handles all ES - similar to `iam.amazonaws.com/permitted` annotation per namespace) + +## Proposal + +### API + +The `ExternalSecret` CustomResourceDefinition is **namespaced**. It defines the following: +1. source for the secret (backend) +2. sink for the secret (fronted) +3. and a mapping to translate the keys + +```yaml +apiVersion: kes.io/v1alpha1 +kind: ExternalSecret +metadata: {...} + +spec: + + # optional. + # used to select the correct KES instance (think: ingress.ingressClassName) + # There is no need for a indirection (e.g. having a extra resource like kind=IngressClass) + # the KES controller is instantiated with a specific class name + # and filters ES based on this property + externalSecretClassName: "dev" + + # This is the "simple" version to specify a single backend which will be used by all keys + # we can have multiple backends per ES (they can be declared per key) + # Each backend does need a configuration (e.g. AWS IAM Roles, AWS Region, GCP project-id, ) + backend: + type: secretsManager + # optional additional config + secretsManager: + region: eu-central-1 + accessKeyID: AKIAIOSFODNN7EXAMPLE + roleARN: arn:aws:iam::YYYYYYYYYYYY:role/kes-full-access + + # there can only be one frontend per ES + # this is the "thing" that is created by KES. + # conceptually speaking a frontend is just a thing that can hold k/v pairs + frontend: + secret: + # do we need a api version here? who handles upgrades? + apiVersion: v1 + name: my-secret + template: + type: kubernetes.io/dockerconfigjson # or TLS... + metadata: + annotations: {} + labels: {} + # of course only one frontend should be defined + configMap: + # do we need a api version here? who handles upgrades? + apiVersion: v1 # + name: my-configmap + template: + metadata: + labels: + app: foo + + data: + # EXAMPLE 1: simple mapping + # one key in a backend may hold multiple values + # we need a way to map the values to the frontend + # it is the responsibility of the backend implementation to know how to extract a value + - key: /corp.org/dev/certs/ingress + property: pubcert + name: tls.crt + - key: /corp.org/dev/certs/ingress + property: privkey + name: tls.key + + # EXAMPLE 2: multiple backends per ES + # we also need a way to fetch secrets from multiple backend + # thus, we need a way to define a backend per data key + - key: /rds/database-secret + name: database + backend: + type: SecretsManager + secretsManager: + roleARN: arn:aws:iam::YYYYYYYYYYYY:role/kes-ssm-access + - key: /users/my-db-user + name: username + backend: + type: SystemsManager + systemsManager: + roleARN: arn:aws:iam::XXXXXXXXXXXX:role/kes-sm-access + +# status holds the timestamp and status of last last sync +status: + lastSync: 2020-09-01T18:19:17.263Z # ISO 8601 date string + status: success # or failure + +``` + +This API makes the options more explicit rather than having annotations. + + +## Alternatives + +### From dd3ec016e2a297d9bc56cb5552d294e7e6a81a49 Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Wed, 2 Sep 2020 12:41:17 +0200 Subject: [PATCH 02/16] docs(kes-draft): add requirements and backend CRD Signed-off-by: Moritz Johner --- keps/20200901-standard-crd.md | 92 ++++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index f52e226d..1f487282 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -54,19 +54,45 @@ This KEP proposes the CRD Spec and documents the use-cases, not the choice of te * Secret: credentials that act as a key to sensitive information ## Use-Cases - -The following use cases are taken from - -1. AS a KES user i want to run multiple KES instances per cluster (e.g. one KES instance per DEV/PROD) -2. AS a KES user i want to integrate **multiple backends** from a **single KES instance** (e.g. dev namespace has access only to dev secrets) +* one global KES instance that manages ES in **all namespaces**, which gives access to **all backends**, with ACL +* multiple global KES instances, each manages access to a single or multiple backends (e.g.: shard by stage or team...) +* one KES per namespace (a user manages his/her own KES instance) + +### User definitions +* `operator :=` i manage one or multiple `KES` instances +* `user :=` i only create `ES`, KES is managed by someone else + +### User Stories +From that we can derive the following requirements or user-stories: +1. AS a KES operator i want to run multiple KES instances per cluster (e.g. one KES instance per DEV/PROD) +2. AS a KES operator or user i want to integrate **multiple backends** from a **single KES instance** (e.g. dev namespace has access only to dev secrets) 3. AS a KES user i want to control the sink for the secrets (aka frontend: store secret as `kind=ConfigMap` or `kind=Secret`) 4. AS a KES user i want to fetch **from multiple** Backends and store the secrets **in a single** Frontend 5. AS a KES operator i want to limit the access to certain backends or subresources (e.g. having one central KES instance that handles all ES - similar to `iam.amazonaws.com/permitted` annotation per namespace) +### Backends + +These backends are relevant: +* AWS Secure Systems Manager Parameter Store +* AWS Secrets Manager +* Hashicorp Vault +* Azure Key Vault +* Alibaba Cloud KMS Secret Manager +* Google Cloud Platform Secret Manager +* noop (see #476) + +### Frontends + +* Kind=Secret +* Kind=ConfigMap +* *potentially* we could sync backend to backend + ## Proposal ### API +### External Secret + The `ExternalSecret` CustomResourceDefinition is **namespaced**. It defines the following: 1. source for the secret (backend) 2. sink for the secret (fronted) @@ -158,6 +184,58 @@ status: This API makes the options more explicit rather than having annotations. -## Alternatives +### External Secret Backend + +The Backend configuration in an `ExternalSecret` may contain a lot of redundancy, this can be factored out into its own CRD. +These backends are defined in a particular namespace using `SecretBackend` **or** globally with `GlobalSecretBackend`. -### +```yaml +apiVerson: kes.io/v1alpha1 +kind: SecretBackend # or GlobalSecretBackend +metadata: + name: vault + namespace: example-ns # TODO: namespaced? +spec: + vault: + server: "https://vault.example.com" + path: secret/data + auth: + kubernetes: + path: kubernetes + role: example-role + secretRef: + name: vault-secret +``` + +Example Secret that uses the reference to a backend +```yaml +apiVersion: kes.io/v1alpha1 +kind: ExternalSecret +metadata: + name: foo +spec: + externalSecretClassName: "example" + backend: + type: BackendRef # or GlobalBackendRef + backendRef: + name: vault # this must exist in the same namespace + frontend: + secret: + name: my-secret + template: + type: kubernetes.io/TLS + data: + - key: /corp.org/dev/certs/ingress + property: pubcert + name: tls.crt + - key: /corp.org/dev/certs/ingress + property: privkey + name: tls.key +``` + +Workflow in a KES instance: +1. A user creates a CRD with a certain `spec.externalSecretClassName` +2. A controller picks up the `ExternalSecret` if it matches the `className` +3. a) It fetches the `SecretBackend` or `GlobalSecretBackend` defined in `spec.backend.backendRef` and applies it. This does also apply to `spec.data[].backend` + +## Alternatives From 1036d2d7ef0e804f3d939e0319ef4d66f0794486 Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Wed, 2 Sep 2020 12:47:19 +0200 Subject: [PATCH 03/16] docs(kes-draft): add kubernetes backend Signed-off-by: Moritz Johner --- keps/20200901-standard-crd.md | 1 + 1 file changed, 1 insertion(+) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index 1f487282..1a6e170e 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -79,6 +79,7 @@ These backends are relevant: * Azure Key Vault * Alibaba Cloud KMS Secret Manager * Google Cloud Platform Secret Manager +* Kubernetes (see #422) * noop (see #476) ### Frontends From dabcf4c8f80c9e8484812209e68f2cb0926a19d5 Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Wed, 2 Sep 2020 12:52:08 +0200 Subject: [PATCH 04/16] chore: typo --- keps/20200901-standard-crd.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index 1a6e170e..2bb0cbd8 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -195,7 +195,7 @@ apiVerson: kes.io/v1alpha1 kind: SecretBackend # or GlobalSecretBackend metadata: name: vault - namespace: example-ns # TODO: namespaced? + namespace: example-ns spec: vault: server: "https://vault.example.com" From b593efa55677faca0b8a52f8d9cb35401b4bee18 Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Wed, 2 Sep 2020 12:56:53 +0200 Subject: [PATCH 05/16] docs: add refreshInterval property Signed-off-by: Moritz Johner --- keps/20200901-standard-crd.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index 2bb0cbd8..d55bc04b 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -113,6 +113,9 @@ spec: # and filters ES based on this property externalSecretClassName: "dev" + # the amount of time before the values will be read again from the backen + refreshInterval: "1h" + # This is the "simple" version to specify a single backend which will be used by all keys # we can have multiple backends per ES (they can be declared per key) # Each backend does need a configuration (e.g. AWS IAM Roles, AWS Region, GCP project-id, ) From 13be99b11a593f0f8c962c98ea28911c2b9a060e Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Tue, 8 Sep 2020 14:44:25 +0200 Subject: [PATCH 06/16] chore: rename backend -> store, remove inline store --- keps/20200901-standard-crd.md | 117 ++++++++++++++++------------------ 1 file changed, 56 insertions(+), 61 deletions(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index d55bc04b..2b981707 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -49,13 +49,13 @@ This KEP proposes the CRD Spec and documents the use-cases, not the choice of te * Kubernetes External Secrets `KES`: A Application that runs a control loop which syncs secrets * KES `instance`: A single entity that runs a control loop. * ExternalSecret `ES`: A CustomResource that declares which secrets should be synced -* Backend: A **source** for secrets. The Backend is external to the cluster. E.g.: Alibabacloud SecretsManager, AWS SystemsManager, Azure KeyVault... +* Store: Is a **source** for secrets. The Store is external to KES. It can be a hosted service like Alibabacloud SecretsManager, AWS SystemsManager, Azure KeyVault... * Frontend: A **sink** for the synced secrets. Usually a `ConfigMap` or `Secret` * Secret: credentials that act as a key to sensitive information ## Use-Cases -* one global KES instance that manages ES in **all namespaces**, which gives access to **all backends**, with ACL -* multiple global KES instances, each manages access to a single or multiple backends (e.g.: shard by stage or team...) +* one global KES instance that manages ES in **all namespaces**, which gives access to **all stores**, with ACL +* multiple global KES instances, each manages access to a single or multiple stores (e.g.: shard by stage or team...) * one KES per namespace (a user manages his/her own KES instance) ### User definitions @@ -65,14 +65,14 @@ This KEP proposes the CRD Spec and documents the use-cases, not the choice of te ### User Stories From that we can derive the following requirements or user-stories: 1. AS a KES operator i want to run multiple KES instances per cluster (e.g. one KES instance per DEV/PROD) -2. AS a KES operator or user i want to integrate **multiple backends** from a **single KES instance** (e.g. dev namespace has access only to dev secrets) +2. AS a KES operator or user i want to integrate **multiple stores** from a **single KES instance** (e.g. dev namespace has access only to dev secrets) 3. AS a KES user i want to control the sink for the secrets (aka frontend: store secret as `kind=ConfigMap` or `kind=Secret`) -4. AS a KES user i want to fetch **from multiple** Backends and store the secrets **in a single** Frontend -5. AS a KES operator i want to limit the access to certain backends or subresources (e.g. having one central KES instance that handles all ES - similar to `iam.amazonaws.com/permitted` annotation per namespace) +4. AS a KES user i want to fetch **from multiple** stores and store the secrets **in a single** Frontend +5. AS a KES operator i want to limit the access to certain stores or subresources (e.g. having one central KES instance that handles all ES - similar to `iam.amazonaws.com/permitted` annotation per namespace) -### Backends +### Stores -These backends are relevant: +These stores are relevant: * AWS Secure Systems Manager Parameter Store * AWS Secrets Manager * Hashicorp Vault @@ -86,7 +86,7 @@ These backends are relevant: * Kind=Secret * Kind=ConfigMap -* *potentially* we could sync backend to backend +* *potentially* we could sync store to store ## Proposal @@ -95,7 +95,7 @@ These backends are relevant: ### External Secret The `ExternalSecret` CustomResourceDefinition is **namespaced**. It defines the following: -1. source for the secret (backend) +1. source for the secret (store) 2. sink for the secret (fronted) 3. and a mapping to translate the keys @@ -106,27 +106,9 @@ metadata: {...} spec: - # optional. - # used to select the correct KES instance (think: ingress.ingressClassName) - # There is no need for a indirection (e.g. having a extra resource like kind=IngressClass) - # the KES controller is instantiated with a specific class name - # and filters ES based on this property - externalSecretClassName: "dev" - - # the amount of time before the values will be read again from the backen + # the amount of time before the values will be read again from the store refreshInterval: "1h" - # This is the "simple" version to specify a single backend which will be used by all keys - # we can have multiple backends per ES (they can be declared per key) - # Each backend does need a configuration (e.g. AWS IAM Roles, AWS Region, GCP project-id, ) - backend: - type: secretsManager - # optional additional config - secretsManager: - region: eu-central-1 - accessKeyID: AKIAIOSFODNN7EXAMPLE - roleARN: arn:aws:iam::YYYYYYYYYYYY:role/kes-full-access - # there can only be one frontend per ES # this is the "thing" that is created by KES. # conceptually speaking a frontend is just a thing that can hold k/v pairs @@ -151,10 +133,11 @@ spec: app: foo data: + # EXAMPLE 1: simple mapping - # one key in a backend may hold multiple values + # one key from a store may hold multiple values # we need a way to map the values to the frontend - # it is the responsibility of the backend implementation to know how to extract a value + # it is the responsibility of the store implementation to know how to extract a value - key: /corp.org/dev/certs/ingress property: pubcert name: tls.crt @@ -162,21 +145,25 @@ spec: property: privkey name: tls.key - # EXAMPLE 2: multiple backends per ES - # we also need a way to fetch secrets from multiple backend - # thus, we need a way to define a backend per data key + # EXAMPLE 2: multiple stores per ES + # we also need a way to fetch secrets from multiple stores + # thus, we need a way to define a store per data key - key: /rds/database-secret name: database - backend: - type: SecretsManager - secretsManager: - roleARN: arn:aws:iam::YYYYYYYYYYYY:role/kes-ssm-access + storeRef: + name: foo - key: /users/my-db-user name: username - backend: - type: SystemsManager - systemsManager: - roleARN: arn:aws:iam::XXXXXXXXXXXX:role/kes-sm-access + storeRef: + name: bar + + # used to fetch all properties from a secret + # properties are merged in specified order + dataFrom: + - key: /user/all-creds + # optional: reference store + storeRef: + name: fuu # status holds the timestamp and status of last last sync status: @@ -188,30 +175,39 @@ status: This API makes the options more explicit rather than having annotations. -### External Secret Backend +### External Secret Store -The Backend configuration in an `ExternalSecret` may contain a lot of redundancy, this can be factored out into its own CRD. -These backends are defined in a particular namespace using `SecretBackend` **or** globally with `GlobalSecretBackend`. +The store configuration in an `ExternalSecret` may contain a lot of redundancy, this can be factored out into its own CRD. +These stores are defined in a particular namespace using `SecretStore` **or** globally with `GlobalSecretStore`. ```yaml apiVerson: kes.io/v1alpha1 -kind: SecretBackend # or GlobalSecretBackend +kind: SecretStore # or ClusterSecretStore metadata: name: vault namespace: example-ns spec: - vault: - server: "https://vault.example.com" - path: secret/data - auth: - kubernetes: - path: kubernetes - role: example-role - secretRef: - name: vault-secret + # optional. + # used to select the correct KES instance (think: ingress.ingressClassName) + # There is no need for a indirection (e.g. having a extra resource like kind=IngressClass) + # the KES controller is instantiated with a specific class name + # and filters ES based on this property + externalSecretClassName: "dev" + + store: + # store implementation + vault: + server: "https://vault.example.com" + path: secret/data + auth: + kubernetes: + path: kubernetes + role: example-role + secretRef: + name: vault-secret ``` -Example Secret that uses the reference to a backend +Example Secret that uses the reference to a store ```yaml apiVersion: kes.io/v1alpha1 kind: ExternalSecret @@ -219,10 +215,9 @@ metadata: name: foo spec: externalSecretClassName: "example" - backend: - type: BackendRef # or GlobalBackendRef - backendRef: - name: vault # this must exist in the same namespace + storeRef: + kind: SecretStore # ClusterSecretStore + name: my-store frontend: secret: name: my-secret @@ -240,6 +235,6 @@ spec: Workflow in a KES instance: 1. A user creates a CRD with a certain `spec.externalSecretClassName` 2. A controller picks up the `ExternalSecret` if it matches the `className` -3. a) It fetches the `SecretBackend` or `GlobalSecretBackend` defined in `spec.backend.backendRef` and applies it. This does also apply to `spec.data[].backend` +3. a) It fetches the `SecretStore` or `GlobalSecretStore` defined in `spec.storeRef` and applies it. This does also apply to `spec.data[].storeRef` ## Alternatives From 36ac681864a7ab70c46d8c201c2636c8b552b8c6 Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Tue, 8 Sep 2020 15:11:31 +0200 Subject: [PATCH 07/16] fix: rename className --- keps/20200901-standard-crd.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index 2b981707..a3bded98 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -106,6 +106,13 @@ metadata: {...} spec: + # optional. + # used to select the correct KES instance (think: ingress.ingressClassName) + # There is no need for a indirection (e.g. having a extra resource like kind=IngressClass) + # the KES controller is instantiated with a specific class name + # and filters ES based on this property + className: "dev" + # the amount of time before the values will be read again from the store refreshInterval: "1h" @@ -187,12 +194,6 @@ metadata: name: vault namespace: example-ns spec: - # optional. - # used to select the correct KES instance (think: ingress.ingressClassName) - # There is no need for a indirection (e.g. having a extra resource like kind=IngressClass) - # the KES controller is instantiated with a specific class name - # and filters ES based on this property - externalSecretClassName: "dev" store: # store implementation @@ -214,7 +215,6 @@ kind: ExternalSecret metadata: name: foo spec: - externalSecretClassName: "example" storeRef: kind: SecretStore # ClusterSecretStore name: my-store From ccebb36dfb502d24dcc2a7bd6c451f18b1337fb9 Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Mon, 14 Sep 2020 20:37:03 +0200 Subject: [PATCH 08/16] fix: change api group --- keps/20200901-standard-crd.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index a3bded98..89f9cb1a 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -100,7 +100,7 @@ The `ExternalSecret` CustomResourceDefinition is **namespaced**. It defines the 3. and a mapping to translate the keys ```yaml -apiVersion: kes.io/v1alpha1 +apiVersion: external-secrets.k8s.io/v1alpha1 kind: ExternalSecret metadata: {...} @@ -188,7 +188,7 @@ The store configuration in an `ExternalSecret` may contain a lot of redundancy, These stores are defined in a particular namespace using `SecretStore` **or** globally with `GlobalSecretStore`. ```yaml -apiVerson: kes.io/v1alpha1 +apiVerson: external-secrets.k8s.io/v1alpha1 kind: SecretStore # or ClusterSecretStore metadata: name: vault @@ -210,7 +210,7 @@ spec: Example Secret that uses the reference to a store ```yaml -apiVersion: kes.io/v1alpha1 +apiVersion: external-secrets.k8s.io/v1alpha1 kind: ExternalSecret metadata: name: foo From d4c9deb4589b64c3a19402531ca12bfc6e8a3770 Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Tue, 15 Sep 2020 19:06:52 +0200 Subject: [PATCH 09/16] * removed `ConfigMap` * added `templateFrom` * add user story to provide app config * use data as object, not array * add related projects --- keps/20200901-standard-crd.md | 87 ++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index 89f9cb1a..81c86d90 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -35,6 +35,15 @@ As a starting point i would like to define a **common denominator** for a Custom Once the CRD API is defined we can move on with more delicate discussions about technology, organization and processes. +List of Projects known so far or related: +* https://github.com/godaddy/kubernetes-external-secrets +* https://github.com/itscontained/secret-manager +* https://github.com/ContainerSolutions/externalsecret-operator +* https://github.com/mumoshu/aws-secret-operator +* https://github.com/cmattoon/aws-ssm +* https://github.com/tuenti/secrets-manager +* https://github.com/kubernetes-sigs/k8s-gsm-tools + ### Goals - Define a alpha CRD @@ -44,13 +53,15 @@ Once the CRD API is defined we can move on with more delicate discussions about This KEP proposes the CRD Spec and documents the use-cases, not the choice of technology or migration path towards implementing the CRD. +We do not want to sync secrets into a `ConfigMap`. + ## Terminology * Kubernetes External Secrets `KES`: A Application that runs a control loop which syncs secrets * KES `instance`: A single entity that runs a control loop. * ExternalSecret `ES`: A CustomResource that declares which secrets should be synced * Store: Is a **source** for secrets. The Store is external to KES. It can be a hosted service like Alibabacloud SecretsManager, AWS SystemsManager, Azure KeyVault... -* Frontend: A **sink** for the synced secrets. Usually a `ConfigMap` or `Secret` +* Frontend: A **sink** for the synced secrets. Usually a `Secret` * Secret: credentials that act as a key to sensitive information ## Use-Cases @@ -66,9 +77,10 @@ This KEP proposes the CRD Spec and documents the use-cases, not the choice of te From that we can derive the following requirements or user-stories: 1. AS a KES operator i want to run multiple KES instances per cluster (e.g. one KES instance per DEV/PROD) 2. AS a KES operator or user i want to integrate **multiple stores** from a **single KES instance** (e.g. dev namespace has access only to dev secrets) -3. AS a KES user i want to control the sink for the secrets (aka frontend: store secret as `kind=ConfigMap` or `kind=Secret`) +3. AS a KES user i want to control the sink for the secrets (aka frontend: store secret as `kind=Secret`) 4. AS a KES user i want to fetch **from multiple** stores and store the secrets **in a single** Frontend 5. AS a KES operator i want to limit the access to certain stores or subresources (e.g. having one central KES instance that handles all ES - similar to `iam.amazonaws.com/permitted` annotation per namespace) +4. AS a KES user i want to provide an application with a configuration that contains a secret ### Stores @@ -85,7 +97,6 @@ These stores are relevant: ### Frontends * Kind=Secret -* Kind=ConfigMap * *potentially* we could sync store to store ## Proposal @@ -126,51 +137,43 @@ spec: name: my-secret template: type: kubernetes.io/dockerconfigjson # or TLS... + # use inline templates + data: + config.yml: | + endpoints: + - https://{{ .data.user }}:{{ .data.password }}@api.exmaple.com metadata: annotations: {} labels: {} - # of course only one frontend should be defined - configMap: - # do we need a api version here? who handles upgrades? - apiVersion: v1 # - name: my-configmap - template: - metadata: - labels: - app: foo + # Uses an existing template from configmap + # secret is fetched, merged and templated within the referenced configMap data + # It does not update the configmap, it creates a secret with: data["alertmanager.yml"] = ...result... + templateFrom: + - configMap: + name: alertmanager + items: + - key: alertmanager.yaml + + + # data contains key/value pairs which correspond to the keys in the resulting secret data: # EXAMPLE 1: simple mapping # one key from a store may hold multiple values # we need a way to map the values to the frontend # it is the responsibility of the store implementation to know how to extract a value - - key: /corp.org/dev/certs/ingress - property: pubcert - name: tls.crt - - key: /corp.org/dev/certs/ingress - property: privkey - name: tls.key - - # EXAMPLE 2: multiple stores per ES - # we also need a way to fetch secrets from multiple stores - # thus, we need a way to define a store per data key - - key: /rds/database-secret - name: database - storeRef: - name: foo - - key: /users/my-db-user - name: username - storeRef: - name: bar - - # used to fetch all properties from a secret - # properties are merged in specified order + tls.crt: + key: /corp.org/dev/certs/ingress + property: pubcert + tls.key: + key: /corp.org/dev/certs/ingress + property: privkey + + # used to fetch all properties from a secret. + # if multiple dataFrom are specified, secrets are merged in the specified order dataFrom: - key: /user/all-creds - # optional: reference store - storeRef: - name: fuu # status holds the timestamp and status of last last sync status: @@ -224,12 +227,12 @@ spec: template: type: kubernetes.io/TLS data: - - key: /corp.org/dev/certs/ingress - property: pubcert - name: tls.crt - - key: /corp.org/dev/certs/ingress - property: privkey - name: tls.key + tls.crt: + key: /corp.org/dev/certs/ingress + property: pubcert + tls.key: + key: /corp.org/dev/certs/ingress + property: privkey ``` Workflow in a KES instance: From 17633cf8bffb3788ea307c82c74902470335d944 Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Thu, 24 Sep 2020 21:03:43 +0200 Subject: [PATCH 10/16] fix: move ES.className to store as spec.controller --- keps/20200901-standard-crd.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index 81c86d90..e2d79c3c 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -117,13 +117,6 @@ metadata: {...} spec: - # optional. - # used to select the correct KES instance (think: ingress.ingressClassName) - # There is no need for a indirection (e.g. having a extra resource like kind=IngressClass) - # the KES controller is instantiated with a specific class name - # and filters ES based on this property - className: "dev" - # the amount of time before the values will be read again from the store refreshInterval: "1h" @@ -198,6 +191,12 @@ metadata: namespace: example-ns spec: + # optional. + # used to select the correct KES controller (think: ingress.ingressClassName) + # The KES controller is instantiated with a specific controller name + # and filters ES based on this property + controller: "dev" + store: # store implementation vault: @@ -236,8 +235,6 @@ spec: ``` Workflow in a KES instance: -1. A user creates a CRD with a certain `spec.externalSecretClassName` -2. A controller picks up the `ExternalSecret` if it matches the `className` -3. a) It fetches the `SecretStore` or `GlobalSecretStore` defined in `spec.storeRef` and applies it. This does also apply to `spec.data[].storeRef` - -## Alternatives +1. A user creates a Store with a certain `spec.controller` +2. A controller picks up the `ExternalSecret` if it matches the `controller` field +3. The controller fetches the secret from the provider and stores it as kind=Secret in the same namespace as ES From 13145ccf00182f65d59f5cdd1e11230a2182af7e Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Thu, 24 Sep 2020 21:36:34 +0200 Subject: [PATCH 11/16] fix: rename spec.frontend to spec.target, add creationPolicy --- keps/20200901-standard-crd.md | 52 ++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index e2d79c3c..ec87e6e5 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -120,24 +120,32 @@ spec: # the amount of time before the values will be read again from the store refreshInterval: "1h" - # there can only be one frontend per ES - # this is the "thing" that is created by KES. - # conceptually speaking a frontend is just a thing that can hold k/v pairs - frontend: - secret: - # do we need a api version here? who handles upgrades? - apiVersion: v1 - name: my-secret - template: - type: kubernetes.io/dockerconfigjson # or TLS... - # use inline templates - data: - config.yml: | - endpoints: - - https://{{ .data.user }}:{{ .data.password }}@api.exmaple.com - metadata: - annotations: {} - labels: {} + # there can only be one target per ES + target: + # The secret name of the resource + # defaults to .metadata.name of the ExternalSecret. immutable. + name: my-secret + + # Enum with values: 'Owner', 'Merge', or 'None' + # Default value of 'Owner' + # Owner creates the secret and sets .metadata.ownerReferences of the resource + # Merge does not create the secret, but merges in the data fields to the secret + # None does not create a secret (future use with injector) + creationPolicy: 'Merge' + + # specify a blueprint for the resulting Kind=Secret + template: + type: kubernetes.io/dockerconfigjson # or TLS... + + metadata: + annotations: {} + labels: {} + + # use inline templates to construct your desired config file that contains your secret + data: + config.yml: | + endpoints: + - https://{{ .data.user }}:{{ .data.password }}@api.exmaple.com # Uses an existing template from configmap # secret is fetched, merged and templated within the referenced configMap data @@ -148,7 +156,6 @@ spec: items: - key: alertmanager.yaml - # data contains key/value pairs which correspond to the keys in the resulting secret data: @@ -238,3 +245,10 @@ Workflow in a KES instance: 1. A user creates a Store with a certain `spec.controller` 2. A controller picks up the `ExternalSecret` if it matches the `controller` field 3. The controller fetches the secret from the provider and stores it as kind=Secret in the same namespace as ES + + +## Backlog + +We have a bunch of features which are not relevant for the MVP implementation. We keep the features here in this backlog. Order is not specific: + +1. Secret injection with a mutating Webhook [#81](https://github.com/godaddy/kubernetes-external-secrets/issues/81) From 08f0a4b530fee3a946e292df6935e94919b72b4d Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Thu, 1 Oct 2020 22:24:21 +0200 Subject: [PATCH 12/16] feat: add status conditions --- keps/20200901-standard-crd.md | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index ec87e6e5..dcbc4033 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -175,10 +175,21 @@ spec: dataFrom: - key: /user/all-creds -# status holds the timestamp and status of last last sync status: - lastSync: 2020-09-01T18:19:17.263Z # ISO 8601 date string - status: success # or failure + # represents the current phase of secret sync: + # * Pending | ES created, controller did not yet sync the ES or other dependencies are missing (e.g. secret store or configmap template) + # * Syncing | ES is being actively synced according to spec + # * Failing | Secret can not be synced, this might require user intervention + # * Failed | ES can not be synced right now and will not able to + # * Completed | ES was synced successfully (one-time use only) + phase: Syncing + conditions: + - type: InSync + status: "True" # False if last sync was not successful + reason: "SecretSynced" + message: "Secret was synced" + lastTransitionTime: "2019-08-12T12:33:02Z" + lastSyncTime: "2020-09-23T16:27:53Z" ``` @@ -215,6 +226,16 @@ spec: role: example-role secretRef: name: vault-secret +status: + # * Pending: e.g. referenced secret containing credentials is missing + # * Running: all dependencies are met, sync + phase: Running + conditions: + - type: Ready + status: "False" + reason: "ErrorConfig" + message: "Unable to assume role arn:xxxx" + lastTransitionTime: "2019-08-12T12:33:02Z" ``` Example Secret that uses the reference to a store From e2522d765bc5bea423dfe739e8de6fd8bf757873 Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Wed, 7 Oct 2020 08:49:47 +0200 Subject: [PATCH 13/16] fix: add behavior section for the control loop --- keps/20200901-standard-crd.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index dcbc4033..8979197e 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -118,6 +118,7 @@ metadata: {...} spec: # the amount of time before the values will be read again from the store + # may be set to zero to fetch and create it once refreshInterval: "1h" # there can only be one target per ES @@ -193,8 +194,9 @@ status: ``` -This API makes the options more explicit rather than having annotations. +#### Behavior +The ExternalSecret control loop **ensures** that the target resource exists and stays up to date with the upstream provider. Because most upstream APIs are limited in throughput the control loop must implement some sort of jitter and retry/backoff mechanic. ### External Secret Store From 310e8bdb31015c1afb13ace9b8622a82a0356e1d Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Tue, 27 Oct 2020 13:55:35 +0100 Subject: [PATCH 14/16] feat: use typed store config --- keps/20200901-standard-crd.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index 8979197e..90a2a06a 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -218,16 +218,12 @@ spec: controller: "dev" store: - # store implementation - vault: + type: vault + parameters: # provider specific k/v pairs server: "https://vault.example.com" - path: secret/data - auth: - kubernetes: - path: kubernetes - role: example-role - secretRef: - name: vault-secret + path: path/on/vault/store + auth: {} # provider specific k/v pairs + status: # * Pending: e.g. referenced secret containing credentials is missing # * Running: all dependencies are met, sync From 2819108b23b4525277203f4d7d8665b80427f3a9 Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Wed, 28 Oct 2020 15:59:15 +0100 Subject: [PATCH 15/16] fix: typo --- keps/20200901-standard-crd.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index 90a2a06a..13d395ce 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -246,7 +246,7 @@ spec: storeRef: kind: SecretStore # ClusterSecretStore name: my-store - frontend: + target: secret: name: my-secret template: From 03f82b116ffd7e880679171c7a56dee0ce78e81c Mon Sep 17 00:00:00 2001 From: Moritz Johner Date: Wed, 28 Oct 2020 17:31:01 +0100 Subject: [PATCH 16/16] fix: ES typo --- keps/20200901-standard-crd.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/keps/20200901-standard-crd.md b/keps/20200901-standard-crd.md index 13d395ce..2bfd0b4c 100644 --- a/keps/20200901-standard-crd.md +++ b/keps/20200901-standard-crd.md @@ -247,10 +247,9 @@ spec: kind: SecretStore # ClusterSecretStore name: my-store target: - secret: - name: my-secret - template: - type: kubernetes.io/TLS + name: my-secret + template: + type: kubernetes.io/TLS data: tls.crt: key: /corp.org/dev/certs/ingress