Skip to content

Commit 0ce21ba

Browse files
committed
Refactor XFCC to use a stand-alone marshaller
1 parent 2fe1203 commit 0ce21ba

File tree

7 files changed

+349
-202
lines changed

7 files changed

+349
-202
lines changed

grpc-contrib/src/main/java/com/salesforce/grpc/contrib/interceptor/XfccServerInterceptor.java

Lines changed: 0 additions & 156 deletions
This file was deleted.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2017, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
package com.salesforce.grpc.contrib.xfcc;
9+
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
13+
/**
14+
* x-forwarded-client-cert (XFCC) is a proxy header which indicates certificate information of part or all of the
15+
* clients or proxies that a request has flowed through, on its way from the client to the server.
16+
*/
17+
public class XForwardedClientCert {
18+
private String by = "";
19+
private String hash = "";
20+
private String san = "";
21+
private String subject = "";
22+
23+
void setBy(String by) {
24+
this.by = by;
25+
}
26+
27+
void setHash(String hash) {
28+
this.hash = hash;
29+
}
30+
31+
void setSan(String san) {
32+
this.san = san;
33+
}
34+
35+
void setSubject(String subject) {
36+
this.subject = subject;
37+
}
38+
39+
/**
40+
* @return The Subject Alternative Name (SAN) of the current proxy’s certificate.
41+
*/
42+
public String getBy() {
43+
return by;
44+
}
45+
46+
/**
47+
* @return The SHA 256 diguest of the current client certificate.
48+
*/
49+
public String getHash() {
50+
return hash;
51+
}
52+
53+
/**
54+
* @return The SAN field (URI type) of the current client certificate.
55+
*/
56+
public String getSan() {
57+
return san;
58+
}
59+
60+
/**
61+
* @return The Subject field of the current client certificate.
62+
*/
63+
public String getSubject() {
64+
return subject;
65+
}
66+
67+
@Override
68+
public String toString() {
69+
List<String> kvp = new ArrayList<>();
70+
if (!by.isEmpty()) {
71+
kvp.add("By=" + enquote(by));
72+
}
73+
if (!hash.isEmpty()) {
74+
kvp.add("Hash=" + enquote(hash));
75+
}
76+
if (!san.isEmpty()) {
77+
kvp.add("SAN=" + enquote(san));
78+
}
79+
if (!subject.isEmpty()) {
80+
kvp.add("Subject=" + enquote(subject));
81+
}
82+
83+
return String.join(";", kvp);
84+
}
85+
86+
private String enquote(String value) {
87+
// Escape inner quotes with \"
88+
value = value.replace("\"", "\\\"");
89+
90+
// Wrap in quotes if ,;= is present
91+
if (value.contains(",") || value.contains(";") || value.contains("=")) {
92+
value = "\"" + value + "\"";
93+
}
94+
95+
return value;
96+
}
97+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2017, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
package com.salesforce.grpc.contrib.xfcc;
9+
10+
import io.grpc.Metadata;
11+
import org.antlr.v4.runtime.CharStreams;
12+
import org.antlr.v4.runtime.CommonTokenStream;
13+
import org.antlr.v4.runtime.tree.ParseTreeWalker;
14+
15+
import java.util.ArrayList;
16+
import java.util.List;
17+
import java.util.stream.Collectors;
18+
19+
/**
20+
* {@code XfccMarshaller} parses the {@code x-forwarded-client-cert} (XFCC) header populated by TLS-terminating
21+
* reverse proxies. For example, Istio and Linkerd.
22+
*
23+
* @see <a href="https://www.envoyproxy.io/docs/envoy/latest/configuration/http_conn_man/headers.html#config-http-conn-man-headers-x-forwarded-client-cert">Envoy XFCC Header</a>
24+
* @see <a href="https://github.com/linkerd/linkerd/issues/1153">Linkerd XFCC Header</a>
25+
*/
26+
public class XfccMarshaller implements Metadata.AsciiMarshaller<List<XForwardedClientCert>> {
27+
@Override
28+
public String toAsciiString(List<XForwardedClientCert> value) {
29+
return value.stream().map(XForwardedClientCert::toString).collect(Collectors.joining(","));
30+
}
31+
32+
@Override
33+
public List<XForwardedClientCert> parseAsciiString(String serialized) {
34+
try {
35+
List<XForwardedClientCert> clientCerts = new ArrayList<>();
36+
37+
XfccLexer lexer = new XfccLexer(CharStreams.fromString(serialized));
38+
CommonTokenStream tokens = new CommonTokenStream(lexer);
39+
XfccParser parser = new XfccParser(tokens);
40+
41+
XfccParser.HeaderContext headerContext = parser.header();
42+
ParseTreeWalker walker = new ParseTreeWalker();
43+
44+
XfccListener listener = new XfccBaseListener() {
45+
private XForwardedClientCert clientCert;
46+
47+
@Override
48+
public void enterElement(XfccParser.ElementContext ctx) {
49+
clientCert = new XForwardedClientCert();
50+
}
51+
52+
@Override
53+
public void enterKvp(XfccParser.KvpContext ctx) {
54+
XfccParser.KeyContext key = ctx.key();
55+
XfccParser.ValueContext value = ctx.value();
56+
57+
if (key.BY() != null) {
58+
clientCert.setBy(value.getText());
59+
}
60+
if (key.HASH() != null) {
61+
clientCert.setHash(value.getText());
62+
}
63+
if (key.SAN() != null) {
64+
clientCert.setSan(value.getText());
65+
}
66+
if (key.SUBJECT() != null) {
67+
clientCert.setSubject(value.getText());
68+
}
69+
}
70+
71+
@Override
72+
public void exitElement(XfccParser.ElementContext ctx) {
73+
clientCerts.add(clientCert);
74+
clientCert = null;
75+
}
76+
};
77+
78+
walker.walk(listener, headerContext);
79+
return clientCerts;
80+
} catch (NoClassDefFoundError err) {
81+
throw new RuntimeException("Likely missing optional dependency on org.antlr:antlr4-runtime:4.7", err);
82+
}
83+
}
84+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) 2017, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
package com.salesforce.grpc.contrib.xfcc;
9+
10+
import io.grpc.*;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
/**
16+
* {@code XfccServerInterceptor} parses the {@code x-forwarded-client-cert} (XFCC) header populated by TLS-terminating
17+
* reverse proxies. For example, Istio and Linkerd. If present, the parsed XFCC header is appended to the
18+
* gRPC {@code Context}.
19+
*
20+
* @see <a href="https://www.envoyproxy.io/docs/envoy/latest/configuration/http_conn_man/headers.html#config-http-conn-man-headers-x-forwarded-client-cert">Envoy XFCC Header</a>
21+
* @see <a href="https://github.com/linkerd/linkerd/issues/1153">Linkerd XFCC Header</a>
22+
*/
23+
public class XfccServerInterceptor implements ServerInterceptor {
24+
/**
25+
* The metadata key used to access any present {@link XForwardedClientCert} objects.
26+
*/
27+
public static final Context.Key<List<XForwardedClientCert>> XFCC_CONTEXT_KEY = Context.key("x-forwarded-client-cert");
28+
29+
private static final Metadata.Key<List<XForwardedClientCert>> XFCC_METADATA_KEY = Metadata.Key.of("x-forwarded-client-cert", new XfccMarshaller());
30+
31+
@Override
32+
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
33+
Iterable<List<XForwardedClientCert>> values = headers.getAll(XFCC_METADATA_KEY);
34+
if (values != null) {
35+
List<XForwardedClientCert> xfccs = new ArrayList<>();
36+
for (List<XForwardedClientCert> value : values) {
37+
xfccs.addAll(value);
38+
}
39+
40+
Context xfccContext = Context.current().withValue(XFCC_CONTEXT_KEY, xfccs);
41+
return Contexts.interceptCall(xfccContext, call, headers, next);
42+
} else {
43+
return next.startCall(call, headers);
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)