Add TLS certs expiration metric
This commit is contained in:
parent
3140a4e0cd
commit
a3327c4430
15 changed files with 291 additions and 62 deletions
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
stdlog "log"
|
stdlog "log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -14,6 +15,7 @@ import (
|
||||||
"github.com/coreos/go-systemd/daemon"
|
"github.com/coreos/go-systemd/daemon"
|
||||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
|
gokitmetrics "github.com/go-kit/kit/metrics"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/traefik/paerser/cli"
|
"github.com/traefik/paerser/cli"
|
||||||
"github.com/traefik/traefik/v2/autogen/genstatic"
|
"github.com/traefik/traefik/v2/autogen/genstatic"
|
||||||
|
@ -260,6 +262,11 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||||
watcher.AddListener(func(conf dynamic.Configuration) {
|
watcher.AddListener(func(conf dynamic.Configuration) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
tlsManager.UpdateConfigs(ctx, conf.TLS.Stores, conf.TLS.Options, conf.TLS.Certificates)
|
tlsManager.UpdateConfigs(ctx, conf.TLS.Stores, conf.TLS.Options, conf.TLS.Certificates)
|
||||||
|
|
||||||
|
gauge := metricsRegistry.TLSCertsNotAfterTimestampGauge()
|
||||||
|
for _, certificate := range tlsManager.GetCertificates() {
|
||||||
|
appendCertMetric(gauge, certificate)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Metrics
|
// Metrics
|
||||||
|
@ -432,6 +439,20 @@ func registerMetricClients(metricsConfig *types.Metrics) []metrics.Registry {
|
||||||
return registries
|
return registries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appendCertMetric(gauge gokitmetrics.Gauge, certificate *x509.Certificate) {
|
||||||
|
sort.Strings(certificate.DNSNames)
|
||||||
|
|
||||||
|
labels := []string{
|
||||||
|
"cn", certificate.Subject.CommonName,
|
||||||
|
"serial", certificate.SerialNumber.String(),
|
||||||
|
"sans", strings.Join(certificate.DNSNames, ","),
|
||||||
|
}
|
||||||
|
|
||||||
|
notAfter := float64(certificate.NotAfter.Unix())
|
||||||
|
|
||||||
|
gauge.With(labels...).Set(notAfter)
|
||||||
|
}
|
||||||
|
|
||||||
func setupAccessLog(conf *types.AccessLog) *accesslog.Handler {
|
func setupAccessLog(conf *types.AccessLog) *accesslog.Handler {
|
||||||
if conf == nil {
|
if conf == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
116
cmd/traefik/traefik_test.go
Normal file
116
cmd/traefik/traefik_test.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-kit/kit/metrics"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FooCert is a PEM-encoded TLS cert.
|
||||||
|
// generated from src/crypto/tls:
|
||||||
|
// go run generate_cert.go --rsa-bits 1024 --host foo.org,foo.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
||||||
|
const fooCert = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIICHzCCAYigAwIBAgIQXQFLeYRwc5X21t457t2xADANBgkqhkiG9w0BAQsFADAS
|
||||||
|
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
|
||||||
|
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
|
||||||
|
iQKBgQDCjn67GSs/khuGC4GNN+tVo1S+/eSHwr/hWzhfMqO7nYiXkFzmxi+u14CU
|
||||||
|
Pda6WOeps7T2/oQEFMxKKg7zYOqkLSbjbE0ZfosopaTvEsZm/AZHAAvoOrAsIJOn
|
||||||
|
SEiwy8h0tLA4z1SNR6rmIVQWyqBZEPAhBTQM1z7tFp48FakCFwIDAQABo3QwcjAO
|
||||||
|
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw
|
||||||
|
AwEB/zAdBgNVHQ4EFgQUDHG3ASzeUezElup9zbPpBn/vjogwGwYDVR0RBBQwEoIH
|
||||||
|
Zm9vLm9yZ4IHZm9vLmNvbTANBgkqhkiG9w0BAQsFAAOBgQBT+VLMbB9u27tBX8Aw
|
||||||
|
ZrGY3rbNdBGhXVTksrjiF+6ZtDpD3iI56GH9zLxnqvXkgn3u0+Ard5TqF/xmdwVw
|
||||||
|
NY0V/aWYfcL2G2auBCQrPvM03ozRnVUwVfP23eUzX2ORNHCYhd2ObQx4krrhs7cJ
|
||||||
|
SWxtKwFlstoXY3K2g9oRD9UxdQ==
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
// BarCert is a PEM-encoded TLS cert.
|
||||||
|
// generated from src/crypto/tls:
|
||||||
|
// go run generate_cert.go --rsa-bits 1024 --host bar.org,bar.com --ca --start-date "Jan 1 00:00:00 1970" --duration=10000h
|
||||||
|
const barCert = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIICHTCCAYagAwIBAgIQcuIcNEXzBHPoxna5S6wG4jANBgkqhkiG9w0BAQsFADAS
|
||||||
|
MRAwDgYDVQQKEwdBY21lIENvMB4XDTcwMDEwMTAwMDAwMFoXDTcxMDIyMTE2MDAw
|
||||||
|
MFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
|
||||||
|
gYEAqtcrP+KA7D6NjyztGNIPMup9KiBMJ8QL+preog/YHR7SQLO3kGFhpS3WKMab
|
||||||
|
SzMypC3ZX1PZjBP5ZzwaV3PFbuwlCkPlyxR2lOWmullgI7mjY0TBeYLDIclIzGRp
|
||||||
|
mpSDDSpkW1ay2iJDSpXjlhmwZr84hrCU7BRTQJo91fdsRTsCAwEAAaN0MHIwDgYD
|
||||||
|
VR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMB
|
||||||
|
Af8wHQYDVR0OBBYEFK8jnzFQvBAgWtfzOyXY4VSkwrTXMBsGA1UdEQQUMBKCB2Jh
|
||||||
|
ci5vcmeCB2Jhci5jb20wDQYJKoZIhvcNAQELBQADgYEAJz0ifAExisC/ZSRhWuHz
|
||||||
|
7qs1i6Nd4+YgEVR8dR71MChP+AMxucY1/ajVjb9xlLys3GPE90TWSdVppabEVjZY
|
||||||
|
Oq11nPKc50ItTt8dMku6t0JHBmzoGdkN0V4zJCBqdQJxhop8JpYJ0S9CW0eT93h3
|
||||||
|
ipYQSsmIINGtMXJ8VkP/MlM=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
type gaugeMock struct {
|
||||||
|
metrics map[string]float64
|
||||||
|
labels string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gaugeMock) With(labelValues ...string) metrics.Gauge {
|
||||||
|
g.labels = strings.Join(labelValues, ",")
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gaugeMock) Set(value float64) {
|
||||||
|
g.metrics[g.labels] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gaugeMock) Add(delta float64) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendCertMetric(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
certs []string
|
||||||
|
expected map[string]float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "No certs",
|
||||||
|
certs: []string{},
|
||||||
|
expected: map[string]float64{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "One cert",
|
||||||
|
certs: []string{fooCert},
|
||||||
|
expected: map[string]float64{
|
||||||
|
"cn,,serial,123624926713171615935660664614975025408,sans,foo.com,foo.org": 3.6e+09,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Two certs",
|
||||||
|
certs: []string{fooCert, barCert},
|
||||||
|
expected: map[string]float64{
|
||||||
|
"cn,,serial,123624926713171615935660664614975025408,sans,foo.com,foo.org": 3.6e+09,
|
||||||
|
"cn,,serial,152706022658490889223053211416725817058,sans,bar.com,bar.org": 3.6e+07,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
gauge := &gaugeMock{
|
||||||
|
metrics: map[string]float64{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cert := range test.certs {
|
||||||
|
block, _ := pem.Decode([]byte(cert))
|
||||||
|
parsedCert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
appendCertMetric(gauge, parsedCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, gauge.metrics)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -98,8 +98,8 @@ See the list of labels in the dedicated [routing](../routing/providers/docker.md
|
||||||
By default, Traefik watches for [container level labels](https://docs.docker.com/config/labels-custom-metadata/) on a standalone Docker Engine.
|
By default, Traefik watches for [container level labels](https://docs.docker.com/config/labels-custom-metadata/) on a standalone Docker Engine.
|
||||||
|
|
||||||
When using Docker Compose, labels are specified by the directive
|
When using Docker Compose, labels are specified by the directive
|
||||||
[`labels`](https://docs.docker.com/compose/compose-file/#labels) from the
|
[`labels`](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels) from the
|
||||||
["services" objects](https://docs.docker.com/compose/compose-file/#service-configuration-reference).
|
["services" objects](https://docs.docker.com/compose/compose-file/compose-file-v3/#service-configuration-reference).
|
||||||
|
|
||||||
!!! tip "Not Only Docker"
|
!!! tip "Not Only Docker"
|
||||||
Please note that any tool like Nomad, Terraform, Ansible, etc.
|
Please note that any tool like Nomad, Terraform, Ansible, etc.
|
||||||
|
@ -186,7 +186,7 @@ set the [`swarmMode`](#swarmmode) directive to `true`.
|
||||||
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
|
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
|
||||||
|
|
||||||
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the
|
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the
|
||||||
[`deploy`](https://docs.docker.com/compose/compose-file/#labels-1) part of your service.
|
[`deploy`](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels-1) part of your service.
|
||||||
|
|
||||||
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file)).
|
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file)).
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,7 @@ Attach labels to your containers and let Traefik do the rest!
|
||||||
!!! important "Labels in Docker Swarm Mode"
|
!!! important "Labels in Docker Swarm Mode"
|
||||||
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
|
While in Swarm Mode, Traefik uses labels found on services, not on individual containers.
|
||||||
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the `deploy` part of your service.
|
Therefore, if you use a compose file with Swarm Mode, labels should be defined in the `deploy` part of your service.
|
||||||
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/#labels-1)).
|
This behavior is only enabled for docker-compose version 3+ ([Compose file reference](https://docs.docker.com/compose/compose-file/compose-file-v3/#labels-1)).
|
||||||
|
|
||||||
## Routing Configuration
|
## Routing Configuration
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ const (
|
||||||
ddEntryPointOpenConnsName = "entrypoint.connections.open"
|
ddEntryPointOpenConnsName = "entrypoint.connections.open"
|
||||||
ddOpenConnsName = "service.connections.open"
|
ddOpenConnsName = "service.connections.open"
|
||||||
ddServerUpName = "service.server.up"
|
ddServerUpName = "service.server.up"
|
||||||
|
ddTLSCertsNotAfterTimestampName = "tls.certs.notAfterTimestamp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterDatadog registers the metrics pusher if this didn't happen yet and creates a datadog Registry instance.
|
// RegisterDatadog registers the metrics pusher if this didn't happen yet and creates a datadog Registry instance.
|
||||||
|
@ -45,6 +46,7 @@ func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry {
|
||||||
configReloadsFailureCounter: datadogClient.NewCounter(ddConfigReloadsName, 1.0).With(ddConfigReloadsFailureTagName, "true"),
|
configReloadsFailureCounter: datadogClient.NewCounter(ddConfigReloadsName, 1.0).With(ddConfigReloadsFailureTagName, "true"),
|
||||||
lastConfigReloadSuccessGauge: datadogClient.NewGauge(ddLastConfigReloadSuccessName),
|
lastConfigReloadSuccessGauge: datadogClient.NewGauge(ddLastConfigReloadSuccessName),
|
||||||
lastConfigReloadFailureGauge: datadogClient.NewGauge(ddLastConfigReloadFailureName),
|
lastConfigReloadFailureGauge: datadogClient.NewGauge(ddLastConfigReloadFailureName),
|
||||||
|
tlsCertsNotAfterTimestampGauge: datadogClient.NewGauge(ddTLSCertsNotAfterTimestampName),
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AddEntryPointsLabels {
|
if config.AddEntryPointsLabels {
|
||||||
|
|
|
@ -36,6 +36,7 @@ func TestDatadog(t *testing.T) {
|
||||||
"traefik.entrypoint.request.duration:10000.000000|h|#entrypoint:test\n",
|
"traefik.entrypoint.request.duration:10000.000000|h|#entrypoint:test\n",
|
||||||
"traefik.entrypoint.connections.open:1.000000|g|#entrypoint:test\n",
|
"traefik.entrypoint.connections.open:1.000000|g|#entrypoint:test\n",
|
||||||
"traefik.service.server.up:1.000000|g|#service:test,url:http://127.0.0.1,one:two\n",
|
"traefik.service.server.up:1.000000|g|#service:test,url:http://127.0.0.1,one:two\n",
|
||||||
|
"traefik.tls.certs.notAfterTimestamp:1.000000|g|#key:value\n",
|
||||||
}
|
}
|
||||||
|
|
||||||
udp.ShouldReceiveAll(t, expected, func() {
|
udp.ShouldReceiveAll(t, expected, func() {
|
||||||
|
@ -50,5 +51,6 @@ func TestDatadog(t *testing.T) {
|
||||||
datadogRegistry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000)
|
datadogRegistry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000)
|
||||||
datadogRegistry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1)
|
datadogRegistry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1)
|
||||||
datadogRegistry.ServiceServerUpGauge().With("service", "test", "url", "http://127.0.0.1", "one", "two").Set(1)
|
datadogRegistry.ServiceServerUpGauge().With("service", "test", "url", "http://127.0.0.1", "one", "two").Set(1)
|
||||||
|
datadogRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ const (
|
||||||
influxDBEntryPointOpenConnsName = "traefik.entrypoint.connections.open"
|
influxDBEntryPointOpenConnsName = "traefik.entrypoint.connections.open"
|
||||||
influxDBOpenConnsName = "traefik.service.connections.open"
|
influxDBOpenConnsName = "traefik.service.connections.open"
|
||||||
influxDBServerUpName = "traefik.service.server.up"
|
influxDBServerUpName = "traefik.service.server.up"
|
||||||
|
influxDBTLSCertsNotAfterTimestampName = "traefik.tls.certs.notAfterTimestamp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -59,6 +60,7 @@ func RegisterInfluxDB(ctx context.Context, config *types.InfluxDB) Registry {
|
||||||
configReloadsFailureCounter: influxDBClient.NewCounter(influxDBConfigReloadsFailureName),
|
configReloadsFailureCounter: influxDBClient.NewCounter(influxDBConfigReloadsFailureName),
|
||||||
lastConfigReloadSuccessGauge: influxDBClient.NewGauge(influxDBLastConfigReloadSuccessName),
|
lastConfigReloadSuccessGauge: influxDBClient.NewGauge(influxDBLastConfigReloadSuccessName),
|
||||||
lastConfigReloadFailureGauge: influxDBClient.NewGauge(influxDBLastConfigReloadFailureName),
|
lastConfigReloadFailureGauge: influxDBClient.NewGauge(influxDBLastConfigReloadFailureName),
|
||||||
|
tlsCertsNotAfterTimestampGauge: influxDBClient.NewGauge(influxDBTLSCertsNotAfterTimestampName),
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AddEntryPointsLabels {
|
if config.AddEntryPointsLabels {
|
||||||
|
|
|
@ -64,6 +64,16 @@ func TestInfluxDB(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
assertMessage(t, msgEntrypoint, expectedEntrypoint)
|
assertMessage(t, msgEntrypoint, expectedEntrypoint)
|
||||||
|
|
||||||
|
expectedTLS := []string{
|
||||||
|
`(traefik\.tls\.certs\.notAfterTimestamp,key=value value=1) [\d]{19}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
msgTLS := udp.ReceiveString(t, func() {
|
||||||
|
influxDBRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
assertMessage(t, msgTLS, expectedTLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInfluxDBHTTP(t *testing.T) {
|
func TestInfluxDBHTTP(t *testing.T) {
|
||||||
|
@ -121,6 +131,15 @@ func TestInfluxDBHTTP(t *testing.T) {
|
||||||
msgEntrypoint := <-c
|
msgEntrypoint := <-c
|
||||||
|
|
||||||
assertMessage(t, *msgEntrypoint, expectedEntrypoint)
|
assertMessage(t, *msgEntrypoint, expectedEntrypoint)
|
||||||
|
|
||||||
|
expectedTLS := []string{
|
||||||
|
`(traefik\.tls\.certs\.notAfterTimestamp,key=value value=1) [\d]{19}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
influxDBRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
|
||||||
|
msgTLS := <-c
|
||||||
|
|
||||||
|
assertMessage(t, *msgTLS, expectedTLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertMessage(t *testing.T, msg string, patterns []string) {
|
func assertMessage(t *testing.T, msg string, patterns []string) {
|
||||||
|
|
|
@ -21,6 +21,9 @@ type Registry interface {
|
||||||
LastConfigReloadSuccessGauge() metrics.Gauge
|
LastConfigReloadSuccessGauge() metrics.Gauge
|
||||||
LastConfigReloadFailureGauge() metrics.Gauge
|
LastConfigReloadFailureGauge() metrics.Gauge
|
||||||
|
|
||||||
|
// TLS
|
||||||
|
TLSCertsNotAfterTimestampGauge() metrics.Gauge
|
||||||
|
|
||||||
// entry point metrics
|
// entry point metrics
|
||||||
EntryPointReqsCounter() metrics.Counter
|
EntryPointReqsCounter() metrics.Counter
|
||||||
EntryPointReqsTLSCounter() metrics.Counter
|
EntryPointReqsTLSCounter() metrics.Counter
|
||||||
|
@ -50,6 +53,7 @@ func NewMultiRegistry(registries []Registry) Registry {
|
||||||
var configReloadsFailureCounter []metrics.Counter
|
var configReloadsFailureCounter []metrics.Counter
|
||||||
var lastConfigReloadSuccessGauge []metrics.Gauge
|
var lastConfigReloadSuccessGauge []metrics.Gauge
|
||||||
var lastConfigReloadFailureGauge []metrics.Gauge
|
var lastConfigReloadFailureGauge []metrics.Gauge
|
||||||
|
var tlsCertsNotAfterTimestampGauge []metrics.Gauge
|
||||||
var entryPointReqsCounter []metrics.Counter
|
var entryPointReqsCounter []metrics.Counter
|
||||||
var entryPointReqsTLSCounter []metrics.Counter
|
var entryPointReqsTLSCounter []metrics.Counter
|
||||||
var entryPointReqDurationHistogram []ScalableHistogram
|
var entryPointReqDurationHistogram []ScalableHistogram
|
||||||
|
@ -74,6 +78,9 @@ func NewMultiRegistry(registries []Registry) Registry {
|
||||||
if r.LastConfigReloadFailureGauge() != nil {
|
if r.LastConfigReloadFailureGauge() != nil {
|
||||||
lastConfigReloadFailureGauge = append(lastConfigReloadFailureGauge, r.LastConfigReloadFailureGauge())
|
lastConfigReloadFailureGauge = append(lastConfigReloadFailureGauge, r.LastConfigReloadFailureGauge())
|
||||||
}
|
}
|
||||||
|
if r.TLSCertsNotAfterTimestampGauge() != nil {
|
||||||
|
tlsCertsNotAfterTimestampGauge = append(tlsCertsNotAfterTimestampGauge, r.TLSCertsNotAfterTimestampGauge())
|
||||||
|
}
|
||||||
if r.EntryPointReqsCounter() != nil {
|
if r.EntryPointReqsCounter() != nil {
|
||||||
entryPointReqsCounter = append(entryPointReqsCounter, r.EntryPointReqsCounter())
|
entryPointReqsCounter = append(entryPointReqsCounter, r.EntryPointReqsCounter())
|
||||||
}
|
}
|
||||||
|
@ -113,6 +120,7 @@ func NewMultiRegistry(registries []Registry) Registry {
|
||||||
configReloadsFailureCounter: multi.NewCounter(configReloadsFailureCounter...),
|
configReloadsFailureCounter: multi.NewCounter(configReloadsFailureCounter...),
|
||||||
lastConfigReloadSuccessGauge: multi.NewGauge(lastConfigReloadSuccessGauge...),
|
lastConfigReloadSuccessGauge: multi.NewGauge(lastConfigReloadSuccessGauge...),
|
||||||
lastConfigReloadFailureGauge: multi.NewGauge(lastConfigReloadFailureGauge...),
|
lastConfigReloadFailureGauge: multi.NewGauge(lastConfigReloadFailureGauge...),
|
||||||
|
tlsCertsNotAfterTimestampGauge: multi.NewGauge(tlsCertsNotAfterTimestampGauge...),
|
||||||
entryPointReqsCounter: multi.NewCounter(entryPointReqsCounter...),
|
entryPointReqsCounter: multi.NewCounter(entryPointReqsCounter...),
|
||||||
entryPointReqsTLSCounter: multi.NewCounter(entryPointReqsTLSCounter...),
|
entryPointReqsTLSCounter: multi.NewCounter(entryPointReqsTLSCounter...),
|
||||||
entryPointReqDurationHistogram: NewMultiHistogram(entryPointReqDurationHistogram...),
|
entryPointReqDurationHistogram: NewMultiHistogram(entryPointReqDurationHistogram...),
|
||||||
|
@ -133,6 +141,7 @@ type standardRegistry struct {
|
||||||
configReloadsFailureCounter metrics.Counter
|
configReloadsFailureCounter metrics.Counter
|
||||||
lastConfigReloadSuccessGauge metrics.Gauge
|
lastConfigReloadSuccessGauge metrics.Gauge
|
||||||
lastConfigReloadFailureGauge metrics.Gauge
|
lastConfigReloadFailureGauge metrics.Gauge
|
||||||
|
tlsCertsNotAfterTimestampGauge metrics.Gauge
|
||||||
entryPointReqsCounter metrics.Counter
|
entryPointReqsCounter metrics.Counter
|
||||||
entryPointReqsTLSCounter metrics.Counter
|
entryPointReqsTLSCounter metrics.Counter
|
||||||
entryPointReqDurationHistogram ScalableHistogram
|
entryPointReqDurationHistogram ScalableHistogram
|
||||||
|
@ -169,6 +178,10 @@ func (r *standardRegistry) LastConfigReloadFailureGauge() metrics.Gauge {
|
||||||
return r.lastConfigReloadFailureGauge
|
return r.lastConfigReloadFailureGauge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *standardRegistry) TLSCertsNotAfterTimestampGauge() metrics.Gauge {
|
||||||
|
return r.tlsCertsNotAfterTimestampGauge
|
||||||
|
}
|
||||||
|
|
||||||
func (r *standardRegistry) EntryPointReqsCounter() metrics.Counter {
|
func (r *standardRegistry) EntryPointReqsCounter() metrics.Counter {
|
||||||
return r.entryPointReqsCounter
|
return r.entryPointReqsCounter
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ const (
|
||||||
configLastReloadSuccessName = metricConfigPrefix + "last_reload_success"
|
configLastReloadSuccessName = metricConfigPrefix + "last_reload_success"
|
||||||
configLastReloadFailureName = metricConfigPrefix + "last_reload_failure"
|
configLastReloadFailureName = metricConfigPrefix + "last_reload_failure"
|
||||||
|
|
||||||
|
// TLS.
|
||||||
|
metricsTLSPrefix = MetricNamePrefix + "tls_"
|
||||||
|
tlsCertsNotAfterTimestamp = metricsTLSPrefix + "certs_not_after"
|
||||||
|
|
||||||
// entry point.
|
// entry point.
|
||||||
metricEntryPointPrefix = MetricNamePrefix + "entrypoint_"
|
metricEntryPointPrefix = MetricNamePrefix + "entrypoint_"
|
||||||
entryPointReqsTotalName = metricEntryPointPrefix + "requests_total"
|
entryPointReqsTotalName = metricEntryPointPrefix + "requests_total"
|
||||||
|
@ -121,12 +125,17 @@ func initStandardRegistry(config *types.Prometheus) Registry {
|
||||||
Name: configLastReloadFailureName,
|
Name: configLastReloadFailureName,
|
||||||
Help: "Last config reload failure",
|
Help: "Last config reload failure",
|
||||||
}, []string{})
|
}, []string{})
|
||||||
|
tlsCertsNotAfterTimesptamp := newGaugeFrom(promState.collectors, stdprometheus.GaugeOpts{
|
||||||
|
Name: tlsCertsNotAfterTimestamp,
|
||||||
|
Help: "Certificate expiration timestamp",
|
||||||
|
}, []string{"cn", "serial", "sans"})
|
||||||
|
|
||||||
promState.describers = []func(chan<- *stdprometheus.Desc){
|
promState.describers = []func(chan<- *stdprometheus.Desc){
|
||||||
configReloads.cv.Describe,
|
configReloads.cv.Describe,
|
||||||
configReloadsFailures.cv.Describe,
|
configReloadsFailures.cv.Describe,
|
||||||
lastConfigReloadSuccess.gv.Describe,
|
lastConfigReloadSuccess.gv.Describe,
|
||||||
lastConfigReloadFailure.gv.Describe,
|
lastConfigReloadFailure.gv.Describe,
|
||||||
|
tlsCertsNotAfterTimesptamp.gv.Describe,
|
||||||
}
|
}
|
||||||
|
|
||||||
reg := &standardRegistry{
|
reg := &standardRegistry{
|
||||||
|
@ -136,6 +145,7 @@ func initStandardRegistry(config *types.Prometheus) Registry {
|
||||||
configReloadsFailureCounter: configReloadsFailures,
|
configReloadsFailureCounter: configReloadsFailures,
|
||||||
lastConfigReloadSuccessGauge: lastConfigReloadSuccess,
|
lastConfigReloadSuccessGauge: lastConfigReloadSuccess,
|
||||||
lastConfigReloadFailureGauge: lastConfigReloadFailure,
|
lastConfigReloadFailureGauge: lastConfigReloadFailure,
|
||||||
|
tlsCertsNotAfterTimestampGauge: tlsCertsNotAfterTimesptamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AddEntryPointsLabels {
|
if config.AddEntryPointsLabels {
|
||||||
|
@ -163,11 +173,13 @@ func initStandardRegistry(config *types.Prometheus) Registry {
|
||||||
entryPointReqDurations.hv.Describe,
|
entryPointReqDurations.hv.Describe,
|
||||||
entryPointOpenConns.gv.Describe,
|
entryPointOpenConns.gv.Describe,
|
||||||
}...)
|
}...)
|
||||||
|
|
||||||
reg.entryPointReqsCounter = entryPointReqs
|
reg.entryPointReqsCounter = entryPointReqs
|
||||||
reg.entryPointReqsTLSCounter = entryPointReqsTLS
|
reg.entryPointReqsTLSCounter = entryPointReqsTLS
|
||||||
reg.entryPointReqDurationHistogram, _ = NewHistogramWithScale(entryPointReqDurations, time.Second)
|
reg.entryPointReqDurationHistogram, _ = NewHistogramWithScale(entryPointReqDurations, time.Second)
|
||||||
reg.entryPointOpenConnsGauge = entryPointOpenConns
|
reg.entryPointOpenConnsGauge = entryPointOpenConns
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AddServicesLabels {
|
if config.AddServicesLabels {
|
||||||
serviceReqs := newCounterFrom(promState.collectors, stdprometheus.CounterOpts{
|
serviceReqs := newCounterFrom(promState.collectors, stdprometheus.CounterOpts{
|
||||||
Name: serviceReqsTotalName,
|
Name: serviceReqsTotalName,
|
||||||
|
|
|
@ -116,6 +116,11 @@ func TestPrometheus(t *testing.T) {
|
||||||
prometheusRegistry.LastConfigReloadSuccessGauge().Set(float64(time.Now().Unix()))
|
prometheusRegistry.LastConfigReloadSuccessGauge().Set(float64(time.Now().Unix()))
|
||||||
prometheusRegistry.LastConfigReloadFailureGauge().Set(float64(time.Now().Unix()))
|
prometheusRegistry.LastConfigReloadFailureGauge().Set(float64(time.Now().Unix()))
|
||||||
|
|
||||||
|
prometheusRegistry.
|
||||||
|
TLSCertsNotAfterTimestampGauge().
|
||||||
|
With("cn", "value", "serial", "value", "sans", "value").
|
||||||
|
Set(float64(time.Now().Unix()))
|
||||||
|
|
||||||
prometheusRegistry.
|
prometheusRegistry.
|
||||||
EntryPointReqsCounter().
|
EntryPointReqsCounter().
|
||||||
With("code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http", "entrypoint", "http").
|
With("code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http", "entrypoint", "http").
|
||||||
|
@ -175,6 +180,15 @@ func TestPrometheus(t *testing.T) {
|
||||||
name: configLastReloadFailureName,
|
name: configLastReloadFailureName,
|
||||||
assert: buildTimestampAssert(t, configLastReloadFailureName),
|
assert: buildTimestampAssert(t, configLastReloadFailureName),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: tlsCertsNotAfterTimestamp,
|
||||||
|
labels: map[string]string{
|
||||||
|
"cn": "value",
|
||||||
|
"serial": "value",
|
||||||
|
"sans": "value",
|
||||||
|
},
|
||||||
|
assert: buildTimestampAssert(t, tlsCertsNotAfterTimestamp),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: entryPointReqsTotalName,
|
name: entryPointReqsTotalName,
|
||||||
labels: map[string]string{
|
labels: map[string]string{
|
||||||
|
|
|
@ -29,6 +29,7 @@ const (
|
||||||
statsdEntryPointOpenConnsName = "entrypoint.connections.open"
|
statsdEntryPointOpenConnsName = "entrypoint.connections.open"
|
||||||
statsdOpenConnsName = "service.connections.open"
|
statsdOpenConnsName = "service.connections.open"
|
||||||
statsdServerUpName = "service.server.up"
|
statsdServerUpName = "service.server.up"
|
||||||
|
statsdTLSCertsNotAfterTimestampName = "tls.certs.notAfterTimestamp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterStatsd registers the metrics pusher if this didn't happen yet and creates a statsd Registry instance.
|
// RegisterStatsd registers the metrics pusher if this didn't happen yet and creates a statsd Registry instance.
|
||||||
|
@ -52,6 +53,7 @@ func RegisterStatsd(ctx context.Context, config *types.Statsd) Registry {
|
||||||
configReloadsFailureCounter: statsdClient.NewCounter(statsdConfigReloadsFailureName, 1.0),
|
configReloadsFailureCounter: statsdClient.NewCounter(statsdConfigReloadsFailureName, 1.0),
|
||||||
lastConfigReloadSuccessGauge: statsdClient.NewGauge(statsdLastConfigReloadSuccessName),
|
lastConfigReloadSuccessGauge: statsdClient.NewGauge(statsdLastConfigReloadSuccessName),
|
||||||
lastConfigReloadFailureGauge: statsdClient.NewGauge(statsdLastConfigReloadFailureName),
|
lastConfigReloadFailureGauge: statsdClient.NewGauge(statsdLastConfigReloadFailureName),
|
||||||
|
tlsCertsNotAfterTimestampGauge: statsdClient.NewGauge(statsdTLSCertsNotAfterTimestampName),
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AddEntryPointsLabels {
|
if config.AddEntryPointsLabels {
|
||||||
|
|
|
@ -35,6 +35,7 @@ func TestStatsD(t *testing.T) {
|
||||||
"traefik.entrypoint.request.duration:10000.000000|ms",
|
"traefik.entrypoint.request.duration:10000.000000|ms",
|
||||||
"traefik.entrypoint.connections.open:1.000000|g\n",
|
"traefik.entrypoint.connections.open:1.000000|g\n",
|
||||||
"traefik.service.server.up:1.000000|g\n",
|
"traefik.service.server.up:1.000000|g\n",
|
||||||
|
"tls.certs.notAfterTimestamp:1.000000|g\n",
|
||||||
}
|
}
|
||||||
|
|
||||||
udp.ShouldReceiveAll(t, expected, func() {
|
udp.ShouldReceiveAll(t, expected, func() {
|
||||||
|
@ -49,6 +50,7 @@ func TestStatsD(t *testing.T) {
|
||||||
statsdRegistry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000)
|
statsdRegistry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000)
|
||||||
statsdRegistry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1)
|
statsdRegistry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1)
|
||||||
statsdRegistry.ServiceServerUpGauge().With("service:test", "url", "http://127.0.0.1").Set(1)
|
statsdRegistry.ServiceServerUpGauge().With("service:test", "url", "http://127.0.0.1").Set(1)
|
||||||
|
statsdRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +77,7 @@ func TestStatsDWithPrefix(t *testing.T) {
|
||||||
"testPrefix.entrypoint.request.duration:10000.000000|ms",
|
"testPrefix.entrypoint.request.duration:10000.000000|ms",
|
||||||
"testPrefix.entrypoint.connections.open:1.000000|g\n",
|
"testPrefix.entrypoint.connections.open:1.000000|g\n",
|
||||||
"testPrefix.service.server.up:1.000000|g\n",
|
"testPrefix.service.server.up:1.000000|g\n",
|
||||||
|
"tls.certs.notAfterTimestamp:1.000000|g\n",
|
||||||
}
|
}
|
||||||
|
|
||||||
udp.ShouldReceiveAll(t, expected, func() {
|
udp.ShouldReceiveAll(t, expected, func() {
|
||||||
|
@ -89,5 +92,6 @@ func TestStatsDWithPrefix(t *testing.T) {
|
||||||
statsdRegistry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000)
|
statsdRegistry.EntryPointReqDurationHistogram().With("entrypoint", "test").Observe(10000)
|
||||||
statsdRegistry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1)
|
statsdRegistry.EntryPointOpenConnsGauge().With("entrypoint", "test").Set(1)
|
||||||
statsdRegistry.ServiceServerUpGauge().With("service:test", "url", "http://127.0.0.1").Set(1)
|
statsdRegistry.ServiceServerUpGauge().With("service:test", "url", "http://127.0.0.1").Set(1)
|
||||||
|
statsdRegistry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,15 +56,16 @@ func (c CertificateStore) getDefaultCertificateDomains() []string {
|
||||||
|
|
||||||
// GetAllDomains return a slice with all the certificate domain.
|
// GetAllDomains return a slice with all the certificate domain.
|
||||||
func (c CertificateStore) GetAllDomains() []string {
|
func (c CertificateStore) GetAllDomains() []string {
|
||||||
allCerts := c.getDefaultCertificateDomains()
|
allDomains := c.getDefaultCertificateDomains()
|
||||||
|
|
||||||
// Get dynamic certificates
|
// Get dynamic certificates
|
||||||
if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil {
|
if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil {
|
||||||
for domains := range c.DynamicCerts.Get().(map[string]*tls.Certificate) {
|
for domain := range c.DynamicCerts.Get().(map[string]*tls.Certificate) {
|
||||||
allCerts = append(allCerts, domains)
|
allDomains = append(allDomains, domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allCerts
|
|
||||||
|
return allDomains
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBestCertificate returns the best match certificate, and caches the response.
|
// GetBestCertificate returns the best match certificate, and caches the response.
|
||||||
|
|
|
@ -131,6 +131,27 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
|
||||||
return tlsConfig, err
|
return tlsConfig, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCertificates returns all stored certificates.
|
||||||
|
func (m *Manager) GetCertificates() []*x509.Certificate {
|
||||||
|
var certificates []*x509.Certificate
|
||||||
|
|
||||||
|
// We iterate over all the certificates.
|
||||||
|
for _, store := range m.stores {
|
||||||
|
if store.DynamicCerts != nil && store.DynamicCerts.Get() != nil {
|
||||||
|
for _, cert := range store.DynamicCerts.Get().(map[string]*tls.Certificate) {
|
||||||
|
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
certificates = append(certificates, x509Cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificates
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) getStore(storeName string) *CertificateStore {
|
func (m *Manager) getStore(storeName string) *CertificateStore {
|
||||||
_, ok := m.stores[storeName]
|
_, ok := m.stores[storeName]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
Loading…
Reference in a new issue