Skip to content

feat(server): Add a KeyStoreScanner#134

Open
ShahimSharafudeen wants to merge 1 commit intoprestodb:masterfrom
ShahimSharafudeen:keyStoreScanner
Open

feat(server): Add a KeyStoreScanner#134
ShahimSharafudeen wants to merge 1 commit intoprestodb:masterfrom
ShahimSharafudeen:keyStoreScanner

Conversation

@ShahimSharafudeen
Copy link
Copy Markdown

To allow for hot reloading the tls keystore

See for more details : https://jetty.org/docs/jetty/12.1/programming-guide/server/http.html#connector-protocol-tls-keystore-auto-reload

Testing

Tested in local and working as expected. Adding the screenshot as below.

Default keystoreScanInterval : 1s

image (30)

keystoreScanInterval : 10s

image (31)

@ShahimSharafudeen ShahimSharafudeen requested a review from a team as a code owner November 14, 2025 18:48
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla bot commented Nov 14, 2025

CLA Missing ID CLA Not Signed

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Nov 14, 2025

Reviewer's Guide

Introduce a KeyStoreScanner to periodically monitor and reload the TLS keystore on file changes, make the scan interval configurable via HttpServerConfig, wire the scanner into the HttpServer lifecycle, and add integration tests verifying reload behavior.

Sequence diagram for TLS keystore hot reload process

sequenceDiagram
    participant "KeyStoreScanner"
    participant "SSLContextFactory"
    participant "HttpServer"
    loop Every keystoreScanInterval seconds
        "KeyStoreScanner"->>"SSLContextFactory": Check for keystore file changes
        alt If keystore changed
            "KeyStoreScanner"->>"SSLContextFactory": Reload keystore
        end
    end
    "HttpServer"->>"KeyStoreScanner": Configure scan interval and add bean
Loading

Entity relationship diagram for keystore scan interval configuration

erDiagram
    HTTP_SERVER_CONFIG {
        int keystoreScanInterval
    }
    KEYSTORE_SCANNER {
        int scanInterval
    }
    HTTP_SERVER_CONFIG ||--o| KEYSTORE_SCANNER : configures
    KEYSTORE_SCANNER ||--|| SSL_CONTEXT_FACTORY : monitors
Loading

Class diagram for KeyStoreScanner integration and HttpServerConfig changes

classDiagram
    class HttpServerConfig {
        -int keystoreScanInterval = 1
        +setKeystoreScanInterval(int)
        +getKeystoreScanInterval()
    }
    class KeyStoreScanner {
        +setScanInterval(int)
        <<new>>
    }
    class HttpServer {
        +HttpServer(...)
        ...
    }
    class SSLContextFactory
    HttpServerConfig --> KeyStoreScanner : configures
    HttpServer --> KeyStoreScanner : adds as bean
    KeyStoreScanner --> SSLContextFactory : monitors
Loading

File-Level Changes

Change Details Files
Add integration test for keystore reload on file change
  • Create temporary keystore file and copy original contents
  • Configure SslContextFactory and start initial keystore load
  • Set up KeyStoreScanner bean on a Jetty Server with a 1s interval
  • Modify keystore timestamp, wait for scan, and verify reload
http-server/src/test/java/com/facebook/airlift/http/server/TestHttpServerProvider.java
Expose keystore scan interval in server configuration
  • Add keystoreScanInterval field with default value 1
  • Add @config setter and getter for http-server.https.keystore.scan-interval
  • Update TestHttpServerConfig with default and explicit mapping cases
http-server/src/main/java/com/facebook/airlift/http/server/HttpServerConfig.java
http-server/src/test/java/com/facebook/airlift/http/server/TestHttpServerConfig.java
Integrate KeyStoreScanner into HttpServer startup
  • Instantiate KeyStoreScanner with SslContextFactory in HttpServer
  • Set scanner interval from config and add it as a server bean
http-server/src/main/java/com/facebook/airlift/http/server/HttpServer.java

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • In testKeyStoreReloadOnKeystoreFileChange, replace Thread.sleep with a polling loop or awaitility to avoid timing flakiness.
  • Consider allowing a scan interval of zero or less to disable keystore scanning rather than always starting the scanner when a keystore is configured.
  • The comment in HttpServer.java mentioning future configurability is now outdated—please remove or update it to reflect that the scan interval is already configurable.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In testKeyStoreReloadOnKeystoreFileChange, replace Thread.sleep with a polling loop or awaitility to avoid timing flakiness.
- Consider allowing a scan interval of zero or less to disable keystore scanning rather than always starting the scanner when a keystore is configured.
- The comment in HttpServer.java mentioning future configurability is now outdated—please remove or update it to reflect that the scan interval is already configurable.

## Individual Comments

### Comment 1
<location> `http-server/src/main/java/com/facebook/airlift/http/server/HttpServerConfig.java:829-835` </location>
<code_context>
+
+    @Config("http-server.https.keystore.scan-interval")
+    @ConfigDescription("Interval in seconds at which the server checks for updates to the HTTPS keystore file.")
+    public HttpServerConfig setKeystoreScanInterval(int keystoreScanInterval)
+    {
+        this.keystoreScanInterval = keystoreScanInterval;
+        return this;
+    }
+
</code_context>

<issue_to_address>
**suggestion:** Validate keystoreScanInterval input to prevent misconfiguration.

Add validation to ensure the scan interval is a positive value within a reasonable range to avoid misconfiguration.

```suggestion
    @Config("http-server.https.keystore.scan-interval")
    @ConfigDescription("Interval in seconds at which the server checks for updates to the HTTPS keystore file.")
    public HttpServerConfig setKeystoreScanInterval(int keystoreScanInterval)
    {
        if (keystoreScanInterval < 1 || keystoreScanInterval > 3600) {
            throw new IllegalArgumentException("keystoreScanInterval must be between 1 and 3600 seconds");
        }
        this.keystoreScanInterval = keystoreScanInterval;
        return this;
    }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@aaneja aaneja self-requested a review November 17, 2025 02:43
if (config.getKeyManagerPassword() != null) {
sslContextFactory.setKeyManagerPassword(config.getKeyManagerPassword());
}
// Scan for keystore file changes every 1s (the default). Interval can be made configurable in the future
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Comment can be removed. Default scan time will be config.getKeystoreScanInterval()

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done.

}

@Config("http-server.https.keystore.scan-interval")
@ConfigDescription("Interval in seconds at which the server checks for updates to the HTTPS keystore file.")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
@ConfigDescription("Interval in seconds at which the server checks for updates to the HTTPS keystore file.")
@ConfigDescription("Interval (in seconds) at which the server checks for updates to the HTTPS keystore file")

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

private Set<String> defaultAllowedRoles = ImmutableSet.of();
private boolean allowUnsecureRequestsInAuthorizer;

private int keystoreScanInterval = 1;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: Lets rename to keyStoreScanIntervalSeconds

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

sslContextFactory.setKeyManagerPassword(config.getKeyManagerPassword());
}
// Scan for keystore file changes every 1s (the default). Interval can be made configurable in the future
KeyStoreScanner keyStoreScanner = new KeyStoreScanner(sslContextFactory);
Copy link
Copy Markdown

@aaneja aaneja Nov 17, 2025

Choose a reason for hiding this comment

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

Should we add a similar scanner to the while creating theadminConnector on line 329 ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added

@Test
public void testKeyStoreReloadOnKeystoreFileChange()
throws Exception
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Let's test instead with a airlift HttpServer created via createAndStartServer.

We can first start a server like in the testClientCertificateJava test -

config.setHttpEnabled(false)
                .setAdminEnabled(false)
                .setHttpsEnabled(true)
                .setHttpsPort(0)
                .setKeystorePath(getResource("clientcert-java/server.keystore").getPath())
                .setKeystorePassword("airlift");

        createAndStartServer(createCertTestServlet());

        try (JettyHttpClient httpClient = new JettyHttpClient(clientConfig)) {
StringResponse response = httpClient.execute(
        prepareGet().setUri(httpServerInfo.getHttpsUri()).build(),
        createStringResponseHandler());

assertEquals(response.getStatusCode(), HttpServletResponse.SC_OK);
assertEquals(response.getBody(), "CN=testing,OU=Client,O=Airlift,L=Palo Alto,ST=CA,C=US");
        }

This asserts that the certificate is loaded as expected

Then the test could replace the contents of clientcert-java/server.keystore with that of a (precreated) clientcert-java/replaced.keystore

See the clientcert.sh commands to pre-create this new keystore. We could add -

openssl req -new -key server.key -subj "/C=US/ST=CA/L=Palo Alto/O=Replacement/OU=Server/CN=localhost" -out replacement.csr
openssl x509 -req -days 9999 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out replacement.crt
openssl pkcs12 -name server -inkey server.key -in replacement.crt -export -passout pass:airlift -out replacement.keystore
keytool -import -noprompt -alias ca -file ca.crt -storetype pkcs12 -storepass airlift -keystore replacement.keystore

to do this

The new httpClient response after the change should assert that the body changed to "CN=testing,OU=Client,O=Replacement,L=Palo Alto,ST=CA,C=US

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Rewritten the testcase based on above suggestion.


@Config("http-server.https.keystore.scan-interval")
@ConfigDescription("Interval (in seconds) at which the server checks for updates to the HTTPS keystore file")
public HttpServerConfig setKeystoreScanInterval(int keyStoreScanIntervalSeconds)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think it would make more sense if this was a configuration value of Duration type instead of an integer. It will be easier to configure. Especially since the current iteration of all the method names omit "seconds".

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The actual Jetty method takes seconds only unfortunatley. Using a fractional duration rounded down to seconds may be confusing to users

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks. Makes sense. I would just recommend making the config name clearer that it accepts a value in seconds (rather than just the description)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done


Files.copy(Path.of(replacementKeyStorePath), Path.of(tempKeyStorePath), StandardCopyOption.REPLACE_EXISTING);

// Wait for the KeyStoreScanner to detect the file change. Default scan interval is 1 second, so sleeping for 3.5 seconds
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

sleeping for 2.5 seconds

Copy link
Copy Markdown

@aaneja aaneja left a comment

Choose a reason for hiding this comment

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

New test looks good ! Just a small comment about removing duplicate code

Files.copy(Path.of(originalKeyStorePath), Path.of(tempKeyStorePath), StandardCopyOption.REPLACE_EXISTING);

config.setHttpEnabled(false)
.setAdminEnabled(true)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Since having admin enabled/disabled is the only difference, lets convert this into a dataprovider test that sets/unsets this flag

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

keytool -import -noprompt -alias ca -file ca.crt -storetype pkcs12 -storepass airlift -keystore client.truststore


#Creating the replacement keystore files for both the server and the client
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit : Let's be more descriptive - A kesytore for testing the KeyStoreScanner's ability to transparently update the ssl config without needing a restart

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

@ShahimSharafudeen ShahimSharafudeen force-pushed the keyStoreScanner branch 2 times, most recently from d0fbe04 to c22054e Compare November 18, 2025 08:12
private Set<String> defaultAllowedRoles = ImmutableSet.of();
private boolean allowUnsecureRequestsInAuthorizer;

private int keyStoreScanIntervalSeconds = 1;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Lets keep the default as 0. This would match the current behavior of only scanning the file on first load. Ref - https://javadoc.jetty.org/jetty-9/org/eclipse/jetty/util/Scanner.html#setScanInterval(int)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done.

Copy link
Copy Markdown

@aaneja aaneja left a comment

Choose a reason for hiding this comment

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

LGTM % lets have the default scan interval as 0 (no scan after first load)

To allow for hot reloading the tls keystore

See for more details : https://jetty.org/docs/jetty/12.1/programming-guide/server/http.html#connector-protocol-tls-keystore-auto-reload

Co-authored-by: Anant Aneja <1797669+aaneja@users.noreply.github.com>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Feb 5, 2026

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