Skip to content

Commit c9fd388

Browse files
committed
PoC dynamic keystore on httpdestination
1 parent fc13905 commit c9fd388

6 files changed

Lines changed: 63 additions & 38 deletions

File tree

cloudplatform/cloudplatform-connectivity/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/DefaultHttpDestination.java

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.Map;
1414
import java.util.Objects;
1515
import java.util.function.Function;
16+
import java.util.function.Supplier;
1617

1718
import javax.annotation.Nonnull;
1819
import javax.annotation.Nullable;
@@ -52,8 +53,8 @@ public final class DefaultHttpDestination implements HttpDestination
5253
@Delegate
5354
private final DestinationProperties baseProperties;
5455

55-
@Nullable
56-
private final KeyStore keyStore;
56+
@Nonnull
57+
private final Supplier<Option<KeyStore>> keyStore;
5758

5859
@Nullable
5960
private final KeyStore trustStore;
@@ -98,7 +99,7 @@ private DefaultHttpDestination(
9899
@Nonnull final DestinationProperties baseProperties,
99100
@Nonnull final ComplexDestinationPropertyFactory destinationPropertyFactory,
100101
@Nullable final List<Header> customHeaders,
101-
@Nullable final KeyStore keyStore,
102+
@Nonnull final Supplier<Option<KeyStore>> keyStore,
102103
@Nullable final KeyStore trustStore,
103104
@Nullable final List<DestinationHeaderProvider> customHeaderProviders )
104105
{
@@ -296,7 +297,7 @@ public Option<ProxyConfiguration> getProxyConfiguration()
296297
@Override
297298
public Option<KeyStore> getKeyStore()
298299
{
299-
return Option.of(keyStore);
300+
return keyStore.get();
300301
}
301302

302303
@Nonnull
@@ -516,7 +517,7 @@ public static Builder fromDestination( @Nonnull final Destination destination )
516517
builder
517518
.headerProviders(httpDestination.getCustomHeaderProviders().toArray(new DestinationHeaderProvider[0]));
518519

519-
httpDestination.getKeyStore().map(builder::keyStore);
520+
builder.keyStoreSupplier(httpDestination.keyStore);
520521
httpDestination.getTrustStore().map(builder::trustStore);
521522
}
522523

@@ -538,7 +539,9 @@ public boolean equals( @Nullable final Object o )
538539
return new EqualsBuilder()
539540
.append(baseProperties, that.baseProperties)
540541
.append(customHeaders, that.customHeaders)
541-
.append(resolveCertificatesOnly(keyStore), resolveCertificatesOnly(that.keyStore))
542+
.append(
543+
resolveCertificatesOnly(keyStore.get().getOrNull()),
544+
resolveCertificatesOnly(that.keyStore.get().getOrNull()))
542545
.append(resolveCertificatesOnly(trustStore), resolveCertificatesOnly(that.trustStore))
543546
.isEquals();
544547
}
@@ -549,7 +552,7 @@ public int hashCode()
549552
return new HashCodeBuilder(17, 37)
550553
.append(baseProperties)
551554
.append(customHeaders)
552-
.append(resolveKeyStoreHashCode(keyStore))
555+
.append(resolveKeyStoreHashCode(keyStore.get().getOrNull()))
553556
.append(resolveKeyStoreHashCode(trustStore))
554557
.toHashCode();
555558
}
@@ -568,11 +571,28 @@ public static class Builder
568571
private DefaultHttpDestinationBuilderProxyHandler proxyHandler =
569572
new DefaultHttpDestinationBuilderProxyHandler();
570573

574+
@Nonnull
575+
Supplier<Option<KeyStore>> keystoreSupplier = Option::none;
576+
571577
/**
572578
* The {@link KeyStore} to be used when communicating over HTTP.
573579
*/
574-
@Setter( onParam_ = @Nullable )
575-
KeyStore keyStore = null;
580+
@Nonnull
581+
public Builder keyStore( @Nullable final KeyStore keyStore )
582+
{
583+
this.keystoreSupplier = () -> Option.of(keyStore);
584+
return this;
585+
}
586+
587+
/**
588+
* A {@link Supplier<KeyStore>} to allow for dynamically resolve certificates at runtime for HTTP communication.
589+
*/
590+
@Nonnull
591+
Builder keyStoreSupplier( @Nonnull final Supplier<Option<KeyStore>> supplier )
592+
{
593+
this.keystoreSupplier = supplier;
594+
return this;
595+
}
576596

577597
/**
578598
* The trust store to be used when communicating over HTTP.
@@ -1022,7 +1042,7 @@ DefaultHttpDestination buildInternal()
10221042
builder.build(),
10231043
new ComplexDestinationPropertyFactory(),
10241044
headers,
1025-
keyStore,
1045+
keystoreSupplier,
10261046
trustStore,
10271047
customHeaderProviders);
10281048
}

cloudplatform/cloudplatform-connectivity/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DefaultHttpDestinationBuilderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ void testFromDestination()
9696
assertThat(sut.get("bar", v -> (int) v)).containsExactly(42);
9797
assertThat(sut.headers).containsExactly(header);
9898
assertThat(sut.customHeaderProviders).containsExactly(headerProvider);
99-
assertThat(sut.keyStore).isSameAs(keyStore);
99+
assertThat(sut.keystoreSupplier.get().getOrNull()).isSameAs(keyStore);
100100
assertThat(sut.trustStore).isSameAs(trustStore);
101101
assertThat(sut.get(DestinationProperty.TRUST_ALL)).containsExactly(true);
102102

cloudplatform/cloudplatform-connectivity/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DefaultHttpDestinationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ void testToBuilderContainsAllProperties()
706706
assertThat(sut.get("bar", v -> (int) v)).containsExactly(42);
707707
assertThat(sut.headers).containsExactly(header);
708708
assertThat(sut.customHeaderProviders).containsExactly(headerProvider);
709-
assertThat(sut.keyStore).isSameAs(keyStore);
709+
assertThat(sut.keystoreSupplier.get().getOrNull()).isSameAs(keyStore);
710710
assertThat(sut.trustStore).isSameAs(trustStore);
711711
assertThat(sut.get(DestinationProperty.TRUST_ALL)).containsExactly(true);
712712
}

cloudplatform/connectivity-oauth/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServicePropertySuppliers.java

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77

88
import java.net.URI;
99
import java.net.URISyntaxException;
10-
import java.security.KeyStore;
1110
import java.util.ArrayList;
1211
import java.util.Collections;
1312
import java.util.List;
1413

1514
import javax.annotation.Nonnull;
16-
import javax.annotation.Nullable;
1715

1816
import com.sap.cloud.environment.servicebinding.api.ServiceIdentifier;
1917
import com.sap.cloud.sdk.cloudplatform.connectivity.BtpServiceOptions.BusinessLoggingOptions;
@@ -264,28 +262,18 @@ private boolean currentTenantIsProvider()
264262
}
265263

266264
private void attachClientKeyStore( @Nonnull final OAuth2Options.Builder optionsBuilder )
267-
{
268-
final KeyStore maybeClientStore = getClientKeyStore();
269-
if( maybeClientStore != null ) {
270-
// note: in case the KS is loaded from ZTIS, the KS used for token retrieval and the KS registered here for mTLS to the target system may diverge
271-
// Token retrieval supports certificate rotation in place, but mTLS to the target system requires re-loading the destination instead.
272-
optionsBuilder.withClientKeyStore(maybeClientStore);
273-
}
274-
}
275-
276-
@Nullable
277-
private KeyStore getClientKeyStore()
278265
{
279266
final ClientIdentity clientIdentity = getClientIdentity();
267+
280268
if( clientIdentity instanceof ZtisClientIdentity ztisClientIdentity ) {
281-
return ztisClientIdentity.getKeyStore();
269+
optionsBuilder.withClientKeyStoreSupplier(ztisClientIdentity::getKeyStore);
270+
return;
282271
}
283272
if( !(clientIdentity instanceof ClientCertificate) ) {
284-
return null;
273+
return;
285274
}
286-
287275
try {
288-
return SSLContextFactory.getInstance().createKeyStore(clientIdentity);
276+
optionsBuilder.withClientKeyStore(SSLContextFactory.getInstance().createKeyStore(clientIdentity));
289277
}
290278
catch( final Exception e ) {
291279
throw new DestinationAccessException("Unable to extract client key store from IAS service binding.", e);

cloudplatform/connectivity-oauth/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/OAuth2Options.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.time.Duration;
55
import java.util.HashMap;
66
import java.util.Map;
7+
import java.util.function.Supplier;
78

89
import javax.annotation.Nonnull;
910
import javax.annotation.Nullable;
@@ -63,13 +64,20 @@ public final class OAuth2Options
6364
@Nonnull
6465
@Getter
6566
private final TimeLimiterConfiguration timeLimiter;
67+
6668
/**
6769
* The {@link KeyStore} to use for building an mTLS connection towards the <b>target system</b>. This
6870
* {@link KeyStore} <b>is not used</b> to build an mTLS connection towards the OAuth2 token service.
6971
*/
7072
@Nullable
71-
@Getter
72-
private final KeyStore clientKeyStore;
73+
public KeyStore getClientKeyStore()
74+
{
75+
return clientKeyStoreSupplier != null ? clientKeyStoreSupplier.get() : null;
76+
}
77+
78+
@Nullable
79+
@Getter( AccessLevel.PACKAGE )
80+
private final Supplier<KeyStore> clientKeyStoreSupplier;
7381

7482
/**
7583
* Configuration for caching OAuth2 tokens.
@@ -121,7 +129,7 @@ public static class Builder
121129
{
122130
private boolean skipTokenRetrieval = false;
123131
private final Map<String, String> additionalTokenRetrievalParameters = new HashMap<>();
124-
private KeyStore clientKeyStore;
132+
private Supplier<KeyStore> clientKeyStoreSupplier;
125133
private TimeLimiterConfiguration timeLimiter = DEFAULT_TIMEOUT;
126134
private TokenCacheParameters tokenCacheParameters = DEFAULT_TOKEN_CACHE_PARAMETERS;
127135

@@ -182,7 +190,14 @@ public Builder withTokenRetrievalParameters( @Nonnull final Map<String, String>
182190
@Nonnull
183191
public Builder withClientKeyStore( @Nonnull final KeyStore clientKeyStore )
184192
{
185-
this.clientKeyStore = clientKeyStore;
193+
this.clientKeyStoreSupplier = () -> clientKeyStore;
194+
return this;
195+
}
196+
197+
@Nonnull
198+
Builder withClientKeyStoreSupplier( @Nonnull final Supplier<KeyStore> clientKeyStore )
199+
{
200+
this.clientKeyStoreSupplier = clientKeyStore;
186201
return this;
187202
}
188203

@@ -236,7 +251,7 @@ public OAuth2Options build()
236251
skipTokenRetrieval,
237252
new HashMap<>(additionalTokenRetrievalParameters),
238253
timeLimiter,
239-
clientKeyStore,
254+
clientKeyStoreSupplier,
240255
tokenCacheParameters);
241256
}
242257
}

cloudplatform/connectivity-oauth/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/OAuth2ServiceBindingDestinationLoader.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,10 @@ HttpDestination toDestination(
255255
destinationBuilder.headerProviders(headerProvider);
256256
}
257257

258-
if( oAuth2Options.getClientKeyStore() != null ) {
258+
if( oAuth2Options.getClientKeyStoreSupplier() != null ) {
259259
log.debug("Securing communication to OAuth2 destination '{}' using mTLS.", idString);
260-
destinationBuilder.keyStore(oAuth2Options.getClientKeyStore());
260+
final var supplier = oAuth2Options.getClientKeyStoreSupplier();
261+
destinationBuilder.keyStoreSupplier(() -> Option.of(supplier.get()));
261262
}
262263

263264
return destinationBuilder.build();
@@ -292,12 +293,13 @@ HttpDestination toProxiedDestination(
292293
destinationBuilder.headerProviders(headerProvider);
293294
}
294295

295-
if( oAuth2Options.getClientKeyStore() != null ) {
296+
if( oAuth2Options.getClientKeyStoreSupplier() != null ) {
296297
log
297298
.debug(
298299
"Securing communication to OAuth2 proxy server for proxied destination '{}' using mTLS.",
299300
destinationName);
300-
destinationBuilder.keyStore(oAuth2Options.getClientKeyStore());
301+
final var supplier = oAuth2Options.getClientKeyStoreSupplier();
302+
destinationBuilder.keyStoreSupplier(() -> Option.of(supplier.get()));
301303
}
302304

303305
// don't override the proxy URL if it has been set explicitly/manually already

0 commit comments

Comments
 (0)