Skip to content

Commit d4c99af

Browse files
committed
minimal impl
1 parent ee1836f commit d4c99af

File tree

60 files changed

+3261
-1370
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+3261
-1370
lines changed

athenz/src/main/java/com/linecorp/armeria/server/athenz/MinifiedAuthZpeClient.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,14 @@
5757
import com.yahoo.rdl.Struct;
5858

5959
import com.linecorp.armeria.client.ClientFactory;
60+
import com.linecorp.armeria.client.ClientTlsSpec;
61+
import com.linecorp.armeria.client.ClientTlsSpecBuilder;
6062
import com.linecorp.armeria.client.athenz.ZtsBaseClient;
63+
import com.linecorp.armeria.common.TlsKeyPair;
6164
import com.linecorp.armeria.common.TlsProvider;
6265
import com.linecorp.armeria.common.annotation.Nullable;
6366
import com.linecorp.armeria.common.util.TlsEngineType;
6467
import com.linecorp.armeria.internal.common.SslContextFactory;
65-
import com.linecorp.armeria.internal.common.SslContextFactory.SslContextMode;
6668

6769
import io.netty.handler.ssl.JdkSslContext;
6870

@@ -238,13 +240,26 @@ private static JwtsSigningKeyResolver newDefaultJwtsSigningKeyResolver(ZtsBaseCl
238240
}
239241
final ClientFactory clientFactory = ztsBaseClient.clientFactory();
240242
final TlsProvider tlsProvider = clientFactory.options().tlsProvider();
241-
final SslContextFactory sslContextFactory = new SslContextFactory(tlsProvider, TlsEngineType.JDK,
242-
null, clientFactory.meterRegistry());
243-
final JdkSslContext sslContext = (JdkSslContext) sslContextFactory.getOrCreate(SslContextMode.CLIENT,
244-
"*");
243+
final SslContextFactory sslContextFactory = new SslContextFactory(null, clientFactory.meterRegistry());
244+
final ClientTlsSpec clientTlsSpec = toTlsSpec(tlsProvider);
245+
final JdkSslContext sslContext = (JdkSslContext) sslContextFactory.getOrCreate(clientTlsSpec);
245246
return new JwtsSigningKeyResolver(ztsUri + oauth2KeysPath, sslContext.context(), proxyUriStr);
246247
}
247248

249+
private static ClientTlsSpec toTlsSpec(TlsProvider tlsProvider) {
250+
final ClientTlsSpecBuilder builder = ClientTlsSpec.builder();
251+
final TlsKeyPair tlsKeyPair = tlsProvider.keyPair("*");
252+
if (tlsKeyPair != null) {
253+
builder.tlsKeyPair(tlsKeyPair);
254+
}
255+
final List<X509Certificate> trustedCertificates = tlsProvider.trustedCertificates("*");
256+
if (trustedCertificates != null) {
257+
builder.trustedCertificates(trustedCertificates);
258+
}
259+
builder.engineType(TlsEngineType.JDK);
260+
return builder.build();
261+
}
262+
248263
/**
249264
* Set the role token allowed offset. this might be necessary
250265
* if the client and server are not ntp synchronized, and we

core/src/main/java/com/linecorp/armeria/client/BlockingWebClientRequestPreparation.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,13 @@ public BlockingWebClientRequestPreparation responseTimeoutMode(ResponseTimeoutMo
338338
return this;
339339
}
340340

341+
@Override
342+
@UnstableApi
343+
public BlockingWebClientRequestPreparation clientTlsSpec(ClientTlsSpec clientTlsSpec) {
344+
delegate.clientTlsSpec(clientTlsSpec);
345+
return this;
346+
}
347+
341348
@Override
342349
public BlockingWebClientRequestPreparation requestOptions(RequestOptions requestOptions) {
343350
delegate.requestOptions(requestOptions);

core/src/main/java/com/linecorp/armeria/client/Bootstraps.java

Lines changed: 25 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@
1919
import static com.linecorp.armeria.common.SessionProtocol.httpAndHttpsValues;
2020

2121
import java.lang.reflect.Array;
22-
import java.net.InetSocketAddress;
2322
import java.net.SocketAddress;
23+
import java.util.Objects;
2424
import java.util.Set;
2525

2626
import com.linecorp.armeria.common.SerializationFormat;
2727
import com.linecorp.armeria.common.SessionProtocol;
2828
import com.linecorp.armeria.common.annotation.Nullable;
2929
import com.linecorp.armeria.internal.common.SslContextFactory;
30-
import com.linecorp.armeria.internal.common.SslContextFactory.SslContextMode;
3130

3231
import io.netty.bootstrap.Bootstrap;
3332
import io.netty.channel.Channel;
@@ -39,28 +38,24 @@
3938
final class Bootstraps {
4039

4140
private final EventLoop eventLoop;
42-
private final SslContext sslCtxHttp1Only;
43-
private final SslContext sslCtxHttp1Or2;
44-
@Nullable
4541
private final SslContextFactory sslContextFactory;
4642

4743
private final HttpClientFactory clientFactory;
4844
private final Bootstrap inetBaseBootstrap;
45+
private final DefaultSslContexts defaultSslContexts;
4946
@Nullable
5047
private final Bootstrap unixBaseBootstrap;
5148
private final Bootstrap[][] inetBootstraps;
5249
private final Bootstrap @Nullable [][] unixBootstraps;
5350

5451
Bootstraps(HttpClientFactory clientFactory, EventLoop eventLoop,
55-
SslContext sslCtxHttp1Or2, SslContext sslCtxHttp1Only,
56-
@Nullable SslContextFactory sslContextFactory) {
52+
SslContextFactory sslContextFactory, DefaultSslContexts defaultSslContexts) {
5753
this.eventLoop = eventLoop;
58-
this.sslCtxHttp1Or2 = sslCtxHttp1Or2;
59-
this.sslCtxHttp1Only = sslCtxHttp1Only;
6054
this.sslContextFactory = sslContextFactory;
6155
this.clientFactory = clientFactory;
6256

6357
inetBaseBootstrap = clientFactory.newInetBootstrap();
58+
this.defaultSslContexts = defaultSslContexts;
6459
inetBaseBootstrap.group(eventLoop);
6560
inetBootstraps = staticBootstrapMap(inetBaseBootstrap);
6661

@@ -80,20 +75,13 @@ private Bootstrap[][] staticBootstrapMap(Bootstrap baseBootstrap) {
8075
// Attempting to access the array with an unallowed protocol will trigger NPE,
8176
// which will help us find a bug.
8277
for (SessionProtocol p : sessionProtocols) {
83-
final SslContext sslCtx = determineSslContext(p);
78+
final SslContext sslCtx = p.isTls() ? defaultSslContexts.getSslContext(p) : null;
8479
createAndSetBootstrap(baseBootstrap, maps, p, sslCtx, true);
8580
createAndSetBootstrap(baseBootstrap, maps, p, sslCtx, false);
8681
}
8782
return maps;
8883
}
8984

90-
/**
91-
* Determine {@link SslContext} by the specified {@link SessionProtocol}.
92-
*/
93-
SslContext determineSslContext(SessionProtocol desiredProtocol) {
94-
return desiredProtocol.isExplicitHttp1() ? sslCtxHttp1Only : sslCtxHttp1Or2;
95-
}
96-
9785
private Bootstrap select(boolean isDomainSocket, SessionProtocol desiredProtocol,
9886
SerializationFormat serializationFormat) {
9987
final Bootstrap[][] bootstraps = isDomainSocket ? unixBootstraps : inetBootstraps;
@@ -102,7 +90,7 @@ private Bootstrap select(boolean isDomainSocket, SessionProtocol desiredProtocol
10290
}
10391

10492
private void createAndSetBootstrap(Bootstrap baseBootstrap, Bootstrap[][] maps,
105-
SessionProtocol desiredProtocol, SslContext sslContext,
93+
SessionProtocol desiredProtocol, @Nullable SslContext sslContext,
10694
boolean webSocket) {
10795
maps[desiredProtocol.ordinal()][toIndex(webSocket)] = newBootstrap(baseBootstrap, desiredProtocol,
10896
sslContext, webSocket, false);
@@ -121,7 +109,7 @@ private static int toIndex(SerializationFormat serializationFormat) {
121109
* {@link SessionProtocol} and {@link SerializationFormat}.
122110
*/
123111
Bootstrap getOrCreate(SocketAddress remoteAddress, SessionProtocol desiredProtocol,
124-
SerializationFormat serializationFormat) {
112+
SerializationFormat serializationFormat, ClientTlsSpec tlsSpec) {
125113
if (!httpAndHttpsValues().contains(desiredProtocol)) {
126114
throw new IllegalArgumentException("Unsupported session protocol: " + desiredProtocol);
127115
}
@@ -132,71 +120,50 @@ Bootstrap getOrCreate(SocketAddress remoteAddress, SessionProtocol desiredProtoc
132120
eventLoop.getClass().getName());
133121
}
134122

135-
if (sslContextFactory == null || !desiredProtocol.isTls()) {
123+
if (!desiredProtocol.isTls()) {
124+
return select(isDomainSocket, desiredProtocol, serializationFormat);
125+
}
126+
final ClientTlsSpec defaultTlsSpec = defaultSslContexts.getClientTlsSpec(desiredProtocol);
127+
if (Objects.equals(defaultTlsSpec, tlsSpec)) {
136128
return select(isDomainSocket, desiredProtocol, serializationFormat);
137129
}
138130

139131
final Bootstrap baseBootstrap = isDomainSocket ? unixBaseBootstrap : inetBaseBootstrap;
140132
assert baseBootstrap != null;
141-
return newBootstrap(baseBootstrap, remoteAddress, desiredProtocol, serializationFormat);
133+
return newBootstrap(baseBootstrap, desiredProtocol, serializationFormat, tlsSpec);
142134
}
143135

144-
private Bootstrap newBootstrap(Bootstrap baseBootstrap, SocketAddress remoteAddress,
136+
private Bootstrap newBootstrap(Bootstrap baseBootstrap,
145137
SessionProtocol desiredProtocol,
146-
SerializationFormat serializationFormat) {
138+
SerializationFormat serializationFormat, ClientTlsSpec tlsSpec) {
147139
final boolean webSocket = serializationFormat == SerializationFormat.WS;
148-
final SslContext sslContext = newSslContext(remoteAddress, desiredProtocol);
140+
final SslContext sslContext = sslContextFactory.getOrCreate(tlsSpec);
149141
return newBootstrap(baseBootstrap, desiredProtocol, sslContext, webSocket, true);
150142
}
151143

152144
private Bootstrap newBootstrap(Bootstrap baseBootstrap, SessionProtocol desiredProtocol,
153-
SslContext sslContext, boolean webSocket, boolean closeSslContext) {
145+
@Nullable SslContext sslContext, boolean webSocket,
146+
boolean closeSslContext) {
154147
final Bootstrap bootstrap = baseBootstrap.clone();
155148
bootstrap.handler(clientChannelInitializer(desiredProtocol, sslContext, webSocket, closeSslContext));
156149
return bootstrap;
157150
}
158151

159-
SslContext getOrCreateSslContext(SocketAddress remoteAddress, SessionProtocol desiredProtocol) {
160-
if (sslContextFactory == null) {
161-
return determineSslContext(desiredProtocol);
162-
} else {
163-
return newSslContext(remoteAddress, desiredProtocol);
164-
}
152+
SslContext getOrCreateSslContext(ClientTlsSpec tlsSpec) {
153+
return sslContextFactory.getOrCreate(tlsSpec);
165154
}
166155

167-
private SslContext newSslContext(SocketAddress remoteAddress, SessionProtocol desiredProtocol) {
168-
final String hostname;
169-
if (remoteAddress instanceof InetSocketAddress) {
170-
hostname = ((InetSocketAddress) remoteAddress).getHostString();
171-
} else {
172-
assert remoteAddress instanceof DomainSocketAddress;
173-
hostname = "unix:" + ((DomainSocketAddress) remoteAddress).path();
174-
}
175-
176-
final SslContextMode sslContextMode =
177-
desiredProtocol.isExplicitHttp1() ? SslContextFactory.SslContextMode.CLIENT_HTTP1_ONLY
178-
: SslContextFactory.SslContextMode.CLIENT;
179-
assert sslContextFactory != null;
180-
return sslContextFactory.getOrCreate(sslContextMode, hostname);
181-
}
182-
183-
boolean shouldReleaseSslContext(SslContext sslContext) {
184-
return sslContext != sslCtxHttp1Only && sslContext != sslCtxHttp1Or2;
185-
}
186-
187-
void releaseSslContext(SslContext sslContext) {
188-
if (sslContextFactory != null) {
189-
sslContextFactory.release(sslContext);
190-
}
156+
void release(SslContext sslContext) {
157+
sslContextFactory.release(sslContext);
191158
}
192159

193-
private ChannelInitializer<Channel> clientChannelInitializer(SessionProtocol p, SslContext sslCtx,
160+
private ChannelInitializer<Channel> clientChannelInitializer(SessionProtocol p, @Nullable SslContext sslCtx,
194161
boolean webSocket, boolean closeSslContext) {
195162
return new ChannelInitializer<Channel>() {
196163
@Override
197164
protected void initChannel(Channel ch) throws Exception {
198-
if (closeSslContext) {
199-
ch.closeFuture().addListener(unused -> releaseSslContext(sslCtx));
165+
if (closeSslContext && sslCtx != null) {
166+
ch.closeFuture().addListener(unused -> release(sslCtx));
200167
}
201168
ch.pipeline().addLast(new HttpClientPipelineConfigurator(
202169
clientFactory, webSocket, p, sslCtx));

core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import com.google.common.base.MoreObjects.ToStringHelper;
5252
import com.google.common.collect.ImmutableList;
5353
import com.google.common.collect.ImmutableMap;
54+
import com.google.common.collect.ImmutableSet;
5455
import com.google.common.primitives.Ints;
5556

5657
import com.linecorp.armeria.client.proxy.ProxyConfig;
@@ -68,7 +69,6 @@
6869
import com.linecorp.armeria.common.outlier.OutlierDetection;
6970
import com.linecorp.armeria.common.util.EventLoopGroups;
7071
import com.linecorp.armeria.common.util.TlsEngineType;
71-
import com.linecorp.armeria.internal.common.IgnoreHostsTrustManager;
7272
import com.linecorp.armeria.internal.common.RequestContextUtil;
7373
import com.linecorp.armeria.internal.common.util.ChannelUtil;
7474

@@ -132,6 +132,7 @@ public final class ClientFactoryBuilder implements TlsSetters {
132132
private TlsProvider tlsProvider;
133133
@Nullable
134134
private ClientTlsConfig tlsConfig;
135+
private ClientTlsSpec clientTlsSpec = ClientTlsSpec.of();
135136
private boolean staticTlsSettingsSet;
136137
private boolean autoCloseConnectionPoolListener = true;
137138

@@ -421,8 +422,10 @@ public ClientFactoryBuilder tls(PrivateKey key, @Nullable String keyPassword,
421422
@Override
422423
public ClientFactoryBuilder tls(TlsKeyPair tlsKeyPair) {
423424
requireNonNull(tlsKeyPair, "tlsKeyPair");
424-
return tlsCustomizer(customizer -> customizer.keyManager(tlsKeyPair.privateKey(),
425-
tlsKeyPair.certificateChain()));
425+
ensureNoTlsProvider();
426+
staticTlsSettingsSet = true;
427+
clientTlsSpec = clientTlsSpec.toBuilder().tlsKeyPair(tlsKeyPair).build();
428+
return this;
426429
}
427430

428431
/**
@@ -431,7 +434,10 @@ public ClientFactoryBuilder tls(TlsKeyPair tlsKeyPair) {
431434
@Override
432435
public ClientFactoryBuilder tls(KeyManagerFactory keyManagerFactory) {
433436
requireNonNull(keyManagerFactory, "keyManagerFactory");
434-
return tlsCustomizer(customizer -> customizer.keyManager(keyManagerFactory));
437+
ensureNoTlsProvider();
438+
staticTlsSettingsSet = true;
439+
clientTlsSpec = clientTlsSpec.toBuilder().keyManagerFactory(keyManagerFactory).build();
440+
return this;
435441
}
436442

437443
/**
@@ -1073,13 +1079,8 @@ private ClientFactoryOptions buildOptions() {
10731079
if (tlsConfig != null) {
10741080
option(ClientFactoryOptions.TLS_CONFIG, tlsConfig);
10751081
}
1076-
} else {
1077-
if (tlsNoVerifySet) {
1078-
tlsCustomizer(b -> b.trustManager(InsecureTrustManagerFactory.INSTANCE));
1079-
} else if (!insecureHosts.isEmpty()) {
1080-
tlsCustomizer(b -> b.trustManager(IgnoreHostsTrustManager.of(insecureHosts)));
1081-
}
10821082
}
1083+
option(ClientFactoryOptions.CLIENT_TLS_SPEC, clientTlsSpec);
10831084

10841085
final ClientFactoryOptions newOptions = ClientFactoryOptions.of(options.values());
10851086
final long maxConnectionAgeMillis = newOptions.maxConnectionAgeMillis();
@@ -1124,7 +1125,9 @@ private ClientFactoryOptions buildOptions() {
11241125
* Returns a newly-created {@link ClientFactory} based on the properties of this builder.
11251126
*/
11261127
public ClientFactory build() {
1127-
return new DefaultClientFactory(new HttpClientFactory(buildOptions(), autoCloseConnectionPoolListener));
1128+
final ClientFactoryOptions options = buildOptions();
1129+
return new DefaultClientFactory(new HttpClientFactory(
1130+
options, autoCloseConnectionPoolListener, tlsNoVerifySet, ImmutableSet.copyOf(insecureHosts)));
11281131
}
11291132

11301133
@Override

core/src/main/java/com/linecorp/armeria/client/ClientFactoryOptions.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,10 @@ private static long clampedDefaultMaxClientConnectionAge() {
374374
public static final ClientFactoryOption<Consumer<? super ChannelPipeline>> CHANNEL_PIPELINE_CUSTOMIZER =
375375
ClientFactoryOption.define("CHANNEL_PIPELINE_CUSTOMIZER", v -> { /* no-op */ });
376376

377+
@UnstableApi
378+
public static final ClientFactoryOption<ClientTlsSpec> CLIENT_TLS_SPEC =
379+
ClientFactoryOption.define("CLIENT_TLS_SPEC", ClientTlsSpec.of());
380+
377381
private static final ClientFactoryOptions EMPTY = new ClientFactoryOptions(ImmutableList.of());
378382

379383
/**
@@ -744,4 +748,12 @@ public ClientTlsConfig tlsConfig() {
744748
public Consumer<? super ChannelPipeline> channelPipelineCustomizer() {
745749
return get(CHANNEL_PIPELINE_CUSTOMIZER);
746750
}
751+
752+
/**
753+
* Returns the default {@link ClientTlsSpec}.
754+
*/
755+
@UnstableApi
756+
public ClientTlsSpec clientTlsSpec() {
757+
return get(CLIENT_TLS_SPEC);
758+
}
747759
}

core/src/main/java/com/linecorp/armeria/client/ClientRequestContext.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,13 @@ default boolean isTimedOut() {
619619
@UnstableApi
620620
ResponseTimeoutMode responseTimeoutMode();
621621

622+
/**
623+
* Returns the request-specific TLS configuration.
624+
*/
625+
@UnstableApi
626+
@Nullable
627+
ClientTlsSpec clientTlsSpec();
628+
622629
@Override
623630
default ClientRequestContext unwrap() {
624631
return (ClientRequestContext) RequestContext.super.unwrap();

core/src/main/java/com/linecorp/armeria/client/ClientRequestContextWrapper.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.linecorp.armeria.common.RequestId;
3232
import com.linecorp.armeria.common.RpcRequest;
3333
import com.linecorp.armeria.common.annotation.Nullable;
34+
import com.linecorp.armeria.common.annotation.UnstableApi;
3435
import com.linecorp.armeria.common.util.TimeoutMode;
3536

3637
/**
@@ -171,6 +172,12 @@ public ResponseTimeoutMode responseTimeoutMode() {
171172
return unwrap().responseTimeoutMode();
172173
}
173174

175+
@Override
176+
@UnstableApi
177+
public @Nullable ClientTlsSpec clientTlsSpec() {
178+
return unwrap().clientTlsSpec();
179+
}
180+
174181
@Override
175182
public void hook(Supplier<? extends AutoCloseable> contextHook) {
176183
unwrap().hook(contextHook);

0 commit comments

Comments
 (0)