Consuming Aspen Mesh certificates via Kubernetes Secrets API#
Aspen Mesh ships with the secure Secret Discovery Service (SDS) mode enabled in release 1.6 and higher as the primary means for provisioning TLS credentials (keys and certificates) for workloads within the mesh. In SDS mode, the TLS credentials are fetched from the Aspen Mesh control plane via APIs without relying on Kubernetes secrets API or any file mounts thereby providing additional security. This SDS provisioning system works well in cases where the sidecar proxies can be injected into the workload pods but can be challenging for legacy and non-Kubernetes native workloads. If you’re using Aspen Mesh as the primary certificate provisioner for all workloads (Kubernetes and legacy) within your cluster, this guide describes how you can securely access Aspen Mesh TLS credentials using Kubernetes secrets API with the default SDS mode enabled. This functionality is useful in scenarios where workloads without sidecar proxies are communicating over TLS with certificates minted by Aspen Mesh with services with sidecar proxies injected or Aspen Mesh gateways.
When this functionality is enabled, Aspen Mesh generates Kubernetes secrets with TLS credentials and automatically manages
the lifecycle and rotation of these credentials. Your application workloads can then use these secrets by mounting them
as file mounts as described here or by consuming the
secrets directly from the Kubernetes API. The generated secret names are based on the
Service Account name associated
with the workload pod. For instance, if httpbin workload is deployed with an associated service account of
httpbin-service-account in the default namespace, the Kubernetes secret istio.httpbin-service-account will be created
in the default namespace with TLS credentials.
Setup#
Warning
This feature requires you to provide your existing Certificate Authority (CA) credentials to be used by Aspen Mesh control plane components (istiod and Citadel).
Follow the instructions on configuring certificates to plug in your own CA certificates.
Continue with your installation of Aspen Mesh, but refer back to this documentation have installation has succeeded.
You will need to add explicit namespace annotations to instruct Aspen Mesh Citadel to generate secrets for workloads in that namespace:
kubectl label --overwrite namespace <NAMESPACE> ca.istio.io/override=true
Note
This annotation is independent of the istio-injection annotation used on namespaces to automatically inject the sidecar proxies which implies that you can use Aspen Mesh generated credentials using this feature for workloads without the sidecar proxies.
You can verify that the Aspen Mesh Citdadel has created credentials by inspecting the generated secrets in your namespace:
kubectl get secret -n <NAMESPACE> istio.default -o yaml | yq r - 'data."cert"' | base64 --decode | openssl x509 -noout -text
Step-by-step example (Do not use in production)#
In this set-by-step example you will deploy 2 applications, one without sidecar proxy which will use the credentials generated by Aspen Mesh Citadel and another injected with sidecar proxy which will use credentials via SDS mode and enable traffic flow between these applications both using Aspen Mesh generated credentials underneath.
Install Aspen Mesh from the installation guide following the guide to plug in your own CA certificates.
Note
Make sure to add this to your overrides file! We will also be enabling Mesh Workload Certificates with SAN DNS and URI entries.
```yaml
citadel:
enabled: true
global:
certificateCustomFields: true
```
Create a test namespace called
without-sidecar-injectionwhere secrets will be generated by Aspen Mesh Citadelkubectl create namespace without-sidecar-injection
Add to the namespace the label
ca.istio.io/overridekubectl label --overwrite namespace without-sidecar-injection ca.istio.io/override=true
Deploy the following
sleepworkload into thewithout-sidecar-injectionnamespacecat <<EOF | kubectl create -n without-sidecar-injection -f - apiVersion: v1 kind: ServiceAccount metadata: name: sleep --- apiVersion: v1 kind: Service metadata: name: sleep labels: app: sleep spec: ports: - port: 80 name: http selector: app: sleep --- apiVersion: apps/v1 kind: Deployment metadata: name: sleep spec: replicas: 1 selector: matchLabels: app: sleep template: metadata: labels: app: sleep spec: serviceAccountName: sleep containers: - name: sleep image: governmentpaas/curl-ssl command: ["/bin/sleep", "3650d"] imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /etc/sleep/tls name: secret-volume - mountPath: /etc/sleep/citadel name: citadel-volume volumes: - name: secret-volume secret: secretName: sleep-secret optional: true - name: citadel-volume secret: secretName: istio.sleep optional: true EOF
Note
The above is a modified version of the samples/sleep/sleep.yaml file that has a volume using the istio.sleep credential created by Aspen Mesh Citadel!
Store the sleep pod name as a variable
export SLEEP_POD=$(kubectl get pod -n without-sidecar-injection -l app=sleep -o jsonpath='{.items[0].metadata.name}')
Verify the certificate
istio.sleepis createdkubectl get secret -n without-sidecar-injection istio.sleep -o yaml | yq r - 'data."cert-chain.pem"' | base64 --decode | openssl x509 -noout -text
Certificate: Data: Version: 3 (0x2) Serial Number: bc:1d:f4:6f:c3:e7:d3:88:4f:94:72:46:dc:95:3c:bd Signature Algorithm: sha256WithRSAEncryption Issuer: O=Juju org Validity Not Before: Oct 29 22:50:41 2020 GMT Not After : Jan 27 22:50:41 2021 GMT Subject: Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: ... Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication, TLS Web Client Authentication X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Alternative Name: critical URI:spiffe://cluster.local/ns/without-sidecar-injection/sa/sleep, DNS:sleep.without-sidecar-injection.svc.cluster.local Signature Algorithm: sha256WithRSAEncryption ...Create a namespace called
with-sidecar-injectionkubectl create namespace with-sidecar-injection
Add the appropriate labels for
istio-injectionset properly to automatically inject sidecars. We will not be adding the Citadel annotations to create secrets in this namespace. The sidecar proxy injected will use SDS mode to provision TLS credentials.kubectl label --overwrite namespace with-sidecar-injection istio-injection=enabled
Deploy the
httpbinapplication to thewith-sidecar-injectionnamespacecat <<EOF | kubectl apply -n with-sidecar-injection -f - apiVersion: v1 kind: ServiceAccount metadata: name: httpbin annotations: "certificate.aspenmesh.io/customFields": '{ "SAN": { "DNS": [ "httpbin.with-sidecar-injection.svc.cluster.local" ] } }' --- apiVersion: v1 kind: Service metadata: name: httpbin labels: app: httpbin spec: ports: - name: http port: 8000 targetPort: 80 selector: app: httpbin --- apiVersion: apps/v1 kind: Deployment metadata: name: httpbin spec: replicas: 1 selector: matchLabels: app: httpbin version: v1 template: metadata: labels: app: httpbin version: v1 spec: serviceAccountName: httpbin containers: - image: docker.io/kennethreitz/httpbin imagePullPolicy: IfNotPresent name: httpbin ports: - containerPort: 80 EOF
Store the details pod name as a variable
export HTTPBIN_POD=$(kubectl get pod -n with-sidecar-injection -l app=httpbin -o jsonpath='{.items[0].metadata.name}')
Inspect the certificate the
httpbinpod is serving to its clients over SDS./bin/istioctl proxy-config secret ${HTTPBIN_POD}.with-sidecar-injection -o json | jq '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' | sed 's/"//g' | base64 --decode | openssl x509 -noout -text
Certificate: Data: Version: 3 (0x2) Serial Number: 3b:6b:04:00:23:58:a4:c0:2b:ae:25:64:d2:32:77:fe Signature Algorithm: sha256WithRSAEncryption Issuer: O=Juju org Validity Not Before: Oct 29 23:05:12 2020 GMT Not After : Oct 30 23:05:12 2020 GMT Subject: Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: ... Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication, TLS Web Client Authentication X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Alternative Name: critical DNS:httpbin.with-sidecar-injection.svc.cluster.local, URI:spiffe://cluster.local/ns/with-sidecar-injection/sa/httpbin Signature Algorithm: sha256WithRSAEncryption ...
Using the certificate minted in the
without-sidecar-injectionnamespace for thesleeppod we will have it communicate with thehttpbinservice deployed in thehttpbinnamespace.kubectl exec -it -n without-sidecar-injection $SLEEP_POD -- curl --key /etc/sleep/citadel/key.pem --cacert /etc/sleep/citadel/root-cert.pem --cert /etc/sleep/citadel/cert-chain.pem https://httpbin.with-sidecar-injection.svc.cluster.local:8000/status/200 -vvvv
* Trying 100.67.121.12:8000... * Connected to httpbin.with-sidecar-injection.svc.cluster.local (100.67.121.12) port 8000 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/sleep/citadel/root-cert.pem CApath: none ... * Server certificate: * subject: [NONE] * start date: Oct 29 23:05:12 2020 GMT * expire date: Oct 30 23:05:12 2020 GMT * subjectAltName: host "httpbin.with-sidecar-injection.svc.cluster.local" matched cert's "httpbin.with-sidecar-injection.svc.cluster.local" * issuer: O=Juju org * SSL certificate verify ok. ... > GET /status/200 HTTP/2 > Host: httpbin.with-sidecar-injection.svc.cluster.local:8000 ... < HTTP/2 200 < server: istio-envoy ...
Congratulations! Services deployed in your Kubernetes cluster without a sidecar can now communicate over TLS with a service with a sidecar proxy.