diff --git a/README.md b/README.md index f4f3b01..6863f01 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Samples are located in the [samples directory](./samples), including: - [Controlled Resource](./samples/controlled-resource) - [Overridden Type and Provider](./samples/overridden-type-provider) - [Multiple Bindings](./samples/multi-binding) +- [External Secrets Operator](./samples/external-secrets) ## Supported Services diff --git a/samples/external-secrets/README.md b/samples/external-secrets/README.md new file mode 100644 index 0000000..841912b --- /dev/null +++ b/samples/external-secrets/README.md @@ -0,0 +1,100 @@ +# External Secrets + +[External Secrets][eso] is an operator to fetch content from secure stores and synthesize that state into a Kubernetes `Secret`. + +This sample uses a fake `SecretStore` (to avoid external infrastructure) and `ExternalSecret` to create a binding Secret. The [Spring PetClinic][petclinic] workload and MySQL database are independently bound to the `ExternalSecret`. + +## Setup + +If not already installed, [install the ServiceBinding CRD and controller][install], and the [External Secrets Operator][eso-install]. + +> NOTE: Provisioned Service support was added to External Secrets in the v0.8.2 release, prior versions are not compatible. + +This sample uses [Carvel Kapp][kapp-install], rather than `kubectl` to install and watch the sample become ready. + +## Deploy + +Apply the `ExternalSecret`, `SecretStore`, PetClinic workload, MySQL service and connect them with `ServiceBinding`s: + +```sh +kapp deploy --app servicebinding-sample-external-secrets -f samples/external-secrets +``` + +When you are done with this sample, all resources in the deploy can be removed by running: + +```sh +kapp delete --app servicebinding-sample-external-secrets +``` + +When prompted, you can review the resource about to be created (updated or deleted) and approve them, or add `--yes` to the above commands. Resources are [tracked between deploys](https://carvel.dev/kapp/docs/latest/diff/), if a resource is removed from the file it will be removed from the cluster on the next deploy. + +Kapp will monitor the [health of the resources](https://carvel.dev/kapp/docs/latest/apply-waiting/) it creates and exit when they become ready, or fail to become ready. The startup [logs from our workload](https://carvel.dev/kapp/docs/latest/apply/#kappk14siodeploy-logs) will also be displayed. + +## Understand + +Inspect the `ExternalSecret`: + +```sh +kubectl describe externalsecrets.external-secrets.io eso-example +``` + +If the `ExternalSecret` is working, a new `Secret` is created and the name of that `Secret` is reflected on the status. +The describe output will contain: + +```txt +... +Status: + Binding: + Name: eso-example-db +... +``` + +The `ServiceBinding` for the PetClinic workload references the `ExternalSecret` resource to discover the binding `Secret` exposed. The [Spring PetClinic sample](../spring-petclinic/) goes into deeper detail for how a Spring Boot workload can consume service bindings at runtime. + +```sh +kubectl describe servicebindings.servicebinding.io eso-example +``` + +```txt +Spec: + Service: + API Version: external-secrets.io/v1beta1 + Kind: ExternalSecret + Name: eso-example-db +``` + +Additional, the `ServiceBinding` for the MySQL database projects environment variables since the MySQL image is not natively aware of service bindings. + +```sh +kubectl describe servicebindings.servicebinding.io eso-example-db +``` + +```txt +... +Spec: + Env: + - Key: username + Name: MYSQL_USER + - Key: password + Name: MYSQL_PASSWORD + - Key: database + Name: MYSQL_DATABASE +... +``` + +## Play + +A key advantage of referencing a Provisioned Service resources over directly referencing the binding `Secret`, is that the name of the `Secret` referenced can be updated at any time, bound workloads will automatically receive the updated `Secret`. + +Try updating the `ExternalSecret` to manage the `Secret` under a different name. + +```sh +kubectl patch externalsecrets.external-secrets.io eso-example-db --type json --patch '[{"op": "replace", "path": "/spec/target/name", "value":"my-new-super-duper-secret"}]' +``` + +Look at the `ExternalSecret` status and the `Deployment`s to see that they have been updated to use the `my-new-super-duper-secret` secret. + +[eso]: https://external-secrets.io/ +[eso-install]: https://external-secrets.io/v0.8.2/introduction/getting-started/ +[install]: ../../README.md#getting-started +[petclinic]: https://github.com/spring-projects/spring-petclinic diff --git a/samples/external-secrets/kapp-config.yaml b/samples/external-secrets/kapp-config.yaml new file mode 100644 index 0000000..97bb081 --- /dev/null +++ b/samples/external-secrets/kapp-config.yaml @@ -0,0 +1,58 @@ +# Copyright 2022 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: kapp-config + labels: + kapp.k14s.io/config: "" +data: + config.yml: | + apiVersion: kapp.k14s.io/v1alpha1 + kind: Config + waitRules: + - supportsObservedGeneration: true + conditionMatchers: + - type: ServiceAvailable + status: "True" + unblockChanges: true + - type: Ready + status: "False" + failure: true + - type: Ready + status: "True" + success: true + resourceMatchers: + - apiVersionKindMatcher: {apiVersion: servicebinding.io/v1beta1, kind: ServiceBinding} + - supportsObservedGeneration: false + conditionMatchers: + - type: Ready + status: "False" + failure: true + - type: Ready + status: "True" + success: true + resourceMatchers: + - apiVersionKindMatcher: {apiVersion: external-secrets.io/v1beta1, kind: ExternalSecret} + - supportsObservedGeneration: false + conditionMatchers: + - type: Ready + status: "False" + failure: true + - type: Ready + status: "True" + success: true + resourceMatchers: + - apiVersionKindMatcher: {apiVersion: external-secrets.io/v1beta1, kind: SecretStore} diff --git a/samples/external-secrets/secret.yaml b/samples/external-secrets/secret.yaml new file mode 100644 index 0000000..59558f4 --- /dev/null +++ b/samples/external-secrets/secret.yaml @@ -0,0 +1,56 @@ +# Copyright 2020 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: eso-example-db + annotations: + kapp.k14s.io/change-group: secret +spec: + refreshInterval: 1h + secretStoreRef: + name: mysql + kind: SecretStore + target: + template: + mergePolicy: Merge + type: servicebinding.io/mysql + dataFrom: + - extract: + key: /petclinic + +--- +apiVersion: external-secrets.io/v1beta1 +kind: SecretStore +metadata: + name: mysql + annotations: + kapp.k14s.io/change-group: secret +spec: + provider: + fake: + data: + - key: "/petclinic" + valueMap: + type: mysql + provider: mariadb + host: eso-example-db + port: "3306" + database: default + # demo credentials + username: user + password: pass diff --git a/samples/external-secrets/service-binding.yaml b/samples/external-secrets/service-binding.yaml new file mode 100644 index 0000000..e3cae84 --- /dev/null +++ b/samples/external-secrets/service-binding.yaml @@ -0,0 +1,34 @@ +# Copyright 2020 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +apiVersion: servicebinding.io/v1beta1 +kind: ServiceBinding +metadata: + name: eso-example + annotations: + kapp.k14s.io/change-group: binding + kapp.k14s.io/change-rule.service: "upsert after upserting service" + kapp.k14s.io/change-rule.service-delete: "delete before deleting service" + kapp.k14s.io/change-rule.workload: "upsert before upserting workload" + kapp.k14s.io/change-rule.workload-delete: "delete after deleting workload" +spec: + service: + apiVersion: external-secrets.io/v1beta1 + kind: ExternalSecret + name: eso-example-db + workload: + apiVersion: apps/v1 + kind: Deployment + name: eso-example diff --git a/samples/external-secrets/service.yaml b/samples/external-secrets/service.yaml new file mode 100644 index 0000000..fe9d600 --- /dev/null +++ b/samples/external-secrets/service.yaml @@ -0,0 +1,90 @@ +# Copyright 2020 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +apiVersion: servicebinding.io/v1beta1 +kind: ServiceBinding +metadata: + name: eso-example-db + annotations: + kapp.k14s.io/change-group: service + kapp.k14s.io/change-rule.service: "upsert after upserting secret" + kapp.k14s.io/change-rule.service-delete: "delete before deleting secret" +spec: + service: + apiVersion: external-secrets.io/v1beta1 + kind: ExternalSecret + name: eso-example-db + workload: + apiVersion: apps/v1 + kind: Deployment + name: eso-example-db + env: + - name: MYSQL_USER + key: username + - name: MYSQL_PASSWORD + key: password + - name: MYSQL_DATABASE + key: database + +--- +apiVersion: v1 +kind: Service +metadata: + name: eso-example-db + annotations: + kapp.k14s.io/change-group: service +spec: + ports: + - port: 3306 + selector: + app: eso-example-db + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: eso-example-db + annotations: + kapp.k14s.io/change-group: service + labels: + app: eso-example-db +spec: + selector: + matchLabels: + app: eso-example-db + template: + metadata: + labels: + app: eso-example-db + spec: + # no persistance configured, the database will be reset when the pod terminates + containers: + - image: mariadb:10.5 + name: mysql + env: + - name: MYSQL_ROOT_PASSWORD + value: root + ports: + - containerPort: 3306 + name: mysql + livenessProbe: + tcpSocket: + port: mysql + readinessProbe: + tcpSocket: + port: mysql + startupProbe: + tcpSocket: + port: mysql diff --git a/samples/external-secrets/workload.yaml b/samples/external-secrets/workload.yaml new file mode 100644 index 0000000..6821e5f --- /dev/null +++ b/samples/external-secrets/workload.yaml @@ -0,0 +1,75 @@ +# Copyright 2020 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +apiVersion: v1 +kind: Service +metadata: + name: eso-example + annotations: + kapp.k14s.io/change-group: workload +spec: + ports: + - port: 80 + targetPort: 8080 + selector: + app: eso-example + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: eso-example + annotations: + kapp.k14s.io/change-group: workload + labels: + app: eso-example +spec: + replicas: 1 + selector: + matchLabels: + app: eso-example + template: + metadata: + annotations: + kapp.k14s.io/deploy-logs: "for-new" + kapp.k14s.io/deploy-logs-container-names: "workload" + labels: + app: eso-example + spec: + containers: + - name: workload + # built with CNB Paketo builder from https://github.com/spring-projects/spring-petclinic + image: ghcr.io/servicebinding/runtime/samples/spring-petclinic@sha256:83ab44832a1db6c03d34e758199b0d9cbf29ce5beeaac4fbf96443a63342b3d4 + env: + # tell the workload to use mysql instead of the default embedded database + - name: SPRING_PROFILES_ACTIVE + value: mysql + ports: + - name: http + containerPort: 8080 + livenessProbe: + httpGet: + path: /actuator/health/liveness + port: http + readinessProbe: + httpGet: + path: /actuator/health/readiness + port: http + startupProbe: + httpGet: + path: /actuator/health/liveness + port: http + failureThreshold: 20 + periodSeconds: 5