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

grpc / grpc-java / #19715

06 Mar 2025 08:10AM UTC coverage: 88.479% (-0.04%) from 88.515%
#19715

push

github

web-flow
xds: xDS-based HTTP CONNECT configuration (#11861)

34489 of 38980 relevant lines covered (88.48%)

0.88 hits per line

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

69.74
/../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 io.grpc.xds.client.XdsResourceType.ResourceInvalidException;
40
import java.util.LinkedHashMap;
41
import java.util.Map;
42
import java.util.concurrent.ScheduledExecutorService;
43
import java.util.function.Function;
44
import javax.annotation.Nullable;
45

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

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

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

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

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

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

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

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

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

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

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

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

122
        /*String clusterName = callOptions.getOption(XdsAttributes.ATTR_CLUSTER_NAME);
123
        if (clusterName == null) {
124
          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";
1✔
133

134
        try {
135
          CallCredentials existingCallCredentials = callOptions.getCredentials();
1✔
136
          CallCredentials newCallCredentials =
1✔
137
              getCallCredentials(callCredentialsCache, audience, credentials);
1✔
138
          if (existingCallCredentials != null) {
1✔
139
            callOptions = callOptions.withCallCredentials(
×
140
                new CompositeCallCredentials(existingCallCredentials, newCallCredentials));
141
          } else {
142
            callOptions = callOptions.withCallCredentials(newCallCredentials);
1✔
143
          }
144
        }
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));
×
150
        }
1✔
151
        return next.newCall(method, callOptions);
1✔
152
      }
153
    };
154
  }
155

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

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

170
  static final class GcpAuthenticationConfig implements FilterConfig {
171

172
    private final int cacheSize;
173

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

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

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

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

191
    private final Status error;
192

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

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

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

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

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

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

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

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

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

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

236
  static class AudienceMetadataParser implements MetadataValueParser {
1✔
237

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

243
    @Override
244
    public String parse(Any any) throws ResourceInvalidException {
245
      Audience audience;
246
      try {
247
        audience = any.unpack(Audience.class);
1✔
248
      } catch (InvalidProtocolBufferException ex) {
×
249
        throw new ResourceInvalidException("Invalid Resource in address proto", ex);
×
250
      }
1✔
251
      String url = audience.getUrl();
1✔
252
      if (url.isEmpty()) {
1✔
253
        throw new ResourceInvalidException(
×
254
            "Audience URL is empty. Metadata value must contain a valid URL.");
255
      }
256
      return url;
1✔
257
    }
258
  }
259
}
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