Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2026 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.grpc.xds;

import io.grpc.NameResolverRegistry;
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
import io.grpc.xds.client.Bootstrapper.ServerInfo;
import io.grpc.xds.internal.grpcservice.AllowedGrpcService;
import io.grpc.xds.internal.grpcservice.AllowedGrpcServices;
import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContext;
import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Optional;

/**
* Concrete implementation of {@link GrpcServiceXdsContextProvider} that uses
* {@link BootstrapInfo} data to resolve context.
*/
final class BootstrapInfoGrpcServiceContextProvider
implements GrpcServiceXdsContextProvider {

private final boolean isTrustedControlPlane;
private final AllowedGrpcServices allowedGrpcServices;
private final NameResolverRegistry nameResolverRegistry;

BootstrapInfoGrpcServiceContextProvider(BootstrapInfo bootstrapInfo, ServerInfo serverInfo) {
this.isTrustedControlPlane = serverInfo.isTrustedXdsServer();
this.allowedGrpcServices = bootstrapInfo.allowedGrpcServices()
.filter(AllowedGrpcServices.class::isInstance)
.map(AllowedGrpcServices.class::cast)
.orElse(AllowedGrpcServices.empty());
this.nameResolverRegistry = NameResolverRegistry.getDefaultRegistry();
}

@Override
public GrpcServiceXdsContext getContextForTarget(String targetUri) {
Optional<AllowedGrpcService> validAllowedGrpcService =
Optional.ofNullable(allowedGrpcServices.services().get(targetUri));

boolean isTargetUriSchemeSupported = false;
try {
URI uri = new URI(targetUri);
String scheme = uri.getScheme();
if (scheme != null) {
isTargetUriSchemeSupported =
nameResolverRegistry.getProviderForScheme(scheme) != null;
}
} catch (URISyntaxException e) {
// Fallback or ignore if not a valid URI
}

return GrpcServiceXdsContext.create(
isTrustedControlPlane,
validAllowedGrpcService,
isTargetUriSchemeSupported
);
}
}
8 changes: 5 additions & 3 deletions xds/src/main/java/io/grpc/xds/FaultFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ public FaultFilter newInstance(String name) {
}

@Override
public ConfigOrError<FaultConfig> parseFilterConfig(Message rawProtoMessage) {
public ConfigOrError<FaultConfig> parseFilterConfig(
Message rawProtoMessage, FilterContext context) {
HTTPFault httpFaultProto;
if (!(rawProtoMessage instanceof Any)) {
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
Expand All @@ -119,8 +120,9 @@ public ConfigOrError<FaultConfig> parseFilterConfig(Message rawProtoMessage) {
}

@Override
public ConfigOrError<FaultConfig> parseFilterConfigOverride(Message rawProtoMessage) {
return parseFilterConfig(rawProtoMessage);
public ConfigOrError<FaultConfig> parseFilterConfigOverride(
Message rawProtoMessage, FilterContext context) {
return parseFilterConfig(rawProtoMessage, context);
}

private static ConfigOrError<FaultConfig> parseHttpFault(HTTPFault httpFault) {
Expand Down
27 changes: 25 additions & 2 deletions xds/src/main/java/io/grpc/xds/Filter.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

package io.grpc.xds;


import com.google.common.base.MoreObjects;
import com.google.protobuf.Message;
import io.grpc.ClientInterceptor;
import io.grpc.ServerInterceptor;
import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider;
import java.io.Closeable;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
Expand Down Expand Up @@ -93,13 +95,15 @@ default boolean isServerFilter() {
* Parses the top-level filter config from raw proto message. The message may be either a {@link
* com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}.
*/
ConfigOrError<? extends FilterConfig> parseFilterConfig(Message rawProtoMessage);
ConfigOrError<? extends FilterConfig> parseFilterConfig(
Message rawProtoMessage, FilterContext context);

/**
* Parses the per-filter override filter config from raw proto message. The message may be
* either a {@link com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}.
*/
ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(Message rawProtoMessage);
ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(
Message rawProtoMessage, FilterContext context);
}

/** Uses the FilterConfigs produced above to produce an HTTP filter interceptor for clients. */
Expand All @@ -125,6 +129,25 @@ default ServerInterceptor buildServerInterceptor(
@Override
default void close() {}

/** Context carrying dynamic metadata for a filter. */
@com.google.auto.value.AutoValue
abstract class FilterContext {
public abstract GrpcServiceXdsContextProvider grpcServiceContextProvider();

public static Builder builder() {
return new AutoValue_Filter_FilterContext.Builder();
}


@com.google.auto.value.AutoValue.Builder
public abstract static class Builder {
public abstract Builder grpcServiceContextProvider(
GrpcServiceXdsContextProvider provider);

public abstract FilterContext build();
}
}

/** Filter config with instance name. */
final class NamedFilterConfig {
// filter instance name
Expand Down
7 changes: 4 additions & 3 deletions xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ public GcpAuthenticationFilter newInstance(String name) {
}

@Override
public ConfigOrError<GcpAuthenticationConfig> parseFilterConfig(Message rawProtoMessage) {
public ConfigOrError<GcpAuthenticationConfig> parseFilterConfig(
Message rawProtoMessage, FilterContext context) {
GcpAuthnFilterConfig gcpAuthnProto;
if (!(rawProtoMessage instanceof Any)) {
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
Expand Down Expand Up @@ -121,8 +122,8 @@ public ConfigOrError<GcpAuthenticationConfig> parseFilterConfig(Message rawProto

@Override
public ConfigOrError<GcpAuthenticationConfig> parseFilterConfigOverride(
Message rawProtoMessage) {
return parseFilterConfig(rawProtoMessage);
Message rawProtoMessage, FilterContext context) {
return parseFilterConfig(rawProtoMessage, context);
}
}

Expand Down
109 changes: 100 additions & 9 deletions xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import io.grpc.CallCredentials;
import io.grpc.ChannelCredentials;
import io.grpc.internal.JsonUtil;
import io.grpc.xds.client.BootstrapperImpl;
import io.grpc.xds.client.XdsInitializationException;
import io.grpc.xds.client.XdsLogger;
import io.grpc.xds.internal.grpcservice.AllowedGrpcService;
import io.grpc.xds.internal.grpcservice.AllowedGrpcServices;
import io.grpc.xds.internal.grpcservice.ChannelCredsConfig;
import io.grpc.xds.internal.grpcservice.ConfiguredChannelCredentials;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;

class GrpcBootstrapperImpl extends BootstrapperImpl {
Expand Down Expand Up @@ -97,7 +103,8 @@ protected String getJsonContent() throws XdsInitializationException, IOException
@Override
protected Object getImplSpecificConfig(Map<String, ?> serverConfig, String serverUri)
throws XdsInitializationException {
return getChannelCredentials(serverConfig, serverUri);
ConfiguredChannelCredentials configuredChannel = getChannelCredentials(serverConfig, serverUri);
return configuredChannel != null ? configuredChannel.channelCredentials() : null;
}

@GuardedBy("GrpcBootstrapperImpl.class")
Expand All @@ -120,26 +127,26 @@ static synchronized BootstrapInfo defaultBootstrap() throws XdsInitializationExc
return defaultBootstrap;
}

private static ChannelCredentials getChannelCredentials(Map<String, ?> serverConfig,
String serverUri)
private static ConfiguredChannelCredentials getChannelCredentials(Map<String, ?> serverConfig,
String serverUri)
throws XdsInitializationException {
List<?> rawChannelCredsList = JsonUtil.getList(serverConfig, "channel_creds");
if (rawChannelCredsList == null || rawChannelCredsList.isEmpty()) {
throw new XdsInitializationException(
"Invalid bootstrap: server " + serverUri + " 'channel_creds' required");
}
ChannelCredentials channelCredentials =
ConfiguredChannelCredentials credentials =
parseChannelCredentials(JsonUtil.checkObjectList(rawChannelCredsList), serverUri);
if (channelCredentials == null) {
if (credentials == null) {
throw new XdsInitializationException(
"Server " + serverUri + ": no supported channel credentials found");
}
return channelCredentials;
return credentials;
}

@Nullable
private static ChannelCredentials parseChannelCredentials(List<Map<String, ?>> jsonList,
String serverUri)
private static ConfiguredChannelCredentials parseChannelCredentials(List<Map<String, ?>> jsonList,
String serverUri)
throws XdsInitializationException {
for (Map<String, ?> channelCreds : jsonList) {
String type = JsonUtil.getString(channelCreds, "type");
Expand All @@ -155,9 +162,93 @@ private static ChannelCredentials parseChannelCredentials(List<Map<String, ?>> j
config = ImmutableMap.of();
}

return provider.newChannelCredentials(config);
ChannelCredentials creds = provider.newChannelCredentials(config);
if (creds == null) {
return null;
}
return ConfiguredChannelCredentials.create(creds, new JsonChannelCredsConfig(type, config));
}
}
return null;
}

@Override
protected Optional<Object> parseAllowedGrpcServices(
@Nullable Map<String, ?> rawAllowedGrpcServices)
throws XdsInitializationException {
if (rawAllowedGrpcServices == null || rawAllowedGrpcServices.isEmpty()) {
return Optional.of(AllowedGrpcServices.empty());
}

ImmutableMap.Builder<String, AllowedGrpcService> builder =
ImmutableMap.builder();
for (String targetUri : rawAllowedGrpcServices.keySet()) {
Map<String, ?> serviceConfig = JsonUtil.getObject(rawAllowedGrpcServices, targetUri);
if (serviceConfig == null) {
throw new XdsInitializationException(
"Invalid allowed_grpc_services config for " + targetUri);
}
ConfiguredChannelCredentials configuredChannel =
getChannelCredentials(serviceConfig, targetUri);

Optional<CallCredentials> callCredentials = Optional.empty();
List<?> rawCallCredsList = JsonUtil.getList(serviceConfig, "call_creds");
if (rawCallCredsList != null && !rawCallCredsList.isEmpty()) {
callCredentials =
parseCallCredentials(JsonUtil.checkObjectList(rawCallCredsList), targetUri);
}

AllowedGrpcService.Builder b = AllowedGrpcService.builder()
.configuredChannelCredentials(configuredChannel);
callCredentials.ifPresent(b::callCredentials);
builder.put(targetUri, b.build());
}
return Optional.of(AllowedGrpcServices.create(builder.build()));
}

@SuppressWarnings("unused")
private static Optional<CallCredentials> parseCallCredentials(List<Map<String, ?>> jsonList,
String targetUri)
throws XdsInitializationException {
// TODO(sauravzg): Currently no xDS call credentials providers are implemented (no
// XdsCallCredentialsRegistry).
// As per A102/A97, we should just ignore unsupported call credentials types
// without throwing an exception.
return Optional.empty();
}

private static final class JsonChannelCredsConfig implements ChannelCredsConfig {
private final String type;
private final Map<String, ?> config;

JsonChannelCredsConfig(String type, Map<String, ?> config) {
this.type = type;
this.config = config;
}

@Override
public String type() {
return type;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
JsonChannelCredsConfig that = (JsonChannelCredsConfig) o;
return java.util.Objects.equals(type, that.type)
&& java.util.Objects.equals(config, that.config);
}

@Override
public int hashCode() {
return java.util.Objects.hash(type, config);
}
}

}

6 changes: 4 additions & 2 deletions xds/src/main/java/io/grpc/xds/RbacFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ public RbacFilter newInstance(String name) {
}

@Override
public ConfigOrError<RbacConfig> parseFilterConfig(Message rawProtoMessage) {
public ConfigOrError<RbacConfig> parseFilterConfig(
Message rawProtoMessage, FilterContext context) {
RBAC rbacProto;
if (!(rawProtoMessage instanceof Any)) {
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
Expand All @@ -109,7 +110,8 @@ public ConfigOrError<RbacConfig> parseFilterConfig(Message rawProtoMessage) {
}

@Override
public ConfigOrError<RbacConfig> parseFilterConfigOverride(Message rawProtoMessage) {
public ConfigOrError<RbacConfig> parseFilterConfigOverride(
Message rawProtoMessage, FilterContext context) {
RBACPerRoute rbacPerRoute;
if (!(rawProtoMessage instanceof Any)) {
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
Expand Down
5 changes: 3 additions & 2 deletions xds/src/main/java/io/grpc/xds/RouterFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,14 @@ public RouterFilter newInstance(String name) {
}

@Override
public ConfigOrError<? extends FilterConfig> parseFilterConfig(Message rawProtoMessage) {
public ConfigOrError<? extends FilterConfig> parseFilterConfig(
Message rawProtoMessage, FilterContext context) {
return ConfigOrError.fromConfig(ROUTER_CONFIG);
}

@Override
public ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(
Message rawProtoMessage) {
Message rawProtoMessage, FilterContext context) {
return ConfigOrError.fromError("Router Filter should not have override config");
}
}
Expand Down
Loading
Loading