Skip to content

Commit a8cb6cf

Browse files
committed
Remove validity threshold from SslInfo
Closes gh-44650
1 parent 1ec8763 commit a8cb6cf

File tree

11 files changed

+67
-108
lines changed

11 files changed

+67
-108
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfiguration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ SslInfoContributor sslInfoContributor(SslInfo sslInfo) {
114114
@Bean
115115
@ConditionalOnMissingBean
116116
@ConditionalOnEnabledInfoContributor(value = "ssl", fallback = InfoContributorFallback.DISABLE)
117-
SslInfo sslInfo(SslBundles sslBundles, SslHealthIndicatorProperties sslHealthIndicatorProperties) {
118-
return new SslInfo(sslBundles, sslHealthIndicatorProperties.getCertificateValidityWarningThreshold());
117+
SslInfo sslInfo(SslBundles sslBundles) {
118+
return new SslInfo(sslBundles);
119119
}
120120

121121
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfiguration.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ public class SslHealthContributorAutoConfiguration {
4040

4141
@Bean
4242
@ConditionalOnMissingBean(name = "sslHealthIndicator")
43-
SslHealthIndicator sslHealthIndicator(SslInfo sslInfo) {
44-
return new SslHealthIndicator(sslInfo);
43+
SslHealthIndicator sslHealthIndicator(SslInfo sslInfo, SslHealthIndicatorProperties properties) {
44+
return new SslHealthIndicator(sslInfo, properties.getCertificateValidityWarningThreshold());
4545
}
4646

4747
@Bean
4848
@ConditionalOnMissingBean
49-
SslInfo sslInfo(SslBundles sslBundles, SslHealthIndicatorProperties sslHealthIndicatorProperties) {
50-
return new SslInfo(sslBundles, sslHealthIndicatorProperties.getCertificateValidityWarningThreshold());
49+
SslInfo sslInfo(SslBundles sslBundles) {
50+
return new SslInfo(sslBundles);
5151
}
5252

5353
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/SslObservabilityAutoConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ SslMeterBinder sslMeterBinder(SslInfo sslInfo, SslBundles sslBundles) {
5252
@Bean
5353
@ConditionalOnMissingBean
5454
SslInfo sslInfoProvider(SslBundles sslBundles, SslHealthIndicatorProperties properties) {
55-
return new SslInfo(sslBundles, properties.getCertificateValidityWarningThreshold());
55+
return new SslInfo(sslBundles);
5656
}
5757

5858
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfigurationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ static class CustomSslInfoConfiguration {
301301

302302
@Bean
303303
SslInfo customSslInfo(SslBundles sslBundles) {
304-
return new SslInfo(sslBundles, Duration.ofDays(7));
304+
return new SslInfo(sslBundles);
305305
}
306306

307307
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfigurationTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ void customBeansShouldBeConfigured() {
110110
}
111111

112112
private static void assertDetailsKeys(Health health) {
113-
assertThat(health.getDetails()).containsOnlyKeys("validChains", "invalidChains");
113+
assertThat(health.getDetails()).containsOnlyKeys("expiringChains", "validChains", "invalidChains");
114114
}
115115

116116
@SuppressWarnings("unchecked")
@@ -128,13 +128,13 @@ SslHealthIndicator sslHealthIndicator(SslInfo sslInfo) {
128128

129129
@Bean
130130
SslInfo customSslInfo(SslBundles sslBundles) {
131-
return new SslInfo(sslBundles, Duration.ofDays(7));
131+
return new SslInfo(sslBundles);
132132
}
133133

134134
static class CustomSslHealthIndicator extends SslHealthIndicator {
135135

136136
CustomSslHealthIndicator(SslInfo sslInfo) {
137-
super(sslInfo);
137+
super(sslInfo, Duration.ofDays(7));
138138
}
139139

140140
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/ssl/SslMeterBinderTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ private SslBundles createSslBundles(String... locations) {
9292
}
9393

9494
private SslInfo createSslInfo(SslBundles sslBundles) {
95-
return new SslInfo(sslBundles, Duration.ofDays(7), CLOCK);
95+
return new SslInfo(sslBundles);
9696
}
9797

9898
}

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/ssl/SslHealthIndicator.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.boot.actuate.ssl;
1818

19+
import java.time.Duration;
20+
import java.time.Instant;
1921
import java.util.ArrayList;
2022
import java.util.List;
2123
import java.util.stream.Stream;
@@ -42,29 +44,37 @@ public class SslHealthIndicator extends AbstractHealthIndicator {
4244

4345
private final SslInfo sslInfo;
4446

45-
public SslHealthIndicator(SslInfo sslInfo) {
47+
private final Duration expiryThreshold;
48+
49+
public SslHealthIndicator(SslInfo sslInfo, Duration expiryThreshold) {
4650
super("SSL health check failed");
4751
Assert.notNull(sslInfo, "'sslInfo' must not be null");
4852
this.sslInfo = sslInfo;
53+
this.expiryThreshold = expiryThreshold;
4954
}
5055

5156
@Override
5257
protected void doHealthCheck(Builder builder) throws Exception {
5358
List<CertificateChainInfo> validCertificateChains = new ArrayList<>();
5459
List<CertificateChainInfo> invalidCertificateChains = new ArrayList<>();
60+
List<CertificateChainInfo> expiringCerificateChains = new ArrayList<>();
5561
for (BundleInfo bundle : this.sslInfo.getBundles()) {
5662
for (CertificateChainInfo certificateChain : bundle.getCertificateChains()) {
5763
if (containsOnlyValidCertificates(certificateChain)) {
5864
validCertificateChains.add(certificateChain);
65+
if (containsExpiringCertificate(certificateChain)) {
66+
expiringCerificateChains.add(certificateChain);
67+
}
5968
}
6069
else if (containsInvalidCertificate(certificateChain)) {
6170
invalidCertificateChains.add(certificateChain);
6271
}
6372
}
6473
}
6574
builder.status((invalidCertificateChains.isEmpty()) ? Status.UP : Status.OUT_OF_SERVICE);
66-
builder.withDetail("validChains", validCertificateChains);
75+
builder.withDetail("expiringChains", expiringCerificateChains);
6776
builder.withDetail("invalidChains", invalidCertificateChains);
77+
builder.withDetail("validChains", validCertificateChains);
6878
}
6979

7080
private boolean containsOnlyValidCertificates(CertificateChainInfo certificateChain) {
@@ -75,6 +85,10 @@ private boolean containsInvalidCertificate(CertificateChainInfo certificateChain
7585
return validatableCertificates(certificateChain).anyMatch(this::isNotValidCertificate);
7686
}
7787

88+
private boolean containsExpiringCertificate(CertificateChainInfo certificateChain) {
89+
return validatableCertificates(certificateChain).anyMatch(this::isExpiringCertificate);
90+
}
91+
7892
private Stream<CertificateInfo> validatableCertificates(CertificateChainInfo certificateChain) {
7993
return certificateChain.getCertificates().stream().filter((certificate) -> certificate.getValidity() != null);
8094
}
@@ -87,4 +101,8 @@ private boolean isNotValidCertificate(CertificateInfo certificate) {
87101
return !isValidCertificate(certificate);
88102
}
89103

104+
private boolean isExpiringCertificate(CertificateInfo certificate) {
105+
return Instant.now().plus(this.expiryThreshold).isAfter(certificate.getValidityEnds());
106+
}
107+
90108
}

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/info/SslInfoContributorTests.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.boot.actuate.info;
1818

19-
import java.time.Duration;
20-
2119
import org.junit.jupiter.api.Test;
2220

2321
import org.springframework.aot.hint.MemberCategory;
@@ -42,7 +40,7 @@ class SslInfoContributorTests {
4240
@Test
4341
void sslInfoShouldBeAdded() {
4442
SslBundles sslBundles = new DefaultSslBundleRegistry("test", mock(SslBundle.class));
45-
SslInfo sslInfo = new SslInfo(sslBundles, Duration.ofDays(14));
43+
SslInfo sslInfo = new SslInfo(sslBundles);
4644
SslInfoContributor sslInfoContributor = new SslInfoContributor(sslInfo);
4745
Info.Builder builder = new Info.Builder();
4846
sslInfoContributor.contribute(builder);

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/ssl/SslHealthIndicatorTests.java

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616

1717
package org.springframework.boot.actuate.ssl;
1818

19+
import java.time.Duration;
20+
import java.time.Instant;
1921
import java.util.List;
2022

2123
import org.junit.jupiter.api.BeforeEach;
2224
import org.junit.jupiter.api.Test;
2325

2426
import org.springframework.boot.actuate.health.Health;
25-
import org.springframework.boot.actuate.health.HealthIndicator;
2627
import org.springframework.boot.actuate.health.Status;
2728
import org.springframework.boot.info.SslInfo;
2829
import org.springframework.boot.info.SslInfo.BundleInfo;
@@ -41,26 +42,27 @@
4142
*/
4243
class SslHealthIndicatorTests {
4344

44-
private HealthIndicator healthIndicator;
45+
private final CertificateInfo certificateInfo = mock(CertificateInfo.class);
4546

46-
private CertificateValidityInfo validity;
47+
private final CertificateValidityInfo validity = mock(CertificateValidityInfo.class);
48+
49+
private SslHealthIndicator healthIndicator;
4750

4851
@BeforeEach
4952
void setUp() {
5053
SslInfo sslInfo = mock(SslInfo.class);
5154
BundleInfo bundle = mock(BundleInfo.class);
5255
CertificateChainInfo certificateChain = mock(CertificateChainInfo.class);
53-
CertificateInfo certificateInfo = mock(CertificateInfo.class);
54-
this.healthIndicator = new SslHealthIndicator(sslInfo);
55-
this.validity = mock(CertificateValidityInfo.class);
56+
this.healthIndicator = new SslHealthIndicator(sslInfo, Duration.ofDays(7));
5657
given(sslInfo.getBundles()).willReturn(List.of(bundle));
5758
given(bundle.getCertificateChains()).willReturn(List.of(certificateChain));
58-
given(certificateChain.getCertificates()).willReturn(List.of(certificateInfo));
59-
given(certificateInfo.getValidity()).willReturn(this.validity);
59+
given(certificateChain.getCertificates()).willReturn(List.of(this.certificateInfo));
60+
given(this.certificateInfo.getValidity()).willReturn(this.validity);
6061
}
6162

6263
@Test
6364
void shouldBeUpIfNoSslIssuesDetected() {
65+
given(this.certificateInfo.getValidityEnds()).willReturn(Instant.now().plus(Duration.ofDays(365)));
6466
given(this.validity.getStatus()).willReturn(CertificateValidityInfo.Status.VALID);
6567
Health health = this.healthIndicator.health();
6668
assertThat(health.getStatus()).isEqualTo(Status.UP);
@@ -101,10 +103,14 @@ void shouldBeOutOfServiceIfACertificateIsNotYetValid() {
101103

102104
@Test
103105
void shouldReportWarningIfACertificateWillExpireSoon() {
104-
given(this.validity.getStatus()).willReturn(CertificateValidityInfo.Status.WILL_EXPIRE_SOON);
106+
given(this.validity.getStatus()).willReturn(CertificateValidityInfo.Status.VALID);
107+
given(this.certificateInfo.getValidityEnds()).willReturn(Instant.now().plus(Duration.ofDays(3)));
105108
Health health = this.healthIndicator.health();
106109
assertThat(health.getStatus()).isEqualTo(Status.UP);
107110
assertDetailsKeys(health);
111+
List<CertificateChainInfo> expiring = getExpiringChains(health);
112+
assertThat(expiring).hasSize(1);
113+
assertThat(expiring.get(0)).isInstanceOf(CertificateChainInfo.class);
108114
List<CertificateChainInfo> validChains = getValidChains(health);
109115
assertThat(validChains).hasSize(1);
110116
assertThat(validChains.get(0)).isInstanceOf(CertificateChainInfo.class);
@@ -113,17 +119,24 @@ void shouldReportWarningIfACertificateWillExpireSoon() {
113119
}
114120

115121
private static void assertDetailsKeys(Health health) {
116-
assertThat(health.getDetails()).containsOnlyKeys("validChains", "invalidChains");
122+
assertThat(health.getDetails()).containsOnlyKeys("expiringChains", "validChains", "invalidChains");
123+
}
124+
125+
private static List<CertificateChainInfo> getExpiringChains(Health health) {
126+
return getChains(health, "expiringChains");
117127
}
118128

119-
@SuppressWarnings("unchecked")
120129
private static List<CertificateChainInfo> getInvalidChains(Health health) {
121-
return (List<CertificateChainInfo>) health.getDetails().get("invalidChains");
130+
return getChains(health, "invalidChains");
122131
}
123132

124-
@SuppressWarnings("unchecked")
125133
private static List<CertificateChainInfo> getValidChains(Health health) {
126-
return (List<CertificateChainInfo>) health.getDetails().get("validChains");
134+
return getChains(health, "validChains");
135+
}
136+
137+
@SuppressWarnings("unchecked")
138+
private static List<CertificateChainInfo> getChains(Health health, String name) {
139+
return (List<CertificateChainInfo>) health.getDetails().get(name);
127140
}
128141

129142
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/SslInfo.java

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
import java.security.cert.CertificateExpiredException;
2323
import java.security.cert.CertificateNotYetValidException;
2424
import java.security.cert.X509Certificate;
25-
import java.time.Clock;
26-
import java.time.Duration;
2725
import java.time.Instant;
2826
import java.util.Arrays;
2927
import java.util.Collections;
@@ -50,32 +48,13 @@ public class SslInfo {
5048

5149
private final SslBundles sslBundles;
5250

53-
private final Duration certificateValidityWarningThreshold;
54-
55-
private final Clock clock;
56-
5751
/**
5852
* Creates a new instance.
59-
* @param sslBundles the {@link SslBundles} to extract the info from
60-
* @param certificateValidityWarningThreshold the certificate validity warning
61-
* threshold
53+
* @param sslBundles the {@link SslBundles} to extract the info from threshold
54+
* @since 4.0.0
6255
*/
63-
public SslInfo(SslBundles sslBundles, Duration certificateValidityWarningThreshold) {
64-
this(sslBundles, certificateValidityWarningThreshold, Clock.systemDefaultZone());
65-
}
66-
67-
/**
68-
* Creates a new instance.
69-
* @param sslBundles the {@link SslBundles} to extract the info from
70-
* @param certificateValidityWarningThreshold the certificate validity warning
71-
* threshold
72-
* @param clock the {@link Clock} to use
73-
* @since 3.5.0
74-
*/
75-
public SslInfo(SslBundles sslBundles, Duration certificateValidityWarningThreshold, Clock clock) {
56+
public SslInfo(SslBundles sslBundles) {
7657
this.sslBundles = sslBundles;
77-
this.certificateValidityWarningThreshold = certificateValidityWarningThreshold;
78-
this.clock = clock;
7958
}
8059

8160
/**
@@ -218,12 +197,9 @@ public CertificateValidityInfo getValidity() {
218197
return extract((certificate) -> {
219198
Instant starts = getValidityStarts();
220199
Instant ends = getValidityEnds();
221-
Duration threshold = SslInfo.this.certificateValidityWarningThreshold;
222200
try {
223201
certificate.checkValidity();
224-
return (!isExpiringSoon(certificate, threshold)) ? CertificateValidityInfo.VALID
225-
: new CertificateValidityInfo(Status.WILL_EXPIRE_SOON,
226-
"Certificate will expire within threshold (%s) at %s", threshold, ends);
202+
return CertificateValidityInfo.VALID;
227203
}
228204
catch (CertificateNotYetValidException ex) {
229205
return new CertificateValidityInfo(Status.NOT_YET_VALID, "Not valid before %s", starts);
@@ -234,12 +210,6 @@ public CertificateValidityInfo getValidity() {
234210
});
235211
}
236212

237-
private boolean isExpiringSoon(X509Certificate certificate, Duration threshold) {
238-
Instant shouldBeValidAt = Instant.now(SslInfo.this.clock).plus(threshold);
239-
Instant expiresAt = certificate.getNotAfter().toInstant();
240-
return shouldBeValidAt.isAfter(expiresAt);
241-
}
242-
243213
private <V, R> R extract(Function<X509Certificate, V> valueExtractor, Function<V, R> resultExtractor) {
244214
return extract(valueExtractor.andThen(resultExtractor));
245215
}
@@ -292,13 +262,7 @@ public enum Status {
292262
/**
293263
* The certificate's validity date range is in the past.
294264
*/
295-
EXPIRED(false),
296-
297-
/**
298-
* The certificate is still valid, but the end of its validity date range is
299-
* within the defined threshold.
300-
*/
301-
WILL_EXPIRE_SOON(true);
265+
EXPIRED(false);
302266

303267
private final boolean valid;
304268

0 commit comments

Comments
 (0)