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
105 changes: 96 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,19 @@
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.ChannelCredsConfig;
import io.grpc.xds.internal.grpcservice.ConfiguredChannelCredentials;
import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContext;
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 +102,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 +126,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 +161,90 @@ private static ChannelCredentials parseChannelCredentials(List<Map<String, ?>> j
config = ImmutableMap.of();
}

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

@Override
protected Optional<Object> parseAllowedGrpcServices(
Map<String, ?> rawAllowedGrpcServices)
throws XdsInitializationException {
ImmutableMap.Builder<String, GrpcServiceXdsContext.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);
}

GrpcServiceXdsContext.AllowedGrpcService.Builder b = GrpcServiceXdsContext.AllowedGrpcService
.builder().configuredChannelCredentials(configuredChannel);
callCredentials.ifPresent(b::callCredentials);
builder.put(targetUri, b.build());
}
ImmutableMap<String, GrpcServiceXdsContext.AllowedGrpcService> parsed = builder.buildOrThrow();
return parsed.isEmpty() ? Optional.empty() : Optional.of(parsed);
}

@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);
}
}

}

10 changes: 10 additions & 0 deletions xds/src/main/java/io/grpc/xds/client/Bootstrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import io.grpc.xds.client.EnvoyProtoData.Node;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -205,6 +206,12 @@ public abstract static class BootstrapInfo {
*/
public abstract ImmutableMap<String, AuthorityInfo> authorities();

/**
* Parsed allowed_grpc_services configuration.
* Returns an opaque object containing the parsed configuration.
*/
public abstract Optional<Object> allowedGrpcServices();

@VisibleForTesting
public static Builder builder() {
return new AutoValue_Bootstrapper_BootstrapInfo.Builder()
Expand All @@ -231,7 +238,10 @@ public abstract Builder clientDefaultListenerResourceNameTemplate(

public abstract Builder authorities(Map<String, AuthorityInfo> authorities);

public abstract Builder allowedGrpcServices(Optional<Object> allowedGrpcServices);

public abstract BootstrapInfo build();
}
}

}
11 changes: 11 additions & 0 deletions xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,20 @@ protected BootstrapInfo.Builder bootstrapBuilder(Map<String, ?> rawData)
builder.authorities(authorityInfoMapBuilder.buildOrThrow());
}

Map<String, ?> rawAllowedGrpcServices = JsonUtil.getObject(rawData, "allowed_grpc_services");
if (rawAllowedGrpcServices != null) {
builder.allowedGrpcServices(parseAllowedGrpcServices(rawAllowedGrpcServices));
}

return builder;
}

protected java.util.Optional<Object> parseAllowedGrpcServices(
Map<String, ?> rawAllowedGrpcServices)
throws XdsInitializationException {
return java.util.Optional.empty();
}

private List<ServerInfo> parseServerInfos(List<?> rawServerConfigs, XdsLogger logger)
throws XdsInitializationException {
logger.log(XdsLogLevel.INFO, "Configured with {0} xDS servers", rawServerConfigs.size());
Expand Down
21 changes: 21 additions & 0 deletions xds/src/main/java/io/grpc/xds/internal/MatcherParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,25 @@ public static Matchers.StringMatcher parseStringMatcher(
"Unknown StringMatcher match pattern: " + proto.getMatchPatternCase());
}
}

/** Translates envoy proto FractionalPercent to internal FractionMatcher. */
public static Matchers.FractionMatcher parseFractionMatcher(
io.envoyproxy.envoy.type.v3.FractionalPercent proto) {
int denominator;
switch (proto.getDenominator()) {
case HUNDRED:
denominator = 100;
break;
case TEN_THOUSAND:
denominator = 10_000;
break;
case MILLION:
denominator = 1_000_000;
break;
case UNRECOGNIZED:
default:
throw new IllegalArgumentException("Unknown denominator type: " + proto.getDenominator());
}
return Matchers.FractionMatcher.create(proto.getNumerator(), denominator);
}
}
145 changes: 145 additions & 0 deletions xds/src/main/java/io/grpc/xds/internal/extauthz/ExtAuthzConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright 2025 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.internal.extauthz;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import io.grpc.Status;
import io.grpc.xds.internal.Matchers;
import io.grpc.xds.internal.grpcservice.GrpcServiceConfig;
import io.grpc.xds.internal.headermutations.HeaderMutationRulesConfig;
import java.util.Optional;

/**
* Represents the configuration for the external authorization (ext_authz) filter. This class
* encapsulates the settings defined in the
* {@link io.envoyproxy.envoy.extensions.filters.http.ext_authz.v3.ExtAuthz} proto, providing a
* structured, immutable representation for use within gRPC. It includes configurations for the gRPC
* service used for authorization, header mutation rules, and other filter behaviors.
*/
@AutoValue
public abstract class ExtAuthzConfig {

/** Creates a new builder for creating {@link ExtAuthzConfig} instances. */
public static Builder newBuilder() {
return new AutoValue_ExtAuthzConfig.Builder().allowedHeaders(ImmutableList.of())
.disallowedHeaders(ImmutableList.of()).statusOnError(Status.PERMISSION_DENIED)
.filterEnabled(Matchers.FractionMatcher.create(100, 100));
}

/**
* The gRPC service configuration for the external authorization service. This is a required
* field.
*
* @see ExtAuthz#getGrpcService()
*/
public abstract GrpcServiceConfig grpcService();

/**
* Changes the filter's behavior on errors from the authorization service. If {@code true}, the
* filter will accept the request even if the authorization service fails or returns an error.
*
* @see ExtAuthz#getFailureModeAllow()
*/
public abstract boolean failureModeAllow();

/**
* Determines if the {@code x-envoy-auth-failure-mode-allowed} header is added to the request when
* {@link #failureModeAllow()} is true.
*
* @see ExtAuthz#getFailureModeAllowHeaderAdd()
*/
public abstract boolean failureModeAllowHeaderAdd();

/**
* Specifies if the peer certificate is sent to the external authorization service.
*
* @see ExtAuthz#getIncludePeerCertificate()
*/
public abstract boolean includePeerCertificate();

/**
* The gRPC status returned to the client when the authorization server returns an error or is
* unreachable. Defaults to {@code PERMISSION_DENIED}.
*
* @see io.envoyproxy.envoy.extensions.filters.http.ext_authz.v3.ExtAuthz#getStatusOnError()
*/
public abstract Status statusOnError();

/**
* Specifies whether to deny requests when the filter is disabled. Defaults to {@code false}.
*
* @see ExtAuthz#getDenyAtDisable()
*/
public abstract boolean denyAtDisable();

/**
* The fraction of requests that will be checked by the authorization service. Defaults to all
* requests.
*
* @see ExtAuthz#getFilterEnabled()
*/
public abstract Matchers.FractionMatcher filterEnabled();

/**
* Specifies which request headers are sent to the authorization service. If empty, all headers
* are sent.
*
* @see ExtAuthz#getAllowedHeaders()
*/
public abstract ImmutableList<Matchers.StringMatcher> allowedHeaders();

/**
* Specifies which request headers are not sent to the authorization service. This overrides
* {@link #allowedHeaders()}.
*
* @see ExtAuthz#getDisallowedHeaders()
*/
public abstract ImmutableList<Matchers.StringMatcher> disallowedHeaders();

/**
* Rules for what modifications an ext_authz server may make to request headers.
*
* @see ExtAuthz#getDecoderHeaderMutationRules()
*/
public abstract Optional<HeaderMutationRulesConfig> decoderHeaderMutationRules();

@AutoValue.Builder
public abstract static class Builder {
public abstract Builder grpcService(GrpcServiceConfig grpcService);

public abstract Builder failureModeAllow(boolean failureModeAllow);

public abstract Builder failureModeAllowHeaderAdd(boolean failureModeAllowHeaderAdd);

public abstract Builder includePeerCertificate(boolean includePeerCertificate);

public abstract Builder statusOnError(Status statusOnError);

public abstract Builder denyAtDisable(boolean denyAtDisable);

public abstract Builder filterEnabled(Matchers.FractionMatcher filterEnabled);

public abstract Builder allowedHeaders(Iterable<Matchers.StringMatcher> allowedHeaders);

public abstract Builder disallowedHeaders(Iterable<Matchers.StringMatcher> disallowedHeaders);

public abstract Builder decoderHeaderMutationRules(HeaderMutationRulesConfig rules);

public abstract ExtAuthzConfig build();
}
}
Loading
Loading