Skip to content

Allow wrapper-controlled init to fix jwk_uri backward compatibility (Issue #3054)#3198

Open
sh1myama wants to merge 1 commit intoAthenZ:masterfrom
sh1myama:oss-zpe-does-not-require-jwksuri
Open

Allow wrapper-controlled init to fix jwk_uri backward compatibility (Issue #3054)#3198
sh1myama wants to merge 1 commit intoAthenZ:masterfrom
sh1myama:oss-zpe-does-not-require-jwksuri

Conversation

@sh1myama
Copy link
Contributor

Description

Context

Changes

  • Move access-token resolver setup out of the static initializer into AuthZpeClient.init() so callers can set properties before initialization.
  • Keep the existing jwk_uri requirement for access-token validation, but make its initialization explicit and controllable.
  • Add support for an optional athenz.zpe.jwk_proxy_uri and pass it into setAccessTokenSignKeyResolver to enable proxied JWK fetches.

Outcome

  • Restores upgrade path for Role Token users by avoiding premature jwk_uri validation during class load, while preserving access-token behavior when init() is called.

Contribution Checklist:

  • The pull request does not introduce any breaking changes
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Attach Screenshots (Optional)

Signed-off-by: Shuya Shimaoka <sshimaok@lycorp.co.jp>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @sh1myama, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical backward compatibility issue where the jwk_uri validation was prematurely triggered during class loading, preventing applications from properly initializing. By refactoring the initialization logic into an explicit method and introducing an optional proxy configuration, it restores the upgrade path for Role Token users and enhances flexibility for JWK fetching, ensuring a smoother integration and operation of the Athenz ZPE client.

Highlights

  • Wrapper-Controlled Initialization: The setup for the access-token resolver has been moved out of the static initializer into the AuthZpeClient.init() method. This allows external wrappers to set system properties before the AuthZpeClient loads, resolving a backward compatibility issue with jwk_uri.
  • Explicit JWK URI Validation: The requirement for jwk_uri remains for access-token validation, but its initialization is now explicit and controllable, preventing premature validation during class loading.
  • JWK Proxy Support: Added support for an optional athenz.zpe.jwk_proxy_uri system property, which is passed into setAccessTokenSignKeyResolver to enable proxied JWK fetches.
Changelog
  • clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/AuthZpeClient.java
    • Moved the access token signing key resolver initialization from the static block to the init() method.
    • Modified the initializeAccessTokenSignKeyResolver() method to retrieve an optional jwk_proxy_uri system property.
    • Updated the setAccessTokenSignKeyResolver method call to include the new proxyUrl parameter.
  • clients/java/zpe/src/main/java/com/yahoo/athenz/zpe/ZpeConsts.java
    • Added a new constant ZPE_PROP_JWK_PROXY_URI for the athenz.zpe.jwk_proxy_uri system property.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the initialization logic in AuthZpeClient by moving the access token resolver setup to an explicit init() method, which resolves issues with jwk_uri system properties and introduces support for a JWK proxy URI. However, a high-severity Server-Side Request Forgery (SSRF) vulnerability has been identified due to a lack of URL validation for the new JWK proxy URI feature; implementing a strict allow-list for proxy servers is recommended. Furthermore, the new init() method has a potential thread-safety issue and should be made idempotent to prevent race conditions during concurrent initialization.

if (serverUrl == null || serverUrl.isEmpty()) {
throw new IllegalArgumentException("Missing required property: " + ZpeConsts.ZPE_PROP_JWK_URI);
}
String proxyUrl = System.getProperty(ZpeConsts.ZPE_PROP_JWK_PROXY_URI);
Copy link
Contributor

Choose a reason for hiding this comment

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

security-high high

The application reads the JWK proxy URL from the system property athenz.zpe.jwk_proxy_uri and uses it to configure an HTTP proxy for fetching the JWK set without validation. An attacker who can set this system property can specify an arbitrary URL, leading to Server-Side Request Forgery (SSRF). This could allow an attacker to redirect network requests to internal services, bypassing firewall controls, or to an attacker-controlled server to intercept sensitive data.

Comment on lines +180 to 186
public static void init() {
if (LOG.isDebugEnabled()) {
LOG.debug("Init: load the ZPE");
}
// initialize the access token signing key resolver

initializeAccessTokenSignKeyResolver();
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The new init() method is not thread-safe or idempotent. If called concurrently (e.g., during application startup or in parallel tests), it can lead to race conditions during the initialization of static resources. It's a best practice to ensure that initialization methods like this execute only once.

You can make this method thread-safe and idempotent by using a static flag with double-checked locking. This will ensure the initialization logic is performed safely and exactly one time.

Example:

// Add this field to the class
private static volatile boolean zpeInitialized = false;

// Modify the init() method
public static void init() {
    // Quick check without lock
    if (zpeInitialized) {
        return;
    }
    synchronized (AuthZpeClient.class) {
        // Check again inside the synchronized block
        if (zpeInitialized) {
            return;
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Init: load the ZPE");
        }

        // ... current init logic ...
        initializeAccessTokenSignKeyResolver();
        // ... other set calls that were moved ...

        zpeInitialized = true;
    }
}

@havetisyan
Copy link
Collaborator

@sh1myama the AuthZpeClient.init() was never required for ZPE functionality. So by moving the actual keyresolver initialization into that function will break most zpe users, so not the correct solution.

I'll check in the next couple of days in more detail and will propose how to address your use case.

@sh1myama
Copy link
Contributor Author

@havetisyan -san

@sh1myama the AuthZpeClient.init() was never required for ZPE functionality. So by moving the actual keyresolver initialization into that function will break most zpe users, so not the correct solution.

I'll check in the next couple of days in more detail and will propose how to address your use case.

Thank you for checking.

In the following Java decentralized access control sample, the documentation says to initialize the ZPE Client in init when performing authorization checks, so I understood that calling init is mandatory.
https://github.com/AthenZ/athenz/blob/master/docs/example_java_decentralized_access.md#authorization-checks

Even if we apply this change and modify it so that initializeAccessTokenSignKeyResolver is called from init, my understanding is that the behavior will not change as long as the implementation is written to always call init itself.

Thank you in advance.

@havetisyan
Copy link
Collaborator

@sh1myama here is what changes we need to make:

  1. introduce a static variable tokenSignKeyResolverInitialized that key resolver is initialized with default value false
  2. update the initializeAccessTokenSignKeyResolver to take a boolean argument whether or not return exceptions or just return without any errors.
  3. in the static initializer call the method with false value. if the variables are not set, it will just return without any exceptions, otherwise it will set the initializer and set the tokenSignKeyResolverInitialized to true.
  4. In the init function, introduce a new system property athenz.zpe.jwk_uri_required with a default value of true. If the value is false, then do nothing and return. If the value is true (default value), it will check the value for tokenSignKeyResolverInitialized. If it's false then it will call the initializeAccessTokenSignKeyResolver method with the true argument so that it will return an exception if there are any errors (current behavior).

this would allow your applications to set the either set the jwks_uri to complete the sign key resolver or jwks_uri_required to false in your application and ignore calling the method completely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants