diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfiguration.java index a1c467a5bb6..08b002a3309 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/info/InfoContributorAutoConfiguration.java @@ -107,14 +107,14 @@ public class InfoContributorAutoConfiguration { @Bean @ConditionalOnEnabledInfoContributor(value = "ssl", fallback = InfoContributorFallback.DISABLE) @Order(DEFAULT_ORDER) - public SslInfoContributor sslInfoContributor(SslInfo sslInfo) { + SslInfoContributor sslInfoContributor(SslInfo sslInfo) { return new SslInfoContributor(sslInfo); } @Bean @ConditionalOnMissingBean @ConditionalOnEnabledInfoContributor(value = "ssl", fallback = InfoContributorFallback.DISABLE) - public SslInfo sslInfo(SslBundles sslBundles, SslHealthIndicatorProperties sslHealthIndicatorProperties) { + SslInfo sslInfo(SslBundles sslBundles, SslHealthIndicatorProperties sslHealthIndicatorProperties) { return new SslInfo(sslBundles, sslHealthIndicatorProperties.getCertificateValidityWarningThreshold()); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfiguration.java index e431611843a..b8f9f95f432 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfiguration.java @@ -40,13 +40,13 @@ public class SslHealthContributorAutoConfiguration { @Bean @ConditionalOnMissingBean(name = "sslHealthIndicator") - public SslHealthIndicator sslHealthIndicator(SslInfo sslInfo) { + SslHealthIndicator sslHealthIndicator(SslInfo sslInfo) { return new SslHealthIndicator(sslInfo); } @Bean @ConditionalOnMissingBean - public SslInfo sslInfo(SslBundles sslBundles, SslHealthIndicatorProperties sslHealthIndicatorProperties) { + SslInfo sslInfo(SslBundles sslBundles, SslHealthIndicatorProperties sslHealthIndicatorProperties) { return new SslInfo(sslBundles, sslHealthIndicatorProperties.getCertificateValidityWarningThreshold()); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 0bda4c7906c..d507ce390f5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -227,12 +227,6 @@ "description": "Whether to enable Redis health check.", "defaultValue": true }, - { - "name": "management.health.ssl.certificate-validity-warning-threshold", - "type": "java.time.Duration", - "description": "If an SSL Certificate will be invalid within the time span defined by this threshold, it should trigger a warning.", - "defaultValue": "14d" - }, { "name": "management.health.ssl.enabled", "type": "java.lang.Boolean", diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfigurationTests.java index 9458b4e1cc4..4895cb3e1ba 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/ssl/SslHealthContributorAutoConfigurationTests.java @@ -39,6 +39,8 @@ import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link SslHealthContributorAutoConfiguration}. + * + * @author Jonatan Ivanov */ class SslHealthContributorAutoConfigurationTests { @@ -56,24 +58,21 @@ class SslHealthContributorAutoConfigurationTests { } @Test - @SuppressWarnings("unchecked") void beansShouldBeConfigured() { this.contextRunner.run((context) -> { assertThat(context).hasSingleBean(SslHealthIndicator.class); assertThat(context).hasSingleBean(SslInfo.class); Health health = context.getBean(SslHealthIndicator.class).health(); assertThat(health.getStatus()).isSameAs(Status.OUT_OF_SERVICE); - assertThat(health.getDetails()).hasSize(1); - List certificateChains = (List) health.getDetails() - .get("certificateChains"); - assertThat(certificateChains).hasSize(1); - assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class); + assertDetailsKeys(health); + List invalidChains = getInvalidChains(health); + assertThat(invalidChains).hasSize(1); + assertThat(invalidChains).first().isInstanceOf(CertificateChain.class); }); } @Test - @SuppressWarnings("unchecked") void beansShouldBeConfiguredWithWarningThreshold() { this.contextRunner.withPropertyValues("management.health.ssl.certificate-validity-warning-threshold=1d") .run((context) -> { @@ -84,16 +83,14 @@ class SslHealthContributorAutoConfigurationTests { .isEqualTo(Duration.ofDays(1)); Health health = context.getBean(SslHealthIndicator.class).health(); assertThat(health.getStatus()).isSameAs(Status.OUT_OF_SERVICE); - assertThat(health.getDetails()).hasSize(1); - List certificateChains = (List) health.getDetails() - .get("certificateChains"); - assertThat(certificateChains).hasSize(1); - assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class); + assertDetailsKeys(health); + List invalidChains = getInvalidChains(health); + assertThat(invalidChains).hasSize(1); + assertThat(invalidChains).first().isInstanceOf(CertificateChain.class); }); } @Test - @SuppressWarnings("unchecked") void customBeansShouldBeConfigured() { this.contextRunner.withUserConfiguration(CustomSslInfoConfiguration.class).run((context) -> { assertThat(context).hasSingleBean(SslHealthIndicator.class); @@ -103,14 +100,22 @@ class SslHealthContributorAutoConfigurationTests { assertThat(context.getBean(SslInfo.class)).isSameAs(context.getBean("customSslInfo")); Health health = context.getBean(SslHealthIndicator.class).health(); assertThat(health.getStatus()).isSameAs(Status.OUT_OF_SERVICE); - assertThat(health.getDetails()).hasSize(1); - List certificateChains = (List) health.getDetails() - .get("certificateChains"); - assertThat(certificateChains).hasSize(1); - assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class); + assertDetailsKeys(health); + List invalidChains = getInvalidChains(health); + assertThat(invalidChains).hasSize(1); + assertThat(invalidChains).first().isInstanceOf(CertificateChain.class); }); } + private static void assertDetailsKeys(Health health) { + assertThat(health.getDetails()).containsOnlyKeys("validChains", "invalidChains"); + } + + @SuppressWarnings("unchecked") + private static List getInvalidChains(Health health) { + return (List) health.getDetails().get("invalidChains"); + } + @Configuration(proxyBeanMethods = false) static class CustomSslInfoConfiguration { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/ssl/SslHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/ssl/SslHealthIndicator.java index 2a01cef1aac..17eaf153d96 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/ssl/SslHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/ssl/SslHealthIndicator.java @@ -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 notValidCertificateChains = this.sslInfo.getBundles() + List certificateChains = this.sslInfo.getBundles() .stream() .flatMap((bundle) -> bundle.getCertificateChains().stream()) - .filter(this::containsNotValidCertificate) .toList(); - - if (notValidCertificateChains.isEmpty()) { + List validCertificateChains = certificateChains.stream() + .filter(this::containsOnlyValidCertificates) + .toList(); + List invalidCertificateChains = certificateChains.stream() + .filter(this::containsInvalidCertificate) + .toList(); + builder.withDetail("validChains", validCertificateChains); + builder.withDetail("invalidChains", invalidCertificateChains); + if (invalidCertificateChains.isEmpty()) { builder.status(Status.UP); } else { - Set 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 collectCertificateStatuses(List 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()); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/ssl/SslHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/ssl/SslHealthIndicatorTests.java index d76fe3c4117..2318eaa299d 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/ssl/SslHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/ssl/SslHealthIndicatorTests.java @@ -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 validChains = getValidChains(health); + assertThat(validChains).hasSize(1); + assertThat(validChains.get(0)).isInstanceOf(CertificateChain.class); + List 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 certificateChains = (List) health.getDetails() - .get("certificateChains"); - assertThat(certificateChains).hasSize(1); - assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class); + assertDetailsKeys(health); + List validChains = getValidChains(health); + assertThat(validChains).isEmpty(); + List 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 certificateChains = (List) health.getDetails() - .get("certificateChains"); - assertThat(certificateChains).hasSize(1); - assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class); + assertDetailsKeys(health); + List validChains = getValidChains(health); + assertThat(validChains).isEmpty(); + List 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 certificateChains = (List) health.getDetails() - .get("certificateChains"); - assertThat(certificateChains).hasSize(1); - assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class); + assertDetailsKeys(health); + List validChains = getValidChains(health); + assertThat(validChains).hasSize(1); + assertThat(validChains.get(0)).isInstanceOf(CertificateChain.class); + List 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 certificateChains = (List) health.getDetails() - .get("certificateChains"); - assertThat(certificateChains).hasSize(1); - assertThat(certificateChains.get(0)).isInstanceOf(CertificateChain.class); + private static List getInvalidChains(Health health) { + return (List) health.getDetails().get("invalidChains"); + } + + @SuppressWarnings("unchecked") + private static List getValidChains(Health health) { + return (List) health.getDetails().get("validChains"); } } diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/endpoints.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/endpoints.adoc index dc9116d953b..c9bd4c7d694 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/endpoints.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/endpoints.adoc @@ -651,12 +651,14 @@ with the `key` listed in the following table: | `ssl` | javadoc:org.springframework.boot.actuate.ssl.SslHealthIndicator[] -| Checks that SSL Cerificates are ok. +| Checks that SSL Certificates are ok. |=== TIP: You can disable them all by setting the configprop:management.health.defaults.enabled[] property. -TIP: The `ssl` `HealthIndicator` has a "warning threshold" property. If an SSL Certificate will be invalid within the time span defined by this threshold, the `HealthIndicator` will warn you but it will still return HTTP 200 to not disrupt the application. You can use this threshold to give yourself enough lead time to rotate the soon to be expired certificate. See the `management.health.ssl.certificate-validity-warning-threshold` property. +TIP: The `ssl` `HealthIndicator` has a "warning threshold" property named configprop:management.health.ssl.certificate-validity-warning-threshold[]. +If an SSL Certificate will be invalid within the time span defined by this threshold, the `HealthIndicator` will warn you but it will still return HTTP 200 to not disrupt the application. +You can use this threshold to give yourself enough lead time to rotate the soon to be expired certificate. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/SslInfo.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/SslInfo.java index c233ce70472..e0c5e533a51 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/SslInfo.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/info/SslInfo.java @@ -216,23 +216,33 @@ public class SslInfo { /** * The certificate is valid. */ - VALID, + VALID(true), /** * The certificate's validity date range is in the future. */ - NOT_YET_VALID, + NOT_YET_VALID(false), /** * The certificate's validity date range is in the past. */ - EXPIRED, + EXPIRED(false), /** - * The certificate is still valid but the end of its validity date range + * The certificate is still valid, but the end of its validity date range * is within the defined threshold. */ - WILL_EXPIRE_SOON + WILL_EXPIRE_SOON(true); + + private final boolean valid; + + Status(boolean valid) { + this.valid = valid; + } + + public boolean isValid() { + return this.valid; + } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/SslInfoTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/SslInfoTests.java index 48646091cda..8bf7fe85793 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/SslInfoTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/info/SslInfoTests.java @@ -154,7 +154,7 @@ class SslInfoTests { assertThat(cert.getIssuer()).isEqualTo(cert.getSubject()); assertThat(cert.getSerialNumber()).isNotEmpty(); assertThat(cert.getVersion()).isEqualTo("V3"); - assertThat(cert.getSignatureAlgorithmName()).isEqualTo("SHA256withRSA"); + assertThat(cert.getSignatureAlgorithmName()).isNotEmpty(); assertThat(cert.getValidityStarts()).isInThePast(); assertThat(cert.getValidityEnds()).isInTheFuture(); assertThat(cert.getValidity()).isNotNull(); @@ -182,7 +182,7 @@ class SslInfoTests { assertThat(cert.getIssuer()).isEqualTo(cert.getSubject()); assertThat(cert.getSerialNumber()).isNotEmpty(); assertThat(cert.getVersion()).isEqualTo("V3"); - assertThat(cert.getSignatureAlgorithmName()).isEqualTo("SHA256withRSA"); + assertThat(cert.getSignatureAlgorithmName()).isNotEmpty(); assertThat(cert.getValidity()).isNotNull(); }); @@ -226,7 +226,6 @@ class SslInfoTests { SslStoreBundle sslStoreBundle = new JksSslStoreBundle(keyStoreDetails, null); sslBundleRegistry.registerBundle("test-%d".formatted(i), SslBundle.of(sslStoreBundle)); } - return new SslInfo(sslBundleRegistry, Duration.ofDays(7)); } @@ -240,7 +239,6 @@ class SslInfoTests { .collect(Collectors.joining("\n")); throw new RuntimeException("Unexpected exit code from keytool: %d\n%s".formatted(exitCode, out)); } - return keyStore; } @@ -261,7 +259,6 @@ class SslInfoTests { ); // @formatter:on processBuilder.redirectErrorStream(true); - return processBuilder; } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat-ssl/src/main/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat-ssl/src/main/resources/application.properties index 8dc1b8a867a..c9f855cfcee 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat-ssl/src/main/resources/application.properties +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat-ssl/src/main/resources/application.properties @@ -3,7 +3,7 @@ server.port=8443 management.endpoints.web.exposure.include=* management.endpoint.health.show-details=always management.health.ssl.certificate-validity-warning-threshold=7d -# management.health.ssl.enabled=true +management.health.ssl.enabled=true management.info.ssl.enabled=true server.ssl.bundle=ssldemo diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat-ssl/src/test/java/smoketest/tomcat/ssl/SampleTomcatSslApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat-ssl/src/test/java/smoketest/tomcat/ssl/SampleTomcatSslApplicationTests.java index b913f0d8f16..c706938811f 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat-ssl/src/test/java/smoketest/tomcat/ssl/SampleTomcatSslApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-tomcat-ssl/src/test/java/smoketest/tomcat/ssl/SampleTomcatSslApplicationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,15 +76,15 @@ class SampleTomcatSslApplicationTests { JsonContent body = new JsonContent(entity.getBody()); assertThat(body).extractingPath("status").isEqualTo("OUT_OF_SERVICE"); assertThat(body).extractingPath("components.ssl.status").isEqualTo("OUT_OF_SERVICE"); - assertThat(body).extractingPath("components.ssl.details.certificateChains[0].alias") + assertThat(body).extractingPath("components.ssl.details.invalidChains[0].alias") .isEqualTo("spring-boot-ssl-sample"); - assertThat(body).extractingPath("components.ssl.details.certificateChains[0].certificates[0].issuer") + assertThat(body).extractingPath("components.ssl.details.invalidChains[0].certificates[0].issuer") .isEqualTo("CN=localhost,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown"); - assertThat(body).extractingPath("components.ssl.details.certificateChains[0].certificates[0].subject") + assertThat(body).extractingPath("components.ssl.details.invalidChains[0].certificates[0].subject") .isEqualTo("CN=localhost,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown"); - assertThat(body).extractingPath("components.ssl.details.certificateChains[0].certificates[0].validity.status") + assertThat(body).extractingPath("components.ssl.details.invalidChains[0].certificates[0].validity.status") .isEqualTo("EXPIRED"); - assertThat(body).extractingPath("components.ssl.details.certificateChains[0].certificates[0].validity.message") + assertThat(body).extractingPath("components.ssl.details.invalidChains[0].certificates[0].validity.message") .asString() .startsWith("Not valid after "); }