Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions docs/metrics/storage/secret-metrics.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Secret Metrics

| Metric name | Metric type | Description | Labels/tags | Status |
| ------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| kube_secret_annotations | Gauge | Kubernetes annotations converted to Prometheus labels controlled via [--metric-annotations-allowlist](../../developer/cli-arguments.md) | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; <br> `annotations_SECRET_ANNOTATION`=&lt;SECRET_ANNOTATION&gt; | EXPERIMENTAL |
| kube_secret_info | Gauge | | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; | STABLE |
| kube_secret_type | Gauge | | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; <br> `type`=&lt;secret-type&gt; | STABLE |
| kube_secret_labels | Gauge | Kubernetes labels converted to Prometheus labels controlled via [--metric-labels-allowlist](../../developer/cli-arguments.md) | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; <br> `label_SECRET_LABEL`=&lt;SECRET_LABEL&gt; | STABLE |
| kube_secret_created | Gauge | | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; | STABLE |
| kube_secret_metadata_resource_version | Gauge | | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; | EXPERIMENTAL |
| kube_secret_owner | Gauge | | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; <br> `owner_kind`=&lt;owner kind&gt; <br> `owner_name`=&lt;owner name&gt; <br> `owner_is_controller`=&lt;whether owner is controller&gt; | EXPERIMENTAL |
| Metric name | Metric type | Description | Labels/tags | Status |
| -------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| kube_secret_annotations | Gauge | Kubernetes annotations converted to Prometheus labels controlled via [--metric-annotations-allowlist](../../developer/cli-arguments.md) | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; <br> `annotations_SECRET_ANNOTATION`=&lt;SECRET_ANNOTATION&gt; | EXPERIMENTAL |
| kube_secret_info | Gauge | | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; | STABLE |
| kube_secret_type | Gauge | | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; <br> `type`=&lt;secret-type&gt; | STABLE |
| kube_secret_labels | Gauge | Kubernetes labels converted to Prometheus labels controlled via [--metric-labels-allowlist](../../developer/cli-arguments.md) | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; <br> `label_SECRET_LABEL`=&lt;SECRET_LABEL&gt; | STABLE |
| kube_secret_created | Gauge | | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; | STABLE |
| kube_secret_metadata_resource_version | Gauge | | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; | EXPERIMENTAL |
| kube_secret_owner | Gauge | | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; <br> `owner_kind`=&lt;owner kind&gt; <br> `owner_name`=&lt;owner name&gt; <br> `owner_is_controller`=&lt;whether owner is controller&gt; | EXPERIMENTAL |
| kube_secret_tls_cert_not_after_seconds | Gauge | | `secret`=&lt;secret-name&gt; <br> `namespace`=&lt;secret-namespace&gt; <br> `cn`=&lt;certificate common name&gt; <br> `sans`=&lt;certificate subject alternative names&gt; <br> `serial`=&lt;certificate serial number&gt; | EXPERIMENTAL |
56 changes: 56 additions & 0 deletions internal/store/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ package store

import (
"context"
"crypto/x509"
"encoding/pem"
"strconv"
"strings"

v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -142,6 +145,46 @@ func secretMetricFamilies(allowAnnotationsList, allowLabelsList []string) []gene
}
}),
),
*generator.NewFamilyGeneratorWithStability(
"kube_secret_tls_cert_not_after_seconds",
"Unix notAfter timestamp of the TLS certificate in the secret.",
metric.Gauge,
basemetrics.ALPHA,
"",
wrapSecretFunc(func(s *v1.Secret) *metric.Family {
if s.Type != v1.SecretTypeTLS {
return &metric.Family{}
}

labelKeys := []string{"cn", "sans", "serial"}

cert, err := mkCert(s.Data["tls.crt"])
if cert == nil || err != nil {
return &metric.Family{}
}

serial := ""
if cert.SerialNumber != nil {
serial = cert.SerialNumber.String()
}

labelValues := []string{
cert.Subject.CommonName,
strings.Join(cert.DNSNames, ","),
serial,
}

return &metric.Family{
Metrics: []*metric.Metric{
{
LabelKeys: labelKeys,
LabelValues: labelValues,
Value: float64(cert.NotAfter.Unix()),
},
},
}
}),
),
*generator.NewFamilyGeneratorWithStability(
"kube_secret_metadata_resource_version",
"Resource version representing a specific version of secret.",
Expand Down Expand Up @@ -218,6 +261,19 @@ func wrapSecretFunc(f func(*v1.Secret) *metric.Family) func(interface{}) *metric
}
}

func mkCert(certData []byte) (*x509.Certificate, error) {
if len(certData) == 0 {
return nil, nil
}

block, _ := pem.Decode(certData)
if block == nil || len(block.Bytes) == 0 {
return nil, nil
}

return x509.ParseCertificate(block.Bytes)
}

func createSecretListWatch(kubeClient clientset.Interface, ns string, fieldSelector string) cache.ListerWatcher {
return &cache.ListWatch{
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
Expand Down
100 changes: 96 additions & 4 deletions internal/store/secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ limitations under the License.
package store

import (
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"math/rand"
"testing"
"time"

v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -29,6 +36,11 @@ func TestSecretStore(t *testing.T) {
var test = true
startTime := 1501569018
metav1StartTime := metav1.Unix(int64(startTime), 0)
tlsCertExpiration := time.Unix(1893456000, 0)
tlsCertPEM, err := generateTestTLSCertPEM(tlsCertExpiration)
if err != nil {
t.Fatalf("failed to generate tls cert: %v", err)
}
cases := []generateMetricsTestCase{
{
Obj: &v1.Secret{
Expand All @@ -45,19 +57,21 @@ func TestSecretStore(t *testing.T) {
# HELP kube_secret_labels [STABLE] Kubernetes labels converted to Prometheus labels.
# HELP kube_secret_metadata_resource_version Resource version representing a specific version of secret.
# HELP kube_secret_owner Information about the Secret's owner.
# HELP kube_secret_tls_cert_not_after_seconds Unix notAfter timestamp of the TLS certificate in the secret.
# HELP kube_secret_type [STABLE] Type about secret.
# TYPE kube_secret_created gauge
# TYPE kube_secret_info gauge
# TYPE kube_secret_labels gauge
# TYPE kube_secret_metadata_resource_version gauge
# TYPE kube_secret_owner gauge
# TYPE kube_secret_tls_cert_not_after_seconds gauge
# TYPE kube_secret_type gauge
kube_secret_info{namespace="ns1",secret="secret1"} 1
kube_secret_owner{namespace="ns1",owner_is_controller="",owner_kind="",owner_name="",secret="secret1"} 1
kube_secret_type{namespace="ns1",secret="secret1",type="Opaque"} 1
kube_secret_metadata_resource_version{namespace="ns1",secret="secret1"} 0
`,
MetricNames: []string{"kube_secret_info", "kube_secret_metadata_resource_version", "kube_secret_created", "kube_secret_labels", "kube_secret_type", "kube_secret_owner"},
MetricNames: []string{"kube_secret_info", "kube_secret_metadata_resource_version", "kube_secret_created", "kube_secret_labels", "kube_secret_type", "kube_secret_owner", "kube_secret_tls_cert_not_after_seconds"},
},
{
Obj: &v1.Secret{
Expand All @@ -75,19 +89,21 @@ func TestSecretStore(t *testing.T) {
# HELP kube_secret_labels [STABLE] Kubernetes labels converted to Prometheus labels.
# HELP kube_secret_metadata_resource_version Resource version representing a specific version of secret.
# HELP kube_secret_owner Information about the Secret's owner.
# HELP kube_secret_tls_cert_not_after_seconds Unix notAfter timestamp of the TLS certificate in the secret.
# HELP kube_secret_type [STABLE] Type about secret.
# TYPE kube_secret_created gauge
# TYPE kube_secret_info gauge
# TYPE kube_secret_labels gauge
# TYPE kube_secret_metadata_resource_version gauge
# TYPE kube_secret_owner gauge
# TYPE kube_secret_tls_cert_not_after_seconds gauge
# TYPE kube_secret_type gauge
kube_secret_info{namespace="ns2",secret="secret2"} 1
kube_secret_owner{namespace="ns2",owner_is_controller="",owner_kind="",owner_name="",secret="secret2"} 1
kube_secret_type{namespace="ns2",secret="secret2",type="kubernetes.io/service-account-token"} 1
kube_secret_created{namespace="ns2",secret="secret2"} 1.501569018e+09
`,
MetricNames: []string{"kube_secret_info", "kube_secret_metadata_resource_version", "kube_secret_created", "kube_secret_labels", "kube_secret_type", "kube_secret_owner"},
MetricNames: []string{"kube_secret_info", "kube_secret_metadata_resource_version", "kube_secret_created", "kube_secret_labels", "kube_secret_type", "kube_secret_owner", "kube_secret_tls_cert_not_after_seconds"},
},
{
Obj: &v1.Secret{
Expand All @@ -106,20 +122,22 @@ func TestSecretStore(t *testing.T) {
# HELP kube_secret_labels [STABLE] Kubernetes labels converted to Prometheus labels.
# HELP kube_secret_metadata_resource_version Resource version representing a specific version of secret.
# HELP kube_secret_owner Information about the Secret's owner.
# HELP kube_secret_tls_cert_not_after_seconds Unix notAfter timestamp of the TLS certificate in the secret.
# HELP kube_secret_type [STABLE] Type about secret.
# TYPE kube_secret_created gauge
# TYPE kube_secret_info gauge
# TYPE kube_secret_labels gauge
# TYPE kube_secret_metadata_resource_version gauge
# TYPE kube_secret_owner gauge
# TYPE kube_secret_tls_cert_not_after_seconds gauge
# TYPE kube_secret_type gauge
kube_secret_info{namespace="ns3",secret="secret3"} 1
kube_secret_owner{namespace="ns3",owner_is_controller="",owner_kind="",owner_name="",secret="secret3"} 1
kube_secret_type{namespace="ns3",secret="secret3",type="kubernetes.io/dockercfg"} 1
kube_secret_created{namespace="ns3",secret="secret3"} 1.501569018e+09
kube_secret_metadata_resource_version{namespace="ns3",secret="secret3"} 0
`,
MetricNames: []string{"kube_secret_info", "kube_secret_metadata_resource_version", "kube_secret_created", "kube_secret_labels", "kube_secret_type", "kube_secret_owner"},
MetricNames: []string{"kube_secret_info", "kube_secret_metadata_resource_version", "kube_secret_created", "kube_secret_labels", "kube_secret_type", "kube_secret_owner", "kube_secret_tls_cert_not_after_seconds"},
},
{
Obj: &v1.Secret{
Expand All @@ -145,20 +163,57 @@ func TestSecretStore(t *testing.T) {
# HELP kube_secret_labels [STABLE] Kubernetes labels converted to Prometheus labels.
# HELP kube_secret_metadata_resource_version Resource version representing a specific version of secret.
# HELP kube_secret_owner Information about the Secret's owner.
# HELP kube_secret_tls_cert_not_after_seconds Unix notAfter timestamp of the TLS certificate in the secret.
# HELP kube_secret_type [STABLE] Type about secret.
# TYPE kube_secret_created gauge
# TYPE kube_secret_info gauge
# TYPE kube_secret_labels gauge
# TYPE kube_secret_metadata_resource_version gauge
# TYPE kube_secret_owner gauge
# TYPE kube_secret_tls_cert_not_after_seconds gauge
# TYPE kube_secret_type gauge
kube_secret_info{namespace="ns4",secret="secret4"} 1
kube_secret_owner{namespace="ns4",owner_is_controller="true",owner_kind="ManagedSecret",owner_name="managed-secret4",secret="secret4"} 1
kube_secret_type{namespace="ns4",secret="secret4",type="Opaque"} 1
kube_secret_created{namespace="ns4",secret="secret4"} 1.501569018e+09
kube_secret_metadata_resource_version{namespace="ns4",secret="secret4"} 0
`,
MetricNames: []string{"kube_secret_info", "kube_secret_metadata_resource_version", "kube_secret_created", "kube_secret_labels", "kube_secret_type", "kube_secret_owner"},
MetricNames: []string{"kube_secret_info", "kube_secret_metadata_resource_version", "kube_secret_created", "kube_secret_labels", "kube_secret_type", "kube_secret_owner", "kube_secret_tls_cert_not_after_seconds"},
},
{
Obj: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "secret5",
Namespace: "ns5",
ResourceVersion: "1",
},
Type: v1.SecretTypeTLS,
Data: map[string][]byte{
"tls.crt": tlsCertPEM,
},
},
Want: `
# HELP kube_secret_created [STABLE] Unix creation timestamp
# HELP kube_secret_info [STABLE] Information about secret.
# HELP kube_secret_labels [STABLE] Kubernetes labels converted to Prometheus labels.
# HELP kube_secret_metadata_resource_version Resource version representing a specific version of secret.
# HELP kube_secret_owner Information about the Secret's owner.
# HELP kube_secret_tls_cert_not_after_seconds Unix notAfter timestamp of the TLS certificate in the secret.
# HELP kube_secret_type [STABLE] Type about secret.
# TYPE kube_secret_created gauge
# TYPE kube_secret_info gauge
# TYPE kube_secret_labels gauge
# TYPE kube_secret_metadata_resource_version gauge
# TYPE kube_secret_owner gauge
# TYPE kube_secret_tls_cert_not_after_seconds gauge
# TYPE kube_secret_type gauge
kube_secret_info{namespace="ns5",secret="secret5"} 1
kube_secret_owner{namespace="ns5",owner_is_controller="",owner_kind="",owner_name="",secret="secret5"} 1
kube_secret_type{namespace="ns5",secret="secret5",type="kubernetes.io/tls"} 1
kube_secret_metadata_resource_version{namespace="ns5",secret="secret5"} 1
kube_secret_tls_cert_not_after_seconds{cn="test",namespace="ns5",sans="test,test2",secret="secret5",serial="1"} 1.893456e+09
`,
MetricNames: []string{"kube_secret_info", "kube_secret_metadata_resource_version", "kube_secret_created", "kube_secret_labels", "kube_secret_type", "kube_secret_owner", "kube_secret_tls_cert_not_after_seconds"},
},
}
for i, c := range cases {
Expand All @@ -170,3 +225,40 @@ func TestSecretStore(t *testing.T) {

}
}

type deterministicReader struct {
random *rand.Rand
}

func (r *deterministicReader) Read(p []byte) (int, error) {
for i := range p {
p[i] = byte(r.random.Intn(256))
}
return len(p), nil
}

func generateTestTLSCertPEM(notAfter time.Time) ([]byte, error) {
reader := &deterministicReader{random: rand.New(rand.NewSource(1))} //nolint:gosec
key, err := rsa.GenerateKey(reader, 2048)
if err != nil {
return nil, err
}

template := &x509.Certificate{
Subject: pkix.Name{
CommonName: "test",
},
DNSNames: []string{"test", "test2"},
SerialNumber: big.NewInt(1),
NotBefore: time.Unix(0, 0),
NotAfter: notAfter,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
}

derBytes, err := x509.CreateCertificate(reader, template, template, &key.PublicKey, key)
if err != nil {
return nil, err
}

return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
}