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

grpc / grpc-java / #19677

05 Feb 2025 09:05AM CUT coverage: 88.604% (+0.03%) from 88.578%
#19677

Pull #11858

github

web-flow
Merge dad68ffd5 into ea3f644ee
Pull Request #11858: core: updates the backoff range as per the A6 redefinition

33773 of 38117 relevant lines covered (88.6%)

0.89 hits per line

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

71.43
/../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.Filter.ClientInterceptorBuilder;
39
import io.grpc.xds.MetadataRegistry.MetadataValueParser;
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, ClientInterceptorBuilder {
1✔
51

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

55
  @Override
56
  public String[] typeUrls() {
57
    return new String[] { TYPE_URL };
×
58
  }
59

60
  @Override
61
  public ConfigOrError<? extends FilterConfig> parseFilterConfig(Message rawProtoMessage) {
62
    GcpAuthnFilterConfig gcpAuthnProto;
63
    if (!(rawProtoMessage instanceof Any)) {
1✔
64
      return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
1✔
65
    }
66
    Any anyMessage = (Any) rawProtoMessage;
1✔
67

68
    try {
69
      gcpAuthnProto = anyMessage.unpack(GcpAuthnFilterConfig.class);
1✔
70
    } catch (InvalidProtocolBufferException e) {
×
71
      return ConfigOrError.fromError("Invalid proto: " + e);
×
72
    }
1✔
73

74
    long cacheSize = 10;
1✔
75
    // Validate cache_config
76
    if (gcpAuthnProto.hasCacheConfig()) {
1✔
77
      TokenCacheConfig cacheConfig = gcpAuthnProto.getCacheConfig();
1✔
78
      cacheSize = cacheConfig.getCacheSize().getValue();
1✔
79
      if (cacheSize == 0) {
1✔
80
        return ConfigOrError.fromError(
1✔
81
            "cache_config.cache_size must be greater than zero");
82
      }
83
      // LruCache's size is an int and briefly exceeds its maximum size before evicting entries
84
      cacheSize = UnsignedLongs.min(cacheSize, Integer.MAX_VALUE - 1);
1✔
85
    }
86

87
    GcpAuthenticationConfig config = new GcpAuthenticationConfig((int) cacheSize);
1✔
88
    return ConfigOrError.fromConfig(config);
1✔
89
  }
90

91
  @Override
92
  public ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(Message rawProtoMessage) {
93
    return parseFilterConfig(rawProtoMessage);
×
94
  }
95

96
  @Nullable
97
  @Override
98
  public ClientInterceptor buildClientInterceptor(FilterConfig config,
99
      @Nullable FilterConfig overrideConfig, ScheduledExecutorService scheduler) {
100

101
    ComputeEngineCredentials credentials = ComputeEngineCredentials.create();
1✔
102
    LruCache<String, CallCredentials> callCredentialsCache =
1✔
103
        new LruCache<>(((GcpAuthenticationConfig) config).getCacheSize());
1✔
104
    return new ClientInterceptor() {
1✔
105
      @Override
106
      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
107
          MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
108

109
        /*String clusterName = callOptions.getOption(XdsAttributes.ATTR_CLUSTER_NAME);
110
        if (clusterName == null) {
111
          return next.newCall(method, callOptions);
112
        }*/
113

114
        // TODO: Fetch the CDS resource for the cluster.
115
        // If the CDS resource is not available, fail the RPC with Status.UNAVAILABLE.
116

117
        // TODO: Extract the audience from the CDS resource metadata.
118
        // If the audience is not found or is in the wrong format, fail the RPC.
119
        String audience = "TEST_AUDIENCE";
1✔
120

121
        try {
122
          CallCredentials existingCallCredentials = callOptions.getCredentials();
1✔
123
          CallCredentials newCallCredentials =
1✔
124
              getCallCredentials(callCredentialsCache, audience, credentials);
1✔
125
          if (existingCallCredentials != null) {
1✔
126
            callOptions = callOptions.withCallCredentials(
×
127
                new CompositeCallCredentials(existingCallCredentials, newCallCredentials));
128
          } else {
129
            callOptions = callOptions.withCallCredentials(newCallCredentials);
1✔
130
          }
131
        }
132
        catch (Exception e) {
×
133
          // If we fail to attach CallCredentials due to any reason, return a FailingClientCall
134
          return new FailingClientCall<>(Status.UNAUTHENTICATED
×
135
              .withDescription("Failed to attach CallCredentials.")
×
136
              .withCause(e));
×
137
        }
1✔
138
        return next.newCall(method, callOptions);
1✔
139
      }
140
    };
141
  }
142

143
  private CallCredentials getCallCredentials(LruCache<String, CallCredentials> cache,
144
      String audience, ComputeEngineCredentials credentials) {
145

146
    synchronized (cache) {
1✔
147
      return cache.getOrInsert(audience, key -> {
1✔
148
        IdTokenCredentials creds = IdTokenCredentials.newBuilder()
1✔
149
            .setIdTokenProvider(credentials)
1✔
150
            .setTargetAudience(audience)
1✔
151
            .build();
1✔
152
        return MoreCallCredentials.from(creds);
1✔
153
      });
154
    }
155
  }
156

157
  static final class GcpAuthenticationConfig implements FilterConfig {
158

159
    private final int cacheSize;
160

161
    public GcpAuthenticationConfig(int cacheSize) {
1✔
162
      this.cacheSize = cacheSize;
1✔
163
    }
1✔
164

165
    public int getCacheSize() {
166
      return cacheSize;
1✔
167
    }
168

169
    @Override
170
    public String typeUrl() {
171
      return GcpAuthenticationFilter.TYPE_URL;
×
172
    }
173
  }
174

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

178
    private final Status error;
179

180
    public FailingClientCall(Status error) {
×
181
      this.error = error;
×
182
    }
×
183

184
    @Override
185
    public void start(ClientCall.Listener<RespT> listener, Metadata headers) {
186
      listener.onClose(error, new Metadata());
×
187
    }
×
188

189
    @Override
190
    public void request(int numMessages) {}
×
191

192
    @Override
193
    public void cancel(String message, Throwable cause) {}
×
194

195
    @Override
196
    public void halfClose() {}
×
197

198
    @Override
199
    public void sendMessage(ReqT message) {}
×
200
  }
201

202
  private static final class LruCache<K, V> {
203

204
    private final Map<K, V> cache;
205

206
    LruCache(int maxSize) {
1✔
207
      this.cache = new LinkedHashMap<K, V>(
1✔
208
          maxSize,
209
          0.75f,
210
          true) {
1✔
211
        @Override
212
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
213
          return size() > maxSize;
1✔
214
        }
215
      };
216
    }
1✔
217

218
    V getOrInsert(K key, Function<K, V> create) {
219
      return cache.computeIfAbsent(key, create);
1✔
220
    }
221
  }
222

223
  static class AudienceMetadataParser implements MetadataValueParser {
1✔
224

225
    @Override
226
    public String getTypeUrl() {
227
      return "type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.Audience";
1✔
228
    }
229

230
    @Override
231
    public String parse(Any any) throws InvalidProtocolBufferException {
232
      Audience audience = any.unpack(Audience.class);
1✔
233
      String url = audience.getUrl();
1✔
234
      if (url.isEmpty()) {
1✔
235
        throw new InvalidProtocolBufferException(
×
236
            "Audience URL is empty. Metadata value must contain a valid URL.");
237
      }
238
      return url;
1✔
239
    }
240
  }
241
}
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

© 2025 Coveralls, Inc