From dfeb02b4ae81d01d097789e187d6b99f19dda526 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Wed, 22 Apr 2026 19:32:11 -0400 Subject: [PATCH] feat(config): add CA certificate config value Signed-off-by: Xe Iaso --- lib/config/metrics.go | 37 ++++++++++++++++------- lib/config/metrics_test.go | 38 ++++++++++++++++++++++++ lib/config/testdata/tls/1.1.1.1/cert.pem | 12 ++++++++ lib/config/testdata/tls/1.1.1.1/key.pem | 6 ++++ lib/config/testdata/tls/minica-key.pem | 6 ++++ lib/config/testdata/tls/minica.pem | 13 ++++++++ 6 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 lib/config/testdata/tls/1.1.1.1/cert.pem create mode 100644 lib/config/testdata/tls/1.1.1.1/key.pem create mode 100644 lib/config/testdata/tls/minica-key.pem create mode 100644 lib/config/testdata/tls/minica.pem diff --git a/lib/config/metrics.go b/lib/config/metrics.go index 327e6c5f..a4fcc7fb 100644 --- a/lib/config/metrics.go +++ b/lib/config/metrics.go @@ -2,6 +2,7 @@ package config import ( "crypto/tls" + "crypto/x509" "errors" "fmt" "os" @@ -9,17 +10,18 @@ import ( ) var ( - ErrInvalidMetricsConfig = errors.New("config: invalid metrics configuration") - ErrInvalidMetricsTLSConfig = errors.New("config: invalid metrics TLS configuration") - ErrNoMetricsBind = errors.New("config.Metrics: must define bind") - ErrNoMetricsNetwork = errors.New("config.Metrics: must define network") - ErrNoMetricsSocketMode = errors.New("config.Metrics: must define socket mode when using unix sockets") - ErrInvalidMetricsSocketMode = errors.New("config.Metrics: invalid unix socket mode") - ErrInvalidMetricsNetwork = errors.New("config.Metrics: invalid metrics network") - ErrNoMetricsTLSCertificate = errors.New("config.Metrics.TLS: must define certificate file") - ErrNoMetricsTLSKey = errors.New("config.Metrics.TLS: must define key file") - ErrInvalidMetricsTLSKeypair = errors.New("config.Metrics.TLS: keypair is invalid") - ErrCantReadFile = errors.New("config: can't read required file") + ErrInvalidMetricsConfig = errors.New("config: invalid metrics configuration") + ErrInvalidMetricsTLSConfig = errors.New("config: invalid metrics TLS configuration") + ErrNoMetricsBind = errors.New("config.Metrics: must define bind") + ErrNoMetricsNetwork = errors.New("config.Metrics: must define network") + ErrNoMetricsSocketMode = errors.New("config.Metrics: must define socket mode when using unix sockets") + ErrInvalidMetricsSocketMode = errors.New("config.Metrics: invalid unix socket mode") + ErrInvalidMetricsNetwork = errors.New("config.Metrics: invalid metrics network") + ErrNoMetricsTLSCertificate = errors.New("config.Metrics.TLS: must define certificate file") + ErrNoMetricsTLSKey = errors.New("config.Metrics.TLS: must define key file") + ErrInvalidMetricsTLSKeypair = errors.New("config.Metrics.TLS: keypair is invalid") + ErrInvalidMetricsCACertificate = errors.New("config.Metrics.TLS: invalid CA certificate") + ErrCantReadFile = errors.New("config: can't read required file") ) type Metrics struct { @@ -70,6 +72,7 @@ func (m *Metrics) Valid() error { type MetricsTLS struct { Certificate string `json:"certificate" yaml:"certificate"` Key string `json:"key" yaml:"key"` + CA string `json:"ca" yaml:"ca"` } func (mt *MetricsTLS) Valid() error { @@ -95,6 +98,18 @@ func (mt *MetricsTLS) Valid() error { errs = append(errs, fmt.Errorf("%w: %w", ErrInvalidMetricsTLSKeypair, err)) } + if mt.CA != "" { + caCert, err := os.ReadFile(mt.CA) + if err != nil { + errs = append(errs, fmt.Errorf("%w %s: %w", ErrCantReadFile, mt.CA, err)) + } + + certPool := x509.NewCertPool() + if !certPool.AppendCertsFromPEM(caCert) { + errs = append(errs, fmt.Errorf("%w %s", ErrInvalidMetricsCACertificate, mt.CA)) + } + } + if len(errs) != 0 { return errors.Join(ErrInvalidMetricsTLSConfig, errors.Join(errs...)) } diff --git a/lib/config/metrics_test.go b/lib/config/metrics_test.go index ec207d29..2e3335f5 100644 --- a/lib/config/metrics_test.go +++ b/lib/config/metrics_test.go @@ -119,6 +119,44 @@ func TestMetricsValid(t *testing.T) { }, err: ErrInvalidMetricsTLSKeypair, }, + { + name: "mTLS with CA", + input: &Metrics{ + Bind: ":9090", + Network: "tcp", + TLS: &MetricsTLS{ + Certificate: "./testdata/tls/selfsigned.crt", + Key: "./testdata/tls/selfsigned.key", + CA: "./testdata/tls/minica.pem", + }, + }, + }, + { + name: "mTLS with nonexistent CA", + input: &Metrics{ + Bind: ":9090", + Network: "tcp", + TLS: &MetricsTLS{ + Certificate: "./testdata/tls/selfsigned.crt", + Key: "./testdata/tls/selfsigned.key", + CA: "./testdata/tls/nonexistent.crt", + }, + }, + err: ErrCantReadFile, + }, + { + name: "mTLS with invalid CA", + input: &Metrics{ + Bind: ":9090", + Network: "tcp", + TLS: &MetricsTLS{ + Certificate: "./testdata/tls/selfsigned.crt", + Key: "./testdata/tls/selfsigned.key", + CA: "./testdata/tls/invalid.crt", + }, + }, + err: ErrInvalidMetricsCACertificate, + }, } { t.Run(tt.name, func(t *testing.T) { if err := tt.input.Valid(); !errors.Is(err, tt.err) { diff --git a/lib/config/testdata/tls/1.1.1.1/cert.pem b/lib/config/testdata/tls/1.1.1.1/cert.pem new file mode 100644 index 00000000..ef50a00a --- /dev/null +++ b/lib/config/testdata/tls/1.1.1.1/cert.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIB1zCCAVygAwIBAgIIYO0SAFtXlVgwCgYIKoZIzj0EAwMwIDEeMBwGA1UEAxMV +bWluaWNhIHJvb3QgY2EgNDE2MmMwMB4XDTI2MDQyMjIzMjUwMVoXDTI4MDUyMjIz +MjUwMVowEjEQMA4GA1UEAxMHMS4xLjEuMTB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BLsuA2LKGbEBuSA4LTm1KaKc7/QCkUOsipXR4+D5/3sWBZiAH7iWUgHwpx5YZf2q +kZn6oRda+ks0JLTQ6VhteQedmb7l86bMeDMR8p4Lg2b38l/xEr7S25UfUDKudXrO +AqNxMG8wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF +BQcDAjAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFE/7VDxF2+cUs9bu0pJM3xoC +L1TSMA8GA1UdEQQIMAaHBAEBAQEwCgYIKoZIzj0EAwMDaQAwZgIxAPLXds9MMH4K +F5FxTf9i0PKPsLQARsABVTgwB94hMR70rqW8Pwbjl7ZGNaYlaeRHUwIxAPMQ8zoF +nim+YS1xLqQek/LXuJto8jxcfkQQBsboVzcTa5uaNRhNd5YwrpomGl3lKA== +-----END CERTIFICATE----- diff --git a/lib/config/testdata/tls/1.1.1.1/key.pem b/lib/config/testdata/tls/1.1.1.1/key.pem new file mode 100644 index 00000000..b41e05ad --- /dev/null +++ b/lib/config/testdata/tls/1.1.1.1/key.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBN8QsHxxHGJpStu8K7 +D/FmaBBNo6c514KGFSIfqGFuREF5aOL3gN/W11yk2OIibdWhZANiAAS7LgNiyhmx +AbkgOC05tSminO/0ApFDrIqV0ePg+f97FgWYgB+4llIB8KceWGX9qpGZ+qEXWvpL +NCS00OlYbXkHnZm+5fOmzHgzEfKeC4Nm9/Jf8RK+0tuVH1AyrnV6zgI= +-----END PRIVATE KEY----- diff --git a/lib/config/testdata/tls/minica-key.pem b/lib/config/testdata/tls/minica-key.pem new file mode 100644 index 00000000..2110df56 --- /dev/null +++ b/lib/config/testdata/tls/minica-key.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDr9QQo7ZaTgUL6d73G +2BG7+YRTFJHAZa0FogRglfc+jYttL1J4/xTig3RmHoqSgrehZANiAASDhijM9Xe0 +G9Vam6AJMeKC6aWDNSLwrxNVmPxemsY/yJ1urBgnxRd9GFH6YW1ki/B8rS+Xl1UX +NnhBrukLaXvgAQQq782/5IUYGsvK5jw8+dSscYVMCQJwGfmQuaNeczQ= +-----END PRIVATE KEY----- diff --git a/lib/config/testdata/tls/minica.pem b/lib/config/testdata/tls/minica.pem new file mode 100644 index 00000000..c8acb8ab --- /dev/null +++ b/lib/config/testdata/tls/minica.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB+zCCAYKgAwIBAgIIQWLAtv4ijQ0wCgYIKoZIzj0EAwMwIDEeMBwGA1UEAxMV +bWluaWNhIHJvb3QgY2EgNDE2MmMwMCAXDTI2MDQyMjIzMjUwMVoYDzIxMjYwNDIy +MjMyNTAxWjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSA0MTYyYzAwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAASDhijM9Xe0G9Vam6AJMeKC6aWDNSLwrxNVmPxemsY/ +yJ1urBgnxRd9GFH6YW1ki/B8rS+Xl1UXNnhBrukLaXvgAQQq782/5IUYGsvK5jw8 ++dSscYVMCQJwGfmQuaNeczSjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQW +MBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1Ud +DgQWBBRP+1Q8RdvnFLPW7tKSTN8aAi9U0jAfBgNVHSMEGDAWgBRP+1Q8RdvnFLPW +7tKSTN8aAi9U0jAKBggqhkjOPQQDAwNnADBkAjBfY7vb7cuLTjg7uoe+kl07FMYT +BGMSnWdhN3yXqMUS3A6XZxD/LntXT6V7yFOlAJYCMH7w8/ATYaTqbk2jBRyQt9/x +ajN+kZ6ZK+fKttqE8CD62mbHg09xoNxRq+K2I3PVyQ== +-----END CERTIFICATE-----