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

grpc / grpc-java / #19232

14 May 2024 01:34PM UTC coverage: 88.419% (+0.02%) from 88.403%
#19232

push

github

ejona86
rls: Guarantee backoff will update RLS picker

Previously, picker was likely null if entering backoff soon after
start-up. This prevented the picker from being updated and directing
queued RPCs to the fallback. It would work for new RPCs if RLS returned
extremely rapidly; both ManagedChannelImpl and DelayedClientTransport do
a pick before enqueuing so the ManagedChannelImpl pick could request
from RLS and DelayedClientTransport could use the response. So the test
uses a delay to purposefully avoid that unlikely-in-real-life case.

Creating a resolving OOB channel for InProcess doesn't actually change
the destination from the parent, because InProcess uses directaddress.
Thus the fakeRlsServiceImpl is now being added to the fake backend
server, because the same server is used for RLS within the test.

b/333185213

31615 of 35756 relevant lines covered (88.42%)

0.88 hits per line

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

79.37
/../util/src/main/java/io/grpc/util/AdvancedTlsX509KeyManager.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.util;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20

21
import io.grpc.ExperimentalApi;
22
import java.io.File;
23
import java.io.FileInputStream;
24
import java.io.IOException;
25
import java.net.Socket;
26
import java.security.GeneralSecurityException;
27
import java.security.Principal;
28
import java.security.PrivateKey;
29
import java.security.cert.CertificateException;
30
import java.security.cert.X509Certificate;
31
import java.util.Arrays;
32
import java.util.concurrent.ScheduledExecutorService;
33
import java.util.concurrent.ScheduledFuture;
34
import java.util.concurrent.TimeUnit;
35
import java.util.logging.Level;
36
import java.util.logging.Logger;
37
import javax.net.ssl.SSLEngine;
38
import javax.net.ssl.X509ExtendedKeyManager;
39

40
/**
41
 * AdvancedTlsX509KeyManager is an {@code X509ExtendedKeyManager} that allows users to configure
42
 * advanced TLS features, such as private key and certificate chain reloading, etc.
43
 */
44
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8024")
45
public final class AdvancedTlsX509KeyManager extends X509ExtendedKeyManager {
46
  private static final Logger log = Logger.getLogger(AdvancedTlsX509KeyManager.class.getName());
1✔
47

48
  // The credential information sent to peers to prove our identity.
49
  private volatile KeyInfo keyInfo;
50

51
  /**
52
   * Constructs an AdvancedTlsX509KeyManager.
53
   */
54
  public AdvancedTlsX509KeyManager() throws CertificateException { }
1✔
55

56
  @Override
57
  public PrivateKey getPrivateKey(String alias) {
58
    if (alias.equals("default")) {
1✔
59
      return this.keyInfo.key;
1✔
60
    }
61
    return null;
×
62
  }
63

64
  @Override
65
  public X509Certificate[] getCertificateChain(String alias) {
66
    if (alias.equals("default")) {
1✔
67
      return Arrays.copyOf(this.keyInfo.certs, this.keyInfo.certs.length);
1✔
68
    }
69
    return null;
×
70
  }
71

72
  @Override
73
  public String[] getClientAliases(String keyType, Principal[] issuers) {
74
    return new String[] {"default"};
1✔
75
  }
76

77
  @Override
78
  public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
79
    return "default";
1✔
80
  }
81

82
  @Override
83
  public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
84
    return "default";
1✔
85
  }
86

87
  @Override
88
  public String[] getServerAliases(String keyType, Principal[] issuers) {
89
    return new String[] {"default"};
1✔
90
  }
91

92
  @Override
93
  public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
94
    return "default";
1✔
95
  }
96

97
  @Override
98
  public String chooseEngineServerAlias(String keyType, Principal[] issuers,
99
      SSLEngine engine) {
100
    return "default";
1✔
101
  }
102

103
  /**
104
   * Updates the current cached private key and cert chains.
105
   *
106
   * @param key  the private key that is going to be used
107
   * @param certs  the certificate chain that is going to be used
108
   */
109
  public void updateIdentityCredentials(PrivateKey key, X509Certificate[] certs) {
110
    // TODO(ZhenLian): explore possibilities to do a crypto check here.
111
    this.keyInfo = new KeyInfo(checkNotNull(key, "key"), checkNotNull(certs, "certs"));
1✔
112
  }
1✔
113

114
  /**
115
   * Schedules a {@code ScheduledExecutorService} to read private key and certificate chains from
116
   * the local file paths periodically, and update the cached identity credentials if they are both
117
   * updated.
118
   *
119
   * @param keyFile  the file on disk holding the private key
120
   * @param certFile  the file on disk holding the certificate chain
121
   * @param period the period between successive read-and-update executions
122
   * @param unit the time unit of the initialDelay and period parameters
123
   * @param executor the execute service we use to read and update the credentials
124
   * @return an object that caller should close when the file refreshes are not needed
125
   */
126
  public Closeable updateIdentityCredentialsFromFile(File keyFile, File certFile,
127
      long period, TimeUnit unit, ScheduledExecutorService executor) throws IOException,
128
      GeneralSecurityException {
129
    UpdateResult newResult = readAndUpdate(keyFile, certFile, 0, 0);
1✔
130
    if (!newResult.success) {
1✔
131
      throw new GeneralSecurityException(
×
132
          "Files were unmodified before their initial update. Probably a bug.");
133
    }
134
    final ScheduledFuture<?> future =
1✔
135
        executor.scheduleWithFixedDelay(
1✔
136
            new LoadFilePathExecution(keyFile, certFile), period, period, unit);
137
    return new Closeable() {
1✔
138
      @Override public void close() {
139
        future.cancel(false);
1✔
140
      }
1✔
141
    };
142
  }
143

144
  /**
145
   * Updates the private key and certificate chains from the local file paths.
146
   *
147
   * @param keyFile  the file on disk holding the private key
148
   * @param certFile  the file on disk holding the certificate chain
149
   */
150
  public void updateIdentityCredentialsFromFile(File keyFile, File certFile) throws IOException,
151
      GeneralSecurityException {
152
    UpdateResult newResult = readAndUpdate(keyFile, certFile, 0, 0);
1✔
153
    if (!newResult.success) {
1✔
154
      throw new GeneralSecurityException(
×
155
          "Files were unmodified before their initial update. Probably a bug.");
156
    }
157
  }
1✔
158

159
  private static class KeyInfo {
160
    // The private key and the cert chain we will use to send to peers to prove our identity.
161
    final PrivateKey key;
162
    final X509Certificate[] certs;
163

164
    public KeyInfo(PrivateKey key, X509Certificate[] certs) {
1✔
165
      this.key = key;
1✔
166
      this.certs = certs;
1✔
167
    }
1✔
168
  }
169

170
  private class LoadFilePathExecution implements Runnable {
171
    File keyFile;
172
    File certFile;
173
    long currentKeyTime;
174
    long currentCertTime;
175

176
    public LoadFilePathExecution(File keyFile, File certFile) {
1✔
177
      this.keyFile = keyFile;
1✔
178
      this.certFile = certFile;
1✔
179
      this.currentKeyTime = 0;
1✔
180
      this.currentCertTime = 0;
1✔
181
    }
1✔
182

183
    @Override
184
    public void run() {
185
      try {
186
        UpdateResult newResult = readAndUpdate(this.keyFile, this.certFile, this.currentKeyTime,
×
187
            this.currentCertTime);
188
        if (newResult.success) {
×
189
          this.currentKeyTime = newResult.keyTime;
×
190
          this.currentCertTime = newResult.certTime;
×
191
        }
192
      } catch (IOException | GeneralSecurityException e) {
×
193
        log.log(Level.SEVERE, "Failed refreshing private key and certificate chain from files. "
×
194
            + "Using previous ones", e);
195
      }
×
196
    }
×
197
  }
198

199
  private static class UpdateResult {
200
    boolean success;
201
    long keyTime;
202
    long certTime;
203

204
    public UpdateResult(boolean success, long keyTime, long certTime) {
1✔
205
      this.success = success;
1✔
206
      this.keyTime = keyTime;
1✔
207
      this.certTime = certTime;
1✔
208
    }
1✔
209
  }
210

211
  /**
212
   * Reads the private key and certificates specified in the path locations. Updates {@code key} and
213
   * {@code cert} if both of their modified time changed since last read.
214
   *
215
   * @param keyFile  the file on disk holding the private key
216
   * @param certFile  the file on disk holding the certificate chain
217
   * @param oldKeyTime the time when the private key file is modified during last execution
218
   * @param oldCertTime the time when the certificate chain file is modified during last execution
219
   * @return the result of this update execution
220
   */
221
  private UpdateResult readAndUpdate(File keyFile, File certFile, long oldKeyTime, long oldCertTime)
222
      throws IOException, GeneralSecurityException {
223
    long newKeyTime = keyFile.lastModified();
1✔
224
    long newCertTime = certFile.lastModified();
1✔
225
    // We only update when both the key and the certs are updated.
226
    if (newKeyTime != oldKeyTime && newCertTime != oldCertTime) {
1✔
227
      FileInputStream keyInputStream = new FileInputStream(keyFile);
1✔
228
      try {
229
        PrivateKey key = CertificateUtils.getPrivateKey(keyInputStream);
1✔
230
        FileInputStream certInputStream = new FileInputStream(certFile);
1✔
231
        try {
232
          X509Certificate[] certs = CertificateUtils.getX509Certificates(certInputStream);
1✔
233
          updateIdentityCredentials(key, certs);
1✔
234
          return new UpdateResult(true, newKeyTime, newCertTime);
1✔
235
        } finally {
236
          certInputStream.close();
1✔
237
        }
238
      } finally {
239
        keyInputStream.close();
1✔
240
      }
241
    }
242
    return new UpdateResult(false, oldKeyTime, oldCertTime);
×
243
  }
244

245
  /**
246
   * Mainly used to avoid throwing IO Exceptions in java.io.Closeable.
247
   */
248
  public interface Closeable extends java.io.Closeable {
249
    @Override
250
    void close();
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

© 2025 Coveralls, Inc