Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ To customize the tags, provide a javadoc:org.springframework.context.annotation.
=== 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.
The metric `ssl.chain.expiry` gauges the expiry date of each certificate chain in key stores and trust stores in seconds.
This number will be negative if the chain has already expired.
This metric is tagged with the following information:

Expand All @@ -830,6 +830,9 @@ This metric is tagged with the following information:

| `chain`
| The name of the certificate chain.

| `source`
| Whether the certificate chain comes from the key store (`keystore`) or trust store (`truststore`)
|===


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ class SslMeterBinder implements MeterBinder {

private static final String CHAIN_EXPIRY_METRIC_NAME = "ssl.chain.expiry";

private static final String SOURCE_TAG_NAME = "source";

private static final String KEY_STORE_SOURCE_TAG_VALUE = "keystore";

private static final String TRUST_STORE_SOURCE_TAG_VALUE = "truststore";

private final Clock clock;

private final SslInfo sslInfo;
Expand Down Expand Up @@ -95,16 +101,23 @@ public void bindTo(MeterRegistry meterRegistry) {
private void createOrUpdateBundleMetrics(MeterRegistry meterRegistry, BundleInfo bundle) {
MultiGauge multiGauge = this.bundleMetrics.getGauge(bundle, meterRegistry);
List<Row<CertificateInfo>> rows = new ArrayList<>();
for (CertificateChainInfo chain : bundle.getCertificateChains()) {
Row<CertificateInfo> row = createRowForChain(bundle, chain);
addRows(rows, bundle, bundle.getCertificateChains(), KEY_STORE_SOURCE_TAG_VALUE);
addRows(rows, bundle, bundle.getTrustStoreCertificateChains(), TRUST_STORE_SOURCE_TAG_VALUE);
multiGauge.register(rows, true);
}

private void addRows(List<Row<CertificateInfo>> rows, BundleInfo bundle, List<CertificateChainInfo> chains,
String source) {
for (CertificateChainInfo chain : chains) {
Row<CertificateInfo> row = createRowForChain(bundle, chain, source);
if (row != null) {
rows.add(row);
}
}
multiGauge.register(rows, true);
}

private @Nullable Row<CertificateInfo> createRowForChain(BundleInfo bundle, CertificateChainInfo chain) {
private @Nullable Row<CertificateInfo> createRowForChain(BundleInfo bundle, CertificateChainInfo chain,
String source) {
CertificateInfo leastValidCertificate = chain.getCertificates()
.stream()
.filter((c) -> c.getValidityEnds() != null)
Expand All @@ -114,8 +127,8 @@ private void createOrUpdateBundleMetrics(MeterRegistry meterRegistry, BundleInfo
return null;
}
String serialNumber = leastValidCertificate.getSerialNumber();
Tags tags = Tags.of("chain", chain.getAlias(), "bundle", bundle.getName(), "certificate",
(serialNumber != null) ? serialNumber : "");
Tags tags = Tags.of("chain", chain.getAlias(), "bundle", bundle.getName(), SOURCE_TAG_NAME, source,
"certificate", (serialNumber != null) ? serialNumber : "");
return Row.of(tags, leastValidCertificate, this::getChainExpiry);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,44 @@ void shouldRegisterChainExpiryMetrics() {
.hasDays(36889);
}

@Test
void shouldRegisterTrustStoreChainExpiryMetrics() {
DefaultSslBundleRegistry sslBundleRegistry = new DefaultSslBundleRegistry();
sslBundleRegistry.registerBundle("test-0",
SslBundle.of(createTrustStoreBundle("classpath:certificates/chains.p12")));
MeterRegistry meterRegistry = bindToRegistry(sslBundleRegistry);
assertThat(Duration
.ofSeconds(findExpiryGauge(meterRegistry, "truststore", "ca", "419224ce190242b2c44069dd3c560192b3b669f3")))
.hasDays(1095);
assertThat(Duration.ofSeconds(findExpiryGauge(meterRegistry, "truststore", "intermediary",
"60f79365fc46bf69149754d377680192b3b6bcf5")))
.hasDays(730);
assertThat(Duration.ofSeconds(
findExpiryGauge(meterRegistry, "truststore", "server", "504c45129526ac050abb11459b1f0192b3b70fe9")))
.hasDays(365);
assertThat(Duration.ofSeconds(
findExpiryGauge(meterRegistry, "truststore", "expired", "562bc5dcf4f26bb179abb13068180192b3bb53dc")))
.hasDays(-386);
assertThat(Duration.ofSeconds(findExpiryGauge(meterRegistry, "truststore", "not-yet-valid",
"7df79335f274e2cfa7467fd5f9ce0192b3bcf4aa")))
.hasDays(36889);
}

@Test
void shouldDifferentiateKeyStoreAndTrustStoreMetrics() {
DefaultSslBundleRegistry sslBundleRegistry = new DefaultSslBundleRegistry();
sslBundleRegistry.registerBundle("test-0",
SslBundle.of(createKeyAndTrustStoreBundle("classpath:certificates/chains.p12",
"classpath:certificates/chains.p12")));
MeterRegistry meterRegistry = bindToRegistry(sslBundleRegistry);
assertThat(Duration
.ofSeconds(findExpiryGauge(meterRegistry, "keystore", "ca", "419224ce190242b2c44069dd3c560192b3b669f3")))
.hasDays(1095);
assertThat(Duration
.ofSeconds(findExpiryGauge(meterRegistry, "truststore", "ca", "419224ce190242b2c44069dd3c560192b3b669f3")))
.hasDays(1095);
}

@Test
void shouldWatchUpdatesForBundlesRegisteredAfterConstruction() {
DefaultSslBundleRegistry sslBundleRegistry = new DefaultSslBundleRegistry();
Expand All @@ -90,6 +128,35 @@ void shouldWatchUpdatesForBundlesRegisteredAfterConstruction() {
.hasDays(36889);
}

@Test
void shouldWatchTrustStoreUpdatesForBundlesRegisteredAfterConstruction() {
DefaultSslBundleRegistry sslBundleRegistry = new DefaultSslBundleRegistry();
sslBundleRegistry.registerBundle("dummy",
SslBundle.of(createTrustStoreBundle("classpath:certificates/chains2.p12")));
MeterRegistry meterRegistry = bindToRegistry(sslBundleRegistry);
sslBundleRegistry.registerBundle("test-0",
SslBundle.of(createTrustStoreBundle("classpath:certificates/chains2.p12")));
sslBundleRegistry.updateBundle("test-0",
SslBundle.of(createTrustStoreBundle("classpath:certificates/chains.p12")));
assertThat(meterRegistry.find("ssl.chain.expiry").tags("bundle", "test-0", "source", "truststore").meters())
.hasSize(5);
assertThat(Duration
.ofSeconds(findExpiryGauge(meterRegistry, "truststore", "ca", "419224ce190242b2c44069dd3c560192b3b669f3")))
.hasDays(1095);
assertThat(Duration.ofSeconds(findExpiryGauge(meterRegistry, "truststore", "intermediary",
"60f79365fc46bf69149754d377680192b3b6bcf5")))
.hasDays(730);
assertThat(Duration.ofSeconds(
findExpiryGauge(meterRegistry, "truststore", "server", "504c45129526ac050abb11459b1f0192b3b70fe9")))
.hasDays(365);
assertThat(Duration.ofSeconds(
findExpiryGauge(meterRegistry, "truststore", "expired", "562bc5dcf4f26bb179abb13068180192b3bb53dc")))
.hasDays(-386);
assertThat(Duration.ofSeconds(findExpiryGauge(meterRegistry, "truststore", "not-yet-valid",
"7df79335f274e2cfa7467fd5f9ce0192b3bcf4aa")))
.hasDays(36889);
}

@Test
void shouldRegisterMetricsIfNoBundleExistsAtBindTime() {
DefaultSslBundleRegistry sslBundleRegistry = new DefaultSslBundleRegistry();
Expand All @@ -100,8 +167,14 @@ void shouldRegisterMetricsIfNoBundleExistsAtBindTime() {
}

private long findExpiryGauge(MeterRegistry meterRegistry, String chain, String certificateSerialNumber) {
return findExpiryGauge(meterRegistry, "keystore", chain, certificateSerialNumber);
}

private long findExpiryGauge(MeterRegistry meterRegistry, String source, String chain,
String certificateSerialNumber) {
return (long) meterRegistry.get("ssl.chain.expiry")
.tag("bundle", "test-0")
.tag("source", source)
.tag("chain", chain)
.tag("certificate", certificateSerialNumber)
.gauge()
Expand All @@ -117,8 +190,19 @@ private SimpleMeterRegistry bindToRegistry(SslBundles sslBundles) {
}

private SslStoreBundle createSslStoreBundle(String location) {
JksSslStoreDetails keyStoreDetails = JksSslStoreDetails.forLocation(location).withPassword("secret");
return new JksSslStoreBundle(keyStoreDetails, null);
return new JksSslStoreBundle(createStoreDetails(location), null);
}

private SslStoreBundle createTrustStoreBundle(String location) {
return new JksSslStoreBundle(null, createStoreDetails(location));
}

private SslStoreBundle createKeyAndTrustStoreBundle(String keyStoreLocation, String trustStoreLocation) {
return new JksSslStoreBundle(createStoreDetails(keyStoreLocation), createStoreDetails(trustStoreLocation));
}

private JksSslStoreDetails createStoreDetails(String location) {
return JksSslStoreDetails.forLocation(location).withPassword("secret");
}

private DefaultSslBundleRegistry createSslBundleRegistry(String... locations) {
Expand Down
Loading