Skip to content

Commit 84c7713

Browse files
authored
xds: propagate audience from cluster resource in gcp auth filter (#11972)
1 parent 908f9f1 commit 84c7713

File tree

4 files changed

+372
-54
lines changed

4 files changed

+372
-54
lines changed

xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java

+78-30
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616

1717
package io.grpc.xds;
1818

19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
import static io.grpc.xds.XdsNameResolver.CLUSTER_SELECTION_KEY;
21+
import static io.grpc.xds.XdsNameResolver.XDS_CONFIG_CALL_OPTION_KEY;
22+
1923
import com.google.auth.oauth2.ComputeEngineCredentials;
2024
import com.google.auth.oauth2.IdTokenCredentials;
25+
import com.google.common.annotations.VisibleForTesting;
2126
import com.google.common.primitives.UnsignedLongs;
2227
import com.google.protobuf.Any;
2328
import com.google.protobuf.InvalidProtocolBufferException;
@@ -34,8 +39,11 @@
3439
import io.grpc.Metadata;
3540
import io.grpc.MethodDescriptor;
3641
import io.grpc.Status;
42+
import io.grpc.StatusOr;
3743
import io.grpc.auth.MoreCallCredentials;
44+
import io.grpc.xds.GcpAuthenticationFilter.AudienceMetadataParser.AudienceWrapper;
3845
import io.grpc.xds.MetadataRegistry.MetadataValueParser;
46+
import io.grpc.xds.XdsConfig.XdsClusterConfig;
3947
import io.grpc.xds.client.XdsResourceType.ResourceInvalidException;
4048
import java.util.LinkedHashMap;
4149
import java.util.Map;
@@ -52,6 +60,13 @@ final class GcpAuthenticationFilter implements Filter {
5260
static final String TYPE_URL =
5361
"type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig";
5462

63+
final String filterInstanceName;
64+
65+
GcpAuthenticationFilter(String name) {
66+
filterInstanceName = checkNotNull(name, "name");
67+
}
68+
69+
5570
static final class Provider implements Filter.Provider {
5671
@Override
5772
public String[] typeUrls() {
@@ -65,7 +80,7 @@ public boolean isClientFilter() {
6580

6681
@Override
6782
public GcpAuthenticationFilter newInstance(String name) {
68-
return new GcpAuthenticationFilter();
83+
return new GcpAuthenticationFilter(name);
6984
}
7085

7186
@Override
@@ -119,34 +134,57 @@ public ClientInterceptor buildClientInterceptor(FilterConfig config,
119134
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
120135
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
121136

122-
/*String clusterName = callOptions.getOption(XdsAttributes.ATTR_CLUSTER_NAME);
137+
String clusterName = callOptions.getOption(CLUSTER_SELECTION_KEY);
123138
if (clusterName == null) {
139+
return new FailingClientCall<>(
140+
Status.UNAVAILABLE.withDescription(
141+
String.format(
142+
"GCP Authn for %s does not contain cluster resource", filterInstanceName)));
143+
}
144+
145+
if (!clusterName.startsWith("cluster:")) {
124146
return next.newCall(method, callOptions);
125-
}*/
126-
127-
// TODO: Fetch the CDS resource for the cluster.
128-
// If the CDS resource is not available, fail the RPC with Status.UNAVAILABLE.
129-
130-
// TODO: Extract the audience from the CDS resource metadata.
131-
// If the audience is not found or is in the wrong format, fail the RPC.
132-
String audience = "TEST_AUDIENCE";
133-
134-
try {
135-
CallCredentials existingCallCredentials = callOptions.getCredentials();
136-
CallCredentials newCallCredentials =
137-
getCallCredentials(callCredentialsCache, audience, credentials);
138-
if (existingCallCredentials != null) {
139-
callOptions = callOptions.withCallCredentials(
140-
new CompositeCallCredentials(existingCallCredentials, newCallCredentials));
141-
} else {
142-
callOptions = callOptions.withCallCredentials(newCallCredentials);
143-
}
144147
}
145-
catch (Exception e) {
146-
// If we fail to attach CallCredentials due to any reason, return a FailingClientCall
147-
return new FailingClientCall<>(Status.UNAUTHENTICATED
148-
.withDescription("Failed to attach CallCredentials.")
149-
.withCause(e));
148+
XdsConfig xdsConfig = callOptions.getOption(XDS_CONFIG_CALL_OPTION_KEY);
149+
if (xdsConfig == null) {
150+
return new FailingClientCall<>(
151+
Status.UNAVAILABLE.withDescription(
152+
String.format(
153+
"GCP Authn for %s with %s does not contain xds configuration",
154+
filterInstanceName, clusterName)));
155+
}
156+
StatusOr<XdsClusterConfig> xdsCluster =
157+
xdsConfig.getClusters().get(clusterName.substring("cluster:".length()));
158+
if (xdsCluster == null) {
159+
return new FailingClientCall<>(
160+
Status.UNAVAILABLE.withDescription(
161+
String.format(
162+
"GCP Authn for %s with %s - xds cluster config does not contain xds cluster",
163+
filterInstanceName, clusterName)));
164+
}
165+
if (!xdsCluster.hasValue()) {
166+
return new FailingClientCall<>(xdsCluster.getStatus());
167+
}
168+
Object audienceObj =
169+
xdsCluster.getValue().getClusterResource().parsedMetadata().get(filterInstanceName);
170+
if (audienceObj == null) {
171+
return next.newCall(method, callOptions);
172+
}
173+
if (!(audienceObj instanceof AudienceWrapper)) {
174+
return new FailingClientCall<>(
175+
Status.UNAVAILABLE.withDescription(
176+
String.format("GCP Authn found wrong type in %s metadata: %s=%s",
177+
clusterName, filterInstanceName, audienceObj.getClass())));
178+
}
179+
AudienceWrapper audience = (AudienceWrapper) audienceObj;
180+
CallCredentials existingCallCredentials = callOptions.getCredentials();
181+
CallCredentials newCallCredentials =
182+
getCallCredentials(callCredentialsCache, audience.audience, credentials);
183+
if (existingCallCredentials != null) {
184+
callOptions = callOptions.withCallCredentials(
185+
new CompositeCallCredentials(existingCallCredentials, newCallCredentials));
186+
} else {
187+
callOptions = callOptions.withCallCredentials(newCallCredentials);
150188
}
151189
return next.newCall(method, callOptions);
152190
}
@@ -186,9 +224,11 @@ public String typeUrl() {
186224
}
187225

188226
/** An implementation of {@link ClientCall} that fails when started. */
189-
private static final class FailingClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
227+
@VisibleForTesting
228+
static final class FailingClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
190229

191-
private final Status error;
230+
@VisibleForTesting
231+
final Status error;
192232

193233
public FailingClientCall(Status error) {
194234
this.error = error;
@@ -235,13 +275,21 @@ V getOrInsert(K key, Function<K, V> create) {
235275

236276
static class AudienceMetadataParser implements MetadataValueParser {
237277

278+
static final class AudienceWrapper {
279+
final String audience;
280+
281+
AudienceWrapper(String audience) {
282+
this.audience = checkNotNull(audience);
283+
}
284+
}
285+
238286
@Override
239287
public String getTypeUrl() {
240288
return "type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.Audience";
241289
}
242290

243291
@Override
244-
public String parse(Any any) throws ResourceInvalidException {
292+
public AudienceWrapper parse(Any any) throws ResourceInvalidException {
245293
Audience audience;
246294
try {
247295
audience = any.unpack(Audience.class);
@@ -253,7 +301,7 @@ public String parse(Any any) throws ResourceInvalidException {
253301
throw new ResourceInvalidException(
254302
"Audience URL is empty. Metadata value must contain a valid URL.");
255303
}
256-
return url;
304+
return new AudienceWrapper(url);
257305
}
258306
}
259307
}

0 commit comments

Comments
 (0)