Skip to content

Add Athenz integration module - #6321

Merged
ikhoon merged 25 commits into
line:mainfrom
ikhoon:athenz
Aug 5, 2025
Merged

Add Athenz integration module#6321
ikhoon merged 25 commits into
line:mainfrom
ikhoon:athenz

Conversation

@ikhoon

@ikhoon ikhoon commented Jul 23, 2025

Copy link
Copy Markdown
Contributor

Motivation:

This PR aims to provide an integration layer for Athenz so that users can easily obtain Athenz tokens and validate them
by decorating clients or services and annotating required Athenz roles declaratively.

Modifications:

  • Client side
    • ZtsBaseClient provides common functionality such as TlsKeyPair management and Athenz client configurations.
      • Users should create ZtsBaseClient first to create AthenzClient and AthenzService.
      • ZtsBaseClient is designed as a resource, and it needs to be closed since Armeria decorators are not closable.
      • The lifecycle of Athenz ClientFactory is delegated to ZtsBaseClient
    • AccessTokenClient acquires OAuth 2.0 token from the/oauth2/token endpoint.
      • The cached tokens are automatically refreshed before expiration.
      • Athenz uses mTLS as the authorization layer, and the client credentials of OAuth 2.0 are unnecessary.
        • This method does not conform to the official OAuth 2.0 specification.
    • RoleTokenClient obtains Athenz role tokens from the /domain/{domainName}/token?role=<roleName> endpoint.
      • It has a similar refreshing logic to AccessTokenClient.
    • AthenzClient is a public decorator that delegates to AccessTokenClient or RoleTokenClient depending on the configuration.
  • Server side
    • AthenzPolicyLoader loads Athenz domain policies from ZTS servers, just like the zpu CLI does.
      • The external zpu cronjob to fetch policies is no longer necessary.
      • Both JWS policy data and signed policy data are supported.
    • AthenzPolicyHandler parses the policy data and verifies it with public keys.
    • MinifiedAuthZpeClient is forked from AuthZpeClient and modified to seamlessly integrate with Armeria.
      • MinifiedAuthZpeClient is responsible for token validation.
      • Reviewers may skip a detailed review of this class.
    • AthenzService is a public decorator to check access permission for projected resources.
    • RequiresAthenzRole allows users to specify an Athenz role using annotations.
      • AthenzServiceDecoratorFactory should be injected via DependencyInjector to use RequiresAthenzRole

Result:

  • You can now use the Athenz module to easily obtain Athenz tokens and validate them.
  • Closes Add Athenz module #6050
  • Server example:
class MyService {
  // 1. Decorate the method with `RequiresAthenzRole` to check Athenz role.
  @RequiresAthenzRole(resource = "user", action = "get")
  @ProducesJson
  @Get("/user")
  public CompletableFuture<User> getUser() {
     ...
  }
}

// 2. Create a `ZtsBaseClient` and `AthenzServiceDecoratorFactory` to use Athenz.
ZtsBaseClient ztsBaseClient =
  ZtsBaseClient
    .builder("https://athenz.example.com:4443/zts/v1")
    .keyPair("/var/lib/athenz/service.key.pem", "/var/lib/athenz/service.cert.pem")
    .build();
final AthenzServiceDecoratorFactory athenzDecoratorFactory =
  AthenzServiceDecoratorFactory
    .builder(ztsBaseClient)
    .policyConfig(new AthenzPolicyConfig("my-domain"))
    .build();

// 3. Create a `DependencyInjector` with the `AthenzServiceDecoratorFactory`
//    and set it to the server. `AthenzServiceDecoratorFactory` is required to
//    create the `RequiresAthenzRole` decorator.
final DependencyInjector di =
  DependencyInjector.ofSingletons(athenzDecoratorFactory)
                    .orElse(DependencyInjector.ofReflective());
serverBuilder.dependencyInjector(di, true);
  • Client example:
ZtsBaseClient ztsBaseClient =
  ZtsBaseClient
    .builder("https://athenz.example.com:4443/zts/v1")
    .keyPair("/var/lib/athenz/service.key.pem", "/var/lib/athenz/service.cert.pem")
    .build();

WebClient
  .builder()
  .decorator(AthenzClient.newDecorator(ztsBaseClient, "my-domain",
                                       TokenType.ROLE_TOKEN)
  ...
  .build();

@ikhoon ikhoon added this to the 1.33.0 milestone Jul 23, 2025
@ikhoon
ikhoon force-pushed the athenz branch 2 times, most recently from 5af1ac7 to 322bb7a Compare July 25, 2025 10:43
@codecov

codecov Bot commented Jul 26, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 60.97308% with 377 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.43%. Comparing base (8150425) to head (909b373).
⚠️ Report is 143 commits behind head on main.

Files with missing lines Patch % Lines
...p/armeria/server/athenz/MinifiedAuthZpeClient.java 44.60% 113 Missing and 41 partials ⚠️
...orp/armeria/server/athenz/AthenzPolicyHandler.java 59.45% 31 Missing and 14 partials ⚠️
...rp/armeria/client/athenz/ZtsBaseClientBuilder.java 29.62% 36 Missing and 2 partials ⚠️
...armeria/server/athenz/AthenzPublicKeyProvider.java 72.36% 20 Missing and 1 partial ⚠️
.../linecorp/armeria/client/athenz/ZtsBaseClient.java 67.21% 16 Missing and 4 partials ⚠️
...corp/armeria/server/athenz/AthenzPolicyConfig.java 50.00% 14 Missing and 2 partials ⚠️
...necorp/armeria/server/athenz/AthenzAssertions.java 39.13% 14 Missing ⚠️
...rp/armeria/server/athenz/AthenzServiceBuilder.java 57.69% 7 Missing and 4 partials ⚠️
...inecorp/armeria/client/athenz/RoleTokenClient.java 78.94% 3 Missing and 5 partials ⚠️
...ecorp/armeria/client/athenz/AccessTokenClient.java 80.55% 3 Missing and 4 partials ⚠️
... and 11 more
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6321      +/-   ##
============================================
- Coverage     74.46%   74.43%   -0.03%     
- Complexity    22234    22967     +733     
============================================
  Files          1963     2061      +98     
  Lines         82437    85695    +3258     
  Branches      10764    11179     +415     
============================================
+ Hits          61385    63791    +2406     
- Misses        15918    16531     +613     
- Partials       5134     5373     +239     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ikhoon
ikhoon marked this pull request as ready for review July 29, 2025 14:49
@ikhoon
ikhoon requested review from jrhee17 and minwoox as code owners July 29, 2025 14:49
@ikhoon
ikhoon requested a review from trustin as a code owner July 29, 2025 14:49

@minwoox minwoox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some nits. 😉

Comment thread athenz/src/main/java/com/linecorp/armeria/client/athenz/AthenzClient.java Outdated
Comment thread athenz/src/main/java/com/linecorp/armeria/client/athenz/AthenzClient.java Outdated
Comment thread athenz/src/main/java/com/linecorp/armeria/client/athenz/RoleTokenClient.java Outdated
Comment thread athenz/src/main/java/com/linecorp/armeria/server/athenz/AthenzAssertions.java Outdated

@minwoox minwoox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 👍 👍

@jrhee17 jrhee17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understood functionally this module is equivalent to ZPE lib and the ZTS lib but with an Armeria flavor.

Comment thread athenz/src/main/java/com/linecorp/armeria/server/athenz/AthenzPolicyHandler.java Outdated
* @param tokenType the type of Athenz token to obtain
*/
public static Function<HttpClient, AthenzClient> newDecorator(ZtsBaseClient ztsBaseClient,
String domainName, TokenType tokenType) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question) I'm not sure of the environment, but is TokenType.ROLE_TOKEN often used? I'm wondering if TokenType.ACCESS_TOKEN should be the default

@ikhoon ikhoon Aug 4, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some LY internal servers only support ROLE_TOKEN type. So I wasn't sure if ACCESS_TOKEN could be a sensible default.

Comment thread athenz/src/main/java/com/linecorp/armeria/client/athenz/ZtsBaseClient.java Outdated
* <pre>{@code
* class MyService {
* // 1. Decorate the method with `RequiresAthenzRole` to check Athenz role.
* @RequiresAthenzRole(resource = "user", action = "get")

@jrhee17 jrhee17 Aug 4, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note) I understood this is an AND condition. i.e.) If users want to allow either both ACCESS_TOKEN and ROLE_TOKEN, they can't do it using the annotations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If tokenType is unset, ACCESS_TOKEN and ROLE_TOKEN are allowed.

TokenType[] tokenType() default { TokenType.ROLE_TOKEN, TokenType.ACCESS_TOKEN };

private AthenzPolicyConfig policyConfig;
private int maxTokenCacheSize = MAX_TOKEN_CACHE_SIZE;

AbstractAthenzServiceBuilder(ZtsBaseClient ztsBaseClient) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The external zpu cronjob to fetch policies is no longer necessary.

I actually understood the zpu cronjob is still necessary since AthenzService still needs to use the credentials created/rotated by the zpu when connecting to zts. Let me know if I misunderstood.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ZPU fetches and refreshes Athenz policy files. IIUC, it is not related to credentials. Generally, creating Service credentials is delegated to Athenz SIA, which is still necessary.
https://github.com/AthenZ/athenz/blob/master/docs/system_view.md#sia-service-identity-agent-provider:~:text=for%20issuing%20tokens.-,SIA%20(Service%20Identity%20Agent,can%20validate%20the%20signature.,-ZPE%20(AuthZ%20Policy

@jrhee17 jrhee17 Aug 4, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I understood that service providers will need to install either an SIA and use this module, or use ZPU with ZPE

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Athenz module does not support creating Service credentials, so an external system such as SIA is still used for mTLS.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Then I understood that the ZPU internally uses SIA to acquire initial credentials for communication with the ZMS/ZTS then.

ikhoon added a commit that referenced this pull request Aug 4, 2025
Motivation:

Athenz SIA refreshes the certs every 24 hours by default.
https://github.com/AthenZ/k8s-athenz-identity#configuration In order to
comply with the specification, `TlsProvider` should detect the updated
certs and automatically refresh them.
Additionally, there were similar requests from Armeria users.
#6054

Modifications:

- Add `RefreshingTlsProvider` that periodically refreshes the given
`TlsKeyPair` provider.
- Currently, only one `TlsKeyPair` is supported for
`RefreshingTlsProvider`
- TODO) Integrate `MappedTlsProvider` with `RefreshingTlsProvider` to
build a more flexible `TlsProvider` using `TlsProviderBuilder`
- Expose `ReflectiveDependencyInjector` via public API via
`DependencyInjector.ofReflective()`
- This is unrelated to this PR, but added to minimize conflicts when
merging the main branch into #6321

Result:

You can now periodically refresh `TlsKeyPair` when using
`TlsProvider.ofSheduled()`

```java
File keyFile = ...;
Fie certFile = ...;

TlsProvider.ofScheduled(() -> {
  return TlsKeyPair.of(keyFile, certFile);
}, Duration.ofHours(1));
```
@ikhoon
ikhoon merged commit 62a2da2 into line:main Aug 5, 2025
14 of 16 checks passed
@ikhoon
ikhoon deleted the athenz branch August 5, 2025 01:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Athenz module

3 participants