Skip to content

Commit 64daddc

Browse files
authored
feat(xds): Implement CheckRequestBuilder for external authorization (grpc#12493)
This PR sits on top of grpc#12492, so only the last commit + any fixups need to be reviewed. This commit introduces the `CheckRequestBuilder` library, which is responsible for constructing the `CheckRequest` message sent to the external authorization service. The `CheckRequestBuilder` gathers information from various sources, including: - `ServerCall` attributes (local and remote addresses, SSL session). - `MethodDescriptor` (full method name). - Request headers. It uses this information to populate the `AttributeContext` of the `CheckRequest` message, which provides the authorization service with the necessary context to make an authorization decision. This commit also introduces the `ExtAuthzCertificateProvider`, a helper class for extracting certificate information, such as the principal and PEM-encoded certificate. The relevant section of the spec is: https://github.com/grpc/proposal/pull/481/files#diff-6bb76a24aa142cc33db9218509688f01b30c8885d2fd8849f164244e68cd54eaR196-R250 Unit tests for the new components are also included.
1 parent 65547bf commit 64daddc

5 files changed

Lines changed: 1230 additions & 0 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2025 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.xds.internal.extauthz;
18+
19+
import com.google.common.io.BaseEncoding;
20+
import java.io.UnsupportedEncodingException;
21+
import java.net.URLEncoder;
22+
import java.nio.charset.StandardCharsets;
23+
import java.security.cert.CertificateEncodingException;
24+
import java.security.cert.CertificateParsingException;
25+
import java.security.cert.X509Certificate;
26+
import java.util.Collection;
27+
import java.util.List;
28+
import java.util.logging.Level;
29+
import java.util.logging.Logger;
30+
31+
/**
32+
* A utility class for certificate-related information.
33+
*/
34+
final class CertificateUtils {
35+
private static final Logger logger = Logger.getLogger(CertificateUtils.class.getName());
36+
// From RFC 5280, section 4.2.1.6, Subject Alternative Name
37+
// dNSName (2)
38+
// iPAddress (7)
39+
private static final int SAN_TYPE_DNS_NAME = 2;
40+
private static final int SAN_TYPE_URI = 6;
41+
42+
private CertificateUtils() {}
43+
44+
/**
45+
* Gets the principal from a certificate. It returns the cert's first URI SAN if set,
46+
* otherwise the cert's first DNS SAN if set, otherwise the subject field of the certificate in
47+
* RFC 2253 format.
48+
*
49+
* @param cert The certificate.
50+
* @return The principal.
51+
*/
52+
static String getPrincipal(X509Certificate cert) {
53+
try {
54+
Collection<List<?>> sans = cert.getSubjectAlternativeNames();
55+
if (sans != null) {
56+
// Look for URI SAN (Priority 1).
57+
for (List<?> san : sans) {
58+
if (san.size() == 2 && san.get(0) instanceof Integer
59+
&& san.get(0).equals(SAN_TYPE_URI)) {
60+
return (String) san.get(1);
61+
}
62+
}
63+
// If no URI SAN, look for DNS SAN (Priority 2).
64+
for (List<?> san : sans) {
65+
if (san.size() == 2 && san.get(0) instanceof Integer
66+
&& san.get(0).equals(SAN_TYPE_DNS_NAME)) {
67+
return (String) san.get(1);
68+
}
69+
}
70+
}
71+
} catch (CertificateParsingException e) {
72+
logger.log(Level.FINE, "Error parsing certificate SANs.", e);
73+
}
74+
return cert.getSubjectX500Principal().getName("RFC2253");
75+
}
76+
77+
78+
/**
79+
* Gets the URL PEM encoded certificate. It Pem encodes first and then urlencodes.
80+
*
81+
* @param cert The certificate.
82+
* @return The URL PEM encoded certificate.
83+
* @throws CertificateEncodingException If an error occurs while encoding the certificate.
84+
* @throws UnsupportedEncodingException If an error occurs while encoding the URL.
85+
*/
86+
static String getUrlPemEncodedCertificate(X509Certificate cert)
87+
throws CertificateEncodingException, UnsupportedEncodingException {
88+
String pemCert = CertPemConverter.toPem(cert);
89+
return URLEncoder.encode(pemCert, StandardCharsets.UTF_8.name());
90+
}
91+
92+
/**
93+
* A utility class for PEM encoding.
94+
*/
95+
private static final class CertPemConverter {
96+
97+
private static final String X509_PEM_HEADER = "-----BEGIN CERTIFICATE-----\n";
98+
private static final String X509_PEM_FOOTER = "\n-----END CERTIFICATE-----\n";
99+
100+
private CertPemConverter() {}
101+
102+
/**
103+
* Converts a certificate to a PEM string.
104+
*
105+
* @param cert The certificate to convert.
106+
* @return The PEM encoded certificate.
107+
* @throws CertificateEncodingException If an error occurs while encoding the certificate.
108+
*/
109+
public static String toPem(X509Certificate cert) throws CertificateEncodingException {
110+
return X509_PEM_HEADER
111+
+ BaseEncoding.base64().withSeparator("\n", 64).encode(cert.getEncoded())
112+
+ X509_PEM_FOOTER;
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)