Skip to content

Commit de45a71

Browse files
Show the whole cert chain in SslHealthIndicator
1 parent 2a1bb3f commit de45a71

File tree

4 files changed

+55
-38
lines changed

4 files changed

+55
-38
lines changed

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

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import org.springframework.boot.autoconfigure.AutoConfigurations;
3030
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
3131
import org.springframework.boot.info.SslInfo;
32-
import org.springframework.boot.info.SslInfo.CertificateInfo;
32+
import org.springframework.boot.info.SslInfo.CertificateChain;
3333
import org.springframework.boot.ssl.SslBundles;
3434
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3535
import org.springframework.context.annotation.Bean;
@@ -64,9 +64,11 @@ void beansShouldBeConfigured() {
6464
Health health = context.getBean(SslHealthIndicator.class).health();
6565
assertThat(health.getStatus()).isSameAs(Status.OUT_OF_SERVICE);
6666
assertThat(health.getDetails()).hasSize(1);
67-
List<CertificateInfo> certificates = (List<CertificateInfo>) health.getDetails().get("certificates");
68-
assertThat(certificates).hasSize(1);
69-
assertThat(certificates.get(0)).isInstanceOf(CertificateInfo.class);
67+
List<CertificateChain> certificateChains = (List<CertificateChain>) health.getDetails()
68+
.get("certificateChains");
69+
assertThat(certificateChains).hasSize(1);
70+
assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class);
71+
7072
});
7173
}
7274

@@ -83,9 +85,10 @@ void beansShouldBeConfiguredWithWarningThreshold() {
8385
Health health = context.getBean(SslHealthIndicator.class).health();
8486
assertThat(health.getStatus()).isSameAs(Status.OUT_OF_SERVICE);
8587
assertThat(health.getDetails()).hasSize(1);
86-
List<CertificateInfo> certificates = (List<CertificateInfo>) health.getDetails().get("certificates");
87-
assertThat(certificates).hasSize(1);
88-
assertThat(certificates.get(0)).isInstanceOf(CertificateInfo.class);
88+
List<CertificateChain> certificateChains = (List<CertificateChain>) health.getDetails()
89+
.get("certificateChains");
90+
assertThat(certificateChains).hasSize(1);
91+
assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class);
8992
});
9093
}
9194

@@ -101,9 +104,10 @@ void customBeansShouldBeConfigured() {
101104
Health health = context.getBean(SslHealthIndicator.class).health();
102105
assertThat(health.getStatus()).isSameAs(Status.OUT_OF_SERVICE);
103106
assertThat(health.getDetails()).hasSize(1);
104-
List<CertificateInfo> certificates = (List<CertificateInfo>) health.getDetails().get("certificates");
105-
assertThat(certificates).hasSize(1);
106-
assertThat(certificates.get(0)).isInstanceOf(CertificateInfo.class);
107+
List<CertificateChain> certificateChains = (List<CertificateChain>) health.getDetails()
108+
.get("certificateChains");
109+
assertThat(certificateChains).hasSize(1);
110+
assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class);
107111
});
108112
}
109113

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

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import org.springframework.boot.actuate.health.HealthIndicator;
2626
import org.springframework.boot.actuate.health.Status;
2727
import org.springframework.boot.info.SslInfo;
28-
import org.springframework.boot.info.SslInfo.CertificateInfo;
28+
import org.springframework.boot.info.SslInfo.CertificateChain;
2929
import org.springframework.boot.info.SslInfo.CertificateInfo.Validity;
3030

3131
/**
@@ -38,9 +38,6 @@
3838
*/
3939
public class SslHealthIndicator extends AbstractHealthIndicator {
4040

41-
private static final Status WILL_EXPIRE_SOON_STATUS = new Status(Validity.Status.WILL_EXPIRE_SOON.name(),
42-
"One of the certificates will expire within the defined threshold.");
43-
4441
private final SslInfo sslInfo;
4542

4643
public SslHealthIndicator(SslInfo sslInfo) {
@@ -49,32 +46,43 @@ public SslHealthIndicator(SslInfo sslInfo) {
4946

5047
@Override
5148
protected void doHealthCheck(Builder builder) throws Exception {
52-
List<CertificateInfo> notValidCertificates = this.sslInfo.getBundles()
49+
List<CertificateChain> notValidCertificateChains = this.sslInfo.getBundles()
5350
.stream()
5451
.flatMap((bundle) -> bundle.getCertificateChains().stream())
55-
.flatMap((certificateChain) -> certificateChain.getCertificates().stream())
56-
.filter((certificate) -> certificate.getValidity() != null)
57-
.filter((certificate) -> certificate.getValidity().getStatus() != Validity.Status.VALID)
52+
.filter(this::containsNotValidCertificate)
5853
.toList();
5954

60-
if (notValidCertificates.isEmpty()) {
55+
if (notValidCertificateChains.isEmpty()) {
6156
builder.status(Status.UP);
6257
}
6358
else {
64-
Set<Validity.Status> statuses = notValidCertificates.stream()
65-
.map((certificate) -> certificate.getValidity().getStatus())
66-
.collect(Collectors.toUnmodifiableSet());
59+
Set<Validity.Status> statuses = collectCertificateStatuses(notValidCertificateChains);
6760
if (statuses.contains(Validity.Status.EXPIRED) || statuses.contains(Validity.Status.NOT_YET_VALID)) {
6861
builder.status(Status.OUT_OF_SERVICE);
6962
}
7063
else if (statuses.contains(Validity.Status.WILL_EXPIRE_SOON)) {
71-
builder.status(WILL_EXPIRE_SOON_STATUS);
64+
builder.status(new Status("WARNING"));
7265
}
7366
else {
7467
builder.status(Status.OUT_OF_SERVICE);
7568
}
76-
builder.withDetail("certificates", notValidCertificates);
69+
builder.withDetail("certificateChains", notValidCertificateChains);
7770
}
7871
}
7972

73+
private boolean containsNotValidCertificate(CertificateChain certificateChain) {
74+
return certificateChain.getCertificates()
75+
.stream()
76+
.filter((certificate) -> certificate.getValidity() != null)
77+
.anyMatch((certificate) -> certificate.getValidity().getStatus() != Validity.Status.VALID);
78+
}
79+
80+
private Set<Validity.Status> collectCertificateStatuses(List<CertificateChain> certificateChains) {
81+
return certificateChains.stream()
82+
.flatMap((certificateChain) -> certificateChain.getCertificates().stream())
83+
.filter((certificate) -> certificate.getValidity() != null)
84+
.map((certificate) -> certificate.getValidity().getStatus())
85+
.collect(Collectors.toUnmodifiableSet());
86+
}
87+
8088
}

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

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,10 @@ void shouldBeOutOfServiceIfACertificateIsExpired() {
7676
Health health = this.healthIndicator.health();
7777
assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE);
7878
assertThat(health.getDetails()).hasSize(1);
79-
List<CertificateInfo> certificates = (List<CertificateInfo>) health.getDetails().get("certificates");
80-
assertThat(certificates).hasSize(1);
81-
assertThat(certificates.get(0)).isInstanceOf(CertificateInfo.class);
79+
List<CertificateChain> certificateChains = (List<CertificateChain>) health.getDetails()
80+
.get("certificateChains");
81+
assertThat(certificateChains).hasSize(1);
82+
assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class);
8283
}
8384

8485
@Test
@@ -88,21 +89,23 @@ void shouldBeOutOfServiceIfACertificateIsNotYetValid() {
8889
Health health = this.healthIndicator.health();
8990
assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE);
9091
assertThat(health.getDetails()).hasSize(1);
91-
List<CertificateInfo> certificates = (List<CertificateInfo>) health.getDetails().get("certificates");
92-
assertThat(certificates).hasSize(1);
93-
assertThat(certificates.get(0)).isInstanceOf(CertificateInfo.class);
92+
List<CertificateChain> certificateChains = (List<CertificateChain>) health.getDetails()
93+
.get("certificateChains");
94+
assertThat(certificateChains).hasSize(1);
95+
assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class);
9496
}
9597

9698
@Test
9799
@SuppressWarnings("unchecked")
98-
void shouldReportWillExpireSoonIfACertificateWillExpireSoon() {
100+
void shouldReportWarningIfACertificateWillExpireSoon() {
99101
given(this.validity.getStatus()).willReturn(Validity.Status.WILL_EXPIRE_SOON);
100102
Health health = this.healthIndicator.health();
101-
assertThat(health.getStatus()).isEqualTo(new Status(Validity.Status.WILL_EXPIRE_SOON.name()));
103+
assertThat(health.getStatus()).isEqualTo(new Status("WARNING"));
102104
assertThat(health.getDetails()).hasSize(1);
103-
List<CertificateInfo> certificates = (List<CertificateInfo>) health.getDetails().get("certificates");
104-
assertThat(certificates).hasSize(1);
105-
assertThat(certificates.get(0)).isInstanceOf(CertificateInfo.class);
105+
List<CertificateChain> certificateChains = (List<CertificateChain>) health.getDetails()
106+
.get("certificateChains");
107+
assertThat(certificateChains).hasSize(1);
108+
assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class);
106109
}
107110

108111
@Test
@@ -112,9 +115,10 @@ void shouldBeOutOfServiceIfACertificateHasUnMappedValidityStatus() {
112115
Health health = this.healthIndicator.health();
113116
assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE);
114117
assertThat(health.getDetails()).hasSize(1);
115-
List<CertificateInfo> certificates = (List<CertificateInfo>) health.getDetails().get("certificates");
116-
assertThat(certificates).hasSize(1);
117-
assertThat(certificates.get(0)).isInstanceOf(CertificateInfo.class);
118+
List<CertificateChain> certificateChains = (List<CertificateChain>) health.getDetails()
119+
.get("certificateChains");
120+
assertThat(certificateChains).hasSize(1);
121+
assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class);
118122
}
119123

120124
}

spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat-ssl/src/test/java/smoketest/tomcat/ssl/SampleTomcatSslApplicationTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ void testSslHealth() {
6464
ResponseEntity<String> entity = this.restTemplate.getForEntity("/actuator/health", String.class);
6565
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
6666
assertThat(entity.getBody()).contains("\"status\":\"OUT_OF_SERVICE\"")
67+
.contains("\"alias\":\"spring-boot-ssl-sample\"")
6768
.contains("\"status\":\"EXPIRED\"")
6869
.contains("\"subject\":\"CN=localhost,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown\"");
6970
}

0 commit comments

Comments
 (0)