Modal Title
Kubernetes / Security / Service Mesh

Securing Istio Workloads with Auth0

Istio has custom resource definitions for configuring user and service-to-service authentication as well as authorization policies.
Jul 2nd, 2021 9:00am by
Featued image for: Securing Istio Workloads with Auth0
Featured image via Pixabay

Peter Jausovec
Peter is a software engineer and content creator at Tetrate with expertise in distributed systems and cloud native solutions. He is the author of books and blogs on cloud native, Kubernetes and Istio, and is the creator of Istio Fundamentals, a free introductory course on Istio from Tetrate Academy.

Istio comes with a couple of custom resource definitions for configuring user and service-to-service authentication as well as authorization policies. This article takes a stab at explaining access control in Istio, what authentication and authorization means and how the different Istio resources work together to answer the access control question: Can a subject perform an action on an object?

If we translate the above question to the Istio and Kubernetes world, it would be “Can service X perform an action on service Y?”

The three key components from this question are: principal, action and object. Both the principal and object are services in Kubernetes. The action, assuming we are talking about HTTP, is a GET request, a POST, a PUT and so on.

How about authentication (authn) and authorization (authz)?

About Authentication

Authentication is all about the principal, or the service’s identity in our case. Authentication is the act of validating some sort of credential and ensuring that the credential is both valid and trusted. Once the authentication is performed, we have an authenticated principal. Next time you travel and you present your passport or an ID to the customs officer, they authenticate it, ensuring your credential (passport or ID) is valid and trusted.

In Kubernetes, each workload is assigned a unique identity that it uses to communicate with every other workload. The identity is provided to workloads in the form of service accounts. Service account is the identity pods present in the runtime.

Istio uses the X.509 certificate from the service account, and it creates a new identity according to the spec called SPIFFE (Secure Production Identity Framework for Everyone).

The identity in the certificate is encoded in the Subject alternate name field of the certificate, and it looks like this:

spiffe://cluster.local/ns/<pod namespace>/sa/<pod service account>

This brings us to the first Istio resources, the PeerAuthentication resource. PeerAuthentication resource controls the communication between the workloads. Using PeerAuthentication, we can configure the mutual TLS (mTLS) mode that’s used when workloads communicate.

When two services try to communicate, mutual TLS requires both of them to provide certificates to each other so both parties know who they are talking to. If we want to enable strict mutual TLS between services, we can use the PeerAuthentication resource to set the mTLS mode to STRICT.

However, Istio also supports a graceful mode where we can opt into mutual TLS one workload or namespace at the time. This mode is called permissive mode. Permissive mode is enabled by default when you install Istio. With permissive mode enabled, if a client tries to connect to me via mutual TLS, I’ll serve mutual TLS. If the client doesn’t use mutual TLS, I can respond in plain text as well. I am permitting the client to do mTLS or not. Using this mode, you can gradually roll out mutual TLS across your mesh.

Here’s an example of PeerAuthentication resource that enables strict mTLS for all workloads in the default namespace:


To quickly recap: PeerAuthentication talks about how the workloads or services communicate; it isn’t saying anything about end-users. So how could we authenticate users?

RequestAuthentication

We can do that using a resource called RequestAuthentication.

RequestAuthentication is used for end-user authentication, and it verifies the credentials attached to the request. The request-level authentication is done with JSON Web Token (JWT) validation.

So just like we used SPIFFE identity to authenticate the services, we can use JWT tokens to authenticate users.

Let’s explain the RequestAuthentication resource using the example below.


The above RequestAuthentication applies to all workloads in the default namespace that have the app: httpbin label set.

Any request made to these workloads will need a JWT token. The RequestAuthentication resource configures how the token and its signature is authenticated using the provided key set in the jwksUri field. If the request to the selected workload does not contain a valid JWT token, that’s if the token doesn’t conform to those rules, the request will be rejected. On the other hand, if we don’t provide a token at all, the request will not be authenticated.

Once we have an authenticated principal, then we can talk about the second part of the original access control question: “performing an action on an object”. This is what authorization is about.

About Authorization

Authorization is answering the access-control portion of the question. Is an authenticated principal allowed to perform an action on an object? Can user A send a GET request to path/hello to Service A?

Note that although the principal could be authenticated, it might not be allowed to perform an action. Your company ID card might be valid and authentic, but I won’t be able to use it to enter offices of a different company. If we continue with the customs officer metaphor from before, we could say authorization is similar to a visa stamp in your passport.

This brings us to the next point: Having authentication without authorization, and vice-versa, doesn’t do us much. For proper access control, we need both. Let me give you an example: If we only authenticate the principals and we don’t authorize them, they can do whatever they want and perform any actions on any objects. Conversely, if we authorize a request, but we don’t authenticate it, we can pretend to be someone else and perform any actions on any objects again.

AuthorizationPolicy

Armed with the authenticated principal, we can now decide to restrict access based on that. To do that, we are bringing in an AuthorizationPolicy.

The AuthorizationPolicy resource is where we can make use of the principal from the PeerAuthentication policies and from the RequestAuthentication policy. Note that if you want to do a check against a peer, not the user, you can use the “principalsfield, instead of the “requestPrincipals.”

Using that principal, we can decide what is and isn’t allowed.

The most general possible AuthorizationPolicy looks like this:

We’re applying the authorization policy to all workloads in the default namespace that match the selector labels (Have the app:httpbin label set).

In the rules section, we are saying that we are allowing the calls from a source that has any ([“*”]) request principal set.

Note that we aren’t checking for any specific request principal, we only care that a request principal is set. We could set a specific requestPrincipal as well.

With this AuthorizationPolicy, combined with the RequestAuthentication resource, we are guaranteeing that only authenticated requests will reach the httpbin workloads.

Let’s break down the difference scenarios and see how these two resources work together:

  1. No token present in the request: The request will not be authenticated, and there isn’t going to be a principal set. This doesn’t satisfy the requestPrincipals rule we set in the AuthorizationPolicy, hence the request is denied.
  2. Invalid token in the request: If the request contains an invalid token, authentication will fail (RequestAuthentication), and it won’t even reach the AuthorizationPolicy.
  3. Valid token in the request: If the token is valid, authentication succeeds, and the request principal will be set. Since we only allow calls with request principal set (AuthorizationPolicy), the request will reach the workload.

Policy and Request Authentication in Action

Let’s use a simple example to see the AuthorizationPolicy and RequestAuthentication resources in action. This example assumes that you have Istio installed on the cluster and the default namespace is labeled for sidecar injection.

We’ll use a web frontend that features a login functionality that integrates with Auth0. Additionally, the web frontend makes a call to the second workload called “customers to retrieve a list of customers. We’ll use AuthorizationPolicy and RequestAuthentication to allow only authenticated calls to the customers’ workload. If the user is not logged in, the request to the customers’ workload will fail.

Configuring Auth0

Head over to Auth0 to create your account. During the signup process you’ll be prompted to pick a tenant domain. Note that you cannot change the tenant name, but you can always create additional tenants.

I’ve picked “istioweekly” for my tenant name and my full tenant domain is https://istioweekly.us.auth0.com. We’ll use this URL when logging in the users. Store the full tenant domain value in the ISSUER_BASE_URL environment variable.

Follow the steps below to create a new application:

  1. From the Auth0 dashboard, click the Applications link from the sidebar
  2. Click the Create Application button
  3. Name the application “Web frontend”
  4. For the application type, select “Regular Web Applications”
  5. Click Create

From the applications details page, click the Settings tab and save the Client ID and Client Secret value in the following environment variables:


On the Settings tag, scroll down to the Application URIs section. For login and logout functionality to work, we have to set the allowed callback URLs as well as the allowed logout URLs.

Since we’ll be running the application in the Kubernetes cluster, the URL will be the external IP address or a domain name set up to resolve to the IP address. I’ll be using the istioweekly.com domain name and have set the allowed callback URL to https://istioweekly.com/callback and allowed logout URLs to https://istioweekly.com.

(Note: Setting up SSL certificates and domains is out of the scope of this article. You can check out SSL certificates in Istio Ingress Gateway video to learn more about that.)

We also have to create an Auth0 API that exposes the identity functionality and supports OAuth, OpenID connect and SAML. We’ll use this API indirectly from an SDK used in the web frontend application.

  1. From the Auth0 dashboard, expand the Applications in the sidebar
  2. Click the APIs link
  3. Click the Create API button
  4. Name the API “Webfrontend API”
  5. Use “https://web-frontend” as the identifier. Note that this could be any other unique value. This identifier corresponds to the audience we’ll use when configuring the web frontend.
  6. Select RS256 for the signing algorithm
  7. Click Create to create the API

Configuring Web Frontend Application

With Auth0 configured, we can now set the following environment variables and configure the web frontend application:

We’ll store the Auth0 secret and the client ID in a Kubernetes secret and the rest of the configuration in a ConfigMap:


With configuration in place, we can create the Kubernetes deployment and service for the web frontend and customers’ application.


Next, update the host you want to use in the Gateway and VirtualService and deploy them as well.

Note that you can also use the external IP. In that case, just replace the hosts field with “*”, remove the TLS settings and change the port to 80:

Save the above YAML to gateway-vs.yaml and create the resources by running
kubectl apply -f gateway-vs.yaml.

To access the web frontend, you can either use the domain name or get the ingress gateways IP address like this:


If you navigate to the GATEWAY_URL, you’ll see a page that looks like the one in the figure below.

Note there’s data displayed under the Customers title. This is coming from the customer’s workload. The data is displayed because we haven’t deployed any resources that would require users to be logged in to display that data.

We can now create the RequestAuthentication resource that applies to the customer’s workload we deployed. Using this resource, we will require all requests to have a JWT token that conforms to the rules.

For the issuer, we’re using the tenant domain we created when we logged in to Auth0. For the jwksUri, we are pointing to the jwks.json file hosted at the same tenant domain.

Save the above YAML to request-auth.yaml and create it with kubectl apply -f request-auth.yaml. If we reload the page, we won’t see any difference. We’ll get back the same response as earlier. That’s because we haven’t set any authorization policies– i.e., we haven’t decided whether the principal can access the customers’ workload.

To do authorization, we need to deploy an AuthorizationPolicy resource that looks like this:

Save the above YAML to authpolicy.yaml and create the policy using kubectl apply -f authpolicy.yaml. With the AuthorizationPolicy in place, we’re now actually checking if the caller is authenticated or not. We’re doing that through the requestPrincipals field and checking that any value is set.

If we reload the page again, you’ll notice the following error:


This error is returned by the customer’s workload and the 403 HTTP status code means access to the resource is forbidden. If you made a direct request to the customer’s workload, you’d receive a message that says “RBAC: access denied.”

This makes sense, because we haven’t proven who we are — we haven’t attached a JWT token.

Let’s try logging in now. Click the Login link and either sign up for an account or log in with an existing one. After you’ve logged in, you’ll be redirected back to the home page. This time, you’ll notice the same customer data displayed as before. This is because now we are authenticated and because we have an access token, we are authorized to call the customers workload.

If you look at the source code, you’ll notice that we’re attaching the access token to the request whenever we call the customers workload (from routes/index.js):

Conclusion

This blog post explained the difference between authorization and authentication and how these concepts can be used with Istio resources. We’ve shown how to set up an Auth0 tenant, use it to authenticate users and then allow/deny access to services running in the cluster using RequestAuthentication and AuthorizationPolicy.

Resources

Group Created with Sketch.
TNS owner Insight Partners is an investor in: Pragma.
THE NEW STACK UPDATE A newsletter digest of the week’s most important stories & analyses.