From 1c73c91133259a278954f8d150f48e144756d26d Mon Sep 17 00:00:00 2001 From: Jon Chambers Date: Tue, 5 May 2020 17:12:47 -0400 Subject: [PATCH] Report the number of days until the CDS CA cert expires as a metric so we can set an alarm. --- .../DirectoryReconciliationClient.java | 19 +++++++++-- .../util/CertificateExpirationGauge.java | 32 +++++++++++++++++++ .../util/CertificateExpirationGaugeTest.java | 31 ++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/util/CertificateExpirationGauge.java create mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/util/CertificateExpirationGaugeTest.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciliationClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciliationClient.java index e2c085e90..3da336f45 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciliationClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/DirectoryReconciliationClient.java @@ -16,12 +16,15 @@ */ package org.whispersystems.textsecuregcm.storage; +import com.codahale.metrics.SharedMetricRegistries; import org.bouncycastle.openssl.PEMReader; import org.glassfish.jersey.SslConfigurator; import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; import org.whispersystems.textsecuregcm.configuration.DirectoryServerConfiguration; import org.whispersystems.textsecuregcm.entities.DirectoryReconciliationRequest; import org.whispersystems.textsecuregcm.entities.DirectoryReconciliationResponse; +import org.whispersystems.textsecuregcm.util.CertificateExpirationGauge; +import org.whispersystems.textsecuregcm.util.Constants; import javax.net.ssl.SSLContext; import javax.ws.rs.client.Client; @@ -37,6 +40,8 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import static com.codahale.metrics.MetricRegistry.name; + public class DirectoryReconciliationClient { private final String replicationUrl; @@ -47,6 +52,10 @@ public class DirectoryReconciliationClient { { this.replicationUrl = directoryServerConfiguration.getReplicationUrl(); this.client = initializeClient(directoryServerConfiguration); + + SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME) + .register(name(getClass(), "days_until_certificate_expiration"), + new CertificateExpirationGauge(getCertificate(directoryServerConfiguration.getReplicationCaCertificate()))); } public DirectoryReconciliationResponse sendChunk(DirectoryReconciliationRequest request) { @@ -74,8 +83,7 @@ public class DirectoryReconciliationClient { throws CertificateException { try { - PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(caCertificatePem.getBytes()))); - X509Certificate certificate = (X509Certificate) reader.readObject(); + X509Certificate certificate = getCertificate(caCertificatePem); if (certificate == null) { throw new CertificateException("No certificate found in parsing!"); @@ -92,4 +100,11 @@ public class DirectoryReconciliationClient { } } + private static X509Certificate getCertificate(final String certificatePem) throws CertificateException { + try (PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(certificatePem.getBytes())))) { + return (X509Certificate) reader.readObject(); + } catch (IOException e) { + throw new CertificateException(e); + } + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/CertificateExpirationGauge.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/CertificateExpirationGauge.java new file mode 100644 index 000000000..f641a377e --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/CertificateExpirationGauge.java @@ -0,0 +1,32 @@ +package org.whispersystems.textsecuregcm.util; + +import com.codahale.metrics.CachedGauge; +import org.bouncycastle.openssl.PEMReader; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +/** + * Measures and reports the number of days until a certificate expires. + */ +public class CertificateExpirationGauge extends CachedGauge { + + private final Instant certificateExpiration; + + public CertificateExpirationGauge(final X509Certificate certificate) { + super(1, TimeUnit.HOURS); + + certificateExpiration = certificate.getNotAfter().toInstant(); + } + + @Override + protected Long loadValue() { + return Duration.between(Instant.now(), certificateExpiration).toDays(); + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/util/CertificateExpirationGaugeTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/util/CertificateExpirationGaugeTest.java new file mode 100644 index 000000000..6d1057c97 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/util/CertificateExpirationGaugeTest.java @@ -0,0 +1,31 @@ +package org.whispersystems.textsecuregcm.util; + +import org.junit.Test; + +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class CertificateExpirationGaugeTest { + + @Test + public void loadValue() { + final X509Certificate certificate = mock(X509Certificate.class); + + final long daysUntilExpiration = 17; + + final Instant now = Instant.now(); + final Instant later = now.plus(Duration.ofDays(daysUntilExpiration)).plus(Duration.ofMinutes(1)); + + when(certificate.getNotAfter()).thenReturn(new Date(later.toEpochMilli())); + + final CertificateExpirationGauge gauge = new CertificateExpirationGauge(certificate); + + assertEquals(daysUntilExpiration, (long) gauge.loadValue()); + } +}