Remove SSL status tag from metrics

Update `SslMeterBinder` to the status tag is no longer included in the
reported metrics. Instead the expiry date should be used directly by
the monitoring system.

Closes gh-45602
This commit is contained in:
Phillip Webb
2025-05-20 17:17:53 -07:00
parent 8070701b5c
commit ab162fa0bb
3 changed files with 4 additions and 78 deletions

View File

@@ -23,14 +23,12 @@ import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MultiGauge;
import io.micrometer.core.instrument.MultiGauge.Row;
@@ -42,21 +40,16 @@ import org.springframework.boot.info.SslInfo;
import org.springframework.boot.info.SslInfo.BundleInfo;
import org.springframework.boot.info.SslInfo.CertificateChainInfo;
import org.springframework.boot.info.SslInfo.CertificateInfo;
import org.springframework.boot.info.SslInfo.CertificateValidityInfo;
import org.springframework.boot.info.SslInfo.CertificateValidityInfo.Status;
import org.springframework.boot.ssl.SslBundles;
/**
* {@link MeterBinder} which registers the SSL chain validity (soonest to expire
* certificate in the chain) as a {@link TimeGauge}. Also contributes two {@link Gauge
* gauges} to count the valid and invalid chains.
* {@link MeterBinder} which registers the SSL chain expiry (soonest to expire certificate
* in the chain) as a {@link TimeGauge}.
*
* @author Moritz Halbritter
*/
class SslMeterBinder implements MeterBinder {
private static final String CHAINS_METRIC_NAME = "ssl.chains";
private static final String CHAIN_EXPIRY_METRIC_NAME = "ssl.chain.expiry";
private final Clock clock;
@@ -91,18 +84,6 @@ class SslMeterBinder implements MeterBinder {
for (BundleInfo bundle : this.sslInfo.getBundles()) {
createOrUpdateBundleMetrics(meterRegistry, bundle);
}
Gauge.builder(CHAINS_METRIC_NAME, () -> countChainsByStatus(Status.VALID))
.tag("status", "valid")
.register(meterRegistry);
Gauge.builder(CHAINS_METRIC_NAME, () -> countChainsByStatus(Status.EXPIRED))
.tag("status", "expired")
.register(meterRegistry);
Gauge.builder(CHAINS_METRIC_NAME, () -> countChainsByStatus(Status.NOT_YET_VALID))
.tag("status", "not-yet-valid")
.register(meterRegistry);
Gauge.builder(CHAINS_METRIC_NAME, () -> countChainsByStatus(Status.WILL_EXPIRE_SOON))
.tag("status", "will-expire-soon")
.register(meterRegistry);
}
private void createOrUpdateBundleMetrics(MeterRegistry meterRegistry, BundleInfo bundle) {
@@ -130,36 +111,6 @@ class SslMeterBinder implements MeterBinder {
return Row.of(tags, leastValidCertificate, this::getChainExpiry);
}
private long countChainsByStatus(Status status) {
long count = 0;
for (BundleInfo bundle : this.bundleMetrics.getBundles()) {
for (CertificateChainInfo chain : bundle.getCertificateChains()) {
if (getChainStatus(chain) == status) {
count++;
}
}
}
return count;
}
private Status getChainStatus(CertificateChainInfo chain) {
EnumSet<Status> statuses = EnumSet.noneOf(Status.class);
for (CertificateInfo certificate : chain.getCertificates()) {
CertificateValidityInfo validity = certificate.getValidity();
statuses.add(validity.getStatus());
}
if (statuses.contains(Status.EXPIRED)) {
return Status.EXPIRED;
}
if (statuses.contains(Status.NOT_YET_VALID)) {
return Status.NOT_YET_VALID;
}
if (statuses.contains(Status.WILL_EXPIRE_SOON)) {
return Status.WILL_EXPIRE_SOON;
}
return statuses.isEmpty() ? null : Status.VALID;
}
private long getChainExpiry(CertificateInfo certificate) {
Duration valid = Duration.between(Instant.now(this.clock), certificate.getValidityEnds());
return valid.get(ChronoUnit.SECONDS);
@@ -184,18 +135,6 @@ class SslMeterBinder implements MeterBinder {
return gauges.getGauge(meterRegistry);
}
/**
* Returns all bundles.
* @return all bundles
*/
Collection<BundleInfo> getBundles() {
List<BundleInfo> result = new ArrayList<>();
for (Gauges metrics : this.gauges.values()) {
result.add(metrics.bundle());
}
return result;
}
/**
* Returns all meter registries.
* @return all meter registries

View File

@@ -44,15 +44,6 @@ class SslMeterBinderTests {
private static final Clock CLOCK = Clock.fixed(Instant.parse("2024-10-21T13:51:40Z"), ZoneId.of("UTC"));
@Test
void shouldRegisterChainMetrics() {
MeterRegistry meterRegistry = bindToRegistry();
assertThat(meterRegistry.get("ssl.chains").tag("status", "valid").gauge().value()).isEqualTo(3.0);
assertThat(meterRegistry.get("ssl.chains").tag("status", "expired").gauge().value()).isEqualTo(1.0);
assertThat(meterRegistry.get("ssl.chains").tag("status", "not-yet-valid").gauge().value()).isEqualTo(1.0);
assertThat(meterRegistry.get("ssl.chains").tag("status", "will-expire-soon").gauge().value()).isEqualTo(0.0);
}
@Test
void shouldRegisterChainExpiryMetrics() {
MeterRegistry meterRegistry = bindToRegistry();

View File

@@ -836,13 +836,9 @@ To customize the tags, provide a javadoc:org.springframework.context.annotation.
[[actuator.metrics.supported.ssl]]
=== SSL bundle metrics
Spring Boot Actuator publishes two metrics about SSL bundles:
The metric `ssl.chains` gauges how many certificate chains have been registered.
The `status` tag can be used to differentiate between valid, not-yet-valid, expired and soon-to-be-expired certificates.
=== SSL Bundle Metrics
Spring Boot Actuator publishes expiry metrics about SSL bundles.
The metric `ssl.chain.expiry` gauges the expiry date of each certificate chain in seconds.
This number will be negative if the chain has already expired.
This metric is tagged with the following information: