Skip to content

Commit

Permalink
Add SslInfoContributor and SslHealthIndicator
Browse files Browse the repository at this point in the history
  • Loading branch information
jonatan-ivanov authored and mhalbritter committed Aug 19, 2024
1 parent 71ca952 commit 5e3796e
Show file tree
Hide file tree
Showing 24 changed files with 1,330 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@

package org.springframework.boot.actuate.autoconfigure.info;

import org.springframework.boot.actuate.autoconfigure.ssl.SslHealthIndicatorProperties;
import org.springframework.boot.actuate.info.BuildInfoContributor;
import org.springframework.boot.actuate.info.EnvironmentInfoContributor;
import org.springframework.boot.actuate.info.GitInfoContributor;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.boot.actuate.info.JavaInfoContributor;
import org.springframework.boot.actuate.info.OsInfoContributor;
import org.springframework.boot.actuate.info.ProcessInfoContributor;
import org.springframework.boot.actuate.info.SslInfoContributor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
Expand All @@ -31,6 +33,8 @@
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.boot.info.SslInfo;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
Expand All @@ -46,7 +50,7 @@
* @since 2.0.0
*/
@AutoConfiguration(after = ProjectInfoAutoConfiguration.class)
@EnableConfigurationProperties(InfoContributorProperties.class)
@EnableConfigurationProperties({ InfoContributorProperties.class, SslHealthIndicatorProperties.class })
public class InfoContributorAutoConfiguration {

/**
Expand Down Expand Up @@ -100,4 +104,18 @@ public ProcessInfoContributor processInfoContributor() {
return new ProcessInfoContributor();
}

@Bean
@ConditionalOnEnabledInfoContributor(value = "ssl", fallback = InfoContributorFallback.DISABLE)
@Order(DEFAULT_ORDER)
public SslInfoContributor sslInfoContributor(SslInfo sslInfo) {
return new SslInfoContributor(sslInfo);
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledInfoContributor(value = "ssl", fallback = InfoContributorFallback.DISABLE)
public SslInfo sslInfo(SslBundles sslBundles, SslHealthIndicatorProperties sslHealthIndicatorProperties) {
return new SslInfo(sslBundles, sslHealthIndicatorProperties.getCertificateValidityWarningThreshold());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.ssl;

import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.ssl.SslHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.info.SslInfo;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;

/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link SslHealthIndicator}.
*
* @author Jonatan Ivanov
* @since 3.4.0
*/
@AutoConfiguration(before = HealthContributorAutoConfiguration.class)
@ConditionalOnEnabledHealthIndicator("ssl")
@EnableConfigurationProperties(SslHealthIndicatorProperties.class)
public class SslHealthContributorAutoConfiguration {

@Bean
@ConditionalOnMissingBean(name = "sslHealthIndicator")
public SslHealthIndicator sslHealthIndicator(SslInfo sslInfo) {
return new SslHealthIndicator(sslInfo);
}

@Bean
@ConditionalOnMissingBean
public SslInfo sslInfo(SslBundles sslBundles, SslHealthIndicatorProperties sslHealthIndicatorProperties) {
return new SslInfo(sslBundles, sslHealthIndicatorProperties.getCertificateValidityWarningThreshold());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.ssl;

import java.time.Duration;

import org.springframework.boot.actuate.ssl.SslHealthIndicator;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* External configuration properties for {@link SslHealthIndicator}.
*
* @author Jonatan Ivanov
* @since 3.4.0
*/
@ConfigurationProperties(prefix = "management.health.ssl")
public class SslHealthIndicatorProperties {

/**
* If an SSL Certificate will be invalid within the time span defined by this
* threshold, it should trigger a warning.
*/
private Duration certificateValidityWarningThreshold = Duration.ofDays(14);

public Duration getCertificateValidityWarningThreshold() {
return this.certificateValidityWarningThreshold;
}

public void setCertificateValidityWarningThreshold(Duration certificateValidityWarningThreshold) {
this.certificateValidityWarningThreshold = certificateValidityWarningThreshold;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Auto-configuration for actuator ssl concerns.
*/
package org.springframework.boot.actuate.autoconfigure.ssl;
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,18 @@
"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",
"description": "Whether to enable SSL Certificate health check.",
"defaultValue": true
},
{
"name": "management.httpexchanges.recording.enabled",
"type": "java.lang.Boolean",
Expand Down Expand Up @@ -283,6 +295,12 @@
"description": "Whether to enable process info.",
"defaultValue": false
},
{
"name": "management.info.ssl.enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable SSL Certificate info.",
"defaultValue": false
},
{
"name": "management.metrics.binders.files.enabled",
"type": "java.lang.Boolean",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagem
org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration
org.springframework.boot.actuate.autoconfigure.session.SessionsEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.startup.StartupEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.ssl.SslHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration
org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@

package org.springframework.boot.actuate.autoconfigure.info;

import java.time.Duration;
import java.util.Map;
import java.util.Properties;

import org.junit.jupiter.api.Test;

import org.springframework.boot.actuate.autoconfigure.ssl.SslHealthIndicatorProperties;
import org.springframework.boot.actuate.info.BuildInfoContributor;
import org.springframework.boot.actuate.info.EnvironmentInfoContributor;
import org.springframework.boot.actuate.info.GitInfoContributor;
Expand All @@ -29,12 +31,16 @@
import org.springframework.boot.actuate.info.JavaInfoContributor;
import org.springframework.boot.actuate.info.OsInfoContributor;
import org.springframework.boot.actuate.info.ProcessInfoContributor;
import org.springframework.boot.actuate.info.SslInfoContributor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.boot.info.JavaInfo;
import org.springframework.boot.info.OsInfo;
import org.springframework.boot.info.ProcessInfo;
import org.springframework.boot.info.SslInfo;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -60,7 +66,8 @@ void envContributor() {

@Test
void defaultInfoContributorsEnabled() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(InfoContributor.class));
this.contextRunner.run(
(context) -> assertThat(context).doesNotHaveBean(InfoContributor.class).doesNotHaveBean(SslInfo.class));
}

@Test
Expand Down Expand Up @@ -176,6 +183,54 @@ void processInfoContributor() {
});
}

@Test
void sslInfoContributor() {
this.contextRunner.withConfiguration(AutoConfigurations.of(SslAutoConfiguration.class))
.withPropertyValues("management.info.ssl.enabled=true", "server.ssl.bundle=ssltest",
"spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks")
.run((context) -> {
assertThat(context).hasSingleBean(SslInfoContributor.class);
assertThat(context).hasSingleBean(SslInfo.class);
Map<String, Object> content = invokeContributor(context.getBean(SslInfoContributor.class));
assertThat(content).containsKey("ssl");
assertThat(content.get("ssl")).isInstanceOf(SslInfo.class);
});
}

@Test
void sslInfoContributorWithWarningThreshold() {
this.contextRunner.withConfiguration(AutoConfigurations.of(SslAutoConfiguration.class))
.withPropertyValues("management.info.ssl.enabled=true", "server.ssl.bundle=ssltest",
"spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks",
"management.health.ssl.certificate-validity-warning-threshold=1d")
.run((context) -> {
assertThat(context).hasSingleBean(SslInfoContributor.class);
assertThat(context).hasSingleBean(SslInfo.class);
assertThat(context).hasSingleBean(SslHealthIndicatorProperties.class);
assertThat(context.getBean(SslHealthIndicatorProperties.class).getCertificateValidityWarningThreshold())
.isEqualTo(Duration.ofDays(1));
Map<String, Object> content = invokeContributor(context.getBean(SslInfoContributor.class));
assertThat(content).containsKey("ssl");
assertThat(content.get("ssl")).isInstanceOf(SslInfo.class);
});
}

@Test
void customSslInfo() {
this.contextRunner.withUserConfiguration(CustomSslInfoConfiguration.class)
.withConfiguration(AutoConfigurations.of(SslAutoConfiguration.class))
.withPropertyValues("management.info.ssl.enabled=true", "server.ssl.bundle=ssltest",
"spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks")
.run((context) -> {
assertThat(context).hasSingleBean(SslInfoContributor.class);
assertThat(context).hasSingleBean(SslInfo.class);
assertThat(context.getBean(SslInfo.class)).isSameAs(context.getBean("customSslInfo"));
Map<String, Object> content = invokeContributor(context.getBean(SslInfoContributor.class));
assertThat(content).containsKey("ssl");
assertThat(content.get("ssl")).isInstanceOf(SslInfo.class);
});
}

private Map<String, Object> invokeContributor(InfoContributor contributor) {
Info.Builder builder = new Info.Builder();
contributor.contribute(builder);
Expand Down Expand Up @@ -241,4 +296,14 @@ BuildInfoContributor customBuildInfoContributor() {

}

@Configuration(proxyBeanMethods = false)
static class CustomSslInfoConfiguration {

@Bean
SslInfo customSslInfo(SslBundles sslBundles) {
return new SslInfo(sslBundles, Duration.ofDays(7));
}

}

}
Loading

0 comments on commit 5e3796e

Please sign in to comment.