diff --git a/readme.md b/readme.md index a9863ff..b275418 100644 --- a/readme.md +++ b/readme.md @@ -70,6 +70,9 @@ cert_exporter_certrequest_expires_in_seconds{cert_request="example-crt-gn762",ce # HELP certrequest_not_after_timestamp Timestamp when the cert in the CertificateRequest expires. # TYPE certrequest_not_after_timestamp gauge cert_exporter_certrequest_not_after_timestamp{cert_request="example-crt-gn762",certrequest_namespace="cert-manager-test",cn="example.com",issuer="example.com"} +# HELP certrequest_not_before_timestamp Activation timestamp for cert in the certrequest. +# TYPE certrequest_not_before_timestamp gauge +cert_exporter_certrequest_not_before_timestamp{cert_request="example-crt-gn762",certrequest_namespace="cert-manager-test",cn="example.com",issuer="example.com"} ``` **cert_exporter_error_total** @@ -90,6 +93,9 @@ The number of seconds until a certificate stored in a cert-manager CertificateRe **cert_exporter_certrequest_not_after_timestamp** The timestamp when a certificate stored in a cert-manager CertificateRequest expires. The `cert_request`, `issuer`, `cn`, and `certrequest_namespace` labels indicate the CertificateRequest, comon name and namespace. +**cert_exporter_certrequest_not_before_timestamp** +The timestamp when a certificate stored in a cert-manager CertificateRequest becomes valid. The `cert_request`, `issuer`, `cn`, and `certrequest_namespace` labels indicate the CertificateRequest, comon name and namespace. + ### Other Docs - [Testing](./docs/testing.md) diff --git a/src/exporters/certExporter.go b/src/exporters/certExporter.go index 3f70b65..d9b06c6 100644 --- a/src/exporters/certExporter.go +++ b/src/exporters/certExporter.go @@ -18,6 +18,7 @@ func (c *CertExporter) ExportMetrics(file, nodeName string) error { for _, metric := range metricCollection { metrics.CertExpirySeconds.WithLabelValues(file, metric.issuer, metric.cn, nodeName).Set(metric.durationUntilExpiry) metrics.CertNotAfterTimestamp.WithLabelValues(file, metric.issuer, metric.cn, nodeName).Set(metric.notAfter) + metrics.CertNotBeforeTimestamp.WithLabelValues(file, metric.issuer, metric.cn, nodeName).Set(metric.notBefore) } return nil @@ -26,4 +27,5 @@ func (c *CertExporter) ExportMetrics(file, nodeName string) error { func (c *CertExporter) ResetMetrics() { metrics.CertExpirySeconds.Reset() metrics.CertNotAfterTimestamp.Reset() -} \ No newline at end of file + metrics.CertNotBeforeTimestamp.Reset() +} diff --git a/src/exporters/certHelpers.go b/src/exporters/certHelpers.go index f201c16..195bb20 100644 --- a/src/exporters/certHelpers.go +++ b/src/exporters/certHelpers.go @@ -14,7 +14,7 @@ import ( type certMetric struct { durationUntilExpiry float64 - notAfter float64 + notAfter, notBefore float64 issuer string cn string } @@ -55,6 +55,7 @@ func secondsToExpiryFromCertAsBytes(certBytes []byte, certPassword string) ([]ce func getCertificateMetrics(cert *x509.Certificate) certMetric { var metric certMetric metric.notAfter = float64(cert.NotAfter.Unix()) + metric.notBefore = float64(cert.NotBefore.Unix()) metric.durationUntilExpiry = time.Until(cert.NotAfter).Seconds() metric.issuer = cert.Issuer.CommonName metric.cn = cert.Subject.CommonName diff --git a/src/exporters/certRequestExporter.go b/src/exporters/certRequestExporter.go index 89cb3d7..0f56fea 100644 --- a/src/exporters/certRequestExporter.go +++ b/src/exporters/certRequestExporter.go @@ -18,6 +18,7 @@ func (c *CertRequestExporter) ExportMetrics(bytes []byte, certrequest, certreque for _, metric := range metricCollection { metrics.CertRequestExpirySeconds.WithLabelValues(metric.issuer, metric.cn, certrequest, certrequestNamespace).Set(metric.durationUntilExpiry) metrics.CertRequestNotAfterTimestamp.WithLabelValues(metric.issuer, metric.cn, certrequest, certrequestNamespace).Set(metric.notAfter) + metrics.CertRequestNotBeforeTimestamp.WithLabelValues(metric.issuer, metric.cn, certrequest, certrequestNamespace).Set(metric.notBefore) } return nil @@ -26,4 +27,5 @@ func (c *CertRequestExporter) ExportMetrics(bytes []byte, certrequest, certreque func (c *CertRequestExporter) ResetMetrics() { metrics.CertRequestExpirySeconds.Reset() metrics.CertRequestNotAfterTimestamp.Reset() + metrics.CertRequestNotBeforeTimestamp.Reset() } diff --git a/src/exporters/configMapExporter.go b/src/exporters/configMapExporter.go index ff6f843..79963a5 100644 --- a/src/exporters/configMapExporter.go +++ b/src/exporters/configMapExporter.go @@ -18,6 +18,7 @@ func (c *ConfigMapExporter) ExportMetrics(bytes []byte, keyName, configMapName, for _, metric := range metricCollection { metrics.ConfigMapExpirySeconds.WithLabelValues(keyName, metric.issuer, metric.cn, configMapName, configMapNamespace).Set(metric.durationUntilExpiry) metrics.ConfigMapNotAfterTimestamp.WithLabelValues(keyName, metric.issuer, metric.cn, configMapName, configMapNamespace).Set(metric.notAfter) + metrics.ConfigMapNotBeforeTimestamp.WithLabelValues(keyName, metric.issuer, metric.cn, configMapName, configMapNamespace).Set(metric.notBefore) } return nil @@ -26,4 +27,5 @@ func (c *ConfigMapExporter) ExportMetrics(bytes []byte, keyName, configMapName, func (c *ConfigMapExporter) ResetMetrics() { metrics.ConfigMapExpirySeconds.Reset() metrics.ConfigMapNotAfterTimestamp.Reset() + metrics.ConfigMapNotBeforeTimestamp.Reset() } diff --git a/src/exporters/kubeConfigExporter.go b/src/exporters/kubeConfigExporter.go index 4690cb5..cf0061d 100644 --- a/src/exporters/kubeConfigExporter.go +++ b/src/exporters/kubeConfigExporter.go @@ -43,6 +43,7 @@ func (c *KubeConfigExporter) ExportMetrics(file, nodeName string) error { for _, metric := range metricCollection { metrics.KubeConfigExpirySeconds.WithLabelValues(file, "cluster", metric.cn, metric.issuer, c.Name, nodeName).Set(metric.durationUntilExpiry) metrics.KubeConfigNotAfterTimestamp.WithLabelValues(file, "cluster", metric.cn, metric.issuer, c.Name, nodeName).Set(metric.notAfter) + metrics.KubeConfigNotBeforeTimestamp.WithLabelValues(file, "cluster", metric.cn, metric.issuer, c.Name, nodeName).Set(metric.notBefore) } } @@ -69,6 +70,7 @@ func (c *KubeConfigExporter) ExportMetrics(file, nodeName string) error { for _, metric := range metricCollection { metrics.KubeConfigExpirySeconds.WithLabelValues(file, "user", metric.cn, metric.issuer, u.Name, nodeName).Set(metric.durationUntilExpiry) metrics.KubeConfigNotAfterTimestamp.WithLabelValues(file, "user", metric.cn, metric.issuer, u.Name, nodeName).Set(metric.notAfter) + metrics.KubeConfigNotBeforeTimestamp.WithLabelValues(file, "user", metric.cn, metric.issuer, u.Name, nodeName).Set(metric.notBefore) } } @@ -87,4 +89,5 @@ func pathToFileFromKubeConfig(file, kubeConfigFile string) string { func (c *KubeConfigExporter) ResetMetrics() { metrics.KubeConfigExpirySeconds.Reset() metrics.KubeConfigNotAfterTimestamp.Reset() -} \ No newline at end of file + metrics.KubeConfigNotBeforeTimestamp.Reset() +} diff --git a/src/exporters/secretExporter.go b/src/exporters/secretExporter.go index b19fd58..0230029 100644 --- a/src/exporters/secretExporter.go +++ b/src/exporters/secretExporter.go @@ -18,6 +18,7 @@ func (c *SecretExporter) ExportMetrics(bytes []byte, keyName, secretName, secret for _, metric := range metricCollection { metrics.SecretExpirySeconds.WithLabelValues(keyName, metric.issuer, metric.cn, secretName, secretNamespace).Set(metric.durationUntilExpiry) metrics.SecretNotAfterTimestamp.WithLabelValues(keyName, metric.issuer, metric.cn, secretName, secretNamespace).Set(metric.notAfter) + metrics.SecretNotBeforeTimestamp.WithLabelValues(keyName, metric.issuer, metric.cn, secretName, secretNamespace).Set(metric.notBefore) } return nil @@ -26,4 +27,5 @@ func (c *SecretExporter) ExportMetrics(bytes []byte, keyName, secretName, secret func (c *SecretExporter) ResetMetrics() { metrics.SecretExpirySeconds.Reset() metrics.SecretNotAfterTimestamp.Reset() + metrics.SecretNotBeforeTimestamp.Reset() } diff --git a/src/exporters/webhookExporter.go b/src/exporters/webhookExporter.go index 323e54e..473fd6f 100644 --- a/src/exporters/webhookExporter.go +++ b/src/exporters/webhookExporter.go @@ -18,6 +18,7 @@ func (c *WebhookExporter) ExportMetrics(bytes []byte, typeName, webhookName, adm for _, metric := range metricCollection { metrics.WebhookExpirySeconds.WithLabelValues(typeName, metric.issuer, metric.cn, webhookName, admissionReviewVersionName).Set(metric.durationUntilExpiry) metrics.WebhookNotAfterTimestamp.WithLabelValues(typeName, metric.issuer, metric.cn, webhookName, admissionReviewVersionName).Set(metric.notAfter) + metrics.WebhookNotBeforeTimestamp.WithLabelValues(typeName, metric.issuer, metric.cn, webhookName, admissionReviewVersionName).Set(metric.notBefore) } return nil @@ -26,4 +27,5 @@ func (c *WebhookExporter) ExportMetrics(bytes []byte, typeName, webhookName, adm func (c *WebhookExporter) ResetMetrics() { metrics.WebhookExpirySeconds.Reset() metrics.WebhookNotAfterTimestamp.Reset() + metrics.WebhookNotBeforeTimestamp.Reset() } diff --git a/src/metrics/metrics.go b/src/metrics/metrics.go index 44d7fe8..887dfb4 100644 --- a/src/metrics/metrics.go +++ b/src/metrics/metrics.go @@ -36,6 +36,16 @@ var ( []string{"filename", "issuer", "cn", "nodename"}, ) + // CertNotBeforeTimestamp is a prometheus gauge that indicates the NotBefore timestamp. + CertNotBeforeTimestamp = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: namespace, + Name: "cert_not_before_timestamp", + Help: "Timestamp of when the certificate becomes valid.", + }, + []string{"filename", "issuer", "cn", "nodename"}, + ) + // KubeConfigExpirySeconds is a prometheus gauge that indicates the number of seconds until a kubeconfig certificate expires. KubeConfigExpirySeconds = prometheus.NewGaugeVec( prometheus.GaugeOpts{ @@ -56,6 +66,16 @@ var ( []string{"filename", "type", "cn", "issuer", "name", "nodename"}, ) + // KubeConfigNotBeforeTimestamp is a prometheus gauge that indicates the NotBefore timestamp. + KubeConfigNotBeforeTimestamp = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: namespace, + Name: "kubeconfig_not_before_timestamp", + Help: "Activation timestamp for cert in the kubeconfig.", + }, + []string{"filename", "type", "cn", "issuer", "name", "nodename"}, + ) + // SecretExpirySeconds is a prometheus gauge that indicates the number of seconds until a kubernetes secret certificate expires SecretExpirySeconds = prometheus.NewGaugeVec( prometheus.GaugeOpts{ @@ -75,6 +95,16 @@ var ( }, []string{"key_name", "issuer", "cn", "secret_name", "secret_namespace"}, ) + + // SecretNotBeforeTimestamp is a prometheus gauge that indicates the NotBefore timestamp. + SecretNotBeforeTimestamp = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: namespace, + Name: "secret_not_before_timestamp", + Help: "Activation timestamp for cert in the secret.", + }, + []string{"key_name", "issuer", "cn", "secret_name", "secret_namespace"}, + ) // CertRequestExpirySeconds is a prometheus gauge that indicates the number of seconds until a certificate in a cert-manager certificate request expires CertRequestExpirySeconds = prometheus.NewGaugeVec( @@ -95,6 +125,16 @@ var ( }, []string{"issuer", "cn", "cert_request", "certrequest_namespace"}, ) + + // CertRequestNotBeforeTimestamp is a prometheus gauge that indicates the NotBefore timestamp. + CertRequestNotBeforeTimestamp = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: namespace, + Name: "certrequest_not_before_timestamp", + Help: "Activation timestamp for cert in the certrequest.", + }, + []string{"issuer", "cn", "cert_request", "certrequest_namespace"}, + ) // AwsCertExpirySeconds is a prometheus gauge that indicates the number of seconds until certificates on AWS expires. AwsCertExpirySeconds = prometheus.NewGaugeVec( @@ -125,6 +165,16 @@ var ( }, []string{"key_name", "issuer", "cn", "configmap_name", "configmap_namespace"}, ) + + // ConfigMapNotBeforeTimestamp is a prometheus gauge that indicates the NotBefore timestamp. + ConfigMapNotBeforeTimestamp = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: namespace, + Name: "configmap_not_before_timestamp", + Help: "Activation timestamp for cert in the configmap.", + }, + []string{"key_name", "issuer", "cn", "configmap_name", "configmap_namespace"}, + ) // WebhookExpirySeconds is a prometheus gauge that indicates the number of seconds until a kubernetes webhook certificate expires WebhookExpirySeconds = prometheus.NewGaugeVec( @@ -145,6 +195,16 @@ var ( }, []string{"type_name", "issuer", "cn", "webhook_name", "admission_review_version_name"}, ) + + // WebhookNotBeforeTimestamp is a prometheus gauge that indicates the NotBefore timestamp. + WebhookNotBeforeTimestamp = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: namespace, + Name: "webhook_not_before_timestamp", + Help: "Activation timestamp for cert in the webhook.", + }, + []string{"type_name", "issuer", "cn", "webhook_name", "admission_review_version_name"}, + ) ) func Init(prometheusExporterMetricsDisabled bool) { @@ -157,15 +217,21 @@ func Init(prometheusExporterMetricsDisabled bool) { prometheus.MustRegister(ErrorTotal) prometheus.MustRegister(CertExpirySeconds) prometheus.MustRegister(CertNotAfterTimestamp) + prometheus.MustRegister(CertNotBeforeTimestamp) prometheus.MustRegister(KubeConfigExpirySeconds) prometheus.MustRegister(KubeConfigNotAfterTimestamp) + prometheus.MustRegister(KubeConfigNotBeforeTimestamp) prometheus.MustRegister(SecretExpirySeconds) prometheus.MustRegister(SecretNotAfterTimestamp) + prometheus.MustRegister(SecretNotBeforeTimestamp) prometheus.MustRegister(CertRequestExpirySeconds) prometheus.MustRegister(CertRequestNotAfterTimestamp) + prometheus.MustRegister(CertRequestNotBeforeTimestamp) prometheus.MustRegister(ConfigMapExpirySeconds) prometheus.MustRegister(ConfigMapNotAfterTimestamp) + prometheus.MustRegister(ConfigMapNotBeforeTimestamp) prometheus.MustRegister(WebhookExpirySeconds) prometheus.MustRegister(WebhookNotAfterTimestamp) + prometheus.MustRegister(WebhookNotBeforeTimestamp) prometheus.MustRegister(AwsCertExpirySeconds) } diff --git a/test/files/test.sh b/test/files/test.sh index 860b421..2ab3657 100755 --- a/test/files/test.sh +++ b/test/files/test.sh @@ -5,9 +5,43 @@ set -o errexit +fetchMetricsTimestampValue() { + local metrics + metrics="$1" + + curl --silent http://localhost:8080/metrics \ + | grep -F "$metrics" \ + | awk '{ printf("%.0f",$2) }' +} + +validateTimestampBefore() { + local metrics + local want + local got + metrics="$1" + want="$2" + got=$(fetchMetricsTimestampValue "$metrics") + + if [ "$got" == "" ]; then + echo "TEST FAILURE: $metrics" + echo " Unable to find metrics string" + return 0 + fi + + if [ "$got" -ge "$want" ]; then + echo "TEST FAILURE: $metrics" + echo " Want : $want" + echo " Got : $got" + else + echo "TEST SUCCESS: $metrics" + fi +} + validateMetrics() { + local metrics + local expectedVal metrics=$1 - expectedVal=$2 + expectedVal=$2 raw=$(curl --silent http://localhost:8080/metrics | grep "$metrics") @@ -55,6 +89,22 @@ sleep 2 curl --silent http://localhost:8080/metrics | grep 'cert_exporter_error_total 0' +activation=$(date +%s) # this timestamp is at least 2 seconds off from the actual cert NotBefore attribute ... +validateTimestampBefore 'cert_exporter_cert_not_before_timestamp{cn="client",filename="certs/client.crt",issuer="root",nodename="master0"}' $activation +validateTimestampBefore 'cert_exporter_cert_not_before_timestamp{cn="root",filename="certs/root.crt",issuer="root",nodename="master0"}' $activation +validateTimestampBefore 'cert_exporter_cert_not_before_timestamp{cn="example.com",filename="certs/server.crt",issuer="root",nodename="master0"}' $activation +validateTimestampBefore 'cert_exporter_cert_not_before_timestamp{cn="bundle-root",filename="certs/bundle.crt",issuer="bundle-root",nodename="master0"}' $activation +validateTimestampBefore 'cert_exporter_cert_not_before_timestamp{cn="example-bundle.be",filename="certs/bundle.crt",issuer="bundle-root",nodename="master0"}' $activation +validateTimestampBefore 'cert_exporter_cert_not_before_timestamp{cn="bundle-root",filename="certs/bundle_pfx.crt",issuer="bundle-root",nodename="master0"}' $activation + +expiration=$((activation + days * 24 * 60 * 60)) # ... and as a result, this values if off as well +validateTimestampBefore 'cert_exporter_cert_not_after_timestamp{cn="client",filename="certs/client.crt",issuer="root",nodename="master0"}' $expiration +validateTimestampBefore 'cert_exporter_cert_not_after_timestamp{cn="root",filename="certs/root.crt",issuer="root",nodename="master0"}' $expiration +validateTimestampBefore 'cert_exporter_cert_not_after_timestamp{cn="example.com",filename="certs/server.crt",issuer="root",nodename="master0"}' $expiration +validateTimestampBefore 'cert_exporter_cert_not_after_timestamp{cn="bundle-root",filename="certs/bundle.crt",issuer="bundle-root",nodename="master0"}' $expiration +validateTimestampBefore 'cert_exporter_cert_not_after_timestamp{cn="example-bundle.be",filename="certs/bundle.crt",issuer="bundle-root",nodename="master0"}' $expiration +validateTimestampBefore 'cert_exporter_cert_not_after_timestamp{cn="bundle-root",filename="certs/bundle_pfx.crt",issuer="bundle-root",nodename="master0"}' $expiration + validateMetrics 'cert_exporter_cert_expires_in_seconds{cn="client",filename="certs/client.crt",issuer="root",nodename="master0"}' $days validateMetrics 'cert_exporter_cert_expires_in_seconds{cn="root",filename="certs/root.crt",issuer="root",nodename="master0"}' $days validateMetrics 'cert_exporter_cert_expires_in_seconds{cn="example.com",filename="certs/server.crt",issuer="root",nodename="master0"}' $days @@ -87,6 +137,16 @@ sleep 2 curl --silent http://localhost:8080/metrics | grep 'cert_exporter_error_total 0' +activation=$(date +%s) # this timestamp is at least 2 seconds off from the actual cert NotBefore attribute ... +validateTimestampBefore 'cert_exporter_cert_not_after_timestamp{cn="client",filename="certsSibling/client.crt",issuer="root",nodename="master0"}' $activation +validateTimestampBefore 'cert_exporter_cert_not_after_timestamp{cn="root",filename="certsSibling/root.crt",issuer="root",nodename="master0"}' $activation +validateTimestampBefore 'cert_exporter_cert_not_after_timestamp{cn="example.com",filename="certsSibling/server.crt",issuer="root",nodename="master0"}' $activation + +expiration=$((activation + days * 24 * 60 * 60)) # ... and as a result, this values if off as well +validateTimestampBefore 'cert_exporter_cert_not_after_timestamp{cn="client",filename="certsSibling/client.crt",issuer="root",nodename="master0"}' $expiration +validateTimestampBefore 'cert_exporter_cert_not_after_timestamp{cn="root",filename="certsSibling/root.crt",issuer="root",nodename="master0"}' $expiration +validateTimestampBefore 'cert_exporter_cert_not_after_timestamp{cn="example.com",filename="certsSibling/server.crt",issuer="root",nodename="master0"}' $expiration + validateMetrics 'cert_exporter_cert_expires_in_seconds{cn="client",filename="certsSibling/client.crt",issuer="root",nodename="master0"}' $days validateMetrics 'cert_exporter_cert_expires_in_seconds{cn="root",filename="certsSibling/root.crt",issuer="root",nodename="master0"}' $days validateMetrics 'cert_exporter_cert_expires_in_seconds{cn="example.com",filename="certsSibling/server.crt",issuer="root",nodename="master0"}' $days