Skip to content

Commit 2595cc5

Browse files
authored
feat(sdk): expose GRPC auth service components (#92)
this will allow gateway to use the same mechanisms to authenticate to the tagging pdp and trust the tagging pdp certificate also let the platform drive whether or not auth is enabled: if no `platform_issuer` is found then we should not crash trying to add auth tokens
1 parent b99de43 commit 2595cc5

File tree

4 files changed

+143
-16
lines changed

4 files changed

+143
-16
lines changed

sdk/src/main/java/io/opentdf/platform/sdk/SDK.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.opentdf.platform.sdk;
22

3+
import io.grpc.ClientInterceptor;
34
import io.grpc.ManagedChannel;
45
import io.opentdf.platform.authorization.AuthorizationServiceGrpc;
56
import io.opentdf.platform.authorization.AuthorizationServiceGrpc.AuthorizationServiceFutureStub;
@@ -13,12 +14,17 @@
1314
import io.opentdf.platform.policy.subjectmapping.SubjectMappingServiceGrpc.SubjectMappingServiceFutureStub;
1415
import io.opentdf.platform.sdk.nanotdf.NanoTDFType;
1516

17+
import javax.net.ssl.TrustManager;
18+
import java.util.Optional;
19+
1620
/**
1721
* The SDK class represents a software development kit for interacting with the opentdf platform. It
1822
* provides various services and stubs for making API calls to the opentdf platform.
1923
*/
2024
public class SDK implements AutoCloseable {
2125
private final Services services;
26+
private final TrustManager trustManager;
27+
private final ClientInterceptor authInterceptor;
2228

2329
@Override
2430
public void close() throws Exception {
@@ -89,11 +95,21 @@ public KAS kas() {
8995
}
9096
}
9197

92-
SDK(Services services) {
98+
public Optional<TrustManager> getTrustManager() {
99+
return Optional.ofNullable(trustManager);
100+
}
101+
102+
public Optional<ClientInterceptor> getAuthInterceptor() {
103+
return Optional.ofNullable(authInterceptor);
104+
}
105+
106+
SDK(Services services, TrustManager trustManager, ClientInterceptor authInterceptor) {
93107
this.services = services;
108+
this.trustManager = trustManager;
109+
this.authInterceptor = authInterceptor;
94110
}
95111

96-
public Services getServices(){
112+
public Services getServices() {
97113
return this.services;
98114
}
99-
}
115+
}

sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.slf4j.Logger;
2121
import org.slf4j.LoggerFactory;
2222

23+
import javax.net.ssl.TrustManager;
2324
import javax.net.ssl.X509ExtendedTrustManager;
2425
import java.io.File;
2526
import java.io.FileInputStream;
@@ -29,6 +30,7 @@
2930
import java.util.ArrayList;
3031
import java.util.List;
3132
import java.util.UUID;
33+
import java.util.function.Function;
3234

3335
/**
3436
* A builder class for creating instances of the SDK class.
@@ -145,8 +147,9 @@ private GRPCAuthInterceptor getGrpcAuthInterceptor(RSAKey rsaKey) {
145147
.getFieldsOrThrow(PLATFORM_ISSUER)
146148
.getStringValue();
147149

148-
} catch (StatusRuntimeException e) {
149-
throw new SDKException("Error getting the issuer from the platform", e);
150+
} catch (IllegalArgumentException e) {
151+
logger.warn("no `platform_issuer` found in well known configuration. requests from the SDK will be unauthenticated", e);
152+
return null;
150153
}
151154

152155
Issuer issuer = new Issuer(platformIssuer);
@@ -164,7 +167,20 @@ private GRPCAuthInterceptor getGrpcAuthInterceptor(RSAKey rsaKey) {
164167
return new GRPCAuthInterceptor(clientAuth, rsaKey, providerMetadata.getTokenEndpointURI(), sslFactory);
165168
}
166169

167-
SDK.Services buildServices() {
170+
static class ServicesAndInternals {
171+
final ClientInterceptor interceptor;
172+
final TrustManager trustManager;
173+
174+
final SDK.Services services;
175+
176+
ServicesAndInternals(ClientInterceptor interceptor, TrustManager trustManager, SDK.Services services) {
177+
this.interceptor = interceptor;
178+
this.trustManager = trustManager;
179+
this.services = services;
180+
}
181+
}
182+
183+
ServicesAndInternals buildServices() {
168184
RSAKey dpopKey;
169185
try {
170186
dpopKey = new RSAKeyGenerator(2048)
@@ -176,13 +192,27 @@ SDK.Services buildServices() {
176192
}
177193

178194
var authInterceptor = getGrpcAuthInterceptor(dpopKey);
179-
var channel = getManagedChannelBuilder(platformEndpoint).intercept(authInterceptor).build();
180-
var client = new KASClient(endpoint -> getManagedChannelBuilder(endpoint).intercept(authInterceptor).build(), dpopKey);
181-
return SDK.Services.newServices(channel, client);
195+
ManagedChannel channel;
196+
Function<String, ManagedChannel> managedChannelFactory;
197+
if (authInterceptor == null) {
198+
channel = getManagedChannelBuilder(platformEndpoint).build();
199+
managedChannelFactory = (String endpoint) -> getManagedChannelBuilder(endpoint).build();
200+
201+
} else {
202+
channel = getManagedChannelBuilder(platformEndpoint).intercept(authInterceptor).build();
203+
managedChannelFactory = (String endpoint) -> getManagedChannelBuilder(endpoint).intercept(authInterceptor).build();
204+
}
205+
var client = new KASClient(managedChannelFactory, dpopKey);
206+
return new ServicesAndInternals(
207+
authInterceptor,
208+
sslFactory == null ? null : sslFactory.getTrustManager().orElse(null),
209+
SDK.Services.newServices(channel, client)
210+
);
182211
}
183212

184213
public SDK build() {
185-
return new SDK(buildServices());
214+
var services = buildServices();
215+
return new SDK(services.services, services.trustManager, services.interceptor);
186216
}
187217

188218
/**

sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@
22

33
import com.google.protobuf.Struct;
44
import com.google.protobuf.Value;
5+
import io.grpc.ClientInterceptor;
56
import io.grpc.Metadata;
67
import io.grpc.Server;
78
import io.grpc.ServerBuilder;
89
import io.grpc.ServerCall;
910
import io.grpc.ServerCallHandler;
1011
import io.grpc.ServerInterceptor;
12+
import io.grpc.StatusRuntimeException;
1113
import io.grpc.stub.StreamObserver;
1214
import io.opentdf.platform.kas.AccessServiceGrpc;
1315
import io.opentdf.platform.kas.RewrapRequest;
1416
import io.opentdf.platform.kas.RewrapResponse;
1517
import io.opentdf.platform.policy.namespaces.GetNamespaceRequest;
18+
import io.opentdf.platform.policy.namespaces.GetNamespaceResponse;
1619
import io.opentdf.platform.policy.namespaces.NamespaceServiceGrpc;
1720
import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationRequest;
1821
import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationResponse;
@@ -37,16 +40,16 @@
3740
import java.security.cert.X509Certificate;
3841
import java.util.Arrays;
3942
import java.util.Base64;
43+
import java.util.concurrent.ExecutionException;
4044
import java.util.concurrent.atomic.AtomicReference;
4145

4246
import static org.assertj.core.api.Assertions.assertThat;
4347
import static org.junit.jupiter.api.Assertions.*;
4448

4549

46-
4750
public class SDKBuilderTest {
4851

49-
final String EXAMPLE_COM_PEM="-----BEGIN CERTIFICATE-----\n" +
52+
final String EXAMPLE_COM_PEM= "-----BEGIN CERTIFICATE-----\n" +
5053
"MIIBqTCCARKgAwIBAgIIT0xFd/5uogEwDQYJKoZIhvcNAQEFBQAwFjEUMBIGA1UEAxMLZXhhbXBs\n" +
5154
"ZS5jb20wIBcNMTcwMTIwMTczOTIwWhgPOTk5OTEyMzEyMzU5NTlaMBYxFDASBgNVBAMTC2V4YW1w\n" +
5255
"bGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2Tl2MdaUFmjAaYwmEwgEVRfVqwJO4\n" +
@@ -224,8 +227,12 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, Re
224227
certificate()).build());
225228
}
226229

227-
services = servicesBuilder
228-
.buildServices();
230+
var servicesAndComponents = servicesBuilder.buildServices();
231+
if (useSSL) {
232+
assertThat(servicesAndComponents.trustManager).isNotNull();
233+
}
234+
assertThat(servicesAndComponents.interceptor).isNotNull();
235+
services = servicesAndComponents.services;
229236

230237
assertThat(services).isNotNull();
231238

@@ -295,6 +302,77 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, Re
295302
}
296303
}
297304

305+
/**
306+
* If auth is disabled then the `platform_issuer` isn't returned during bootstrapping. The SDK
307+
* should still function without auth if auth is disabled on the server
308+
* @throws IOException
309+
*/
310+
@Test
311+
public void testSdkWithNoIssuerMakesRequests() throws IOException {
312+
WellKnownServiceGrpc.WellKnownServiceImplBase wellKnownService = new WellKnownServiceGrpc.WellKnownServiceImplBase() {
313+
@Override
314+
public void getWellKnownConfiguration(GetWellKnownConfigurationRequest request, StreamObserver<GetWellKnownConfigurationResponse> responseObserver) {
315+
// don't return a platform issuer
316+
responseObserver.onNext(GetWellKnownConfigurationResponse.getDefaultInstance());
317+
responseObserver.onCompleted();
318+
}
319+
};
320+
321+
var authHeader = new AtomicReference<String>(null);
322+
var getNsCalled = new AtomicReference<Boolean>(false);
323+
324+
var platformServices = ServerBuilder
325+
.forPort(getRandomPort())
326+
.directExecutor()
327+
.intercept(new ServerInterceptor() {
328+
@Override
329+
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
330+
authHeader.set(
331+
headers.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER))
332+
);
333+
return next.startCall(call, headers);
334+
}
335+
})
336+
.addService(wellKnownService)
337+
.addService(new NamespaceServiceGrpc.NamespaceServiceImplBase() {
338+
@Override
339+
public void getNamespace(GetNamespaceRequest request, StreamObserver<GetNamespaceResponse> responseObserver) {
340+
getNsCalled.set(true);
341+
responseObserver.onNext(GetNamespaceResponse.getDefaultInstance());
342+
responseObserver.onCompleted();
343+
}
344+
})
345+
.build();
346+
347+
SDK sdk;
348+
try {
349+
platformServices.start();
350+
351+
sdk = SDKBuilder.newBuilder()
352+
.clientSecret("user", "password")
353+
.platformEndpoint("localhost:" + platformServices.getPort())
354+
.useInsecurePlaintextConnection(true)
355+
.build();
356+
assertThat(sdk.getAuthInterceptor()).isEmpty();
357+
358+
359+
try {
360+
sdk.getServices().namespaces().getNamespace(GetNamespaceRequest.getDefaultInstance()).get();
361+
} catch (StatusRuntimeException ignored) {
362+
} catch (ExecutionException e) {
363+
throw new RuntimeException(e);
364+
} catch (InterruptedException e) {
365+
Thread.currentThread().interrupt();
366+
throw new RuntimeException(e);
367+
}
368+
369+
assertThat(getNsCalled.get()).isTrue();
370+
assertThat(authHeader.get()).isNullOrEmpty();
371+
} finally {
372+
platformServices.shutdownNow();
373+
}
374+
}
375+
298376
public static int getRandomPort() throws IOException {
299377
int randomPort;
300378
try (ServerSocket socket = new ServerSocket(0)) {

sdk/src/test/java/io/opentdf/platform/sdk/TDFE2ETest.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ public void createAndDecryptTdfIT() throws Exception {
2222
.clientSecret("opentdf-sdk", "secret")
2323
.useInsecurePlaintextConnection(true)
2424
.platformEndpoint("localhost:8080")
25-
.buildServices();
25+
.buildServices()
26+
.services;
27+
2628

2729
var kasInfo = new Config.KASInfo();
2830
kasInfo.URL = "localhost:8080";
@@ -50,7 +52,8 @@ public void createAndDecryptNanoTDF() throws Exception {
5052
.clientSecret("opentdf-sdk", "secret")
5153
.useInsecurePlaintextConnection(true)
5254
.platformEndpoint("localhost:8080")
53-
.buildServices();
55+
.buildServices()
56+
.services;
5457

5558
var kasInfo = new Config.KASInfo();
5659
kasInfo.URL = "http://localhost:8080";

0 commit comments

Comments
 (0)