Athenz integration
Table of contents
- Prerequisites
- Creating a ZTS client
- Checking permissions with the type://AthenzService decorator
- Checking permissions with the type://@RequiresAthenzRole annotation
- Obtaining athenz tokens with type://AthenzClient
This document explains how to integrate Armeria with Athenz, a platform for service authentication and authorization. It assumes that you have already configured an Athenz domain and installed the necessary service identity certificates locally.
Prerequisites
Add the following dependencies to your build.gradle file:
dependencies {
implementation platform('com.linecorp.armeria:armeria-bom:1.33.0')
...
implementation 'com.linecorp.armeria:armeria-athenz'
}
Creating a ZTS client
The ZtsBaseClient
is a client used to interact with the Athenz ZTS (authZ Token System).
It is required to create AthenzClient
and AthenzService
decorators.
You can create an instance of ZtsBaseClient
as follows:
import com.linecorp.armeria.client.athenz.ZtsBaseClient;
import com.linecorp.armeria.client.athenz.ZtsBaseClientBuilder;
ZtsBaseClient ztsBaseClient =
ZtsBaseClient
.builder("https://athenz.example.com:8443/zts/v1")
// You need to specify your Athenz service key and certificate files.
.keyPair("/var/lib/athenz/service.key.pem",
"/var/lib/athenz/service.cert.pem")
// Uncomment the following line to use a proxy.
// .proxyUri("http://my-proxy.example.com:8080")
.build();
// ...
// Close the client when it is no longer needed.
ztsBaseClient.close();
Checking permissions with the AthenzService
decorator
You can enforce Athenz authorization on your services by applying the AthenzService
decorator.
This decorator intercepts incoming requests and validates the client's token against the configured Athenz policies.
The code below configures a decorator for paths under /files
. It will only grant access to requests that
present a token with the specified action
("get") and resource
("files") for your Athenz domain.
The action
and resource
must correspond to an assertion you have configured in Athenz.
import com.linecorp.armeria.server.Server;
import com.linecorp.armeria.server.ServerBuilder;
import com.linecorp.armeria.server.athenz.AthenzService;
import com.linecorp.armeria.server.athenz.AthenzPolicyConfig;
ServerBuilder serverBuilder = Server.builder();
serverBuilder.decoratorUnder(
"/files",
AthenzService.builder(ztsBaseClient)
.action("get")
.resource("files")
.policyConfig(new AthenzPolicyConfig("your-domain"))
.newDecorator());
serverBuilder.serviceUnder("/files", FileService.of(...));
...
The AthenzPolicyConfig
object replaces the need for a zpu.conf
file. It allows the server to download
policy files directly at startup and perform authorization checks without relying on the zpu
command-line utility.
If you are only using the decorator for permission checks, this is all the configuration required to integrate Athenz with Armeria.
Checking permissions with the @RequiresAthenzRole
annotation
As an alternative to programmatically applying decorators, you can use the @RequiresAthenzRole
annotation
on service methods or classes for a more declarative approach.
import com.linecorp.armeria.server.athenz.RequiresAthenzRole;
class MyService {
// Decorate the method with `@RequiresAthenzRole` to check the Athenz role.
@RequiresAthenzRole(resource = "user", action = "get")
@ProducesJson
@Get("/user")
public CompletableFuture<User> getUser() {
// ...
}
}
To enable this annotation, you must register an AthenzServiceDecoratorFactory
with the server.
This factory uses the ZtsBaseClient
to create the necessary decorators that enforce the roles specified
in the annotations.
import com.linecorp.armeria.common.DependencyInjector;
import com.linecorp.armeria.server.athenz.AthenzPolicyConfig;
import com.linecorp.armeria.server.athenz.AthenzServiceDecoratorFactory;
import com.linecorp.armeria.server.athenz.AthenzServiceDecoratorFactoryBuilder;
// Create and register `AthenzServiceDecoratorFactory`.
AthenzServiceDecoratorFactory athenzDecoratorFactory =
AthenzServiceDecoratorFactory
.builder(ztsBaseClient)
.policyConfig(new AthenzPolicyConfig("my-domain"))
.build();
DependencyInjector di =
DependencyInjector.ofSingletons(athenzDecoratorFactory)
.orElse(DependencyInjector.ofReflective());
serverBuilder.dependencyInjector(di, true);
The @RequiresAthenzRole
annotation can be applied not only to annotated services but also to methods in
Thrift and gRPC services.
class UserGrpcServiceImpl extends UserGrpcServiceImplBase {
@RequiresAthenzRole(resource = "user", action = "get")
public void getUser(UserRequest request,
StreamObserver<UserResponse> responseObserver) {
// ...
}
}
Obtaining athenz tokens with AthenzClient
To communicate with other Athenz-protected services, you can use the AthenzClient
decorator.
This client automatically acquires an Athenz token from the ZTS, caches it, and attaches it to outgoing requests.
It also handles token refreshes before expiration, so you do not need to manage the token lifecycle yourself.
To obtain an access token and send it in the Authorization
header, configure the decorator with TokenType.ACCESS_TOKEN
.
import com.linecorp.armeria.client.athenz.AthenzClient;
import com.linecorp.armeria.common.athenz.TokenType;
WebClient client =
WebClient
.builder("https://api.example.com/")
.decorator(AthenzClient.newDecorator(ztsBaseClient, "my-domain",
TokenType.ACCESS_TOKEN))
.build();
// An Athenz access token is automatically acquired and set in the `Authorization` header.
client.get("/files");
To obtain a role token and send it in a role token header (e.g., Athenz-Role-Auth or Yahoo-Role-Auth), specify
TokenType.ATHENZ_ROLE_TOKEN
or TokenType.YAHOO_ROLE_TOKEN
.
WebClient client =
WebClient
.builder("https://api.example.com/")
.decorator(AthenzClient.newDecorator(ztsBaseClient, "my-domain",
TokenType.ATHENZ_ROLE_TOKEN))
.build();