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

grpc / grpc-java / #19544

08 Nov 2024 05:03AM UTC coverage: 84.607% (+0.04%) from 84.566%
#19544

push

github

web-flow
xds: Spiffe Trust Bundle Support (#11627)

Adds verification of SPIFFE based identities using SPIFFE trust bundles.

For in-progress gRFC A87.

34100 of 40304 relevant lines covered (84.61%)

0.85 hits per line

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

95.92
/../xds/src/main/java/io/grpc/xds/internal/security/certprovider/FileWatcherCertificateProvider.java
1
/*
2
 * Copyright 2020 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.internal.security.certprovider;
18

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

22
import com.google.common.annotations.VisibleForTesting;
23
import io.grpc.Status;
24
import io.grpc.internal.SpiffeUtil;
25
import io.grpc.internal.TimeProvider;
26
import io.grpc.xds.internal.security.trust.CertificateUtils;
27
import java.io.ByteArrayInputStream;
28
import java.nio.file.Files;
29
import java.nio.file.Path;
30
import java.nio.file.Paths;
31
import java.nio.file.attribute.FileTime;
32
import java.security.PrivateKey;
33
import java.security.cert.X509Certificate;
34
import java.util.Arrays;
35
import java.util.HashMap;
36
import java.util.concurrent.ScheduledExecutorService;
37
import java.util.concurrent.ScheduledFuture;
38
import java.util.concurrent.TimeUnit;
39
import java.util.logging.Level;
40
import java.util.logging.Logger;
41

42
// TODO(sanjaypujare): abstract out common functionality into an an abstract superclass
43
/** Implementation of {@link CertificateProvider} for file watching cert provider. */
44
final class FileWatcherCertificateProvider extends CertificateProvider implements Runnable {
45
  private static final Logger logger =
1✔
46
      Logger.getLogger(FileWatcherCertificateProvider.class.getName());
1✔
47

48
  private final ScheduledExecutorService scheduledExecutorService;
49
  private final TimeProvider timeProvider;
50
  private final Path certFile;
51
  private final Path keyFile;
52
  private final Path trustFile;
53
  private final Path spiffeTrustMapFile;
54
  private final long refreshIntervalInSeconds;
55
  @VisibleForTesting ScheduledFuture<?> scheduledFuture;
56
  private FileTime lastModifiedTimeCert;
57
  private FileTime lastModifiedTimeKey;
58
  private FileTime lastModifiedTimeRoot;
59
  private FileTime lastModifiedTimespiffeTrustMap;
60
  private boolean shutdown;
61

62
  FileWatcherCertificateProvider(
63
      DistributorWatcher watcher,
64
      boolean notifyCertUpdates,
65
      String certFile,
66
      String keyFile,
67
      String trustFile,
68
      String spiffeTrustMapFile,
69
      long refreshIntervalInSeconds,
70
      ScheduledExecutorService scheduledExecutorService,
71
      TimeProvider timeProvider) {
72
    super(watcher, notifyCertUpdates);
1✔
73
    this.scheduledExecutorService =
1✔
74
        checkNotNull(scheduledExecutorService, "scheduledExecutorService");
1✔
75
    this.timeProvider = checkNotNull(timeProvider, "timeProvider");
1✔
76
    this.certFile = Paths.get(checkNotNull(certFile, "certFile"));
1✔
77
    this.keyFile = Paths.get(checkNotNull(keyFile, "keyFile"));
1✔
78
    checkArgument((trustFile != null || spiffeTrustMapFile != null),
1✔
79
        "either trustFile or spiffeTrustMapFile must be present");
80
    if (spiffeTrustMapFile != null) {
1✔
81
      this.spiffeTrustMapFile = Paths.get(spiffeTrustMapFile);
1✔
82
      this.trustFile = null;
1✔
83
    } else {
84
      this.spiffeTrustMapFile = null;
1✔
85
      this.trustFile = Paths.get(trustFile);
1✔
86
    }
87
    this.refreshIntervalInSeconds = refreshIntervalInSeconds;
1✔
88
  }
1✔
89

90
  @Override
91
  public void start() {
92
    scheduleNextRefreshCertificate(/* delayInSeconds= */0);
1✔
93
  }
1✔
94

95
  @Override
96
  public synchronized void close() {
97
    shutdown = true;
1✔
98
    scheduledExecutorService.shutdownNow();
1✔
99
    if (scheduledFuture != null) {
1✔
100
      scheduledFuture.cancel(true);
1✔
101
      scheduledFuture = null;
1✔
102
    }
103
    getWatcher().close();
1✔
104
  }
1✔
105

106
  private synchronized void scheduleNextRefreshCertificate(long delayInSeconds) {
107
    if (!shutdown) {
1✔
108
      scheduledFuture = scheduledExecutorService.schedule(this, delayInSeconds, TimeUnit.SECONDS);
1✔
109
    }
110
  }
1✔
111

112
  @VisibleForTesting
113
  void checkAndReloadCertificates() {
114
    try {
115
      try {
116
        FileTime currentCertTime = Files.getLastModifiedTime(certFile);
1✔
117
        FileTime currentKeyTime = Files.getLastModifiedTime(keyFile);
1✔
118
        if (!currentCertTime.equals(lastModifiedTimeCert)
1✔
119
            && !currentKeyTime.equals(lastModifiedTimeKey)) {
1✔
120
          byte[] certFileContents = Files.readAllBytes(certFile);
1✔
121
          byte[] keyFileContents = Files.readAllBytes(keyFile);
1✔
122
          FileTime currentCertTime2 = Files.getLastModifiedTime(certFile);
1✔
123
          FileTime currentKeyTime2 = Files.getLastModifiedTime(keyFile);
1✔
124
          if (currentCertTime2.equals(currentCertTime) && currentKeyTime2.equals(currentKeyTime)) {
1✔
125
            try (ByteArrayInputStream certStream = new ByteArrayInputStream(certFileContents);
1✔
126
                ByteArrayInputStream keyStream = new ByteArrayInputStream(keyFileContents)) {
1✔
127
              PrivateKey privateKey = CertificateUtils.getPrivateKey(keyStream);
1✔
128
              X509Certificate[] certs = CertificateUtils.toX509Certificates(certStream);
1✔
129
              getWatcher().updateCertificate(privateKey, Arrays.asList(certs));
1✔
130
            }
131
            lastModifiedTimeCert = currentCertTime;
1✔
132
            lastModifiedTimeKey = currentKeyTime;
1✔
133
          }
134
        }
135
      } catch (Throwable t) {
1✔
136
        generateErrorIfCurrentCertExpired(t);
1✔
137
      }
1✔
138
      try {
139
        if (spiffeTrustMapFile != null) {
1✔
140
          FileTime currentSpiffeTime = Files.getLastModifiedTime(spiffeTrustMapFile);
1✔
141
          if (!currentSpiffeTime.equals(lastModifiedTimespiffeTrustMap)) {
1✔
142
            SpiffeUtil.SpiffeBundle trustBundle = SpiffeUtil
1✔
143
                .loadTrustBundleFromFile(spiffeTrustMapFile.toString());
1✔
144
            getWatcher().updateSpiffeTrustMap(new HashMap<>(trustBundle.getBundleMap()));
1✔
145
            lastModifiedTimespiffeTrustMap = currentSpiffeTime;
1✔
146
          }
147
        }
148
      } catch (Throwable t) {
1✔
149
        getWatcher().onError(Status.fromThrowable(t));
1✔
150
      }
1✔
151
      try {
152
        if (trustFile != null) {
1✔
153
          FileTime currentRootTime = Files.getLastModifiedTime(trustFile);
1✔
154
          if (!currentRootTime.equals(lastModifiedTimeRoot)) {
1✔
155
            byte[] rootFileContents = Files.readAllBytes(trustFile);
1✔
156
            FileTime currentRootTime2 = Files.getLastModifiedTime(trustFile);
1✔
157
            if (currentRootTime2.equals(currentRootTime)) {
1✔
158
              try (ByteArrayInputStream rootStream = new ByteArrayInputStream(rootFileContents)) {
1✔
159
                X509Certificate[] caCerts = CertificateUtils.toX509Certificates(rootStream);
1✔
160
                getWatcher().updateTrustedRoots(Arrays.asList(caCerts));
1✔
161
              }
162
              lastModifiedTimeRoot = currentRootTime;
1✔
163
            }
164
          }
165
        }
166
      } catch (Throwable t) {
1✔
167
        getWatcher().onError(Status.fromThrowable(t));
1✔
168
      }
1✔
169
    } finally {
170
      scheduleNextRefreshCertificate(refreshIntervalInSeconds);
1✔
171
    }
172
  }
1✔
173

174
  private void generateErrorIfCurrentCertExpired(Throwable t) {
175
    X509Certificate currentCert = getWatcher().getLastIdentityCert();
1✔
176
    if (currentCert != null) {
1✔
177
      long delaySeconds = computeDelaySecondsToCertExpiry(currentCert);
1✔
178
      if (delaySeconds > refreshIntervalInSeconds) {
1✔
179
        logger.log(Level.FINER, "reload certificate error", t);
1✔
180
        return;
1✔
181
      }
182
      // The current cert is going to expire in less than {@link refreshIntervalInSeconds}
183
      // Clear the current cert and notify our watchers thru {@code onError}
184
      getWatcher().clearValues();
1✔
185
    }
186
    getWatcher().onError(Status.fromThrowable(t));
1✔
187
  }
1✔
188

189
  @SuppressWarnings("JdkObsolete")
190
  private long computeDelaySecondsToCertExpiry(X509Certificate lastCert) {
191
    checkNotNull(lastCert, "lastCert");
1✔
192
    return TimeUnit.NANOSECONDS.toSeconds(
1✔
193
        TimeUnit.MILLISECONDS.toNanos(lastCert.getNotAfter().getTime())
1✔
194
            - timeProvider.currentTimeNanos());
1✔
195
  }
196

197
  @Override
198
  public void run() {
199
    if (!shutdown) {
1✔
200
      try {
201
        checkAndReloadCertificates();
1✔
202
      } catch (Throwable t) {
×
203
        logger.log(Level.SEVERE, "Uncaught exception!", t);
×
204
        if (t instanceof InterruptedException) {
×
205
          Thread.currentThread().interrupt();
×
206
        }
207
      }
1✔
208
    }
209
  }
1✔
210

211
  abstract static class Factory {
1✔
212
    private static final Factory DEFAULT_INSTANCE =
1✔
213
        new Factory() {
1✔
214
          @Override
215
          FileWatcherCertificateProvider create(
216
              DistributorWatcher watcher,
217
              boolean notifyCertUpdates,
218
              String certFile,
219
              String keyFile,
220
              String trustFile,
221
              String spiffeTrustMapFile,
222
              long refreshIntervalInSeconds,
223
              ScheduledExecutorService scheduledExecutorService,
224
              TimeProvider timeProvider) {
225
            return new FileWatcherCertificateProvider(
1✔
226
                watcher,
227
                notifyCertUpdates,
228
                certFile,
229
                keyFile,
230
                trustFile,
231
                spiffeTrustMapFile,
232
                refreshIntervalInSeconds,
233
                scheduledExecutorService,
234
                timeProvider);
235
          }
236
        };
237

238
    static Factory getInstance() {
239
      return DEFAULT_INSTANCE;
1✔
240
    }
241

242
    abstract FileWatcherCertificateProvider create(
243
        DistributorWatcher watcher,
244
        boolean notifyCertUpdates,
245
        String certFile,
246
        String keyFile,
247
        String trustFile,
248
        String spiffeTrustMapFile,
249
        long refreshIntervalInSeconds,
250
        ScheduledExecutorService scheduledExecutorService,
251
        TimeProvider timeProvider);
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