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

grpc / grpc-java / #20073

13 Nov 2025 02:41PM UTC coverage: 88.522% (-0.03%) from 88.552%
#20073

push

github

ejona86
alts: Metadata server address modification to account for default port

Fixing the utilization of the GCE Metadata host server address
environment variable to account for the case where the user does not
specify a port (defaults to port 8080)

b/451639946

34969 of 39503 relevant lines covered (88.52%)

0.89 hits per line

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

86.44
/../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 static com.google.common.base.Preconditions.checkNotNull;
20
import static io.grpc.xds.XdsNameResolver.CLUSTER_SELECTION_KEY;
21
import static io.grpc.xds.XdsNameResolver.XDS_CONFIG_CALL_OPTION_KEY;
22

23
import com.google.auth.oauth2.ComputeEngineCredentials;
24
import com.google.auth.oauth2.IdTokenCredentials;
25
import com.google.common.annotations.VisibleForTesting;
26
import com.google.common.primitives.UnsignedLongs;
27
import com.google.protobuf.Any;
28
import com.google.protobuf.InvalidProtocolBufferException;
29
import com.google.protobuf.Message;
30
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.Audience;
31
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig;
32
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.TokenCacheConfig;
33
import io.grpc.CallCredentials;
34
import io.grpc.CallOptions;
35
import io.grpc.Channel;
36
import io.grpc.ClientCall;
37
import io.grpc.ClientInterceptor;
38
import io.grpc.CompositeCallCredentials;
39
import io.grpc.Metadata;
40
import io.grpc.MethodDescriptor;
41
import io.grpc.Status;
42
import io.grpc.StatusOr;
43
import io.grpc.auth.MoreCallCredentials;
44
import io.grpc.xds.GcpAuthenticationFilter.AudienceMetadataParser.AudienceWrapper;
45
import io.grpc.xds.MetadataRegistry.MetadataValueParser;
46
import io.grpc.xds.XdsConfig.XdsClusterConfig;
47
import io.grpc.xds.client.XdsResourceType.ResourceInvalidException;
48
import java.util.LinkedHashMap;
49
import java.util.Map;
50
import java.util.concurrent.ScheduledExecutorService;
51
import java.util.function.Function;
52
import javax.annotation.Nullable;
53

54
/**
55
 * A {@link Filter} that injects a {@link CallCredentials} to handle
56
 * authentication for xDS credentials.
57
 */
58
final class GcpAuthenticationFilter implements Filter {
59

60
  static final String TYPE_URL =
61
      "type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig";
62
  private final LruCache<String, CallCredentials> callCredentialsCache;
63
  final String filterInstanceName;
64

65
  GcpAuthenticationFilter(String name, int cacheSize) {
1✔
66
    filterInstanceName = checkNotNull(name, "name");
1✔
67
    this.callCredentialsCache = new LruCache<>(cacheSize);
1✔
68
  }
1✔
69

70
  static final class Provider implements Filter.Provider {
1✔
71
    private final int cacheSize = 10;
1✔
72

73
    @Override
74
    public String[] typeUrls() {
75
      return new String[]{TYPE_URL};
1✔
76
    }
77

78
    @Override
79
    public boolean isClientFilter() {
80
      return true;
1✔
81
    }
82

83
    @Override
84
    public GcpAuthenticationFilter newInstance(String name) {
85
      return new GcpAuthenticationFilter(name, cacheSize);
×
86
    }
87

88
    @Override
89
    public ConfigOrError<GcpAuthenticationConfig> parseFilterConfig(Message rawProtoMessage) {
90
      GcpAuthnFilterConfig gcpAuthnProto;
91
      if (!(rawProtoMessage instanceof Any)) {
1✔
92
        return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
1✔
93
      }
94
      Any anyMessage = (Any) rawProtoMessage;
1✔
95

96
      try {
97
        gcpAuthnProto = anyMessage.unpack(GcpAuthnFilterConfig.class);
1✔
98
      } catch (InvalidProtocolBufferException e) {
×
99
        return ConfigOrError.fromError("Invalid proto: " + e);
×
100
      }
1✔
101

102
      long cacheSize = 10;
1✔
103
      // Validate cache_config
104
      if (gcpAuthnProto.hasCacheConfig()) {
1✔
105
        TokenCacheConfig cacheConfig = gcpAuthnProto.getCacheConfig();
1✔
106
        if (cacheConfig.hasCacheSize()) {
1✔
107
          cacheSize = cacheConfig.getCacheSize().getValue();
1✔
108
          if (cacheSize == 0) {
1✔
109
            return ConfigOrError.fromError(
1✔
110
                "cache_config.cache_size must be greater than zero");
111
          }
112
        }
113

114
        // LruCache's size is an int and briefly exceeds its maximum size before evicting entries
115
        cacheSize = UnsignedLongs.min(cacheSize, Integer.MAX_VALUE - 1);
1✔
116
      }
117

118
      GcpAuthenticationConfig config = new GcpAuthenticationConfig((int) cacheSize);
1✔
119
      return ConfigOrError.fromConfig(config);
1✔
120
    }
121

122
    @Override
123
    public ConfigOrError<GcpAuthenticationConfig> parseFilterConfigOverride(
124
        Message rawProtoMessage) {
125
      return parseFilterConfig(rawProtoMessage);
×
126
    }
127
  }
128

129
  @Nullable
130
  @Override
131
  public ClientInterceptor buildClientInterceptor(FilterConfig config,
132
      @Nullable FilterConfig overrideConfig, ScheduledExecutorService scheduler) {
133

134
    ComputeEngineCredentials credentials = ComputeEngineCredentials.create();
1✔
135
    synchronized (callCredentialsCache) {
1✔
136
      callCredentialsCache.resizeCache(((GcpAuthenticationConfig) config).getCacheSize());
1✔
137
    }
1✔
138
    return new ClientInterceptor() {
1✔
139
      @Override
140
      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
141
          MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
142

143
        String clusterName = callOptions.getOption(CLUSTER_SELECTION_KEY);
1✔
144
        if (clusterName == null) {
1✔
145
          return new FailingClientCall<>(
1✔
146
              Status.UNAVAILABLE.withDescription(
1✔
147
                  String.format(
1✔
148
                      "GCP Authn for %s does not contain cluster resource", filterInstanceName)));
149
        }
150

151
        if (!clusterName.startsWith("cluster:")) {
1✔
152
          return next.newCall(method, callOptions);
1✔
153
        }
154
        XdsConfig xdsConfig = callOptions.getOption(XDS_CONFIG_CALL_OPTION_KEY);
1✔
155
        if (xdsConfig == null) {
1✔
156
          return new FailingClientCall<>(
1✔
157
              Status.UNAVAILABLE.withDescription(
1✔
158
                  String.format(
1✔
159
                      "GCP Authn for %s with %s does not contain xds configuration",
160
                      filterInstanceName, clusterName)));
161
        }
162
        StatusOr<XdsClusterConfig> xdsCluster =
1✔
163
            xdsConfig.getClusters().get(clusterName.substring("cluster:".length()));
1✔
164
        if (xdsCluster == null) {
1✔
165
          return new FailingClientCall<>(
1✔
166
              Status.UNAVAILABLE.withDescription(
1✔
167
                  String.format(
1✔
168
                      "GCP Authn for %s with %s - xds cluster config does not contain xds cluster",
169
                      filterInstanceName, clusterName)));
170
        }
171
        if (!xdsCluster.hasValue()) {
1✔
172
          return new FailingClientCall<>(xdsCluster.getStatus());
1✔
173
        }
174
        Object audienceObj =
1✔
175
            xdsCluster.getValue().getClusterResource().parsedMetadata().get(filterInstanceName);
1✔
176
        if (audienceObj == null) {
1✔
177
          return next.newCall(method, callOptions);
×
178
        }
179
        if (!(audienceObj instanceof AudienceWrapper)) {
1✔
180
          return new FailingClientCall<>(
1✔
181
              Status.UNAVAILABLE.withDescription(
1✔
182
                  String.format("GCP Authn found wrong type in %s metadata: %s=%s",
1✔
183
                      clusterName, filterInstanceName, audienceObj.getClass())));
1✔
184
        }
185
        AudienceWrapper audience = (AudienceWrapper) audienceObj;
1✔
186
        CallCredentials existingCallCredentials = callOptions.getCredentials();
1✔
187
        CallCredentials newCallCredentials =
1✔
188
            getCallCredentials(callCredentialsCache, audience.audience, credentials);
1✔
189
        if (existingCallCredentials != null) {
1✔
190
          callOptions = callOptions.withCallCredentials(
×
191
              new CompositeCallCredentials(existingCallCredentials, newCallCredentials));
192
        } else {
193
          callOptions = callOptions.withCallCredentials(newCallCredentials);
1✔
194
        }
195
        return next.newCall(method, callOptions);
1✔
196
      }
197
    };
198
  }
199

200
  private CallCredentials getCallCredentials(LruCache<String, CallCredentials> cache,
201
      String audience, ComputeEngineCredentials credentials) {
202

203
    synchronized (cache) {
1✔
204
      return cache.getOrInsert(audience, key -> {
1✔
205
        IdTokenCredentials creds = IdTokenCredentials.newBuilder()
1✔
206
            .setIdTokenProvider(credentials)
1✔
207
            .setTargetAudience(audience)
1✔
208
            .build();
1✔
209
        return MoreCallCredentials.from(creds);
1✔
210
      });
211
    }
212
  }
213

214
  static final class GcpAuthenticationConfig implements FilterConfig {
215

216
    private final int cacheSize;
217

218
    public GcpAuthenticationConfig(int cacheSize) {
1✔
219
      this.cacheSize = cacheSize;
1✔
220
    }
1✔
221

222
    public int getCacheSize() {
223
      return cacheSize;
1✔
224
    }
225

226
    @Override
227
    public String typeUrl() {
228
      return GcpAuthenticationFilter.TYPE_URL;
×
229
    }
230
  }
231

232
  /** An implementation of {@link ClientCall} that fails when started. */
233
  @VisibleForTesting
234
  static final class FailingClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
235

236
    @VisibleForTesting
237
    final Status error;
238

239
    public FailingClientCall(Status error) {
1✔
240
      this.error = error;
1✔
241
    }
1✔
242

243
    @Override
244
    public void start(ClientCall.Listener<RespT> listener, Metadata headers) {
245
      listener.onClose(error, new Metadata());
×
246
    }
×
247

248
    @Override
249
    public void request(int numMessages) {}
×
250

251
    @Override
252
    public void cancel(String message, Throwable cause) {}
×
253

254
    @Override
255
    public void halfClose() {}
×
256

257
    @Override
258
    public void sendMessage(ReqT message) {}
×
259
  }
260

261
  private static final class LruCache<K, V> {
262

263
    private Map<K, V> cache;
264
    private int maxSize;
265

266
    LruCache(int maxSize) {
1✔
267
      this.maxSize = maxSize;
1✔
268
      this.cache = createEvictingMap(maxSize);
1✔
269
    }
1✔
270

271
    V getOrInsert(K key, Function<K, V> create) {
272
      return cache.computeIfAbsent(key, create);
1✔
273
    }
274

275
    private void resizeCache(int newSize) {
276
      if (newSize >= maxSize) {
1✔
277
        maxSize = newSize;
1✔
278
        return;
1✔
279
      }
280
      Map<K, V> newCache = createEvictingMap(newSize);
1✔
281
      maxSize = newSize;
1✔
282
      newCache.putAll(cache);
1✔
283
      cache = newCache;
1✔
284
    }
1✔
285

286
    private Map<K, V> createEvictingMap(int size) {
287
      return new LinkedHashMap<K, V>(size, 0.75f, true) {
1✔
288
        @Override
289
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
290
          return size() > LruCache.this.maxSize;
1✔
291
        }
292
      };
293
    }
294
  }
295

296
  static class AudienceMetadataParser implements MetadataValueParser {
1✔
297

298
    static final class AudienceWrapper {
299
      final String audience;
300

301
      AudienceWrapper(String audience) {
1✔
302
        this.audience = checkNotNull(audience);
1✔
303
      }
1✔
304
    }
305

306
    @Override
307
    public String getTypeUrl() {
308
      return "type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.Audience";
1✔
309
    }
310

311
    @Override
312
    public AudienceWrapper parse(Any any) throws ResourceInvalidException {
313
      Audience audience;
314
      try {
315
        audience = any.unpack(Audience.class);
1✔
316
      } catch (InvalidProtocolBufferException ex) {
×
317
        throw new ResourceInvalidException("Invalid Resource in address proto", ex);
×
318
      }
1✔
319
      String url = audience.getUrl();
1✔
320
      if (url.isEmpty()) {
1✔
321
        throw new ResourceInvalidException(
×
322
            "Audience URL is empty. Metadata value must contain a valid URL.");
323
      }
324
      return new AudienceWrapper(url);
1✔
325
    }
326
  }
327
}
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