Skip to content
Merged
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
103 changes: 103 additions & 0 deletions xds/src/main/java/io/grpc/xds/ExtAuthzConfigParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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;

import com.google.common.collect.ImmutableList;
import io.envoyproxy.envoy.extensions.filters.http.ext_authz.v3.ExtAuthz;
import io.grpc.internal.GrpcUtil;
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
import io.grpc.xds.client.Bootstrapper.ServerInfo;
import io.grpc.xds.internal.MatcherParser;
import io.grpc.xds.internal.extauthz.ExtAuthzConfig;
import io.grpc.xds.internal.extauthz.ExtAuthzParseException;
import io.grpc.xds.internal.grpcservice.GrpcServiceConfig;
import io.grpc.xds.internal.grpcservice.GrpcServiceParseException;
import io.grpc.xds.internal.headermutations.HeaderMutationRulesParseException;
import io.grpc.xds.internal.headermutations.HeaderMutationRulesParser;


/**
* Parser for {@link io.envoyproxy.envoy.extensions.filters.http.ext_authz.v3.ExtAuthz}.
*/
final class ExtAuthzConfigParser {

private ExtAuthzConfigParser() {}

/**
* Parses the {@link io.envoyproxy.envoy.extensions.filters.http.ext_authz.v3.ExtAuthz} proto to
* create an {@link ExtAuthzConfig} instance.
*
* @param extAuthzProto The ext_authz proto to parse.
* @return An {@link ExtAuthzConfig} instance.
* @throws ExtAuthzParseException if the proto is invalid or contains unsupported features.
*/
public static ExtAuthzConfig parse(
ExtAuthz extAuthzProto, BootstrapInfo bootstrapInfo, ServerInfo serverInfo)
throws ExtAuthzParseException {
if (!extAuthzProto.hasGrpcService()) {
throw new ExtAuthzParseException(
"unsupported ExtAuthz service type: only grpc_service is supported");
}
GrpcServiceConfig grpcServiceConfig;
try {
grpcServiceConfig =
GrpcServiceConfigParser.parse(extAuthzProto.getGrpcService(), bootstrapInfo, serverInfo);
} catch (GrpcServiceParseException e) {
throw new ExtAuthzParseException("Failed to parse GrpcService config: " + e.getMessage(), e);
}
ExtAuthzConfig.Builder builder = ExtAuthzConfig.builder().grpcService(grpcServiceConfig)
.failureModeAllow(extAuthzProto.getFailureModeAllow())
.failureModeAllowHeaderAdd(extAuthzProto.getFailureModeAllowHeaderAdd())
.includePeerCertificate(extAuthzProto.getIncludePeerCertificate())
.denyAtDisable(extAuthzProto.getDenyAtDisable().getDefaultValue().getValue());

if (extAuthzProto.hasFilterEnabled()) {
try {
builder.filterEnabled(
MatcherParser.parseFractionMatcher(extAuthzProto.getFilterEnabled().getDefaultValue()));
} catch (IllegalArgumentException e) {
throw new ExtAuthzParseException(e.getMessage());
}
}

if (extAuthzProto.hasStatusOnError()) {
builder.statusOnError(
GrpcUtil.httpStatusToGrpcStatus(extAuthzProto.getStatusOnError().getCodeValue()));
}

if (extAuthzProto.hasAllowedHeaders()) {
builder.allowedHeaders(extAuthzProto.getAllowedHeaders().getPatternsList().stream()
.map(MatcherParser::parseStringMatcher).collect(ImmutableList.toImmutableList()));
}

if (extAuthzProto.hasDisallowedHeaders()) {
builder.disallowedHeaders(extAuthzProto.getDisallowedHeaders().getPatternsList().stream()
.map(MatcherParser::parseStringMatcher).collect(ImmutableList.toImmutableList()));
}

if (extAuthzProto.hasDecoderHeaderMutationRules()) {
try {
builder.decoderHeaderMutationRules(
HeaderMutationRulesParser.parse(extAuthzProto.getDecoderHeaderMutationRules()));
} catch (HeaderMutationRulesParseException e) {
throw new ExtAuthzParseException(e.getMessage(), e);
}
}

return builder.build();
}
}
34 changes: 34 additions & 0 deletions xds/src/main/java/io/grpc/xds/GrpcBootstrapImplConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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;

import com.google.auto.value.AutoValue;
import io.grpc.Internal;
import io.grpc.xds.client.AllowedGrpcServices;

/**
* Custom configuration for gRPC xDS bootstrap implementation.
*/
@Internal
@AutoValue
public abstract class GrpcBootstrapImplConfig {
public abstract AllowedGrpcServices allowedGrpcServices();

public static GrpcBootstrapImplConfig create(AllowedGrpcServices services) {
return new AutoValue_GrpcBootstrapImplConfig(services);
}
}
111 changes: 102 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.AllowedGrpcServices;
import io.grpc.xds.client.AllowedGrpcServices.AllowedGrpcService;
import io.grpc.xds.client.BootstrapperImpl;
import io.grpc.xds.client.ConfiguredChannelCredentials;
import io.grpc.xds.client.ConfiguredChannelCredentials.ChannelCredsConfig;
import io.grpc.xds.client.XdsInitializationException;
import io.grpc.xds.client.XdsLogger;
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,95 @@ 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> parseImplSpecificObject(
@Nullable Map<String, ?> rawAllowedGrpcServices)
throws XdsInitializationException {
if (rawAllowedGrpcServices == null || rawAllowedGrpcServices.isEmpty()) {
return Optional.of(GrpcBootstrapImplConfig.create(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());
}
GrpcBootstrapImplConfig customConfig =
GrpcBootstrapImplConfig.create(AllowedGrpcServices.create(builder.build()));
return Optional.of(customConfig);
}

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

}

Loading
Loading