Skip to content

Commit

Permalink
add metrics for x509 NotBefore attribute (#170)
Browse files Browse the repository at this point in the history
this is similar to the NotAfter metrics, except it depicts the
timestamp of when the certificate *starts* to be valid.
  • Loading branch information
UiP9AV6Y authored Jul 30, 2024
1 parent f7c4215 commit 5565b9f
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 4 deletions.
6 changes: 6 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand All @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion src/exporters/certExporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,4 +27,5 @@ func (c *CertExporter) ExportMetrics(file, nodeName string) error {
func (c *CertExporter) ResetMetrics() {
metrics.CertExpirySeconds.Reset()
metrics.CertNotAfterTimestamp.Reset()
}
metrics.CertNotBeforeTimestamp.Reset()
}
3 changes: 2 additions & 1 deletion src/exporters/certHelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

type certMetric struct {
durationUntilExpiry float64
notAfter float64
notAfter, notBefore float64
issuer string
cn string
}
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/exporters/certRequestExporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
}
2 changes: 2 additions & 0 deletions src/exporters/configMapExporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
}
5 changes: 4 additions & 1 deletion src/exporters/kubeConfigExporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand All @@ -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)
}
}

Expand All @@ -87,4 +89,5 @@ func pathToFileFromKubeConfig(file, kubeConfigFile string) string {
func (c *KubeConfigExporter) ResetMetrics() {
metrics.KubeConfigExpirySeconds.Reset()
metrics.KubeConfigNotAfterTimestamp.Reset()
}
metrics.KubeConfigNotBeforeTimestamp.Reset()
}
2 changes: 2 additions & 0 deletions src/exporters/secretExporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
}
2 changes: 2 additions & 0 deletions src/exporters/webhookExporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
}
66 changes: 66 additions & 0 deletions src/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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{
Expand All @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand All @@ -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) {
Expand All @@ -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)
}
62 changes: 61 additions & 1 deletion test/files/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 5565b9f

Please sign in to comment.