Back to microservices with Istio. Part 3

Original author: Rinor Maloku
  • Transfer
  • Tutorial


Note perev. : The first part of this series was devoted to introducing Istio's capabilities and demonstrating them in action, the second to finely tuned routing and network traffic management. Now we will talk about security: to demonstrate the basic functions associated with it, the author uses the Auth0 identity service, however, other providers can be configured by analogy with it.

We set up a Kubernetes cluster in which Istio and the Sentiment Analysis microservice application example were deployed - this is how Istio's capabilities were demonstrated.

Using Istio, we were able to keep the services small in size because they do not need to implement such “layers” as retries, Retouts, Timeouts, Circuit Breakers, Tracing, Monitoring . In addition, we used advanced testing and deployment techniques: A / B testing, mirroring and canary rollouts.



In the new material, we will deal with the final layers on the way to business value: authentication and authorization - and in Istio this is a real pleasure!

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 point of view to make these topics fascinating and even more so that they will inspire you as well?

The answer is simple: Istio transfers responsibility for these features from your services to Envoy proxies. By the time the requests reach the services, they are already authenticated and authorized, so you just have to write code useful for business.

That sounds good? Let's look inside!

Authentication with Auth0


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

First, go to the Auth0 Portal with your account, create a tenant (tenant - “tenant”, logical isolation unit, for more details see the documentation - approx. Transl.) And go to Applications> Default App , choosing Domain , as shown in the screenshot below :



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

Having such a resource, Pilot (one of the three basic components of Control Plane in Istio - approx. Transl.) Configures Envoys to authenticate requests before redirecting them to services: sa-web-appand sa-feedback. At the same time, the configuration does not apply to the service Envoys sa-frontend, allowing us to leave the frontend unauthenticated. To apply a policy, execute the command:

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

Return to the page and make a request - you will see that it ends in 401 Unauthorized status . Now we redirect front-end users to authentication with Auth0.

Request Authentication 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: The



important information here is Identifier , which we will use in the script later. We write it out for ourselves like this:

  • Audience : {YOUR_AUDIENCE}

The remaining details we need are located on the Auth0 Portal in the Applications section - select Test Application (it is created automatically with the API).

Here we write:

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

Scroll in the Test Application to the Allowed Callback URLs text box (allowed URLs for the callback), in which we indicate the URL where 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 logging out), add:

http://{EXTERNAL_IP}/logout

Let's move on to the frontend.

Frontend Update


Switch to the auth0repository branch [istio-mastery]. In this thread, the frontend code has been changed to redirect users to Auth0 for authentication and use the JWT token in requests for other services. The latter is 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 translate the frontend to use tenant's data in Auth0, open sa-frontend/src/services/Auth.jsand replace the values ​​that we wrote above ( Auth.js ) in it:

const Config = {
    clientID: '{YOUR_CLIENT_ID}',
    domain:'{YOUR_DOMAIN}',
    audience: '{YOUR_AUDIENCE}',
    ingressIP: '{EXTERNAL_IP}' // Используется для редиректа после аутентификации
}

The application is ready. Indicate 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 app! You will be redirected to Auth0, where you need to log in (or register), after which you will be sent back to the page from which already authenticated requests will be made. If you try the curl commands mentioned in the first parts of the article, you will get the 401 Status Code , which indicates that the request is not authorized.

Let's take the next step - authorize requests.

Authorization with Auth0


Authentication allows us to understand who the user is, but in order to find out what he has access to, authorization is required. Istio offers tools for this as well.

As an example, we will 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.


The concept of authorization

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

Installation and configuration of Auth0 Authorization


On the Auth0 portal, go to Extensions and install Auth0 Authorization . After installation, go to the Authorization Extension , and there - to the tenant’s configuration by clicking on the top right and selecting the appropriate menu option (Configuration) . Activate Groups and click on the Publish rule button .



Group creation


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

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

Add Group Claim to Access Token


Users are added to groups, however this information should be reflected in access tokens. In order to comply with OpenID Connect and at the same time return the groups we need, the token will need to add its custom claim . It is implemented through the rules of Auth0.

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



Copy the code below and save it as the new Add Group Claim rule ( 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 user group 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 Rules page and verify 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 receives the auth0-authorization-extension rule and is then added as a claim by the second rule. The result is such an access token:

{
 "https://sa.io/group": "Moderators",
 "iss": "https://sentiment-analysis.eu.auth0.com/",
 "sub": "google-oauth2|196405271625531691872"
 // [сокращено для наглядности]
}

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

Istio authorization configuration


For authorization to work, you must enable RBAC for Istio. To do this, use the following configuration:

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 - enable RBAC only for services and namespaces listed in the field Inclusion;
  • 2 - list the list of our services.


We apply the configuration with this 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 prohibited and will lead to an answer RBAC: access denied. Now allow access to authorized users.

Access configuration for regular users


All users must have access to the SA-Frontend and SA-WebApp services. Implemented using the following Istio resources:

  • ServiceRole - defines the rights that the user has;
  • ServiceRoleBinding - Determines to whom this ServiceRole belongs.

For ordinary users, 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-bindingServiceRole we apply to all visitors to 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 gain access to SA WebApp? No, the policy will verify the validity of the JWT token.

Apply configuration:

$ 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 has a claim https://sa.io/groupwith a 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" 

Apply configuration:

$ 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 envoys, it may take a couple of minutes for the authorization rules to take effect. After that, you can make sure that users and moderators have different access levels.

Conclusion on this part


Well, 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 in order to achieve fine-grained control over authentication and authorization of end-user access to services.

In addition, we took care of these issues out of our services in envoy, achieving:

  • Reducing the amount of sample code that may include security problems and bugs;
  • reducing the number of stupid situations in which one endpoint turned out to be accessible from the outside and forgot to report it;
  • eliminating the need to update all services each time a new role or right is added;
  • that new services remain simple, secure and fast.

Conclusion


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

The article (in three parts) provided basic knowledge and ready-made practical instructions for starting work with Istio in real projects.

PS from the translator


Read also in our blog:


Also popular now: