Skip to content

Commit ea26e76

Browse files
committed
Fixup: Request builder review fixes
1 parent 0b20245 commit ea26e76

3 files changed

Lines changed: 241 additions & 75 deletions

File tree

xds/src/main/java/io/grpc/xds/internal/extauthz/CertificateUtils.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
/**
3131
* A utility class for certificate-related information.
3232
*/
33-
public final class CertificateUtils {
33+
final class CertificateUtils {
3434
private static final Logger logger = Logger.getLogger(CertificateUtils.class.getName());
3535
// From RFC 5280, section 4.2.1.6, Subject Alternative Name
3636
// dNSName (2)
@@ -48,21 +48,21 @@ private CertificateUtils() {}
4848
* @param cert The certificate.
4949
* @return The principal.
5050
*/
51-
public static String getPrincipal(X509Certificate cert) {
51+
static String getPrincipal(X509Certificate cert) {
5252
try {
5353
Collection<List<?>> sans = cert.getSubjectAlternativeNames();
5454
if (sans != null) {
5555
// Look for URI SAN (Priority 1).
5656
for (List<?> san : sans) {
5757
if (san.size() == 2 && san.get(0) instanceof Integer
58-
&& (Integer) san.get(0) == SAN_TYPE_URI) {
58+
&& san.get(0).equals(SAN_TYPE_URI)) {
5959
return (String) san.get(1);
6060
}
6161
}
6262
// If no URI SAN, look for DNS SAN (Priority 2).
6363
for (List<?> san : sans) {
6464
if (san.size() == 2 && san.get(0) instanceof Integer
65-
&& (Integer) san.get(0) == SAN_TYPE_DNS_NAME) {
65+
&& san.get(0).equals(SAN_TYPE_DNS_NAME)) {
6666
return (String) san.get(1);
6767
}
6868
}
@@ -82,10 +82,10 @@ public static String getPrincipal(X509Certificate cert) {
8282
* @throws CertificateEncodingException If an error occurs while encoding the certificate.
8383
* @throws UnsupportedEncodingException If an error occurs while encoding the URL.
8484
*/
85-
public static String getUrlPemEncodedCertificate(X509Certificate cert)
85+
static String getUrlPemEncodedCertificate(X509Certificate cert)
8686
throws CertificateEncodingException, UnsupportedEncodingException {
8787
String pemCert = CertPemConverter.toPem(cert);
88-
return URLEncoder.encode(pemCert, StandardCharsets.UTF_8.toString());
88+
return URLEncoder.encode(pemCert, StandardCharsets.UTF_8.name());
8989
}
9090

9191
/**
@@ -106,7 +106,9 @@ private CertPemConverter() {}
106106
* @throws CertificateEncodingException If an error occurs while encoding the certificate.
107107
*/
108108
public static String toPem(X509Certificate cert) throws CertificateEncodingException {
109-
return X509_PEM_HEADER + BaseEncoding.base64().encode(cert.getEncoded()) + X509_PEM_FOOTER;
109+
return X509_PEM_HEADER
110+
+ BaseEncoding.base64().withSeparator("\n", 64).encode(cert.getEncoded())
111+
+ X509_PEM_FOOTER;
110112
}
111113
}
112114
}

xds/src/main/java/io/grpc/xds/internal/extauthz/CheckRequestBuilder.java

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818

1919

2020
import com.google.common.io.BaseEncoding;
21+
import com.google.protobuf.ByteString;
2122
import com.google.protobuf.Timestamp;
2223
import io.envoyproxy.envoy.config.core.v3.Address;
24+
import io.envoyproxy.envoy.config.core.v3.HeaderMap;
25+
import io.envoyproxy.envoy.config.core.v3.HeaderValue;
2326
import io.envoyproxy.envoy.config.core.v3.SocketAddress;
2427
import io.envoyproxy.envoy.service.auth.v3.AttributeContext;
2528
import io.envoyproxy.envoy.service.auth.v3.CheckRequest;
@@ -33,24 +36,24 @@
3336
import java.security.cert.Certificate;
3437
import java.security.cert.CertificateEncodingException;
3538
import java.security.cert.X509Certificate;
36-
import java.util.ArrayList;
37-
import java.util.List;
3839
import java.util.Locale;
3940
import java.util.logging.Level;
4041
import java.util.logging.Logger;
4142
import javax.annotation.Nullable;
43+
import javax.annotation.concurrent.ThreadSafe;
4244
import javax.net.ssl.SSLPeerUnverifiedException;
4345
import javax.net.ssl.SSLSession;
4446

4547
/**
46-
* Interface for building external authorization check requests.
48+
* Builds external authorization check requests from gRPC call metadata.
4749
*/
50+
@ThreadSafe
4851
public class CheckRequestBuilder {
4952

5053
/**
5154
* An interface for providing certificate-related information.
5255
*/
53-
public interface CertificateProvider {
56+
interface CertificateProvider {
5457
/**
5558
* Gets the principal from a certificate.
5659
*
@@ -72,11 +75,17 @@ String getUrlPemEncodedCertificate(X509Certificate cert)
7275
}
7376

7477
private static final Logger logger = Logger.getLogger(CheckRequestBuilder.class.getName());
78+
private static final BaseEncoding BASE64_NO_PAD = BaseEncoding.base64().omitPadding();
7579

7680

7781
private final ExtAuthzConfig config;
7882
private final CertificateProvider certificateProvider;
7983

84+
/**
85+
* Constructs a new {@link CheckRequestBuilder} with the default certificate provider.
86+
*
87+
* @param config The external authorization configuration.
88+
*/
8089
public CheckRequestBuilder(ExtAuthzConfig config) {
8190
this(config, new CertificateProvider() {
8291
@Override
@@ -92,18 +101,40 @@ public String getUrlPemEncodedCertificate(X509Certificate cert)
92101
});
93102
}
94103

95-
public CheckRequestBuilder(ExtAuthzConfig config, CertificateProvider certificateProvider) {
104+
/**
105+
* Constructs a new {@link CheckRequestBuilder} with a custom certificate provider.
106+
*
107+
* @param config The external authorization configuration.
108+
* @param certificateProvider The certificate provider.
109+
*/
110+
CheckRequestBuilder(ExtAuthzConfig config, CertificateProvider certificateProvider) {
96111
this.config = config;
97112
this.certificateProvider = certificateProvider;
98113
}
99114

100115

116+
/**
117+
* Builds a check request for a client-side call.
118+
*
119+
* @param methodDescriptor The method descriptor of the RPC.
120+
* @param headers The initial metadata headers.
121+
* @param requestTime The timestamp when the request was initiated.
122+
* @return The constructed {@link CheckRequest}.
123+
*/
101124
public CheckRequest buildRequest(MethodDescriptor<?, ?> methodDescriptor, Metadata headers,
102125
Timestamp requestTime) {
103126
return build(methodDescriptor, headers, requestTime, null, null, null);
104127
}
105128

106129

130+
/**
131+
* Builds a check request for a server-side call.
132+
*
133+
* @param serverCall The server call.
134+
* @param headers The initial metadata headers.
135+
* @param requestTime The timestamp when the request was initiated.
136+
* @return The constructed {@link CheckRequest}.
137+
*/
107138
public CheckRequest buildRequest(ServerCall<?, ?> serverCall, Metadata headers,
108139
Timestamp requestTime) {
109140
java.net.SocketAddress localAddress =
@@ -171,10 +202,24 @@ private AttributeContext.Peer buildPeer(java.net.SocketAddress socketAddress) {
171202
AttributeContext.Peer.Builder peerBuilder = AttributeContext.Peer.newBuilder();
172203
if (socketAddress instanceof InetSocketAddress) {
173204
InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
205+
// Prefer the resolved IP address, but fall back to the hostname string for
206+
// unresolved addresses. In practice, Netty transports always provide resolved
207+
// InetSocketAddress instances for active connections, and other gRPC
208+
// implementations (C++, Go) always produce IP addresses because they operate
209+
// on real TCP sockets. However, Envoy's address.proto permits hostnames (the
210+
// only constraint is a non-empty string), so we gracefully fall back to
211+
// getHostString() for robustness. See also TcpMetrics.java for precedent:
212+
// https://github.com/grpc/grpc-java/blob/master/netty/src/main/java/io/grpc/netty/TcpMetrics.java
213+
String address;
214+
if (inetSocketAddress.getAddress() != null) {
215+
address = inetSocketAddress.getAddress().getHostAddress();
216+
} else {
217+
address = inetSocketAddress.getHostString();
218+
}
174219
peerBuilder
175220
.setAddress(Address.newBuilder()
176221
.setSocketAddress(SocketAddress.newBuilder()
177-
.setAddress(inetSocketAddress.getAddress().getHostAddress())
222+
.setAddress(address)
178223
.setPortValue(inetSocketAddress.getPort()))
179224
.build());
180225
}
@@ -190,36 +235,55 @@ private AttributeContext.Request buildAttributeRequest(Metadata headers, String
190235
httpReqBuilder.setMethod("POST");
191236
httpReqBuilder.setProtocol("HTTP/2");
192237
httpReqBuilder.setSize(-1);
238+
239+
HeaderMap.Builder headerMapBuilder = HeaderMap.newBuilder();
193240
for (String key : headers.keys()) {
194241
if (!isAllowed(key)) {
195242
continue;
196243
}
197-
String value;
244+
String lowerCaseKey = key.toLowerCase(Locale.ROOT);
198245
if (key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
199-
value = getBinaryHeaderValue(headers, key);
246+
populateBinaryHeaderValues(headers, key, lowerCaseKey, headerMapBuilder);
200247
} else {
201-
value = getAsciiHeaderValue(headers, key);
248+
populateAsciiHeaderValues(headers, key, lowerCaseKey, headerMapBuilder);
202249
}
203-
httpReqBuilder.putHeaders(key.toLowerCase(Locale.ROOT), value);
204250
}
251+
httpReqBuilder.setHeaderMap(headerMapBuilder);
205252
reqBuilder.setHttp(httpReqBuilder);
206253
return reqBuilder.build();
207254
}
208255

209-
private String getBinaryHeaderValue(Metadata headers, String key) {
256+
private void populateBinaryHeaderValues(Metadata headers, String key, String lowerCaseKey,
257+
HeaderMap.Builder headerMapBuilder) {
210258
Iterable<byte[]> binaryValues =
211259
headers.getAll(Metadata.Key.of(key, Metadata.BINARY_BYTE_MARSHALLER));
212-
List<String> base64Values = new ArrayList<>();
213-
for (byte[] value : binaryValues) {
214-
base64Values.add(BaseEncoding.base64().encode(value));
260+
if (binaryValues != null) {
261+
for (byte[] value : binaryValues) {
262+
// Binary header values are base64-encoded before storing in rawValue,
263+
// matching Envoy's behavior for CheckRequest header serialization.
264+
String base64Value = BASE64_NO_PAD.encode(value);
265+
headerMapBuilder.addHeaders(
266+
HeaderValue.newBuilder()
267+
.setKey(lowerCaseKey)
268+
.setRawValue(ByteString.copyFromUtf8(base64Value))
269+
.build());
270+
}
215271
}
216-
return String.join(",", base64Values);
217272
}
218273

219-
private String getAsciiHeaderValue(Metadata headers, String key) {
274+
private void populateAsciiHeaderValues(Metadata headers, String key, String lowerCaseKey,
275+
HeaderMap.Builder headerMapBuilder) {
220276
Iterable<String> stringValues =
221277
headers.getAll(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER));
222-
return String.join(",", stringValues);
278+
if (stringValues != null) {
279+
for (String value : stringValues) {
280+
headerMapBuilder.addHeaders(
281+
HeaderValue.newBuilder()
282+
.setKey(lowerCaseKey)
283+
.setRawValue(ByteString.copyFromUtf8(value))
284+
.build());
285+
}
286+
}
223287
}
224288

225289
private boolean isAllowed(String header) {

0 commit comments

Comments
 (0)