Skip to content

Commit 890b4ed

Browse files
committed
Further polish SslInfoContributor and SslHealthIndicator
See gh-41205
1 parent d5c0d9e commit 890b4ed

File tree

8 files changed

+228
-211
lines changed

8 files changed

+228
-211
lines changed

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

Lines changed: 9 additions & 9 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.CertificateChain;
32+
import org.springframework.boot.info.SslInfo.CertificateChainInfo;
3333
import org.springframework.boot.ssl.SslBundles;
3434
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3535
import org.springframework.context.annotation.Bean;
@@ -65,9 +65,9 @@ void beansShouldBeConfigured() {
6565
Health health = context.getBean(SslHealthIndicator.class).health();
6666
assertThat(health.getStatus()).isSameAs(Status.OUT_OF_SERVICE);
6767
assertDetailsKeys(health);
68-
List<CertificateChain> invalidChains = getInvalidChains(health);
68+
List<CertificateChainInfo> invalidChains = getInvalidChains(health);
6969
assertThat(invalidChains).hasSize(1);
70-
assertThat(invalidChains).first().isInstanceOf(CertificateChain.class);
70+
assertThat(invalidChains).first().isInstanceOf(CertificateChainInfo.class);
7171

7272
});
7373
}
@@ -84,9 +84,9 @@ void beansShouldBeConfiguredWithWarningThreshold() {
8484
Health health = context.getBean(SslHealthIndicator.class).health();
8585
assertThat(health.getStatus()).isSameAs(Status.OUT_OF_SERVICE);
8686
assertDetailsKeys(health);
87-
List<CertificateChain> invalidChains = getInvalidChains(health);
87+
List<CertificateChainInfo> invalidChains = getInvalidChains(health);
8888
assertThat(invalidChains).hasSize(1);
89-
assertThat(invalidChains).first().isInstanceOf(CertificateChain.class);
89+
assertThat(invalidChains).first().isInstanceOf(CertificateChainInfo.class);
9090
});
9191
}
9292

@@ -101,9 +101,9 @@ void customBeansShouldBeConfigured() {
101101
Health health = context.getBean(SslHealthIndicator.class).health();
102102
assertThat(health.getStatus()).isSameAs(Status.OUT_OF_SERVICE);
103103
assertDetailsKeys(health);
104-
List<CertificateChain> invalidChains = getInvalidChains(health);
104+
List<CertificateChainInfo> invalidChains = getInvalidChains(health);
105105
assertThat(invalidChains).hasSize(1);
106-
assertThat(invalidChains).first().isInstanceOf(CertificateChain.class);
106+
assertThat(invalidChains).first().isInstanceOf(CertificateChainInfo.class);
107107
});
108108
}
109109

@@ -112,8 +112,8 @@ private static void assertDetailsKeys(Health health) {
112112
}
113113

114114
@SuppressWarnings("unchecked")
115-
private static List<CertificateChain> getInvalidChains(Health health) {
116-
return (List<CertificateChain>) health.getDetails().get("invalidChains");
115+
private static List<CertificateChainInfo> getInvalidChains(Health health) {
116+
return (List<CertificateChainInfo>) health.getDetails().get("invalidChains");
117117
}
118118

119119
@Configuration(proxyBeanMethods = false)

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

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

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

19+
import java.util.ArrayList;
1920
import java.util.List;
21+
import java.util.stream.Stream;
2022

2123
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
2224
import org.springframework.boot.actuate.health.Health.Builder;
2325
import org.springframework.boot.actuate.health.HealthIndicator;
2426
import org.springframework.boot.actuate.health.Status;
2527
import org.springframework.boot.info.SslInfo;
26-
import org.springframework.boot.info.SslInfo.CertificateChain;
28+
import org.springframework.boot.info.SslInfo.BundleInfo;
29+
import org.springframework.boot.info.SslInfo.CertificateChainInfo;
30+
import org.springframework.boot.info.SslInfo.CertificateInfo;
2731

2832
/**
2933
* {@link HealthIndicator} that checks the certificates the application uses and reports
@@ -42,38 +46,41 @@ public SslHealthIndicator(SslInfo sslInfo) {
4246

4347
@Override
4448
protected void doHealthCheck(Builder builder) throws Exception {
45-
List<CertificateChain> certificateChains = this.sslInfo.getBundles()
46-
.stream()
47-
.flatMap((bundle) -> bundle.getCertificateChains().stream())
48-
.toList();
49-
List<CertificateChain> validCertificateChains = certificateChains.stream()
50-
.filter(this::containsOnlyValidCertificates)
51-
.toList();
52-
List<CertificateChain> invalidCertificateChains = certificateChains.stream()
53-
.filter(this::containsInvalidCertificate)
54-
.toList();
49+
List<CertificateChainInfo> validCertificateChains = new ArrayList<>();
50+
List<CertificateChainInfo> invalidCertificateChains = new ArrayList<>();
51+
for (BundleInfo bundle : this.sslInfo.getBundles()) {
52+
for (CertificateChainInfo certificateChain : bundle.getCertificateChains()) {
53+
if (containsOnlyValidCertificates(certificateChain)) {
54+
validCertificateChains.add(certificateChain);
55+
}
56+
else if (containsInvalidCertificate(certificateChain)) {
57+
invalidCertificateChains.add(certificateChain);
58+
}
59+
}
60+
}
61+
builder.status((invalidCertificateChains.isEmpty()) ? Status.UP : Status.OUT_OF_SERVICE);
5562
builder.withDetail("validChains", validCertificateChains);
5663
builder.withDetail("invalidChains", invalidCertificateChains);
57-
if (invalidCertificateChains.isEmpty()) {
58-
builder.status(Status.UP);
59-
}
60-
else {
61-
builder.status(Status.OUT_OF_SERVICE);
62-
}
6364
}
6465

65-
private boolean containsOnlyValidCertificates(CertificateChain certificateChain) {
66-
return certificateChain.getCertificates()
67-
.stream()
68-
.filter((certificate) -> certificate.getValidity() != null)
69-
.allMatch((certificate) -> certificate.getValidity().getStatus().isValid());
66+
private boolean containsOnlyValidCertificates(CertificateChainInfo certificateChain) {
67+
return validatableCertificates(certificateChain).allMatch(this::isValidCertificate);
68+
}
69+
70+
private boolean containsInvalidCertificate(CertificateChainInfo certificateChain) {
71+
return validatableCertificates(certificateChain).anyMatch(this::isNotValidCertificate);
72+
}
73+
74+
private Stream<CertificateInfo> validatableCertificates(CertificateChainInfo certificateChain) {
75+
return certificateChain.getCertificates().stream().filter((certificate) -> certificate.getValidity() != null);
76+
}
77+
78+
private boolean isValidCertificate(CertificateInfo certificate) {
79+
return certificate.getValidity().getStatus().isValid();
7080
}
7181

72-
private boolean containsInvalidCertificate(CertificateChain certificateChain) {
73-
return certificateChain.getCertificates()
74-
.stream()
75-
.filter((certificate) -> certificate.getValidity() != null)
76-
.anyMatch((certificate) -> !certificate.getValidity().getStatus().isValid());
82+
private boolean isNotValidCertificate(CertificateInfo certificate) {
83+
return !isValidCertificate(certificate);
7784
}
7885

7986
}

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

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
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.Bundle;
29-
import org.springframework.boot.info.SslInfo.CertificateChain;
28+
import org.springframework.boot.info.SslInfo.BundleInfo;
29+
import org.springframework.boot.info.SslInfo.CertificateChainInfo;
3030
import org.springframework.boot.info.SslInfo.CertificateInfo;
31-
import org.springframework.boot.info.SslInfo.CertificateInfo.Validity;
31+
import org.springframework.boot.info.SslInfo.CertificateValidityInfo;
3232

3333
import static org.assertj.core.api.Assertions.assertThat;
3434
import static org.mockito.BDDMockito.given;
@@ -43,18 +43,16 @@ class SslHealthIndicatorTests {
4343

4444
private HealthIndicator healthIndicator;
4545

46-
private Validity validity;
46+
private CertificateValidityInfo validity;
4747

4848
@BeforeEach
4949
void setUp() {
5050
SslInfo sslInfo = mock(SslInfo.class);
51-
Bundle bundle = mock(Bundle.class);
52-
CertificateChain certificateChain = mock(CertificateChain.class);
51+
BundleInfo bundle = mock(BundleInfo.class);
52+
CertificateChainInfo certificateChain = mock(CertificateChainInfo.class);
5353
CertificateInfo certificateInfo = mock(CertificateInfo.class);
54-
5554
this.healthIndicator = new SslHealthIndicator(sslInfo);
56-
this.validity = mock(Validity.class);
57-
55+
this.validity = mock(CertificateValidityInfo.class);
5856
given(sslInfo.getBundles()).willReturn(List.of(bundle));
5957
given(bundle.getCertificateChains()).willReturn(List.of(certificateChain));
6058
given(certificateChain.getCertificates()).willReturn(List.of(certificateInfo));
@@ -63,54 +61,54 @@ void setUp() {
6361

6462
@Test
6563
void shouldBeUpIfNoSslIssuesDetected() {
66-
given(this.validity.getStatus()).willReturn(Validity.Status.VALID);
64+
given(this.validity.getStatus()).willReturn(CertificateValidityInfo.Status.VALID);
6765
Health health = this.healthIndicator.health();
6866
assertThat(health.getStatus()).isEqualTo(Status.UP);
6967
assertDetailsKeys(health);
70-
List<CertificateChain> validChains = getValidChains(health);
68+
List<CertificateChainInfo> validChains = getValidChains(health);
7169
assertThat(validChains).hasSize(1);
72-
assertThat(validChains.get(0)).isInstanceOf(CertificateChain.class);
73-
List<CertificateChain> invalidChains = getInvalidChains(health);
70+
assertThat(validChains.get(0)).isInstanceOf(CertificateChainInfo.class);
71+
List<CertificateChainInfo> invalidChains = getInvalidChains(health);
7472
assertThat(invalidChains).isEmpty();
7573
}
7674

7775
@Test
7876
void shouldBeOutOfServiceIfACertificateIsExpired() {
79-
given(this.validity.getStatus()).willReturn(Validity.Status.EXPIRED);
77+
given(this.validity.getStatus()).willReturn(CertificateValidityInfo.Status.EXPIRED);
8078
Health health = this.healthIndicator.health();
8179
assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE);
8280
assertDetailsKeys(health);
83-
List<CertificateChain> validChains = getValidChains(health);
81+
List<CertificateChainInfo> validChains = getValidChains(health);
8482
assertThat(validChains).isEmpty();
85-
List<CertificateChain> invalidChains = getInvalidChains(health);
83+
List<CertificateChainInfo> invalidChains = getInvalidChains(health);
8684
assertThat(invalidChains).hasSize(1);
87-
assertThat(invalidChains.get(0)).isInstanceOf(CertificateChain.class);
85+
assertThat(invalidChains.get(0)).isInstanceOf(CertificateChainInfo.class);
8886
}
8987

9088
@Test
9189
void shouldBeOutOfServiceIfACertificateIsNotYetValid() {
92-
given(this.validity.getStatus()).willReturn(Validity.Status.NOT_YET_VALID);
90+
given(this.validity.getStatus()).willReturn(CertificateValidityInfo.Status.NOT_YET_VALID);
9391
Health health = this.healthIndicator.health();
9492
assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE);
9593
assertDetailsKeys(health);
96-
List<CertificateChain> validChains = getValidChains(health);
94+
List<CertificateChainInfo> validChains = getValidChains(health);
9795
assertThat(validChains).isEmpty();
98-
List<CertificateChain> invalidChains = getInvalidChains(health);
96+
List<CertificateChainInfo> invalidChains = getInvalidChains(health);
9997
assertThat(invalidChains).hasSize(1);
100-
assertThat(invalidChains.get(0)).isInstanceOf(CertificateChain.class);
98+
assertThat(invalidChains.get(0)).isInstanceOf(CertificateChainInfo.class);
10199

102100
}
103101

104102
@Test
105103
void shouldReportWarningIfACertificateWillExpireSoon() {
106-
given(this.validity.getStatus()).willReturn(Validity.Status.WILL_EXPIRE_SOON);
104+
given(this.validity.getStatus()).willReturn(CertificateValidityInfo.Status.WILL_EXPIRE_SOON);
107105
Health health = this.healthIndicator.health();
108106
assertThat(health.getStatus()).isEqualTo(Status.UP);
109107
assertDetailsKeys(health);
110-
List<CertificateChain> validChains = getValidChains(health);
108+
List<CertificateChainInfo> validChains = getValidChains(health);
111109
assertThat(validChains).hasSize(1);
112-
assertThat(validChains.get(0)).isInstanceOf(CertificateChain.class);
113-
List<CertificateChain> invalidChains = getInvalidChains(health);
110+
assertThat(validChains.get(0)).isInstanceOf(CertificateChainInfo.class);
111+
List<CertificateChainInfo> invalidChains = getInvalidChains(health);
114112
assertThat(invalidChains).isEmpty();
115113
}
116114

@@ -119,13 +117,13 @@ private static void assertDetailsKeys(Health health) {
119117
}
120118

121119
@SuppressWarnings("unchecked")
122-
private static List<CertificateChain> getInvalidChains(Health health) {
123-
return (List<CertificateChain>) health.getDetails().get("invalidChains");
120+
private static List<CertificateChainInfo> getInvalidChains(Health health) {
121+
return (List<CertificateChainInfo>) health.getDetails().get("invalidChains");
124122
}
125123

126124
@SuppressWarnings("unchecked")
127-
private static List<CertificateChain> getValidChains(Health health) {
128-
return (List<CertificateChain>) health.getDetails().get("validChains");
125+
private static List<CertificateChainInfo> getValidChains(Health health) {
126+
return (List<CertificateChainInfo>) health.getDetails().get("validChains");
129127
}
130128

131129
}

0 commit comments

Comments
 (0)