Skip to content

Commit 91637e5

Browse files
committed
xds: honor requested_server_name from TLS SNI
Read requested_server_name from the TLS SNI on ExtendedSSLSession so RBAC policies can match the value Envoy config provides. Add a regression test covering a deny policy that depends on the requested server name.
1 parent cc841ee commit 91637e5

2 files changed

Lines changed: 96 additions & 2 deletions

File tree

xds/src/main/java/io/grpc/xds/internal/rbac/engine/GrpcAuthorizationEngine.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
import java.util.logging.Level;
4242
import java.util.logging.Logger;
4343
import javax.annotation.Nullable;
44+
import javax.net.ssl.ExtendedSSLSession;
45+
import javax.net.ssl.SNIHostName;
46+
import javax.net.ssl.SNIServerName;
4447
import javax.net.ssl.SSLPeerUnverifiedException;
4548
import javax.net.ssl.SSLSession;
4649

@@ -411,6 +414,20 @@ private int getDestinationPort() {
411414
}
412415

413416
private String getRequestedServerName() {
417+
SSLSession sslSession = serverCall.getAttributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION);
418+
if (!(sslSession instanceof ExtendedSSLSession)) {
419+
return "";
420+
}
421+
List<SNIServerName> requestedServerNames =
422+
((ExtendedSSLSession) sslSession).getRequestedServerNames();
423+
if (requestedServerNames == null) {
424+
return "";
425+
}
426+
for (SNIServerName requestedServerName : requestedServerNames) {
427+
if (requestedServerName instanceof SNIHostName) {
428+
return ((SNIHostName) requestedServerName).getAsciiName();
429+
}
430+
}
414431
return "";
415432
}
416433
}

xds/src/test/java/io/grpc/xds/internal/rbac/engine/GrpcAuthorizationEngineTest.java

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,10 @@
5555
import java.util.Arrays;
5656
import java.util.Collections;
5757
import java.util.List;
58+
import javax.net.ssl.ExtendedSSLSession;
59+
import javax.net.ssl.SNIHostName;
60+
import javax.net.ssl.SNIServerName;
5861
import javax.net.ssl.SSLPeerUnverifiedException;
59-
import javax.net.ssl.SSLSession;
6062
import javax.security.auth.x500.X500Principal;
6163
import org.junit.Before;
6264
import org.junit.Rule;
@@ -85,12 +87,13 @@ public class GrpcAuthorizationEngineTest {
8587
@Mock
8688
private ServerCall<Void,Void> serverCall;
8789
@Mock
88-
private SSLSession sslSession;
90+
private ExtendedSSLSession sslSession;
8991

9092
@Before
9193
public void setUp() throws Exception {
9294
X509Certificate[] certs = {TestUtils.loadX509Cert("server1.pem")};
9395
when(sslSession.getPeerCertificates()).thenReturn(certs);
96+
when(sslSession.getRequestedServerNames()).thenReturn(Collections.<SNIServerName>emptyList());
9497
Attributes attributes = Attributes.newBuilder()
9598
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(IP_ADDR2, PORT))
9699
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InetSocketAddress(IP_ADDR1, PORT))
@@ -354,6 +357,80 @@ public void multiplePolicies() throws Exception {
354357
assertThat(decision.matchingPolicyName()).isEqualTo(POLICY_NAME);
355358
}
356359

360+
@Test
361+
public void requestedServerNameMatcher_matchesTlsSni() {
362+
when(sslSession.getRequestedServerNames()).thenReturn(
363+
Collections.<SNIServerName>singletonList(new SNIHostName("blocked.example")));
364+
GrpcAuthorizationEngine.RequestedServerNameMatcher requestedServerNameMatcher =
365+
GrpcAuthorizationEngine.RequestedServerNameMatcher.create(
366+
StringMatcher.forExact("blocked.example", false));
367+
OrMatcher permission = OrMatcher.create(requestedServerNameMatcher);
368+
OrMatcher principal = OrMatcher.create(AlwaysTrueMatcher.INSTANCE);
369+
PolicyMatcher policyMatcher = PolicyMatcher.create("deny-sni", permission, principal);
370+
371+
GrpcAuthorizationEngine engine = new GrpcAuthorizationEngine(
372+
AuthConfig.create(Collections.singletonList(policyMatcher), Action.DENY));
373+
AuthDecision decision = engine.evaluate(new Metadata(), serverCall);
374+
assertThat(decision.decision()).isEqualTo(Action.DENY);
375+
assertThat(decision.matchingPolicyName()).isEqualTo("deny-sni");
376+
}
377+
378+
@Test
379+
public void requestedServerNameMatcher_noTlsSessionDoesNotMatch() {
380+
Attributes attributes = Attributes.newBuilder()
381+
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InetSocketAddress(IP_ADDR2, PORT))
382+
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, new InetSocketAddress(IP_ADDR1, PORT))
383+
.build();
384+
when(serverCall.getAttributes()).thenReturn(attributes);
385+
GrpcAuthorizationEngine.RequestedServerNameMatcher requestedServerNameMatcher =
386+
GrpcAuthorizationEngine.RequestedServerNameMatcher.create(
387+
StringMatcher.forExact("blocked.example", false));
388+
OrMatcher permission = OrMatcher.create(requestedServerNameMatcher);
389+
OrMatcher principal = OrMatcher.create(AlwaysTrueMatcher.INSTANCE);
390+
PolicyMatcher policyMatcher = PolicyMatcher.create("deny-sni", permission, principal);
391+
392+
GrpcAuthorizationEngine engine = new GrpcAuthorizationEngine(
393+
AuthConfig.create(Collections.singletonList(policyMatcher), Action.DENY));
394+
AuthDecision decision = engine.evaluate(new Metadata(), serverCall);
395+
assertThat(decision.decision()).isEqualTo(Action.ALLOW);
396+
assertThat(decision.matchingPolicyName()).isNull();
397+
}
398+
399+
@Test
400+
public void requestedServerNameMatcher_nullTlsSniListDoesNotMatch() {
401+
when(sslSession.getRequestedServerNames()).thenReturn(null);
402+
GrpcAuthorizationEngine.RequestedServerNameMatcher requestedServerNameMatcher =
403+
GrpcAuthorizationEngine.RequestedServerNameMatcher.create(
404+
StringMatcher.forExact("blocked.example", false));
405+
OrMatcher permission = OrMatcher.create(requestedServerNameMatcher);
406+
OrMatcher principal = OrMatcher.create(AlwaysTrueMatcher.INSTANCE);
407+
PolicyMatcher policyMatcher = PolicyMatcher.create("deny-sni", permission, principal);
408+
409+
GrpcAuthorizationEngine engine = new GrpcAuthorizationEngine(
410+
AuthConfig.create(Collections.singletonList(policyMatcher), Action.DENY));
411+
AuthDecision decision = engine.evaluate(new Metadata(), serverCall);
412+
assertThat(decision.decision()).isEqualTo(Action.ALLOW);
413+
assertThat(decision.matchingPolicyName()).isNull();
414+
}
415+
416+
@Test
417+
public void requestedServerNameMatcher_nonHostTlsSniDoesNotMatch() {
418+
when(sslSession.getRequestedServerNames()).thenReturn(
419+
Collections.singletonList(new SNIServerName(1, new byte[] {1}) { }));
420+
GrpcAuthorizationEngine.RequestedServerNameMatcher requestedServerNameMatcher =
421+
GrpcAuthorizationEngine.RequestedServerNameMatcher.create(
422+
StringMatcher.forExact("blocked.example", false));
423+
OrMatcher permission = OrMatcher.create(requestedServerNameMatcher);
424+
OrMatcher principal = OrMatcher.create(AlwaysTrueMatcher.INSTANCE);
425+
PolicyMatcher policyMatcher = PolicyMatcher.create("deny-sni", permission, principal);
426+
427+
GrpcAuthorizationEngine engine = new GrpcAuthorizationEngine(
428+
AuthConfig.create(Collections.singletonList(policyMatcher), Action.DENY));
429+
AuthDecision decision = engine.evaluate(new Metadata(), serverCall);
430+
assertThat(decision.decision()).isEqualTo(Action.ALLOW);
431+
assertThat(decision.matchingPolicyName()).isNull();
432+
}
433+
357434
@Test
358435
public void matchersEqualHashcode() throws Exception {
359436
PathMatcher pathMatcher = PathMatcher.create(STRING_MATCHER);

0 commit comments

Comments
 (0)