Istio secures communication between microservices without microservices requiring any code changes. In Chapter 4, we briefly touched upon the topic of security. We configured transport layer security by exposing our sockshop application over HTTPS. We created certificates and configured the Istio Ingress gateway to bind those certificates to hostnames in SIMPLE TLS mode. We also implemented TLS-based security for multiple hosts managed by a single Ingress gateway.
In this chapter, we will dive deeper into some advanced topics of security. We will start by understanding Istio security architecture. We will implement mutual TLS for service communication with other services in the mesh, and we will also implement mutual TLS with downstream clients outside the mesh. We will then perform various hands-on exercises to create custom security policies for authentication and authorization. We will go through these topics in the following order:
Important note
The technical prerequisites for this chapter are the same as Chapters 4 and 5.
In Chapter 3, we discussed how the Istio control plane is responsible for the injection of sidecars and establishing trust so that sidecars can communicate with the control plane securely and security policies are eventually enforced by the sidecar. When deployed in Kubernetes, Istio relies on Kubernetes service accounts to identify the roles of workloads in a Service Mesh. The Istio CA watches the Kubernetes API server for the addition/deletion/modification of any service accounts in the namespace with Istio injection enabled. It creates a key and certificates for each service account and, during Pod creation, the certificate and key are mounted onto the sidecar. The Istio CA is responsible for managing the life cycle of the certificates distributed to the sidecars, including the rotation and management of private keys. Using the Secure Production Identity Framework for Everyone (SPIFFE) format identities, Istio provides a strong identity to each service along with service naming, which represents the role that can be taken up by the identity assigned to the service.
SPIFFE is a set of open source standards for software identity. SPIFFE provides platform-agnostic interoperable software identities along with interfaces and documents required to obtain and validate cryptographic identity in a fully automated fashion.
In Istio, each workload is automatically assigned an identity represented in the X.509 certificate format. The creation and signing of certificate signing request (CSRs) are managed by the Istio control plane, as discussed in Chapter 3. The X.509 certificate follows the SPIFFE format.
Let’s redeploy the envoydummy service and inspect the envoydummy Pods:
$ kubectl apply -f Chapter6/01-envoy-dummy.yaml $ istioctl proxy-config all po/envoydummy-2-7488b58cd7-m5vpv -n utilities -o json | jq -r '.. |."secret"?' | jq -r 'select(.name == "default")' | jq -r '.tls_certificate.certificate_chain.inline_bytes' | base64 -d - | step certificate inspect --short X.509v3 TLS Certificate (RSA 2048) [Serial: 3062...1679] Subject: spiffe://cluster.local/ns/utilities/sa/default Issuer: Valid from: 2022-09-11T22:18:13Z to: 2022-09-12T22:20:13Z
step CLI
You will need to install step CLI to be able to run the preceding command. To install it, please follow the documentation at https://smallstep.com/docs/step-cli.
In the output of the preceding command, you will notice that the Subject Alternative Name (SAN) is spiffe://cluster.local/ns/utilities/sa/default. This is the SPIFFE ID, which functions as the unique name:
The value default for service accounts comes from the service account attached to the workload. In our example of envoydummy, we didn’t associate any service accounts so, by default, Kubernetes associated the default service account. You can find the service account name associated with a Pod using the following command:
kubectl get po/envoydummy-2-7488b58cd7-m5vpv -n utilities -o json | jq .spec.serviceAccountName "default"
You will notice that default is the default name for service accounts associated with all Pods in all namespaces, such as sock-shop, utilities, and so on. Kubernetes creates a service account named default in every namespace:
% kubectl get sa -n utilities NAME SECRETS AGE default 1 13d % kubectl get sa -n sock-shop NAME SECRETS AGE default 1 27d
Kubernetes service accounts
A service account is an identity assigned to workloads in Kubernetes. When processes running inside a workload try to access other Kubernetes resources, they are identified and authenticated as per the details of their service accounts. You can find more details about service accounts at https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/.
Secure Naming is a technology that decouples the name of services from the identities the services are running as. In the previous example, spiffe://cluster.local/ns/utilities/sa/default is the identity of the service presented during mutual TLS presented by the istio-proxy sidecar in envoydummy-2-7488b58cd7-m5vpv workload. From the SPIFFE ID, the other party (istio-proxy in another Pod) in the MTLS session can validate that the endpoint has the identity of a service account named default in the utilities namespace. The Istio control plane propagates the secure naming information to all sidecars in the mesh and during mutual TLS, the sidecar not only verifies that the identity is correct but also that the respective service is assuming the correct identity.
The following diagram summarizes the Istio security architecture:
Figure 6.1 – Istio security architecture
These are the key concepts to remember:
Reminder
Make sure to clean up Chapter6/01-envoy-dummy.yaml to avoid conflict in upcoming exercises.
In the next section, we will read about how to secure data in transit between microservices in a Service Mesh.
Mutual TLS (mTLS) is a technique for authenticating two parties at each end of a network connection. Through mTLS, each party can verify that the other party is what they are claiming to be. Certificate authorities play a critical role in mTLS, and hence we had the previous section on Istio security architecture describing certificate authorities and secure naming in Istio.
mTLS is one of the most frequently used authentication mechanisms for implementing the zero-trust security framework, in which no party trusts another party by default, irrespective of where the other party is placed in the network. Zero trust assumes that there are no traditional network edges and boundaries and hence every party needs to be authenticated and authorized. This helps to eliminate many security vulnerabilities that arise because of the assumption-based trust model.
In the following two subsections, we will look at how Istio helps you implement mTLS for service-to-service authentication inside a mesh, also called east-west traffic, and mTLS between client/downstream systems that are outside the mesh, with services in the mesh called north-south communication.
Istio provides service-to-service authentication by using mTLS for transport authentication. During traffic processing, Istio performs the following:
Istio provides the following two options when implementing mTLS:
Mutual TLS traffic can be established between clients outside of a mesh trying to access a workload within the mesh, as well as clients within the mesh trying to access other workloads in the mesh. For the former, we will discuss the details in the next section. For the latter set of clients, we will go through some examples in this section.
Let’s set up service-to-service communication using mTLS:
$ kubectl apply -f Chapter6/01-httpbin-deployment.yaml
Most of the config in this deployment is the usual, except that we have also created a default Kubernetes service account called httpbin in the Chapter6 namespace:
apiVersion: v1 kind: ServiceAccount metadata: name: httpbin namespace: chapter6
The httpbin identity is then assigned to an httpbin Pod by following these specs:
Spec: serviceAccountName: httpbin containers: - image: docker.io/kennethreitz/httpbin imagePullPolicy: IfNotPresent name: httpbin ports: - containerPort: 80
$ kubectl apply -f Chapter6/01-curl-deployment.yaml
Make sure the istio-injection label is not applied. If it is, you can remove it using the following command:
$ kubectl label ns utilities istio-injection-
$ kubectl exec -it curl -n utilities – curl -v http://httpbin.chapter6.svc.cluster.local:8000/get { "args": {}, "headers": { "Accept": "*/*", "Host": "httpbin.chapter6.svc.cluster.local:8000", "User-Agent": "curl/7.87.0-DEV", "X-B3-Sampled": "1", "X-B3-Spanid": "a00a50536c3ec2f5", "X-B3-Traceid": "49b6942c85c7c1f2a00a50536c3ec2f5" }, "origin": "127.0.0.6", "url": "http://httpbin.chapter6.svc.cluster.local:8000/get"
apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: "httpbin-strict-tls" namespace: chapter6 spec: mtls: mode: STRICT selector: matchLabels: app: httpbin
In the PeerAuthentication policy, we defined the following configuration parameters:
To summarize the configuration, we have created httpbin-strict-tls, which is a PeerAuthentication policy in the Chapter6 namespace. The policy enforces string mTLS for all workloads that have a label of app=httpbin. The configuration is available at Chapter6/02-httpbin-strictTLS.yaml.
$ kubectl apply -f Chapter6/02-httpbin-strictTLS.yaml peerauthentication.security.istio.io/httpbin-strict-tls created
$ kubectl exec -it curl -n utilities – curl -v http://httpbin.chapter6.svc.cluster.local:8000/get * Connected to httpbin.chapter6.svc.cluster.local (172.20.147.104) port 8000 (#0) > GET /get HTTP/1.1 > Host: httpbin.chapter6.svc.cluster.local:8000 > User-Agent: curl/7.87.0-DEV > Accept: */* > * Recv failure: Connection reset by peer * Closing connection 0 curl: (56) Recv failure: Connection reset by peer command terminated with exit code 56
curl is not able to connect because the curl Pod is running in a namespace with Istio injection disabled, whereas the httpbin Pod is running in the mesh with the PeerAuthentication policy enforcing STRICT mTLS. One option is to manually establish an mTLS connection, which is equivalent to modifying your application code to perform mTLS. In this case, as we are trying to simulate service communication within the mesh, we can simply turn on Istio injection and let Istio take care of client-side mTLS as well.
Once the curl Pod is in the RUNNING state, along with the istio-proxy sidecar, you can perform curl on the httpbin service and you will see the following output:
$ kubectl exec -it curl -n utilities -- curl -s http://httpbin.chapter6.svc.cluster.local:8000/get { "args": {}, "headers": { "Accept": "*/*", "Host": "httpbin.chapter6.svc.cluster.local:8000", "User-Agent": "curl/7.85.0-DEV", "X-B3-Parentspanid": "a35412ed46b7ec46", "X-B3-Sampled": "1", "X-B3-Spanid": "0728b578e88b72fb", "X-B3-Traceid": "830ed3d5d867a460a35412ed46b7ec46", "X-Envoy-Attempt-Count": "1", "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/chapter6/sa/ httpbin;Hash=b1b88fe241c557bd1281324b458503274eec3f04b1d439758508842d6d5b7018;Subject="";URI=spiffe://cluster.local/ns/utilities/sa/curl" }, "origin": "127.0.0.6", "url": "http://httpbin.chapter6.svc.cluster.local:8000/get" }
In the response from the httpbin service, you will notice all the headers that were received by the httpbin Pod. The most interesting header is X-Forwarded-Client-Cert, also called XFCC. There are two parts of the XFCC header value that shed light on mTLS:
There is also Hash, which is the SHA256 digest of istio-proxy’s client certificate of the httpbin Pod.
You can selectively apply mTLS configuration at the port level also. In the following configuration, we are implying that mTLS is enforced strictly for all ports except port 8080, which should allow permissive connections. The configuration is available at Chapter6/03-httpbin-strictTLSwithException.yaml:
apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: "httpbin-strict-tls" namespace: chapter6 spec: portLevelMtls: 8080: mode: PERMISSIVE 8000: mode: STRICT selector: matchLabels: app: httpbin
So, in this section, we learned how to perform mTLS between services inside the mesh. mTLS can be enabled at the service level as well as at the port level. In the next section, we will read about performing mTLS with clients outside the mesh.
Reminder
Make sure to clean up Chapter6/01-httpbin-deployment.yaml, Chapter6/01-curl-deployment.yaml and Chapter6/02-httpbin-strictTLS.yaml to avoid conflict in upcoming exercises.
For clients outside the mesh, Istio supports mTLS with the Istio Ingress gateway. In Chapter 5, we configured HTTPS at the Ingress gateway. In this section, we will extend that configuration to also support mTLS.
We will now configure mTLS for the httpbin Pod. Notice that the first five steps are very similar to steps 1-5 of Exposing Ingress over HTTPS of Chapter 5. The steps are as follows:
$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=sock Inc./CN=sock.inc' -keyout sock.inc.key -out sock.inc.crt
$ openssl req -out httpbin.org.csr -newkey rsa:2048 -nodes -keyout httpbin.org.key -subj "/CN=httpbin.org/O=sockshop.inc" Generating a 2048 bit RSA private key .........+++ ..+++ writing new private key to 'httpbin.org.key'
$ openssl x509 -req -sha256 -days 365 -CA sock.inc.crt -CAkey sock.inc.key -set_serial 0 -in httpbin.org.csr -out httpbin.org.crt Signature ok subject=/CN=httpbin.org/O=sockshop.inc Getting CA Private Key
$ kubectl create -n istio-system secret generic httpbin-credential --from-file=tls.key=httpbin.org.key --from-file=tls.crt=httpbin.org.crt --from-file=ca.crt=sock.inc.crt secret/httpbin-credential created
tls: mode: MUTUAL credentialName: httpbin-credential
$ kubectl apply -f Chapter6/02-httpbin-deployment-MTLS.yaml
$ openssl req -out bootstrapistio.packt.com.csr -newkey rsa:2048 -nodes -keyout bootstrapistio.packt.com.key -subj "/CN= bootstrapistio.packt.com/O=packt.com" $ openssl x509 -req -sha256 -days 365 -CA sock.inc.crt -CAkey sock.inc.key -set_serial 0 -in bootstrapistio.packt.com.csr -out bootstrapistio.packt.com.crt
% curl -v -HHost:httpbin.org --connect-to "httpbin.org:443:a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com" --cacert sock.inc.crt --cert bootstrapistio.packt.com.crt --key bootstrapistio.packt.com.key https://httpbin.org:443/get
Reminder
Don’t forget to clean up using kubectl delete -n istio-system secret httpbin-credential and kubectl delete -f Chapter6/02-httpbin-deployment-MTLS.yaml.
Like service-to-service authentication, Istio can also authenticate an end user or validate that an end user has been authenticated based on assertions presented by the end user. The RequestAuthentication policy is used to specify what authentication methods are supported by a workload. This policy identifies the authenticated identity but doesn’t enforce whether the request should be allowed or denied. Rather, it provides information about the authenticated identity to the authorization policy, which we will go through in the next section. In this section, we will learn how to make use of the Istio RequestAuthentication policy to validate an end user who has been authenticated by Auth0 and is providing a bearer token as security credentials to Istio. If you are not familiar with OAuth then you can read more about it at https://auth0.com/docs/authenticate/protocols/oauth.
We will follow the hands-on steps to configure Auth0 and perform an OAuth flow while at the same time demystifying all that is happening under the hood:
Figure 6.2 – Auth0 signup
Figure 6.3 – Create application in Auth0
Figure 6.4 – Create an API in Auth0
Figure 6.5 – API scopes in Auth0
Figure 6.6 – Enable RBAC for the API in Auth0
Figure 6.7 – Grant permission to the application in Auth0
Figure 6.8 – Quickstart example to get the access token in Auth0
Copy the curl string, including client_id, client_secret, and so on, and with this, we have completed all the steps in Auth0.
Now, using the curl string you copied in the previous steps, get the access token from the terminal:
$ curl --request POST --url https://dev-0ej7x7k2.us.auth0.com/oauth/token --header 'content-type:application/json' --data '{"client_id":"XXXXXX-id","client_secret":"XXXXX-secret"," "audience":"http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/","grant_type":"client_credentials"}' {"access_token":"xxxxxx-accesstoken" "scope":"read:dummyData","expires_in":86400,"token_type":"Bearer"}%
Once we have received the access token, we will apply the RequestAuthentication policy. The RequestAuthentication policy specifies the details of how to validate the JWT provided during authentication. Following is the RequestAuthentication policy:
apiVersion: security.istio.io/v1beta1 kind: RequestAuthentication metadata: name: "auth0" namespace: chapter6 spec: selector: matchLabels: name: envoydummy jwtRules: - issuer: "https://dev-0ej7x7k2.us.auth0.com/" jwksUri: "https://dev-0ej7x7k2.us.auth0.com/.well-known/jwks.json"
In the preceding configuration, also available in Chapter6/01-requestAuthentication.yaml, we are declaring a RequestAuthentication policy with the name auth0 in the chapter6 namespace with the following specifications:
Figure 6.9 – Application domain
When using the RequestAuthentication policy, it is best practice to also configure the RequestAuthentication and AuthorizationPolicy together and enforce a rule that any request with an empty principal should not be allowed. Following is an example of a sample authorization policy – you will read more about authorization policies in the next section:
apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: auth0-authz namespace: chapter6 spec: action: DENY selector: matchLabels: name: envoydummy rules: - from: - source: notPrincipals: ["*"]
In the previous section, we configured a RequestAuthentication policy, which verifies a JWT token against the issuer and JWK details as per the JWKS location. We configured Auth0 as the authentication provider and the one that generates the bearer token. In this section, we will learn about how to make use of the information provided by authentication policies such as peer authentication and request authentication to authorize client access to the server (the requested resource, Pod, workload, service, etc.).
We will first focus on implementing an authorization policy in conjunction with the RequestAuthentication policy from the previous section.
To let curl access the envoy dummy using the access token issued by Auth0, we need to create an AuthorizationPolicy:
apiVersion: "security.istio.io/v1beta1" kind: "AuthorizationPolicy" metadata: name: "envoydummy-authz-policy" namespace: utilities spec: action: ALLOW selector: matchLabels: name: envoydummy rules: - when: - key: request.auth.claims[permissions] values: ["read:profile"]
AuthorizationPolicy contains the following data:
In this example, we are defining an authorization policy that allows access to Pods with the label name:envoydummy if the request contains an authenticated JWT token with a claim of read:profile.
Before we apply the changes, make sure that you can access the dummy data and make sure you have the Ingress gateway and envoydummy Pods deployed in the utilities namespace – if not, you can do that by applying the following commands:
$ curl -Hhost:mockshop.com http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/ V1----------Bootstrap Service Mesh Implementation with Istio----------V1%
Go ahead and apply both of the policies:
% kubectl apply -f Chapter6/01-requestAuthentication.yaml requestauthentication.security.istio.io/auth0 created % kubectl apply -f Chapter6/02-requestAuthorization.yaml authorizationpolicy.security.istio.io/envoydummy-authz-policy created
Check that you are able to access mockshop.com:
% curl -Hhost:mockshop.com http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/ RBAC: access denied
The access is denied because we need to provide a valid access token as part of the request. Copy the access token you got from the previous request and try again in the following fashion:
$ curl -Hhost:mockshop.com -H "authorization: Bearer xxxxxx-accesstoken " http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/ RBAC: access denied%
Although the JWT verification succeeded, the request failed due to RBAC controls. The error is deliberate because instead of providing read:dummyData in Chapter6/02-requestAuthorization.yaml, we provide read:profile. The changes are updated in Chapter6/03-requestAuthorization.yaml. Apply the changes and test the APIs:
% kubectl apply -f Chapter6/03-requestAuthorization.yaml authorizationpolicy.security.istio.io/envoydummy-authz-policy configured $ curl -Hhost:mockshop.com -H "authorization: Bearer xxxxxx-accesstoken " http://a816bb2638a5e4a8c990ce790b47d429-1565783620.us-east-1.elb.amazonaws.com/ V2----------Bootstrap Service Mesh Implementation with Istio----------V2%
To summarize, we did the following, including the previous section:
Next, we will learn how to configure request authorization in conjunction with PeerAuthentication, which we configured in the Service-to-service authentication section.
We will modify the curl Pod to use a different service account, and let’s call it chapter6sa:
apiVersion: v1 kind: ServiceAccount metadata: name: chapter6sa namespace: utilities
As you cannot change the service account of an existing Pod, you need to delete the previous deployment and redeploy with a new service account:
Kubectl delete -f Chapter6/01-curl-deployment.yaml kubectl apply -f Chapter6/02-curl-deployment.yaml
You can check that the curl Pod is running with the identity of the chapter6sa service account. After this, let’s create an AuthorizationPolicy to allow a request to the httpbin Pod if the principal of the requestor is cluster.local/ns/utilities/sa/curl:
apiVersion: "security.istio.io/v1beta1" kind: "AuthorizationPolicy" metadata: name: "httpbin-authz-policy" namespace: chapter6 spec: action: ALLOW selector: matchLabels: app: httpbin rules: - from: - source: principals: ["cluster.local/ns/utilities/sa/curl"] to: - operation: methods: ['*']
Previously, we looked at AuthorizationPolicy and you will be familiar with most of the configuration in this example. In this example, we are building AuthorizationPolicy on top of peer authentication rather than request authentication. The most interesting part is the source field in the rules section. In the source configuration, we define the source identities of the request. All fields in the source request need to match for the rule to be successful.
The following fields can be defined in the source configuration:
Go ahead and apply the configuration and test whether you are able to curl to httpbin:
$ kubectl apply -f Chapter6/04-httpbinAuthorizationForSpecificSA.yaml authorizationpolicy.security.istio.io/httpbin-authz-policy configured $ kubectl exec -it curl -n utilities -- curl -v http://httpbin.chapter6.svc.cluster.local:8000/headers * Trying 172.20.152.62:8000... * Connected to httpbin.chapter6.svc.cluster.local (172.20.152.62) port 8000 (#0) > GET /headers HTTP/1.1 > Host: httpbin.chapter6.svc.cluster.local:8000 > User-Agent: curl/7.85.0-DEV > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 403 Forbidden < content-length: 19 < content-type: text/plain < date: Tue, 20 Sep 2022 02:20:39 GMT < server: envoy < x-envoy-upstream-service-time: 15 < * Connection #0 to host httpbin.chapter6.svc.cluster.local left intact RBAC: access denied%
Istio denies the request from the curl Pod to httpbin because the peer certificate presented by the curl Pod contains cluster.local/ns/utilities/sa/chapter6sa instead of cluster.local/ns/utilities/sa/curl as the principal. Although the curl Pod is part of the mesh and contains a valid certificate, it is not authorized to access the httpbin Pod.
Go ahead and fix the problem by assigning the correct service account to the curl Pod.
Tip
You can use the following commands to fix the problem:
$ kubectl delete -f Chapter6/02-curl-deployment.yaml $ kubectl apply -f Chapter6/03-curl-deployment.yaml
We will implement one more authorization policy, but this time the policy will enforce that using the utilities or curl service account, the requestor can access only /headers of httpbin.
Following is the authorization policy:
apiVersion: "security.istio.io/v1beta1" kind: "AuthorizationPolicy" metadata: name: "httpbin-authz-policy" namespace: chapter6 spec: action: ALLOW selector: matchLabels: app: httpbin rules: - from: - source: requestPrincipals: ["cluster.local/ns/utilities/sa/curl"] - to: - operation: methods: ["GET"] paths: ["/get"]
In this policy, we have defined the HTTP method and HTTP paths in the to field on the rule. The to field contains a list of operations on which the rules will be applied. The operation field supports the following parameters:
Apply the changes:
kubectl apply -f Chapter6/05-httpbinAuthorizationForSpecificPath.yaml
And then try to access httpbin:
kubectl exec -it curl -n utilities -- curl -X GET -v http://httpbin.chapter6.svc.cluster.local:8000/headers …… RBAC: access denied%
Access to the request is denied because the authorization policy only allows the /get request made using the HTTP GET method. The following is the correct request:
kubectl exec -it curl -n utilities -- curl -X GET -v http://httpbin.chapter6.svc.cluster.local:8000/get
This concludes our lesson on how you can build custom policies for performing request authentication and request authorization. To get more familiar with them, I suggest going through the examples in this chapter a few times and maybe building your own variations to learn how to use these policies effectively.
In this chapter, we read about how Istio provides authentication and authorization. We also read about how to implement service-to-service authentication using mutual TLS within a Service Mesh using the PeerAuthentication policy, as well as mutual TLS with clients external to a Service Mesh by using the mutual TLS mode at the Ingress gateway. We then read about end user authentication using the RequestAuthentication policy. We configured Auth0 to gain some real-life experience in using authentication and identity providers.
To finish off, we then read about AuthorizationPolicy and how it can be used to enforce various authorization checks to ensure that the authenticated identity is authorized to access the requested resources.
In the next chapter, we will read about how Istio helps in making microservices observable and how various observability tools and software can be integrated with Istio.