1818
1919
2020import com .google .common .io .BaseEncoding ;
21+ import com .google .protobuf .ByteString ;
2122import com .google .protobuf .Timestamp ;
2223import 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 ;
2326import io .envoyproxy .envoy .config .core .v3 .SocketAddress ;
2427import io .envoyproxy .envoy .service .auth .v3 .AttributeContext ;
2528import io .envoyproxy .envoy .service .auth .v3 .CheckRequest ;
3336import java .security .cert .Certificate ;
3437import java .security .cert .CertificateEncodingException ;
3538import java .security .cert .X509Certificate ;
36- import java .util .ArrayList ;
37- import java .util .List ;
3839import java .util .Locale ;
3940import java .util .logging .Level ;
4041import java .util .logging .Logger ;
4142import javax .annotation .Nullable ;
43+ import javax .annotation .concurrent .ThreadSafe ;
4244import javax .net .ssl .SSLPeerUnverifiedException ;
4345import 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
4851public 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