Skip to content

Add SSL/TLS and basic auth support for external OpenSearch connections#1019

Open
rexbut wants to merge 2 commits intokomoot:masterfrom
rexbut:add-opensearch-auth-ssl
Open

Add SSL/TLS and basic auth support for external OpenSearch connections#1019
rexbut wants to merge 2 commits intokomoot:masterfrom
rexbut:add-opensearch-auth-ssl

Conversation

@rexbut
Copy link
Copy Markdown

@rexbut rexbut commented Feb 11, 2026

Summary

Add support for basic authentication, SSL/TLS and mutual TLS (mTLS) when connecting to an external OpenSearch cluster.

#942

Motivation

In production environments (e.g. Kubernetes), external OpenSearch clusters typically require authentication and encrypted connections. This was not possible until now.

Changes

  • PhotonDBConfig: 7 new CLI parameters (-opensearch-user, -opensearch-password, -opensearch-ssl, -opensearch-truststore, -opensearch-truststore-password, -opensearch-keystore, -opensearch-keystore-password)
  • OpenSearchTransportBuilder (new): configures the Apache HttpClient 5 transport with basic auth and/or SSL following the official OpenSearch Java client documentation
  • Server: delegates external transport construction to OpenSearchTransportBuilder
  • docs/usage.md: new section documenting the three usage scenarios (basic auth, SSL with custom CA, mTLS)

Usage examples

Basic authentication only:

java -jar photon.jar serve \
    -transport-addresses opensearch.example.com:9200 \
    -opensearch-user photon -opensearch-password secret

With SSL and a custom CA:

java -jar photon.jar serve \
    -transport-addresses opensearch.example.com:9200 \
    -opensearch-ssl \
    -opensearch-user photon -opensearch-password secret \
    -opensearch-truststore /path/to/truststore.p12 \
    -opensearch-truststore-password changeit

With mutual TLS (mTLS):

java -jar photon.jar serve \
    -transport-addresses opensearch.example.com:9200 \
    -opensearch-ssl \
    -opensearch-truststore /path/to/truststore.p12 \
    -opensearch-truststore-password changeit \
    -opensearch-keystore /path/to/client-keystore.p12 \
    -opensearch-keystore-password clientpass

Tests

  • OpenSearchTransportBuilderTest: host parsing/scheme selection, SSL context construction with truststore, full auth+SSL smoke test
  • PhotonDBConfigTest: CLI parameter parsing round-trip and defaults

Proof of actual usage

Tested against an external OpenSearch 3.x cluster with basic auth and SSL enabled:

Capture d’écran 2026-02-11 à 21 57 13 Capture d’écran 2026-02-11 à 21 56 48

AI disclosure

This PR was developed with AI assistance (Cursor / Claude). All code was reviewed, tested against a real OpenSearch cluster, and validated by the author.

return config.getTransportAddresses().stream()
.map(addr -> addr.split(":", 2))
.map(parts -> new HttpHost(scheme, parts[0],
parts.length > 1 ? Integer.parseInt(parts[1]) : 9201))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Default port 9201 wrong for external OpenSearch connections

Medium Severity

The buildHosts() method defaults to port 9201 when no port is specified in a transport address, but the standard OpenSearch HTTP port is 9200. This causes connection failures for users who specify an address without an explicit port (e.g., opensearch.example.com). The value 9201 was carried over from internal runner discovery config and is not correct for external connections. The test also codifies this incorrect default.

Additional Locations (1)

Fix in Cursor Fix in Web

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.

Just moving existing code (refactoring) from Server.java without changes

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm leaning toward fixing this while reworking this code.

@lonvia
Copy link
Copy Markdown
Collaborator

lonvia commented Feb 13, 2026

Thanks. I need to put this on hold for a bit. httpclient5 has a new version 5.6 out which causes failing tests in Photon. This needs resolved first before adding additional features.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

@Parameter(names = "-opensearch-password", category = GROUP, password = true, description = """
Password for basic authentication with an external OpenSearch cluster
""")
private String opensearchPassword;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JCommander password=true prevents command-line value acceptance

High Severity

The password = true attribute on the -opensearch-password, -opensearch-truststore-password, and -opensearch-keystore-password parameters sets their arity to 0 in JCommander, causing them to prompt for console input rather than accepting values from the command line. Without an explicit arity = 1, passing e.g. -opensearch-password secret won't consume secret as the value — it becomes a stray argument and likely triggers a parse error. The existing -password parameter in PostgresqlConfig notably does not use password = true, following the correct pattern.

Additional Locations (2)

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you comment on that? cursor seems to be right here.

@rexbut
Copy link
Copy Markdown
Author

rexbut commented Feb 13, 2026

Hi @lonvia,

I've updated the dependencies to the latest versions:

  • org.apache.httpcomponents.client5:httpclient5 5.5.1 to 5.6
  • org.opensearch.client:opensearch-java 3.5.0 to 3.6.0

I ran the tests locally with ./gradlew test and everything is passing successfully (see screenshot below). This should resolve the concerns regarding the new HttpClient version.
Capture d’écran 2026-02-13 à 19 54 14

Copy link
Copy Markdown
Collaborator

@lonvia lonvia left a comment

Choose a reason for hiding this comment

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

The code looks okay but it is very verbose, even for Java standards. I'm also rather reluctant to add 7 more parameters, especially when 3 of them are passwords. I can already see the "security bug bounty hunters" having a field day with that.

It's really hard to find decent documentation on this whole SSL/TLS thing but I see references coming up of being able to configure key and trust stores via Java system variables like this:

-Djavax.net.ssl.keyStoreType=pkcs12
-Djavax.net.ssl.trustStoreType=jks
-Djavax.net.ssl.keyStore=keystore.p12
-Djavax.net.ssl.trustStore=truststore.jks
-Djavax.net.ssl.keyStorePassword=$PASS
-Djavax.net.ssl.trustStorePassword=$PASS

So there must be some 'default' way of setting up a SSL connection that is hopefully less verbose and just does the right thing. This would be my preferred way of configuring.

The -opensearch-ssl parameter might be obliterated by requiring a protocol (https or http) in front of the node URLs and assuming http when it is omitted for backwards compatibility.

That would leave the user and password for the OpenSearch database proper. I don't see a good way around that.

final var builder = SSLContextBuilder.create();

if (config.getOpensearchTruststore() != null) {
final var truststore = KeyStore.getInstance("PKCS12");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

KeyStore has a getInstance() that takes a file and password. Would that be usable here?

return config.getTransportAddresses().stream()
.map(addr -> addr.split(":", 2))
.map(parts -> new HttpHost(scheme, parts[0],
parts.length > 1 ? Integer.parseInt(parts[1]) : 9201))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm leaning toward fixing this while reworking this code.

@Parameter(names = "-opensearch-password", category = GROUP, password = true, description = """
Password for basic authentication with an external OpenSearch cluster
""")
private String opensearchPassword;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you comment on that? cursor seems to be right here.

@otbutz
Copy link
Copy Markdown
Contributor

otbutz commented Mar 6, 2026

I don't see why we can't create Trust- and KeyStore in-memory from supplied PEM files?

@lonvia
Copy link
Copy Markdown
Collaborator

lonvia commented Mar 6, 2026

My knowledge on Trust/Keystores is rather limited, I'm afraid. Can you elaborate on what you mean and/or point to documentation?

@otbutz
Copy link
Copy Markdown
Contributor

otbutz commented Mar 6, 2026

You can create an empty KeyStore on the fly and add certificates/keys:

KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);

CertificateFactory factory = CertificateFactory.getInstance("X.509");
InputStream certContent = // load PEM content
X509Certificate cert = (X509Certificate) factory.generateCertificate(certContent);

trustStore.setCertificateEntry("ca-root", cert);

https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/security/KeyStore.html#load(java.io.InputStream,char%5B%5D)

https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/security/cert/CertificateFactory.html#generateCertificate(java.io.InputStream)

IMHO handling PEM files is a lot easier from a sysop perspective than having to deal with Java truststore shenanigans. We can also remove any kind of password handling if we rely on the files having reasonable permissions.

@lonvia
Copy link
Copy Markdown
Collaborator

lonvia commented Mar 6, 2026

I see. But that would mean customizing the process even further. Anything where we can rely on standard Java processes is preferable, even if the standard Java process is a bit convoluted. The goal here is to minimize the code that needs to be maintained in Photon.

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