mTLS and authorization
Requests between meshed workloads within the cluster are protected by mutual TLS. This means both the server and the client will present a certificate and mutually verify each other’s identity (peer authentication). This makes it possible to apply authorization rules within the mesh, allowing or denying access based on these authentication and request properties. The rules may also contain Layer 7 (HTTP) level properties of the request to control traffic.
If your application is already on the mesh and it only communicates with the ingress, you don’t need to configure anything else.
- Authentication (authN): verifying TLS certificates mutually
- Authorization (authZ): allowing traffic based on authentication and request properties
- Principal: identity defined by a certificate. It always refers the Kubernetes service account that is running the workload in the following format:
cluster.local/ns/<namespace>/sa/<service-account> - Selector: refers to a label to select the workloads to apply the rules on.
applicationNamelabel can be used to apply the rule on all components of an application orapplabel which also contains the deployment, so it’s more specific.
Internal requests should use the cluster internal address of services instead of the fully qualified external domain name, so that requests go directly to the service, skipping the ingress controller.
Within the cluster any
<service-name>.<namespace>(or optionally with thesvc.cluster.localsuffix) will resolve. Theservice-namehere is the actual name of the Kubernetes service, which is by default uses theapplicationName-deploymentpattern. You can list your service names using thekubectl get svc -n <my-namespace>command.Example: GAP Docs uses the
gap-docs.gservice.emarsys.netFQDN externally, but internally it can be reached on thegap-docs-web.cloud-platform(.svc.cluster.local)address.
For applications that should receive internal traffic only, authorization rules can list the principals that can send requests, note that the below rule may also be set on a deployment level authorizationPolicy:
# gap.yaml
name: "name-of-your-application"
namespace: "your-teams-namespace"
defaultAuthorizationPolicy:
rules:
- from:
- source:
principals:
- namespace: rti
serviceAccountName: ["program-scheduler"]
- namespace: cloud-platform
serviceAccountName: ["gap-example-docker"]
See more info on the defaultAuthorizationPolicy docs here
While external traffic through the NGINX Ingress Controller is automatically allowed if ingress is enabled on the application’s deployment, you may override this behaviour by setting a rule with the ingress-nginx principal, which can be a more fine-grained solution. Since these requests are proxied from outside the cluster further authentication and authorization are needed on the application level (ex. Escher). An example AuthorizationPolicy would look similar to this:
# gap.yaml
name: "name-of-your-application"
namespace: "your-teams-namespace"
deployments:
web:
command: ["command", "to", "run", "in", "web2"]
ingress:
enabled: true
authorizationPolicy:
rules:
# for external requests only public
- from:
- source:
principals:
- namespace: ingress-nginx
serviceAccountName: ["ingress-nginx"]
to:
- operation:
paths: ["/api/public/*"]
# rule to allow any Escher signed request through (it's app responsibility to validate Escher signature)
when:
- key: request.headers[x-ems-auth]
values: ["*"]
# for internal requests
- from:
- source:
principals:
- namespace: cloud-platform
serviceAccountName: ["gap-example-docker"]
to:
- operation:
paths:
- "/api/internal/*"
In this case the application should handle requests so that the ones arriving to the /api/public path are sent to Escher validation, while the /api/internal endpoint can be considered as already authenticated and authorized.
An inverted use case, like allow everything but /api/internal to be accessed through ingress can be implemented as follows:
# gap.yaml
name: "name-of-your-application"
namespace: "your-teams-namespace"
deployments:
web:
command: ["command", "to", "run", "in", "web2"]
ingress:
enabled: true
authorizationPolicy:
rules:
# for external requests to any path except internal
- from:
- source:
principals:
- namespace: ingress-nginx
serviceAccountName: ["ingress-nginx"]
to:
- operation:
notPaths: ["/api/internal/*"]
# rule to allow any Escher signed request through (it's app responsibility to validate Escher signature)
when:
- key: request.headers[x-ems-auth]
values: ["*"]
# for internal requests to internal endpoints
- from:
- source:
principals:
- namespace: cloud-platform
serviceAccountName: ["gap-example-docker"]
to:
- operation:
paths: ["/api/internal/*"]
If separating the endpoints by path is not possible (ex. one path is external and internal at the same time), the application can use the mTLS metadata passed down in the x-forwarded-client-cert header to determine the source of the request and decide whether further authentication is needed (see below). If you receive internal requests with Escher authentication (not via ingress), you can define a rule (see above) that allows any traffic with the x-ems-auth header. In this case it is your application’s responsibility to validate the signature.
If the ingress is removed testing your service’s endpoints can be done by kubectl/k9s’ port forwarding feature.
Testing the service mesh auth policies can be done using the gap-cli mtls-proxy command.
If you have set up a defaultAuthorizationPolicy or an authorizationPolicy for your workload, but still need information about the caller in the application level, the mesh passes this info in the x-forwarded-client-cert header, which looks similar to this:
By=spiffe://cluster.local/ns/cloud-platform/sa/gap-example-docker;
Hash=2003bcfaf5b84edc635409daa42fb3df0a30ad679a48529a2e96bfc88d7335be;
Subject=\"\";
URI=spiffe://cluster.local/ns/cloud-platform/sa/default
By: This is the Subject Alternative Name (URI) of the current proxy certificate (the principal of your application).Hash: The SHA 256 digest of the current client certificate.Subject: (n/a) The Subject field of the current client certificate.URI: The URI type Subject Alternative Name field of the current client certificate (refers to the caller’s principal)