Skip to content

Commit 283b42d

Browse files
feat: add mTLS client certificate support
1 parent 1380a8f commit 283b42d

2 files changed

Lines changed: 66 additions & 2 deletions

File tree

src/main/java/ai/spice/SpiceClient.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ public class SpiceClient implements AutoCloseable {
156156
private URI flightAddress;
157157
private URI httpAddress;
158158
private int maxRetries;
159+
private String tlsClientCertFile;
160+
private String tlsClientKeyFile;
161+
private String tlsRootCertFile;
159162
private FlightSqlClient flightClient;
160163
private CredentialCallOption authCallOptions = null;
161164
private BufferAllocator allocator;
@@ -200,11 +203,24 @@ public static SpiceClientBuilder builder() throws URISyntaxException {
200203
*/
201204
public SpiceClient(String appId, String apiKey, URI flightAddress, URI httpAddress, int maxRetries,
202205
String userAgent, long memoryLimitMB) {
206+
this(appId, apiKey, flightAddress, httpAddress, maxRetries, userAgent, memoryLimitMB, null, null);
207+
}
208+
209+
public SpiceClient(String appId, String apiKey, URI flightAddress, URI httpAddress, int maxRetries,
210+
String userAgent, long memoryLimitMB, String tlsClientCertFile, String tlsClientKeyFile) {
211+
this(appId, apiKey, flightAddress, httpAddress, maxRetries, userAgent, memoryLimitMB, tlsClientCertFile, tlsClientKeyFile, null);
212+
}
213+
214+
public SpiceClient(String appId, String apiKey, URI flightAddress, URI httpAddress, int maxRetries,
215+
String userAgent, long memoryLimitMB, String tlsClientCertFile, String tlsClientKeyFile, String tlsRootCertFile) {
203216
this.appId = appId;
204217
this.apiKey = apiKey;
205218
this.maxRetries = maxRetries;
206219
this.httpAddress = httpAddress;
207220
this.userAgent = userAgent;
221+
this.tlsClientCertFile = tlsClientCertFile;
222+
this.tlsClientKeyFile = tlsClientKeyFile;
223+
this.tlsRootCertFile = tlsRootCertFile;
208224

209225
// Arrow Flight requires URI to be grpc protocol, convert http/https for
210226
// convinience
@@ -274,8 +290,17 @@ private synchronized void buildFlightClient() {
274290
NettyChannelBuilder channelBuilder = NettyChannelBuilder.forTarget(target);
275291
if (useTls) {
276292
try {
293+
var sslContextBuilder = GrpcSslContexts.forClient();
294+
if (this.tlsClientCertFile != null && this.tlsClientKeyFile != null) {
295+
sslContextBuilder.keyManager(
296+
new java.io.File(this.tlsClientCertFile),
297+
new java.io.File(this.tlsClientKeyFile));
298+
}
299+
if (this.tlsRootCertFile != null) {
300+
sslContextBuilder.trustManager(new java.io.File(this.tlsRootCertFile));
301+
}
277302
channelBuilder.useTransportSecurity()
278-
.sslContext(GrpcSslContexts.forClient().build());
303+
.sslContext(sslContextBuilder.build());
279304
} catch (Exception e) {
280305
throw new RuntimeException("Failed to configure TLS for Flight client", e);
281306
}

src/main/java/ai/spice/SpiceClientBuilder.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public class SpiceClientBuilder {
3939
private URI httpAddress;
4040
private int maxRetries = 3;
4141
private long memoryLimitMB = Long.MAX_VALUE; // Default is all available memory.
42+
private String tlsClientCertFile;
43+
private String tlsClientKeyFile;
44+
private String tlsRootCertFile;
4245

4346
/**
4447
* Constructs a new SpiceClientBuilder instance
@@ -167,12 +170,48 @@ public SpiceClientBuilder withArrowMemoryLimitMB(long memoryLimitMB) {
167170
return this;
168171
}
169172

173+
/**
174+
* Sets the path to a PEM-encoded client certificate file for mTLS.
175+
* Must be used together with {@link #withTlsClientKeyFile(String)}.
176+
*
177+
* @param certFile Path to the client certificate PEM file
178+
* @return The current instance of SpiceClientBuilder for method chaining.
179+
*/
180+
public SpiceClientBuilder withTlsClientCertFile(String certFile) {
181+
this.tlsClientCertFile = certFile;
182+
return this;
183+
}
184+
185+
/**
186+
* Sets the path to a PEM-encoded client private key file for mTLS.
187+
* Must be used together with {@link #withTlsClientCertFile(String)}.
188+
*
189+
* @param keyFile Path to the client private key PEM file
190+
* @return The current instance of SpiceClientBuilder for method chaining.
191+
*/
192+
public SpiceClientBuilder withTlsClientKeyFile(String keyFile) {
193+
this.tlsClientKeyFile = keyFile;
194+
return this;
195+
}
196+
197+
/**
198+
* Sets the path to a PEM-encoded CA certificate file for server verification.
199+
* When set, this CA is used instead of the system trust store.
200+
*
201+
* @param caFile Path to the CA certificate PEM file
202+
* @return The current instance of SpiceClientBuilder for method chaining.
203+
*/
204+
public SpiceClientBuilder withTlsRootCertFile(String caFile) {
205+
this.tlsRootCertFile = caFile;
206+
return this;
207+
}
208+
170209
/**
171210
* Creates SpiceClient with provided parameters.
172211
*
173212
* @return The SpiceClient instance
174213
*/
175214
public SpiceClient build() {
176-
return new SpiceClient(appId, apiKey, flightAddress, httpAddress, maxRetries, userAgent, memoryLimitMB);
215+
return new SpiceClient(appId, apiKey, flightAddress, httpAddress, maxRetries, userAgent, memoryLimitMB, tlsClientCertFile, tlsClientKeyFile, tlsRootCertFile);
177216
}
178217
}

0 commit comments

Comments
 (0)