Implementation of an external authorizer for Istio in Java
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
- Create the project.
- Build the jar:
gradle build - Build a Docker image:
docker build -t java-authz:v1 . - 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. :)