Back to microservices with Istio. Part 3

Back to microservices with Istio. Part 3

Note: translation.: The first part This cycle was dedicated to familiarizing us with the capabilities of Istio and demonstrating them in action. Two — finely tuned routing and network traffic management. Now, we will talk about security: to demonstrate the associated basic functions, the author uses the identity service Auth0, although similar setups can be made with other providers.

We configured a Kubernetes cluster where we deployed Istio and an example microservice application, Sentiment Analysis, thus showcasing Istio's capabilities.

With Istio, we managed to keep the services lightweight, as they do not require the implementation of layers such as retries, timeouts, circuit breakers, tracing, and monitoring. Additionally, we employed advanced testing and deployment techniques: A/B testing, mirroring, and canary releases.

Back to microservices with Istio. Part 3

In this new material, we will dive into the final layers on the path to business value: authentication and authorization — and in Istio, it’s a sheer delight!

Authentication and Authorization in Istio

I would never have believed that I would be inspired by authentication and authorization. What can Istio offer from a technological perspective to make these topics engaging and even inspire you?

The answer is simple: Istio shifts the responsibility for these functionalities from your services to the Envoy proxy. By the time requests reach the services, they are already authenticated and authorized, allowing you to focus on writing business-relevant code.

Sounds good? Let's take a look inside!

Authentication with Auth0

We will use Auth0 as the server for identity management and access. It has a trial version, is intuitive to use, and I simply like it. However, the same principles can be applied to any other OpenID Connect implementation: KeyCloak, IdentityServer, and many others.

To get started, go to Auth0 Portal with your account, create a tenant (tenant — a logical unit of isolation, see more in the documentation - translator's note) and go to Applications > Default App, selecting Domain, as shown in the screenshot below:

Back to microservices with Istio. Part 3

Specify this domain in the file resource-manifests/istio/security/auth-policy.yaml (source):

apiVersion: authentication.istio.io/v1alpha1
kind: Policy
metadata:
  name: auth-policy
spec:
  targets:
  - name: sa-web-app
  - name: sa-feedback
  origins:
  - jwt:
      issuer: "https://{YOUR_DOMAIN}/"
      jwksUri: "https://{YOUR_DOMAIN}/.well-known/jwks.json"
  principalBinding: USE_ORIGIN

With this resource, Pilot (one of the three core components of the Control Plane in Istio — ed.) configures the Envoys to authenticate requests before routing them to the services: sa-web-app and sa-feedback. At the same time, the configuration does not apply to the service Envoys, sa-frontendallowing us to keep the frontend unauthenticated. To apply the policy, run the command:

$ kubectl apply -f resource-manifests/istio/security/auth-policy.yaml
policy.authentication.istio.io “auth-policy” created

Go back to the page and make a request — you will see it end with status 401 Unauthorized. Now let's redirect frontend users to authenticate with Auth0.

Authenticating requests with Auth0

To authenticate end-user requests, you need to create an API in Auth0 that will represent authenticated services (reviews, details, and ratings). To create an API, go to Auth0 Portal > APIs > Create API and fill out the form:

Back to microservices with Istio. Part 3

An important piece of information here is Identifier, which we will later use in the script. Let's jot it down as:

  • Audience: {YOUR_AUDIENCE}

The remaining necessary details are located on the Auth0 Portal in the section Applications — select Test Application (created automatically along with the API).

Here we will write down:

  • Domain: {YOUR_DOMAIN}
  • Client Id: {YOUR_CLIENT_ID}

Scroll to the Test Application until you find the text field Allowed Callback URLs (allowed URLs for callback), where we will specify the URL to which the call should be sent after authentication is complete. In our case, it is:

http://{EXTERNAL_IP}/callback

And for Allowed Logout URLs (allowed URLs for logout) we will add:

http://{EXTERNAL_IP}/logout

Let's move to the frontend.

Frontend update

Switch to the branch. auth0 repository [istio-mastery]. In this branch, the frontend code has been modified to redirect users to Auth0 for authentication and to utilize the JWT token in requests to the other services. This has been implemented as follows (App.js):

analyzeSentence() {
    fetch('/sentiment', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${auth.getAccessToken()}` // Access Token
        },
        body: JSON.stringify({ sentence: this.textField.getValue() })
    })
        .then(response => response.json())
        .then(data => this.setState(data));
}

To switch the frontend to using tenant data in Auth0, open sa-frontend/src/services/Auth.js and replace the values in it that we recorded above (Auth.js):

const Config = {
    clientID: '{YOUR_CLIENT_ID}',
    domain:'{YOUR_DOMAIN}',
    audience: '{YOUR_AUDIENCE}',
    ingressIP: '{EXTERNAL_IP}' // Used for redirect after authentication
}

The application is ready. Enter your Docker ID in the commands below when building and deploying the changes made:

$ docker build -f sa-frontend/Dockerfile 
 -t $DOCKER_USER_ID/sentiment-analysis-frontend:istio-auth0 
 sa-frontend

$ docker push $DOCKER_USER_ID/sentiment-analysis-frontend:istio-auth0

$ kubectl set image deployment/sa-frontend 
 sa-frontend=$DOCKER_USER_ID/sentiment-analysis-frontend:istio-auth0

Try the application! You will be redirected to Auth0, where you need to log in (or sign up), after which you will be sent back to the page from which authenticated requests will be made. If you try the commands mentioned in the earlier parts of the article with curl, you will get the code 401 Status Code, indicating that the request is unauthorized.

Let's take the next step — authorize the requests.

Authorization with Auth0

Authentication allows us to know who the user is, but to find out what they have access to, authorization is required. Istio provides tools for this as well.

As an example, let's create two user groups (see the diagram below):

  • Users (users) — with access only to the SA-WebApp and SA-Frontend services;
  • Moderators (moderators) — with access to all three services.

Back to microservices with Istio. Part 3
The concept of authorization

To create these groups, we will use the Auth0 Authorization extension and use Istio to provide them with different levels of access.

Installing and configuring Auth0 Authorization

On the Auth0 portal, go to the extensions (Extensions) and install Auth0 Authorization. After installation, go to Authorization Extension, and then to the tenant configuration by clicking on the top right and selecting the corresponding menu option (Configuration). Activate groups (Groups) and click the publish rule button (Publish rule).

Back to microservices with Istio. Part 3

Creating groups

In the Authorization Extension, go to Groups and create a group Moderators. Since we will consider all authenticated users as regular users, there's no need to create an additional group for them.

Select the group Moderators, click on Add Members, add your main account. Leave some users without any group to ensure their access is denied. (New users can be created manually via Auth0 Portal > Users > Create User.)

Add Group Claim to Access Token

Users have been added to the groups, but this information must also be reflected in the access tokens. To comply with OpenID Connect and at the same time return the groups we need, the token will need to add its custom claim. This is implemented through Auth0 rules.

To create a rule, go to the Auth0 Portal to Rules, click on Create Rule and select an empty rule from the templates.

Back to microservices with Istio. Part 3

Copy the code below and save it as a new rule Add Group Claim (namespacedGroup.js):

function (user, context, callback) {
    context.accessToken['https://sa.io/group'] = user.groups[0];
    return callback(null, user, context);
}

Note: this code takes the first group of the user defined in the Authorization Extension and adds it to the access token as a custom claim (under its namespace, as required by Auth0).

Go back to the page Rules and check that you have two rules written in the following order:

  • auth0-authorization-extension
  • Add Group Claim

The order is important because the group field asynchronously gets the rule auth0-authorization-extension and then is added as a claim by the second rule. As a result, the access token looks like this:

{
 "https://sa.io/group": "Moderators",
 "iss": "https://sentiment-analysis.eu.auth0.com/",
 "sub": "google-oauth2|196405271625531691872"
 // [truncated for clarity]
}

Now you need to configure the Envoy proxy to check user access, where the group will be pulled from the claim (https://sa.io/group) in the returned access token. This is a topic for the next section of the article.

Authorization Configuration in Istio

To enable authorization, you need to turn on RBAC for Istio. We will use the following configuration for this:

apiVersion: "rbac.istio.io/v1alpha1"
kind: RbacConfig
metadata:
  name: default
spec:
  mode: 'ON_WITH_INCLUSION'                     # 1
  inclusion:
    services:                                   # 2
    - "sa-frontend.default.svc.cluster.local"
    - "sa-web-app.default.svc.cluster.local"
    - "sa-feedback.default.svc.cluster.local" 

Explanations:

  • 1 — RBAC is enabled only for the services and namespaces listed in the Inclusion;
  • 2 — we list our services.

Apply the configuration with the following command:

$ kubectl apply -f resource-manifests/istio/security/enable-rbac.yaml
rbacconfig.rbac.istio.io/default created

Now all services require role-based access control. In other words, access to all services is denied and will result in the response RBAC: access denied. Now let's allow access to authorized users.

Access Configuration for Regular Users

All users should have access to the SA-Frontend and SA-WebApp services. This is implemented using the following Istio resources:

  • ServiceRole defines the rights that the user has;
  • ServiceRoleBinding determines to whom this ServiceRole applies.

For regular users, we will allow access to certain services (servicerole.yaml):

apiVersion: "rbac.istio.io/v1alpha1"
kind: ServiceRole
metadata:
  name: regular-user
  namespace: default
spec:
  rules:
  - services: 
    - "sa-frontend.default.svc.cluster.local" 
    - "sa-web-app.default.svc.cluster.local"
    paths: ["*"]
    methods: ["*"]

And through regular-user-binding we will apply the ServiceRole to all visitors of the page (regular-user-service-role-binding.yaml):

apiVersion: "rbac.istio.io/v1alpha1"
kind: ServiceRoleBinding
metadata:
  name: regular-user-binding
  namespace: default
spec:
  subjects:
  - user: "*"
  roleRef:
    kind: ServiceRole
    name: "regular-user"

Does "all users" mean that unauthenticated users will also gain access to the SA WebApp? No, the policy checks the validity of the JWT token.

We will apply configurations:

$ kubectl apply -f resource-manifests/istio/security/user-role.yaml
servicerole.rbac.istio.io/regular-user created
servicerolebinding.rbac.istio.io/regular-user-binding created

Access configuration for moderators

For moderators, we want to enable access to all services (mod-service-role.yaml):

apiVersion: "rbac.istio.io/v1alpha1"
kind: ServiceRole
metadata:
  name: mod-user
  namespace: default
spec:
  rules:
  - services: ["*"]
    paths: ["*"]
    methods: ["*"]

But we want such rights only for those users whose access token contains the claim https://sa.io/group with the value Moderators (mod-service-role-binding.yaml):

apiVersion: "rbac.istio.io/v1alpha1"
kind: ServiceRoleBinding
metadata:
  name: mod-user-binding
  namespace: default
spec:
  subjects:
  - properties:
      request.auth.claims[https://sa.io/group]: "Moderators"
  roleRef:
    kind: ServiceRole
name: "mod-user" 

We will apply configurations:

$ kubectl apply -f resource-manifests/istio/security/mod-role.yaml
servicerole.rbac.istio.io/mod-user created
servicerolebinding.rbac.istio.io/mod-user-binding created

Due to caching in the envoys, it may take a couple of minutes for the authorization rules to take effect. After that, you will be able to verify that users and moderators have different access levels.

Conclusion on this part

Seriously: have you ever seen a simpler, effortless, scalable, and secure approach to authentication and authorization?

Only three Istio resources (RbacConfig, ServiceRole, and ServiceRoleBinding) were required to achieve fine-grained control over authentication and authorization of end users' access to services.

In addition, we moved the burden of these issues from our services to the envoys, achieving:

  • a reduction in boilerplate code where security issues and bugs can arise;
  • a decreased likelihood of stupid situations where one endpoint became accessible from the outside and forgot to report it;
  • eliminating the need to update all services whenever a new role or permission is added;
  • ensuring that new services remain simple, secure, and fast.

Output

Istio allows teams to focus their resources on business-critical tasks without adding overhead to services, returning them to a 'micro' status.

The article (in three parts) provided foundational knowledge and a ready-to-use practical guide for getting started with Istio in real projects.

P.S. from the translator

Also read in our blog:

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster