You've already forked ssl_exporter
mirror of
https://github.com/ribbybibby/ssl_exporter.git
synced 2025-07-09 23:45:52 +02:00
feat: add http_file prober
This commit is contained in:
21
README.md
21
README.md
@ -1,9 +1,11 @@
|
|||||||
# SSL Certificate Exporter
|
# SSL Certificate Exporter
|
||||||
|
|
||||||
Exports metrics for certificates collected from various sources:
|
Exports metrics for certificates collected from various sources:
|
||||||
|
|
||||||
- [TCP probes](#tcp)
|
- [TCP probes](#tcp)
|
||||||
- [HTTPS probes](#https)
|
- [HTTPS probes](#https)
|
||||||
- [PEM files](#file)
|
- [PEM files](#file)
|
||||||
|
- [PEM files hosted via HTTP](#http_file)
|
||||||
- [Kubernetes secrets](#kubernetes)
|
- [Kubernetes secrets](#kubernetes)
|
||||||
- [Kubeconfig files](#kubeconfig)
|
- [Kubeconfig files](#kubeconfig)
|
||||||
|
|
||||||
@ -57,9 +59,9 @@ Flags:
|
|||||||
## Metrics
|
## Metrics
|
||||||
|
|
||||||
| Metric | Meaning | Labels | Probers |
|
| Metric | Meaning | Labels | Probers |
|
||||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ---------- |
|
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------- |
|
||||||
| ssl_cert_not_after | The date after which a peer certificate expires. Expressed as a Unix Epoch Time. | serial_no, issuer_cn, cn, dnsnames, ips, emails, ou | tcp, https |
|
| ssl_cert_not_after | The date after which a peer certificate expires. Expressed as a Unix Epoch Time. | serial_no, issuer_cn, cn, dnsnames, ips, emails, ou | tcp, https, http_file |
|
||||||
| ssl_cert_not_before | The date before which a peer certificate is not valid. Expressed as a Unix Epoch Time. | serial_no, issuer_cn, cn, dnsnames, ips, emails, ou | tcp, https |
|
| ssl_cert_not_before | The date before which a peer certificate is not valid. Expressed as a Unix Epoch Time. | serial_no, issuer_cn, cn, dnsnames, ips, emails, ou | tcp, https, http_file |
|
||||||
| ssl_file_cert_not_after | The date after which a certificate found by the file prober expires. Expressed as a Unix Epoch Time. | file, serial_no, issuer_cn, cn, dnsnames, ips, emails, ou | file |
|
| ssl_file_cert_not_after | The date after which a certificate found by the file prober expires. Expressed as a Unix Epoch Time. | file, serial_no, issuer_cn, cn, dnsnames, ips, emails, ou | file |
|
||||||
| ssl_file_cert_not_before | The date before which a certificate found by the file prober is not valid. Expressed as a Unix Epoch Time. | file, serial_no, issuer_cn, cn, dnsnames, ips, emails, ou | file |
|
| ssl_file_cert_not_before | The date before which a certificate found by the file prober is not valid. Expressed as a Unix Epoch Time. | file, serial_no, issuer_cn, cn, dnsnames, ips, emails, ou | file |
|
||||||
| ssl_kubernetes_cert_not_after | The date after which a certificate found by the kubernetes prober expires. Expressed as a Unix Epoch Time. | namespace, secret, key, serial_no, issuer_cn, cn, dnsnames, ips, emails, ou | kubernetes |
|
| ssl_kubernetes_cert_not_after | The date after which a certificate found by the kubernetes prober expires. Expressed as a Unix Epoch Time. | namespace, secret, key, serial_no, issuer_cn, cn, dnsnames, ips, emails, ou | kubernetes |
|
||||||
@ -175,6 +177,19 @@ scrape_configs:
|
|||||||
replacement: ${1}:9219
|
replacement: ${1}:9219
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### HTTP File
|
||||||
|
|
||||||
|
The `http_file` prober exports `ssl_cert_not_after` and
|
||||||
|
`ssl_cert_not_before` metrics for PEM encoded certificates hosted via HTTP or
|
||||||
|
HTTPS.
|
||||||
|
|
||||||
|
This is useful for monitoring PEM certificates that are served from a webserver
|
||||||
|
for the purposes of [BIMI email authentication](https://postmarkapp.com/blog/what-the-heck-is-bimi).
|
||||||
|
|
||||||
|
```
|
||||||
|
curl 'localhost:9219/probe?module=http_file&target=https://amplify.valimail.com/bimi/time-warner/rWgzqvey7wX-cable_news_network_inc.pem'
|
||||||
|
```
|
||||||
|
|
||||||
### Kubernetes
|
### Kubernetes
|
||||||
|
|
||||||
The `kubernetes` prober exports `ssl_kubernetes_cert_not_after` and
|
The `kubernetes` prober exports `ssl_kubernetes_cert_not_after` and
|
||||||
|
@ -26,6 +26,9 @@ var (
|
|||||||
"https": Module{
|
"https": Module{
|
||||||
Prober: "https",
|
Prober: "https",
|
||||||
},
|
},
|
||||||
|
"http_file": Module{
|
||||||
|
Prober: "http_file",
|
||||||
|
},
|
||||||
"file": Module{
|
"file": Module{
|
||||||
Prober: "file",
|
Prober: "file",
|
||||||
},
|
},
|
||||||
@ -71,6 +74,7 @@ type Module struct {
|
|||||||
Timeout time.Duration `yaml:"timeout,omitempty"`
|
Timeout time.Duration `yaml:"timeout,omitempty"`
|
||||||
TLSConfig TLSConfig `yaml:"tls_config,omitempty"`
|
TLSConfig TLSConfig `yaml:"tls_config,omitempty"`
|
||||||
HTTPS HTTPSProbe `yaml:"https,omitempty"`
|
HTTPS HTTPSProbe `yaml:"https,omitempty"`
|
||||||
|
HTTPFile HTTPFileProbe `yaml:"http_file,omitempty"`
|
||||||
TCP TCPProbe `yaml:"tcp,omitempty"`
|
TCP TCPProbe `yaml:"tcp,omitempty"`
|
||||||
Kubernetes KubernetesProbe `yaml:"kubernetes,omitempty"`
|
Kubernetes KubernetesProbe `yaml:"kubernetes,omitempty"`
|
||||||
}
|
}
|
||||||
@ -137,6 +141,11 @@ type HTTPSProbe struct {
|
|||||||
ProxyURL URL `yaml:"proxy_url,omitempty"`
|
ProxyURL URL `yaml:"proxy_url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPFileProbe configures a http_file probe
|
||||||
|
type HTTPFileProbe struct {
|
||||||
|
ProxyURL URL `yaml:"proxy_url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// KubernetesProbe configures a kubernetes probe
|
// KubernetesProbe configures a kubernetes probe
|
||||||
type KubernetesProbe struct {
|
type KubernetesProbe struct {
|
||||||
Kubeconfig string `yaml:"kubeconfig,omitempty"`
|
Kubeconfig string `yaml:"kubeconfig,omitempty"`
|
||||||
|
@ -33,6 +33,8 @@ modules:
|
|||||||
prober: tcp
|
prober: tcp
|
||||||
tcp:
|
tcp:
|
||||||
starttls: smtp
|
starttls: smtp
|
||||||
|
http_file:
|
||||||
|
prober: http_file
|
||||||
file:
|
file:
|
||||||
prober: file
|
prober: file
|
||||||
file_ca_certificates:
|
file_ca_certificates:
|
||||||
|
83
prober/http_file.go
Normal file
83
prober/http_file.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package prober
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/go-kit/log"
|
||||||
|
"github.com/go-kit/log/level"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/ribbybibby/ssl_exporter/v2/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProbeHTTPFile performs a http_file probe
|
||||||
|
func ProbeHTTPFile(ctx context.Context, logger log.Logger, target string, module config.Module, registry *prometheus.Registry) error {
|
||||||
|
tlsConfig, err := config.NewTLSConfig(&module.TLSConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetURL, err := url.Parse(target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If server name isn't set, then use the target hostname
|
||||||
|
if tlsConfig.ServerName == "" {
|
||||||
|
tlsConfig.ServerName = targetURL.Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := http.ProxyFromEnvironment
|
||||||
|
if module.HTTPS.ProxyURL.URL != nil {
|
||||||
|
proxy = http.ProxyURL(module.HTTPS.ProxyURL.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
Proxy: proxy,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue a GET request to the target
|
||||||
|
request, err := http.NewRequest(http.MethodGet, targetURL.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request = request.WithContext(ctx)
|
||||||
|
resp, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_, err := io.Copy(ioutil.Discard, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
level.Error(logger).Log("msg", err)
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
certs, err := decodeCertificates(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(certs) == 0 {
|
||||||
|
return fmt.Errorf("no certificates in response body")
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectCertificateMetrics(certs, registry)
|
||||||
|
}
|
81
prober/http_file_test.go
Normal file
81
prober/http_file_test.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package prober
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/ribbybibby/ssl_exporter/v2/config"
|
||||||
|
"github.com/ribbybibby/ssl_exporter/v2/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProbeHTTPFile(t *testing.T) {
|
||||||
|
certPEM, _ := test.GenerateTestCertificate(time.Now().Add(time.Hour * 1))
|
||||||
|
block, _ := pem.Decode([]byte(certPEM))
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parsing cert: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write(certPEM)
|
||||||
|
}))
|
||||||
|
|
||||||
|
server.Start()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeHTTPFile(ctx, newTestLogger(), server.URL+"/file", config.Module{}, registry); err != nil {
|
||||||
|
t.Fatalf("error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCertificateMetrics(cert, registry, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProbeHTTPFile_HTTPS(t *testing.T) {
|
||||||
|
server, certPEM, _, caFile, teardown, err := test.SetupHTTPSServer()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
block, _ := pem.Decode([]byte(certPEM))
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parsing cert: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write(certPEM)
|
||||||
|
})
|
||||||
|
|
||||||
|
server.StartTLS()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
module := config.Module{
|
||||||
|
TLSConfig: config.TLSConfig{
|
||||||
|
CAFile: caFile,
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeHTTPFile(ctx, newTestLogger(), server.URL+"/file", module, registry); err != nil {
|
||||||
|
t.Fatalf("error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCertificateMetrics(cert, registry, t)
|
||||||
|
}
|
@ -13,6 +13,7 @@ var (
|
|||||||
Probers = map[string]ProbeFn{
|
Probers = map[string]ProbeFn{
|
||||||
"https": ProbeHTTPS,
|
"https": ProbeHTTPS,
|
||||||
"http": ProbeHTTPS,
|
"http": ProbeHTTPS,
|
||||||
|
"http_file": ProbeHTTPFile,
|
||||||
"tcp": ProbeTCP,
|
"tcp": ProbeTCP,
|
||||||
"file": ProbeFile,
|
"file": ProbeFile,
|
||||||
"kubernetes": ProbeKubernetes,
|
"kubernetes": ProbeKubernetes,
|
||||||
|
Reference in New Issue
Block a user