Back to microservices with Istio. Part 3

Back to microservices with Istio. Part 3

Note. transl.: The first part this cycle was devoted to getting acquainted with the capabilities of Istio and demonstrating them in action, second - finely tuned routing and network traffic management. Now we will talk about security: to demonstrate the basic functions related to it, the author uses the Auth0 identity service, but other providers can be configured by analogy with it.

We set up a Kubernetes cluster in which we deployed Istio and a sample Sentiment Analysis microservice application to demonstrate the capabilities of Istio.

With the help of Istio, we managed to keep the size of services small, since they do not need to implement such "layers" as connection retries (Retries), timeouts (Timeouts), circuit breakers (Circuit Breakers), tracing (Tracing), monitoring (Monitoring) . In addition, we used advanced testing and deployment techniques: A/B testing, mirroring, and canary rollouts.

Back to microservices with Istio. Part 3

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 does Istio have to offer from a technological point of view to make these topics exciting and even more so that they inspire you too?

The answer is simple: Istio shifts the responsibility for these capabilities from your services to the Envoy proxy. By the time the requests reach the services, they are already authenticated and authorized, so all you have to do is write business-useful code.

Sounds good? Let's take a look inside!

Authentication with Auth0

We will use Auth0 as a server for identity and access management, which has a trial version, which is intuitive to use and just loves me. However, the same principles can be applied to any other OpenID Connect implementations: KeyCloak, IdentityServer and more.

To get started, go to Auth0 Portal with your account, create a tenant (tenant - "tenant", a logical unit of isolation, see details in documentation - approx. transl.) and go to Applications > Default Appchoosing Domainas 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 such a resource, Pilot (one of the three basic components of Control Plane in Istio - approx. transl.) configures Envoy to authenticate requests before forwarding them to services: sa-web-app ΠΈ sa-feedback. At the same time, the configuration is not applied to service Envoys sa-frontend, allowing us to leave the frontend unauthenticated. To apply the policy (Policy), run 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 will end with a status 401 Unauthorized. Now let's redirect frontend users to authenticate with Auth0.

Authenticate requests with Auth0

To authenticate end user requests, you need to create an API in Auth0 that will represent the 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

The important information here is Identify, which we will later use in the script. Let's write it out like this:

  • Audience: {YOUR_AUDIENCE}

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

Here we will write:

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

Scroll to Test Application to text field Allowed Callback URLs (allowed URLs for the callback), in which we specify the URL where the call should be sent after the authentication is completed. In our case, this 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 branch auth0 repository [istio-mastery]. In this branch, the frontend code has been changed to redirect users to Auth0 for authentication and use the JWT token in requests to 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 change the frontend to use tenant data in Auth0, open sa-frontend/src/services/Auth.js and replace in it the values ​​that we wrote above (Auth.js):

const Config = {
    clientID: '{YOUR_CLIENT_ID}',
    domain:'{YOUR_DOMAIN}',
    audience: '{YOUR_AUDIENCE}',
    ingressIP: '{EXTERNAL_IP}' // Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ для Ρ€Π΅Π΄ΠΈΡ€Π΅ΠΊΡ‚Π° послС Π°ΡƒΡ‚Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ
}

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

$ 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 the already authenticated requests will be made. If you try the commands mentioned in the first parts of the article with curl, you will get the code 401 status codeAn that 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 know what he has access to, authorization is required. Istio offers tools for this as well.

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

  • Members (users) β€” with access only to SA-WebApp and SA-Frontend services;
  • Moderators (moderators) β€” with access to all three services.

Back to microservices with Istio. Part 3
Authorization concept

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

Installing and configuring Auth0 Authorization

In the Auth0 portal, navigate to extensions (Extensions) and install Auth0 Authorization. Once installed, go to Authorization extension, and there - to the tenant's configuration by clicking on the top right and selecting the appropriate menu option (Configuration). Activate groups (groups) and click on the publish rule button (Publish rule).

Back to microservices with Istio. Part 3

Create groups

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

Select a group Moderators, Press Add Members, add your main account. Leave some users without any group to make sure they are denied access. (New users can be created manually via Auth0 Portal > Users > Create User.)

Add Group Claim to Access Token

Users are added to groups, however this information must be reflected in access tokens as well. To match OpenID Connect and at the same time return the groups we need, the token will need to add its custom claim. Implemented via Auth0 rules.

To create a rule, go to the Auth0 Portal to Rules, Press Create rule and select an empty rule from 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 user group defined in the Authorization Extension and adds it to the access token as a custom claim (under its own namespace, as required by Auth0).

Back to 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 gets the rule asynchronously auth0-authorization-extension and after that is added as a claim by the second rule. The result is the following 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 out of the claim (https://sa.io/group) in the returned access token. This is the topic for the next section of the article.

Authorization configuration in Istio

For authorization to work, you need to enable RBAC for Istio. To do this, we 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" 

Explanation:

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

All services now require Role-Based Access Control. In other words, access to all services is denied and will result in a response RBAC: access denied. Now let's allow access to authorized users.

Access Configuration for General Users

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

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

For ordinary 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 apply the ServiceRole to all page visitors (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 non-authenticated users will also have access to the SA WebApp? No, the policy will check 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 contains a claim https://sa.io/group with the value of 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. You can then 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 control over authentication and authorization of end user access to services.

In addition, we've taken care of these issues out of our envoy services by achieving:

  • reducing the amount of generic code that may contain security problems and bugs;
  • reducing the number of stupid situations in which one endpoint was available from the outside and forgot to report it;
  • eliminate the need to update all services each time a new role or right is added;
  • that new services remain simple, secure and fast.

Hack and predictor Aviator

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

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

PS from translator

Read also on our blog:

Source: habr.com

Add a comment