-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
netty: Per-rpc call option authority verification against peer cert subject names #11724
base: master
Are you sure you want to change the base?
Changes from 1 commit
fa36f83
f31b8bc
0152478
5e2e22e
b0f86cf
5ba5431
e42492b
5285353
ea969ef
94172de
0ddd42c
5109115
d5f3968
1e072d4
32104ce
95aae4a
c0327b5
b24a605
862b95b
8902dbd
01b0eb2
8dd8749
3d744d9
4ef0fdb
fdc6e94
60efd84
d2af807
2594842
916d0d5
040035b
44f2412
6449728
4273452
a9a019b
1f56282
b8b70bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -842,64 +842,99 @@ public void tlsNegotiationServerExecutorShouldSucceed() throws Exception { | |
@Test | ||
public void authorityOverrideInCallOptions_noX509ExtendedTrustManager_newStreamCreationFails() | ||
throws IOException, InterruptedException, GeneralSecurityException { | ||
startServer(); | ||
InputStream caCert = TlsTesting.loadCert("ca.pem"); | ||
X509TrustManager x509ExtendedTrustManager = | ||
(X509TrustManager) getX509ExtendedTrustManager(caCert).get(); | ||
ProtocolNegotiators.FromChannelCredentialsResult result = | ||
ProtocolNegotiators.from(TlsChannelCredentials.newBuilder() | ||
.trustManager(new FakeTrustManager(x509ExtendedTrustManager)).build()); | ||
NettyClientTransport transport = newTransport(result.negotiator.newNegotiator()); | ||
FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); | ||
callMeMaybe(transport.start(fakeClientTransportListener)); | ||
synchronized (fakeClientTransportListener) { | ||
fakeClientTransportListener.wait(10000); | ||
System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); | ||
try { | ||
startServer(); | ||
InputStream caCert = TlsTesting.loadCert("ca.pem"); | ||
X509TrustManager x509ExtendedTrustManager = | ||
(X509TrustManager) getX509ExtendedTrustManager(caCert).get(); | ||
ProtocolNegotiators.FromChannelCredentialsResult result = | ||
ProtocolNegotiators.from(TlsChannelCredentials.newBuilder() | ||
.trustManager(new FakeTrustManager(x509ExtendedTrustManager)).build()); | ||
NettyClientTransport transport = newTransport(result.negotiator.newNegotiator()); | ||
FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); | ||
callMeMaybe(transport.start(fakeClientTransportListener)); | ||
synchronized (fakeClientTransportListener) { | ||
fakeClientTransportListener.wait(10000); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait is allowed to spuriously return early, which means this could be flaky. It also looks like Dealing with the spurious wakeup is slightly annoying when there's a timeout. I suggest using a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
} | ||
assertThat(fakeClientTransportListener.isConnected).isTrue(); | ||
|
||
ClientStream stream = transport.newStream( | ||
Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), | ||
new ClientStreamTracer[]{new ClientStreamTracer() { | ||
}}); | ||
|
||
assertThat(stream).isInstanceOf(FailingClientStream.class); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The instanceOf is a bit of a code smell, but within acceptability, but abusing the timeout insight is not great. The nice opaque/black-box testing here is to pass a Listener (a mock is fine) to stream.start() and see that the stream fails (closed() is called, and you can see the passed status). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
InsightBuilder insightBuilder = new InsightBuilder(); | ||
stream.appendTimeoutInsight(insightBuilder); | ||
assertThat(insightBuilder.toString()).contains( | ||
"Status{code=FAILED_PRECONDITION, description=Can't allow authority override in rpc when " | ||
+ "SslEngine or X509ExtendedTrustManager is not available, cause=null}"); | ||
} finally { | ||
System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); | ||
} | ||
assertThat(fakeClientTransportListener.isConnected).isTrue(); | ||
|
||
ClientStream stream = transport.newStream( | ||
Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), | ||
new ClientStreamTracer[]{new ClientStreamTracer() { | ||
}}); | ||
|
||
assertThat(stream).isInstanceOf(FailingClientStream.class); | ||
InsightBuilder insightBuilder = new InsightBuilder(); | ||
stream.appendTimeoutInsight(insightBuilder); | ||
assertThat(insightBuilder.toString()).contains( | ||
"Status{code=FAILED_PRECONDITION, description=Can't allow authority override in rpc when " | ||
+ "SslEngine or X509ExtendedTrustManager is not available, cause=null}"); | ||
} | ||
|
||
@Test | ||
public void authorityOverrideInCallOptions_doesntMatchServerPeerHost_newStreamCreationFails() | ||
throws IOException, InterruptedException, GeneralSecurityException { | ||
startServer(); | ||
NettyClientTransport transport = newTransport(newNegotiator()); | ||
FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); | ||
callMeMaybe(transport.start(fakeClientTransportListener)); | ||
synchronized (fakeClientTransportListener) { | ||
fakeClientTransportListener.wait(10000); | ||
System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); | ||
try { | ||
startServer(); | ||
NettyClientTransport transport = newTransport(newNegotiator()); | ||
FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); | ||
callMeMaybe(transport.start(fakeClientTransportListener)); | ||
synchronized (fakeClientTransportListener) { | ||
fakeClientTransportListener.wait(10000); | ||
} | ||
assertThat(fakeClientTransportListener.isConnected).isTrue(); | ||
|
||
ClientStream stream = transport.newStream( | ||
Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), | ||
new ClientStreamTracer[]{new ClientStreamTracer() { | ||
}}); | ||
|
||
assertThat(stream).isInstanceOf(FailingClientStream.class); | ||
InsightBuilder insightBuilder = new InsightBuilder(); | ||
stream.appendTimeoutInsight(insightBuilder); | ||
assertThat(insightBuilder.toString()).contains( | ||
"Status{code=UNAVAILABLE, description=Peer hostname verification during rpc failed for" | ||
+ " authority 'foo.test.google.in'"); | ||
assertThat(insightBuilder.toString()).contains("cause=java.security.cert.CertificateException:" | ||
+ " No subject alternative DNS name matching foo.test.google.in found."); | ||
} finally { | ||
System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); | ||
} | ||
assertThat(fakeClientTransportListener.isConnected).isTrue(); | ||
|
||
ClientStream stream = transport.newStream( | ||
Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), | ||
new ClientStreamTracer[]{new ClientStreamTracer() { | ||
}}); | ||
|
||
assertThat(stream).isInstanceOf(FailingClientStream.class); | ||
InsightBuilder insightBuilder = new InsightBuilder(); | ||
stream.appendTimeoutInsight(insightBuilder); | ||
assertThat(insightBuilder.toString()).contains( | ||
"Status{code=UNAVAILABLE, description=Peer hostname verification during rpc failed for" | ||
+ " authority 'foo.test.google.in'"); | ||
assertThat(insightBuilder.toString()).contains("cause=java.security.cert.CertificateException:" | ||
+ " No subject alternative DNS name matching foo.test.google.in found."); | ||
} | ||
|
||
@Test | ||
public void authorityOverrideInCallOptions_matchesServerPeerHost_newStreamCreationSucceeds() | ||
throws IOException, InterruptedException, GeneralSecurityException { | ||
System.setProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK", "true"); | ||
try { | ||
startServer(); | ||
NettyClientTransport transport = newTransport(newNegotiator()); | ||
FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); | ||
callMeMaybe(transport.start(fakeClientTransportListener)); | ||
synchronized (fakeClientTransportListener) { | ||
fakeClientTransportListener.wait(10000); | ||
} | ||
assertThat(fakeClientTransportListener.isConnected).isTrue(); | ||
|
||
ClientStream stream = transport.newStream( | ||
Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("zoo.test.google.fr"), | ||
new ClientStreamTracer[]{new ClientStreamTracer() { | ||
}}); | ||
|
||
assertThat(stream).isNotInstanceOf(FailingClientStream.class); | ||
} finally { | ||
System.clearProperty("GRPC_ENABLE_PER_RPC_AUTHORITY_CHECK"); | ||
} | ||
} | ||
|
||
@Test | ||
public void authorityOverrideInCallOptions_notMatches_flagDisabled_createsStream() | ||
throws IOException, InterruptedException, GeneralSecurityException { | ||
startServer(); | ||
NettyClientTransport transport = newTransport(newNegotiator()); | ||
FakeClientTransportListener fakeClientTransportListener = new FakeClientTransportListener(); | ||
|
@@ -910,11 +945,11 @@ public void authorityOverrideInCallOptions_matchesServerPeerHost_newStreamCreati | |
assertThat(fakeClientTransportListener.isConnected).isTrue(); | ||
|
||
ClientStream stream = transport.newStream( | ||
Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("zoo.test.google.fr"), | ||
new ClientStreamTracer[]{new ClientStreamTracer() { | ||
}}); | ||
Rpc.METHOD, new Metadata(), CallOptions.DEFAULT.withAuthority("foo.test.google.in"), | ||
new ClientStreamTracer[]{new ClientStreamTracer() { | ||
}}); | ||
|
||
assertThat(stream).isNotInstanceOf(FailingClientStream.class); | ||
assertThat(stream).isInstanceOf(NettyClientStream.class); | ||
} | ||
|
||
private Throwable getRootCause(Throwable t) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see now that we're getting this per-RPC authority separately from the data flow that uses the authority. That makes me nervous, especially for security. As the code changes, it's likely this will break. In fact, it is already broken because the LB-based override only influences
setAuthority()
.For using the authority, the per-RPC authority is copied to the stream in ClientCallImpl. NettyClientStream then copies it into the Netty Http2Headers (while running an application's thread).
Having the logic in NettyClientTransport has been a bit odd, but fine, and I saw why it was done to avoid plumbing of the protocol negotiator to other places. Normally we'd add RPC logic in NettyClientHandler/NettyClientStream. NettyClientTransport doesn't do much other than create/manage the Netty channel (connection). I suggest we move this logic to NettyClientHandler and forgo any cache synchronization because it will always run on the transport thread.