Polish "Add SslInfoContributor and SslHealthIndicator"

See gh-41205
This commit is contained in:
Moritz Halbritter
2024-08-19 09:45:57 +02:00
parent 5e3796e814
commit fd1472784e
11 changed files with 109 additions and 103 deletions

View File

@@ -17,8 +17,6 @@
package org.springframework.boot.actuate.ssl;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health.Builder;
@@ -26,12 +24,10 @@ import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.info.SslInfo;
import org.springframework.boot.info.SslInfo.CertificateChain;
import org.springframework.boot.info.SslInfo.CertificateInfo.Validity;
/**
* {@link HealthIndicator} that checks the certificates the application uses and reports
* {@link Status#OUT_OF_SERVICE} when a certificate is invalid or "WILL_EXPIRE_SOON" if it
* will expire within the configurable threshold.
* {@link Status#OUT_OF_SERVICE} when a certificate is invalid.
*
* @author Jonatan Ivanov
* @since 3.4.0
@@ -46,43 +42,38 @@ public class SslHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Builder builder) throws Exception {
List<CertificateChain> notValidCertificateChains = this.sslInfo.getBundles()
List<CertificateChain> certificateChains = this.sslInfo.getBundles()
.stream()
.flatMap((bundle) -> bundle.getCertificateChains().stream())
.filter(this::containsNotValidCertificate)
.toList();
if (notValidCertificateChains.isEmpty()) {
List<CertificateChain> validCertificateChains = certificateChains.stream()
.filter(this::containsOnlyValidCertificates)
.toList();
List<CertificateChain> invalidCertificateChains = certificateChains.stream()
.filter(this::containsInvalidCertificate)
.toList();
builder.withDetail("validChains", validCertificateChains);
builder.withDetail("invalidChains", invalidCertificateChains);
if (invalidCertificateChains.isEmpty()) {
builder.status(Status.UP);
}
else {
Set<Validity.Status> statuses = collectCertificateStatuses(notValidCertificateChains);
if (statuses.contains(Validity.Status.EXPIRED) || statuses.contains(Validity.Status.NOT_YET_VALID)) {
builder.status(Status.OUT_OF_SERVICE);
}
else if (statuses.contains(Validity.Status.WILL_EXPIRE_SOON)) {
builder.status(Status.UP);
}
else {
builder.status(Status.OUT_OF_SERVICE);
}
builder.withDetail("certificateChains", notValidCertificateChains);
builder.status(Status.OUT_OF_SERVICE);
}
}
private boolean containsNotValidCertificate(CertificateChain certificateChain) {
private boolean containsOnlyValidCertificates(CertificateChain certificateChain) {
return certificateChain.getCertificates()
.stream()
.filter((certificate) -> certificate.getValidity() != null)
.anyMatch((certificate) -> certificate.getValidity().getStatus() != Validity.Status.VALID);
.allMatch((certificate) -> certificate.getValidity().getStatus().isValid());
}
private Set<Validity.Status> collectCertificateStatuses(List<CertificateChain> certificateChains) {
return certificateChains.stream()
.flatMap((certificateChain) -> certificateChain.getCertificates().stream())
private boolean containsInvalidCertificate(CertificateChain certificateChain) {
return certificateChain.getCertificates()
.stream()
.filter((certificate) -> certificate.getValidity() != null)
.map((certificate) -> certificate.getValidity().getStatus())
.collect(Collectors.toUnmodifiableSet());
.anyMatch((certificate) -> !certificate.getValidity().getStatus().isValid());
}
}

View File

@@ -66,59 +66,66 @@ class SslHealthIndicatorTests {
given(this.validity.getStatus()).willReturn(Validity.Status.VALID);
Health health = this.healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).isEmpty();
assertDetailsKeys(health);
List<CertificateChain> validChains = getValidChains(health);
assertThat(validChains).hasSize(1);
assertThat(validChains.get(0)).isInstanceOf(CertificateChain.class);
List<CertificateChain> invalidChains = getInvalidChains(health);
assertThat(invalidChains).isEmpty();
}
@Test
@SuppressWarnings("unchecked")
void shouldBeOutOfServiceIfACertificateIsExpired() {
given(this.validity.getStatus()).willReturn(Validity.Status.EXPIRED);
Health health = this.healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE);
assertThat(health.getDetails()).hasSize(1);
List<CertificateChain> certificateChains = (List<CertificateChain>) health.getDetails()
.get("certificateChains");
assertThat(certificateChains).hasSize(1);
assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class);
assertDetailsKeys(health);
List<CertificateChain> validChains = getValidChains(health);
assertThat(validChains).isEmpty();
List<CertificateChain> invalidChains = getInvalidChains(health);
assertThat(invalidChains).hasSize(1);
assertThat(invalidChains.get(0)).isInstanceOf(CertificateChain.class);
}
@Test
@SuppressWarnings("unchecked")
void shouldBeOutOfServiceIfACertificateIsNotYetValid() {
given(this.validity.getStatus()).willReturn(Validity.Status.NOT_YET_VALID);
Health health = this.healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE);
assertThat(health.getDetails()).hasSize(1);
List<CertificateChain> certificateChains = (List<CertificateChain>) health.getDetails()
.get("certificateChains");
assertThat(certificateChains).hasSize(1);
assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class);
assertDetailsKeys(health);
List<CertificateChain> validChains = getValidChains(health);
assertThat(validChains).isEmpty();
List<CertificateChain> invalidChains = getInvalidChains(health);
assertThat(invalidChains).hasSize(1);
assertThat(invalidChains.get(0)).isInstanceOf(CertificateChain.class);
}
@Test
@SuppressWarnings("unchecked")
void shouldReportWarningIfACertificateWillExpireSoon() {
given(this.validity.getStatus()).willReturn(Validity.Status.WILL_EXPIRE_SOON);
Health health = this.healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).hasSize(1);
List<CertificateChain> certificateChains = (List<CertificateChain>) health.getDetails()
.get("certificateChains");
assertThat(certificateChains).hasSize(1);
assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class);
assertDetailsKeys(health);
List<CertificateChain> validChains = getValidChains(health);
assertThat(validChains).hasSize(1);
assertThat(validChains.get(0)).isInstanceOf(CertificateChain.class);
List<CertificateChain> invalidChains = getInvalidChains(health);
assertThat(invalidChains).isEmpty();
}
private static void assertDetailsKeys(Health health) {
assertThat(health.getDetails()).containsOnlyKeys("validChains", "invalidChains");
}
@Test
@SuppressWarnings("unchecked")
void shouldBeOutOfServiceIfACertificateHasUnMappedValidityStatus() {
given(this.validity.getStatus()).willReturn(mock(Validity.Status.class));
Health health = this.healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE);
assertThat(health.getDetails()).hasSize(1);
List<CertificateChain> certificateChains = (List<CertificateChain>) health.getDetails()
.get("certificateChains");
assertThat(certificateChains).hasSize(1);
assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class);
private static List<CertificateChain> getInvalidChains(Health health) {
return (List<CertificateChain>) health.getDetails().get("invalidChains");
}
@SuppressWarnings("unchecked")
private static List<CertificateChain> getValidChains(Health health) {
return (List<CertificateChain>) health.getDetails().get("validChains");
}
}