• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #19839

02 Jun 2025 05:31AM UTC coverage: 88.584% (+0.04%) from 88.545%
#19839

Pull #12120

github

web-flow
Merge 23e5bbfbc into 80ea4e9e6
Pull Request #12120: Revert "xds: xDS-based HTTP CONNECT configuration (v1.71.x backport)"

34276 of 38693 relevant lines covered (88.58%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.23
/../xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java
1
/*
2
 * Copyright 2021 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.xds;
18

19
import com.google.auth.oauth2.ComputeEngineCredentials;
20
import com.google.auth.oauth2.IdTokenCredentials;
21
import com.google.common.primitives.UnsignedLongs;
22
import com.google.protobuf.Any;
23
import com.google.protobuf.InvalidProtocolBufferException;
24
import com.google.protobuf.Message;
25
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.Audience;
26
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig;
27
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.TokenCacheConfig;
28
import io.grpc.CallCredentials;
29
import io.grpc.CallOptions;
30
import io.grpc.Channel;
31
import io.grpc.ClientCall;
32
import io.grpc.ClientInterceptor;
33
import io.grpc.CompositeCallCredentials;
34
import io.grpc.Metadata;
35
import io.grpc.MethodDescriptor;
36
import io.grpc.Status;
37
import io.grpc.auth.MoreCallCredentials;
38
import io.grpc.xds.MetadataRegistry.MetadataValueParser;
39
import java.util.LinkedHashMap;
40
import java.util.Map;
41
import java.util.concurrent.ScheduledExecutorService;
42
import java.util.function.Function;
43
import javax.annotation.Nullable;
44

45
/**
46
 * A {@link Filter} that injects a {@link CallCredentials} to handle
47
 * authentication for xDS credentials.
48
 */
49
final class GcpAuthenticationFilter implements Filter {
1✔
50

51
  static final String TYPE_URL =
52
      "type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig";
53

54
  static final class Provider implements Filter.Provider {
1✔
55
    @Override
56
    public String[] typeUrls() {
57
      return new String[]{TYPE_URL};
×
58
    }
59

60
    @Override
61
    public boolean isClientFilter() {
62
      return true;
1✔
63
    }
64

65
    @Override
66
    public GcpAuthenticationFilter newInstance() {
67
      return new GcpAuthenticationFilter();
×
68
    }
69

70
    @Override
71
    public ConfigOrError<GcpAuthenticationConfig> parseFilterConfig(Message rawProtoMessage) {
72
      GcpAuthnFilterConfig gcpAuthnProto;
73
      if (!(rawProtoMessage instanceof Any)) {
1✔
74
        return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
1✔
75
      }
76
      Any anyMessage = (Any) rawProtoMessage;
1✔
77

78
      try {
79
        gcpAuthnProto = anyMessage.unpack(GcpAuthnFilterConfig.class);
1✔
80
      } catch (InvalidProtocolBufferException e) {
×
81
        return ConfigOrError.fromError("Invalid proto: " + e);
×
82
      }
1✔
83

84
      long cacheSize = 10;
1✔
85
      // Validate cache_config
86
      if (gcpAuthnProto.hasCacheConfig()) {
1✔
87
        TokenCacheConfig cacheConfig = gcpAuthnProto.getCacheConfig();
1✔
88
        cacheSize = cacheConfig.getCacheSize().getValue();
1✔
89
        if (cacheSize == 0) {
1✔
90
          return ConfigOrError.fromError(
1✔
91
              "cache_config.cache_size must be greater than zero");
92
        }
93
        // LruCache's size is an int and briefly exceeds its maximum size before evicting entries
94
        cacheSize = UnsignedLongs.min(cacheSize, Integer.MAX_VALUE - 1);
1✔
95
      }
96

97
      GcpAuthenticationConfig config = new GcpAuthenticationConfig((int) cacheSize);
1✔
98
      return ConfigOrError.fromConfig(config);
1✔
99
    }
100

101
    @Override
102
    public ConfigOrError<GcpAuthenticationConfig> parseFilterConfigOverride(
103
        Message rawProtoMessage) {
104
      return parseFilterConfig(rawProtoMessage);
×
105
    }
106
  }
107

108
  @Nullable
109
  @Override
110
  public ClientInterceptor buildClientInterceptor(FilterConfig config,
111
      @Nullable FilterConfig overrideConfig, ScheduledExecutorService scheduler) {
112

113
    ComputeEngineCredentials credentials = ComputeEngineCredentials.create();
1✔
114
    LruCache<String, CallCredentials> callCredentialsCache =
1✔
115
        new LruCache<>(((GcpAuthenticationConfig) config).getCacheSize());
1✔
116
    return new ClientInterceptor() {
1✔
117
      @Override
118
      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
119
          MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
120

121
        /*String clusterName = callOptions.getOption(XdsAttributes.ATTR_CLUSTER_NAME);
122
        if (clusterName == null) {
123
          return next.newCall(method, callOptions);
124
        }*/
125

126
        // TODO: Fetch the CDS resource for the cluster.
127
        // If the CDS resource is not available, fail the RPC with Status.UNAVAILABLE.
128

129
        // TODO: Extract the audience from the CDS resource metadata.
130
        // If the audience is not found or is in the wrong format, fail the RPC.
131
        String audience = "TEST_AUDIENCE";
1✔
132

133
        try {
134
          CallCredentials existingCallCredentials = callOptions.getCredentials();
1✔
135
          CallCredentials newCallCredentials =
1✔
136
              getCallCredentials(callCredentialsCache, audience, credentials);
1✔
137
          if (existingCallCredentials != null) {
1✔
138
            callOptions = callOptions.withCallCredentials(
×
139
                new CompositeCallCredentials(existingCallCredentials, newCallCredentials));
140
          } else {
141
            callOptions = callOptions.withCallCredentials(newCallCredentials);
1✔
142
          }
143
        }
144
        catch (Exception e) {
×
145
          // If we fail to attach CallCredentials due to any reason, return a FailingClientCall
146
          return new FailingClientCall<>(Status.UNAUTHENTICATED
×
147
              .withDescription("Failed to attach CallCredentials.")
×
148
              .withCause(e));
×
149
        }
1✔
150
        return next.newCall(method, callOptions);
1✔
151
      }
152
    };
153
  }
154

155
  private CallCredentials getCallCredentials(LruCache<String, CallCredentials> cache,
156
      String audience, ComputeEngineCredentials credentials) {
157

158
    synchronized (cache) {
1✔
159
      return cache.getOrInsert(audience, key -> {
1✔
160
        IdTokenCredentials creds = IdTokenCredentials.newBuilder()
1✔
161
            .setIdTokenProvider(credentials)
1✔
162
            .setTargetAudience(audience)
1✔
163
            .build();
1✔
164
        return MoreCallCredentials.from(creds);
1✔
165
      });
166
    }
167
  }
168

169
  static final class GcpAuthenticationConfig implements FilterConfig {
170

171
    private final int cacheSize;
172

173
    public GcpAuthenticationConfig(int cacheSize) {
1✔
174
      this.cacheSize = cacheSize;
1✔
175
    }
1✔
176

177
    public int getCacheSize() {
178
      return cacheSize;
1✔
179
    }
180

181
    @Override
182
    public String typeUrl() {
183
      return GcpAuthenticationFilter.TYPE_URL;
×
184
    }
185
  }
186

187
  /** An implementation of {@link ClientCall} that fails when started. */
188
  private static final class FailingClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
189

190
    private final Status error;
191

192
    public FailingClientCall(Status error) {
×
193
      this.error = error;
×
194
    }
×
195

196
    @Override
197
    public void start(ClientCall.Listener<RespT> listener, Metadata headers) {
198
      listener.onClose(error, new Metadata());
×
199
    }
×
200

201
    @Override
202
    public void request(int numMessages) {}
×
203

204
    @Override
205
    public void cancel(String message, Throwable cause) {}
×
206

207
    @Override
208
    public void halfClose() {}
×
209

210
    @Override
211
    public void sendMessage(ReqT message) {}
×
212
  }
213

214
  private static final class LruCache<K, V> {
215

216
    private final Map<K, V> cache;
217

218
    LruCache(int maxSize) {
1✔
219
      this.cache = new LinkedHashMap<K, V>(
1✔
220
          maxSize,
221
          0.75f,
222
          true) {
1✔
223
        @Override
224
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
225
          return size() > maxSize;
1✔
226
        }
227
      };
228
    }
1✔
229

230
    V getOrInsert(K key, Function<K, V> create) {
231
      return cache.computeIfAbsent(key, create);
1✔
232
    }
233
  }
234

235
  static class AudienceMetadataParser implements MetadataValueParser {
1✔
236

237
    @Override
238
    public String getTypeUrl() {
239
      return "type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.Audience";
1✔
240
    }
241

242
    @Override
243
    public String parse(Any any) throws InvalidProtocolBufferException {
244
      Audience audience = any.unpack(Audience.class);
1✔
245
      String url = audience.getUrl();
1✔
246
      if (url.isEmpty()) {
1✔
247
        throw new InvalidProtocolBufferException(
×
248
            "Audience URL is empty. Metadata value must contain a valid URL.");
249
      }
250
      return url;
1✔
251
    }
252
  }
253
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc