Send mTLS Requests Using Istio Egress Gateway in SAP BTP, Kyma Runtime
Learn how to configure and use the Istio egress Gateway to allow mTLS-secured outbound traffic between clusters.
Overview
You will learn
- How to generate mTLS certificates
- How to configure the Istio egress Gateway to allow mTLS-secured outbound traffic
- How to send HTTP requests using the configured Istio egress Gateway
Prerequisites
Prerequisites
- You need two clusters:
- One with the Istio and API Gateway modules added. You will use it to expose the target workload.
- One with the Istio module added. You will use it to send requests.
Steps
Intro
To follow this tutorial, you must prepare two clusters. You will use one of them to host the target workload, and the other to send requests. The tutorial guides you through the process of generating mTLS certificates and configuring the Istio egress Gateway in SAP BTP, Kyma runtime. This configuration allows you to make mTLS-secured requests from your Kyma runtime cluster to a workload in another cluster.
Export the kubeconfig file of the cluster that will contain the target workload:
Shell/Bashexport KUBECONFIG={target-workload-cluster-config}Export the name of your domain (for example,
nginx.example.com), the domain of the client cluster (for example,client.example.com), and the namespace you wnat to use. You will use these values throughout the whole tutorial.Shell/Bashexport DOMAIN={your-workload-host} export CLIENT={client-cluster-domain} export NAMESPACE={your-namespace}Generate the root certificate:
Shell/Bashopenssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj "/O=example Inc./CN=$DOMAIN" -keyout egress.key -out egress.crtGenerate the host certificate:
Shell/Bashopenssl req -out "$DOMAIN".csr -newkey rsa:2048 -nodes -keyout "$DOMAIN".key -subj "/CN="$DOMAIN"/O=some organization" openssl x509 -req -sha256 -days 365 -CA egress.crt -CAkey egress.key -set_serial 0 -in "$DOMAIN".csr -out "$DOMAIN".crtGenerate the client certificate:
Shell/Bashopenssl req -out "$CLIENT".csr -newkey rsa:2048 -nodes -keyout "$CLIENT".key -subj "/CN="$CLIENT"/O=client organization" openssl x509 -req -sha256 -days 365 -CA egress.crt -CAkey egress.key -set_serial 1 -in "$CLIENT".csr -out "$CLIENT".crt
Use the same kubeconfig file youโve already exported.
Create an HTTPBin Deployment:
Shell/Bashkubectl create ns $NAMESPACE kubectl label namespace $NAMESPACE istio-injection=enabled --overwrite kubectl -n $NAMESPACE create -f https://raw.githubusercontent.com/istio/istio/master/samples/httpbin/httpbin.yamlCreate a Secret containing your generated certificates and key:
Shell/Bashkubectl create secret generic -n istio-system kyma-mtls-certs --from-file=cacert=egress.crt --from-file=key=$DOMAIN.key --from-file=cert=$DOMAIN.crtCreate a Gateway with mTLS configuration:
Shell/Bashcat <<EOF | kubectl apply -f - apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: kyma-mtls-gateway namespace: $NAMESPACE spec: selector: app: istio-ingressgateway istio: ingressgateway servers: - port: number: 443 name: mtls protocol: HTTPS tls: mode: MUTUAL credentialName: kyma-mtls-certs hosts: - $DOMAIN EOFCreate an APIRule exposing your workload:
Shell/Bashcat <<EOF | kubectl apply -f - apiVersion: gateway.kyma-project.io/v2alpha1 kind: APIRule metadata: name: httpbin namespace: $NAMESPACE spec: hosts: - $DOMAIN service: name: httpbin namespace: $NAMESPACE port: 8000 gateway: $NAMESPACE/kyma-mtls-gateway rules: - path: /* methods: ["GET"] noAuth: true EOFSend a request to your workload without a certificate and key:
Shell/Bashcurl -ik -X GET https://$DOMAIN/headersYou get an SSL error similar to this one:
curl: (56) LibreSSL SSL_read: LibreSSL/3.3.6: error:1404C45C:SSL routines:ST_OK:reason(1116), errno 0Send a request with the certificate and key:
Shell/Bashcurl --cert $CLIENT.crt --key $CLIENT.key --cacert egress.crt -ik -X GET https://$DOMAIN/headersYou get the
200response code from the workload containing headers. One of them should be"X-Forwarded-Client-Cert": ["xxx"].
Export the
kubeconfigfile of another cluster:Shell/Bashexport KUBECONFIG={egress-cluster-config}Create a new namespace for the sample application:
Shell/Bashkubectl create ns $NAMESPACE kubectl label namespace $NAMESPACE istio-injection=enabled --overwriteCreate a Secret with the client certificate and key:
Shell/Bashkubectl create secret -n istio-system generic client-credential --from-file=tls.key=$CLIENT.key \ --from-file=tls.crt=$CLIENT.crt --from-file=ca.crt=egress.crtEnable the egress Gateway in the Istio custom resource:
Shell/Bashkubectl apply -f - <<EOF apiVersion: operator.kyma-project.io/v1alpha2 kind: Istio metadata: name: default namespace: kyma-system labels: app.kubernetes.io/name: default spec: components: egressGateway: enabled: true EOFEnable additional sidecar logs to see the egress Gateway being used in requests:
Shell/Bashkubectl apply -f - <<EOF apiVersion: telemetry.istio.io/v1 kind: Telemetry metadata: name: mesh-default namespace: istio-system spec: accessLogging: - providers: - name: envoy EOFApply the
curlDeployment to send the requests:Shell/Bashkubectl apply -f - <<EOF apiVersion: v1 kind: ServiceAccount metadata: name: curl namespace: ${NAMESPACE} --- apiVersion: v1 kind: Service metadata: name: curl namespace: ${NAMESPACE} labels: app: curl service: curl spec: ports: - port: 443 name: https selector: app: curl --- apiVersion: apps/v1 kind: Deployment metadata: name: curl namespace: ${NAMESPACE} spec: replicas: 1 selector: matchLabels: app: curl template: metadata: labels: app: curl spec: terminationGracePeriodSeconds: 0 serviceAccountName: curl containers: - name: curl image: curlimages/curl command: ["/bin/sleep", "infinity"] imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /etc/curl/tls name: secret-volume volumes: - name: secret-volume secret: secretName: curl-secret optional: true EOFExport the name of the
curlPod:Shell/Bashexport SOURCE_POD=$(kubectl get pod -n "$NAMESPACE" -l app=curl -o jsonpath={.items..metadata.name})Define a ServiceEntry which adds the
kyma-project.iohostname to the mesh:Shell/Bashkubectl apply -f - <<EOF apiVersion: networking.istio.io/v1 kind: ServiceEntry metadata: name: httpbin namespace: $NAMESPACE spec: hosts: - $DOMAIN ports: - number: 80 name: http protocol: HTTP - number: 443 name: https protocol: HTTPS resolution: DNS EOFCreate an egress Gateway, DestinationRule, and VirtualService to direct traffic:
Shell/Bashkubectl apply -f - <<EOF apiVersion: networking.istio.io/v1 kind: Gateway metadata: name: istio-egressgateway namespace: ${NAMESPACE} spec: selector: istio: egressgateway servers: - port: number: 80 name: http protocol: HTTP hosts: - $DOMAIN --- apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: egressgateway-for-httpbin namespace: ${NAMESPACE} spec: host: istio-egressgateway.istio-system.svc.cluster.local subsets: - name: httpbin --- apiVersion: networking.istio.io/v1 kind: VirtualService metadata: name: direct-httpbin-through-egress-gateway namespace: ${NAMESPACE} spec: hosts: - $DOMAIN gateways: - istio-egressgateway - mesh http: - match: - gateways: - mesh port: 80 route: - destination: host: istio-egressgateway.istio-system.svc.cluster.local subset: httpbin port: number: 80 - match: - gateways: - istio-egressgateway port: 80 route: - destination: host: $DOMAIN port: number: 443 weight: 100 EOFCreate a DestinationRule to use mTLS credentials:
Shell/Bashkubectl apply -n istio-system -f - <<EOF apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: originate-mtls-for-httpbin spec: host: $DOMAIN trafficPolicy: portLevelSettings: - port: number: 443 tls: mode: MUTUAL credentialName: client-credential sni: $DOMAIN EOF
Send an HTTP request to the Kyma project website:
When you send an HTTP request, Istio uses egress to redirect the HTTPS to the website.
Shellkubectl exec -n $NAMESPACE "$SOURCE_POD" -c curl -- curl -ik -X GET http://$DOMAIN/headersIf successful, you get the
200response code from the workload containing headers. One of these headers should be"X-Forwarded-Client-Cert": ["xxx"].Check the logs of the Istio egress Gateway:
Shellkubectl logs -l istio=egressgateway -n istio-systemIf successful, the logs contain the request made by the egress Gateway:
Code{"authority":"{YOUR_DOMAIN}":"outbound|443||{YOUR_DOMAIN}",[...]}
By default, Istio cannot securely enforce that egress traffic is routed through the Istio egress Gateway. It only enables the flow through sidecar proxies.
However, you can use Kubernetes NetworkPolicies to restrict namespace traffic, so it only passes through the Istio egress Gateway. NetworkPolicies are the Kubernetes method for enforcing traffic rules within a namespace.
Support for NetworkPolicies depends on the Kubernetes CNI plugin used in the cluster. By default, SAP BTP, Kyma runtime uses the CNI configuration provided and managed by Gardener, which supports NetworkPolicies. However, if youโve made any changes, make sure to check the relevant documentation.
In Gardener-based clusters, such as SAP BTP, Kyma runtime, the Network Policy restricting DNS traffic may not work as expected. It is due to the local DNS service used in discovery working outside the CNI. In such cases, define the IP CIDR of the
kube-dnsservice in the NetworkPolicy’s ipBlock section to allow proper DNS resolution.
Fetch the IP address of the
kube-dnsService:Shell/Bashexport KUBE_DNS_ADDRESS=$(kubectl get svc -n kube-system kube-dns -o jsonpath='{.spec.clusterIP}')Create a NetworkPolicy with the fetched IP address in the ipBlock section. The NetworkPolicy allows only egress traffic to the Istio egress Gateway, blocking all other egress traffic.
Shell/Bashkubectl apply -f - <<EOF apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: network-policy-allow-egress-traffic namespace: ${NAMESPACE} spec: egress: - ports: - port: 53 protocol: UDP to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system - ipBlock: cidr: ${KUBE_DNS_ADDRESS}/32 - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: istio-system podSelector: {} policyTypes: - Egress EOFSend an HTTPS request to the Kyma project website:
Shell/Bashkubectl exec -n "$NAMESPACE" "$SOURCE_POD" -c curl -- curl -sSL -o /dev/null -D - https://kyma-project.ioIf successful, you get a response from the website similar to this one:
Shell/BashHTTP/2 200 accept-ranges: bytes age: 203 ...Send an HTTPS request to an external website:
Shell/Bashkubectl exec -n "$NAMESPACE" "$SOURCE_POD" -c curl -- curl -sSL -o /dev/null -D - https://www.google.comThe request should fail with an error message similar to this one:
Shell/Bashcurl: (35) Recv failure: Connection reset by peer command terminated with exit code 35
You have successfully secured the egress traffic in your namespace using Istio egress Gateway and Kubernetes NetworkPolicies.
Resources
Discussion
Share feedback on this tutorial or join the conversation in SAP Community.