Implementation of an external authorizer for Istio in Java

Posted on Oct 18, 2022
Note: This article was written a while ago and may contain outdated information. Please verify the details before relying on it. If I express opinions or recommendations, they might not reflect my current views. For this reason, I recommend checking for more recent articles on the same topic.

In my last posts I was writing about using an external authorizer for Envoy to use Istio to authorize requests (check part 1 and part 2).

In part 2 I wrote that I will also show an example of the authorization service in Java. Here is it.

Code example

Full code example available at GitHub.

You can see the full code example with a Dockerfile in my repository. The following subsections explain you to it step-by-step. This project uses grpc-java for the server.

Dependencies

Create a new project. I was using a gradle based project with the following dependencies:

runtimeOnly 'io.grpc:grpc-netty-shaded:1.50.0'
implementation 'io.grpc:grpc-protobuf:1.50.0'
implementation 'io.grpc:grpc-stub:1.50.0'
implementation "io.grpc:grpc-services:1.50.0"
implementation 'io.envoyproxy.controlplane:api:0.1.35'
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+

The server

The server is nearly the same as the RouteGuideServer sample and registers the authorization service.

Furthermore it needs the ProtoReflectionService to be registered. Otherwise the requests won’t work and be seen. This is an important part, which took me some time to realize that it is missing.

AuthorizationServer.java:

package de.dkwr.authzexample;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.protobuf.services.ProtoReflectionService;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * External authorization server for Istio implemented in Java.
 * Uses grpc-java: https://github.com/grpc/grpc-java
 * This bases upon the grpc-java example 'RouteGuideServer' (Apache License, Version 2.0): https://github.com/grpc/grpc-java/tree/master/examples/src/main/java/io/grpc/examples/routeguide
 */
public class AuthorizationServer {
    private final int port;
    private final Server server;

    /** Create a AuthorizationServer using serverBuilder as a base and features as data. */
    public AuthorizationServer(ServerBuilder<?> serverBuilder, int port) {
        this.port = port;
        server = serverBuilder.addService(new AuthorizationServiceImpl())
                .addService(ProtoReflectionService.newInstance())
                .build();
    }

    /** Start serving requests. */
    public void start() throws IOException {
        server.start();
        System.out.println("Server started, listening on " + port);
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    AuthorizationServer.this.stop();
                } catch (InterruptedException e) {
                    e.printStackTrace(System.err);
                }
            }
        });
    }

    /** Stop serving requests and shutdown resources. */
    public void stop() throws InterruptedException {
        if (server != null) {
            server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
        }
    }

    /**
     * Await termination on the main thread since the grpc library uses daemon threads.
     */
    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    public static void main(String[] args) throws Exception {
        AuthorizationServer server = new AuthorizationServer(ServerBuilder.forPort(9000), 9000);
        server.start();
        server.blockUntilShutdown();
    }

}

The AuthorizationService

The authorization service extends the AuthorizationGrpc.AuthorizationImplBase class.

It checks the x-ext-authz header and sends accordingly a response (either Code.UNAUTHENTICATED_VALUE or Code.OK_VALUE).

AuthorizationServiceImpl.java

package de.dkwr.authzexample;

import com.google.rpc.Code;
import com.google.rpc.Status;
import io.envoyproxy.envoy.service.auth.v3.AuthorizationGrpc;
import io.envoyproxy.envoy.service.auth.v3.CheckResponse;

import java.util.Map;
import java.util.Objects;

/**
 * Implementation of the ext-authz service for Envoy.
 * @see <a href="https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/auth/v3/external_auth.proto">Proto file</a>
 */
public class AuthorizationServiceImpl extends AuthorizationGrpc.AuthorizationImplBase {
    @Override
    public void check(io.envoyproxy.envoy.service.auth.v3.CheckRequest request,
                      io.grpc.stub.StreamObserver<io.envoyproxy.envoy.service.auth.v3.CheckResponse> done) {
        Map<String, String> headers = request.getAttributes().getRequest().getHttp().getHeadersMap();

        if (!Objects.equals(headers.get("x-ext-authz"), "allow")) {
            done.onNext(
                    CheckResponse
                            .newBuilder()
                            .setStatus(Status.newBuilder().setCode(Code.UNAUTHENTICATED_VALUE).build())
                            .setDeniedResponse(CheckResponse.getDefaultInstance().getDeniedResponse())
                            .build());

            done.onCompleted();
            return;
        }

        done.onNext(CheckResponse
                .newBuilder()
                .setStatus(Status.newBuilder().setCode(Code.OK_VALUE).build())
                .setOkResponse(CheckResponse.getDefaultInstance().getOkResponse())
                .build());
        done.onCompleted();
    }
}

Usage

  1. Create the project.
  2. Build the jar: gradle build
  3. Build a Docker image: docker build -t java-authz:v1 .
  4. Deploy into your Kubernetes cluster.

Now you can test the filter for example with cURL. Requests with the -H "x-ext-authz: allow" header should pass. Other ones fail.

E.g.:

curl -H "x-ext-authz: allow" localhost/books

Addendum: Kubernetes resource file

Here is a K8S resource file for the namespace sidecar-test:

apiVersion: v1
kind: Service
metadata:
  name: ext-authz
  namespace: sidecar-test
  labels:
    app: ext-authz
spec:
  ports:
  - name: grpc
    port: 9000
    targetPort: 9000
  selector:
    app: ext-authz
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ext-authz
  namespace: sidecar-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ext-authz
  template:
    metadata:
      labels:
        app: ext-authz
    spec:
      containers:
      - image: java-authz:v1
        imagePullPolicy: IfNotPresent
        name: ext-authz
        ports:
        - containerPort: 9000
---

Further notes

This project depends upon the grpc-java project and orientates on the routeguide service.

Usually the services for gRPC are defined by protocol buffers (see this tutorial if you want to know more).

For Envoy this definitions also exist. For example in GitHub.

But there is also a repository which can be used: io.envoyproxy.controlplane. So no ProtoBuf Plugins and dependencies needed!

What about Spring Boot? Well, there is also the gRPC Spring Boot starter project, which you can use. But let’s keep it simple for now. :)

Want to know more?

Keep on reading and choose one of the related articles. You can also check the home page for my latest thoughts, notes and articles.