knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/observability/metrics/prometheus/server_test.go (about) 1 /* 2 Copyright 2025 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package prometheus 18 19 import ( 20 "crypto/rand" 21 "crypto/rsa" 22 "crypto/tls" 23 "crypto/x509" 24 "encoding/pem" 25 "math/big" 26 "os" 27 "strings" 28 "testing" 29 "time" 30 31 "github.com/google/go-cmp/cmp" 32 ) 33 34 func TestNewServerWithOptions(t *testing.T) { 35 s, err := NewServer( 36 WithHost("127.0.0.1"), 37 WithPort("57289"), 38 ) 39 if err != nil { 40 t.Fatal("NewServer() =", err) 41 } 42 43 got := s.http.Addr 44 want := "127.0.0.1:57289" 45 46 if diff := cmp.Diff(want, got); diff != "" { 47 t.Error("unexpected diff (-want, +got) : ", diff) 48 } 49 } 50 51 func TestNewServerEnvOverride(t *testing.T) { 52 t.Setenv(prometheusHostEnvName, "0.0.0.0") 53 t.Setenv(prometheusPortEnvName, "1028") 54 55 s, err := NewServer( 56 WithHost("127.0.0.1"), 57 WithPort("57289"), 58 ) 59 if err != nil { 60 t.Fatal("NewServer() =", err) 61 } 62 63 got := s.http.Addr 64 want := "0.0.0.0:1028" 65 66 if diff := cmp.Diff(want, got); diff != "" { 67 t.Error("unexpected diff (-want, +got) : ", diff) 68 } 69 } 70 71 func TestNewServerFailure(t *testing.T) { 72 if _, err := NewServer(WithPort("1000000")); err == nil { 73 t.Error("expected port parsing to fail") 74 } 75 76 if _, err := NewServer(WithPort("80")); err == nil { 77 t.Error("expected below port range to fail") 78 } 79 80 if _, err := NewServer(WithPort("65536")); err == nil { 81 t.Error("expected above port range to fail") 82 } 83 } 84 85 func TestNewServerTLSRequiresBothCertAndKey(t *testing.T) { 86 t.Run("only cert set", func(t *testing.T) { 87 t.Setenv(prometheusTLSCertEnvName, "/etc/tls/tls.crt") 88 t.Setenv(prometheusTLSKeyEnvName, "") // ensure key is unset 89 _, err := NewServer() 90 if err == nil { 91 t.Fatal("expected NewServer to fail when only TLS cert is set") 92 } 93 if !strings.Contains(err.Error(), "must be set or neither") { 94 t.Errorf("unexpected error: %v", err) 95 } 96 }) 97 t.Run("only key set", func(t *testing.T) { 98 t.Setenv(prometheusTLSCertEnvName, "") 99 t.Setenv(prometheusTLSKeyEnvName, "/etc/tls/tls.key") 100 _, err := NewServer() 101 if err == nil { 102 t.Fatal("expected NewServer to fail when only TLS key is set") 103 } 104 if !strings.Contains(err.Error(), "must be set or neither") { 105 t.Errorf("unexpected error: %v", err) 106 } 107 }) 108 } 109 110 func TestNewServerWithTLSConfigFromEnv(t *testing.T) { 111 t.Setenv(prometheusTLSCertEnvName, "/etc/tls/tls.crt") 112 t.Setenv(prometheusTLSKeyEnvName, "/etc/tls/tls.key") 113 t.Setenv("METRICS_PROMETHEUS_TLS_MIN_VERSION", "1.2") 114 115 s, err := NewServer() 116 if err != nil { 117 t.Fatal("NewServer() =", err) 118 } 119 120 if s.http.TLSConfig == nil { 121 t.Fatal("expected TLSConfig to be set when cert/key env vars are set") 122 } 123 if s.http.TLSConfig.MinVersion != tls.VersionTLS12 { 124 t.Errorf("expected MinVersion TLS 1.2 from env, got %v", s.http.TLSConfig.MinVersion) 125 } 126 } 127 128 func TestNewServerWithTLSEnvVars(t *testing.T) { 129 t.Setenv(prometheusTLSCertEnvName, "/etc/tls/tls.crt") 130 t.Setenv(prometheusTLSKeyEnvName, "/etc/tls/tls.key") 131 132 s, err := NewServer() 133 if err != nil { 134 t.Fatal("NewServer() =", err) 135 } 136 137 if s.certFile != "/etc/tls/tls.crt" { 138 t.Errorf("expected certFile to be /etc/tls/tls.crt, got %s", s.certFile) 139 } 140 141 if s.keyFile != "/etc/tls/tls.key" { 142 t.Errorf("expected keyFile to be /etc/tls/tls.key, got %s", s.keyFile) 143 } 144 } 145 146 func TestTLSConfigWithCertFilesFromEnv(t *testing.T) { 147 t.Setenv(prometheusTLSCertEnvName, "/etc/tls/tls.crt") 148 t.Setenv(prometheusTLSKeyEnvName, "/etc/tls/tls.key") 149 t.Setenv("METRICS_PROMETHEUS_TLS_MIN_VERSION", "1.3") 150 t.Setenv("METRICS_PROMETHEUS_TLS_MAX_VERSION", "1.3") 151 t.Setenv("METRICS_PROMETHEUS_TLS_CIPHER_SUITES", "TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256") 152 153 s, err := NewServer() 154 if err != nil { 155 t.Fatal("NewServer() =", err) 156 } 157 158 if s.http.TLSConfig == nil { 159 t.Fatal("expected TLSConfig to be set") 160 } 161 if s.http.TLSConfig.MinVersion != tls.VersionTLS13 { 162 t.Errorf("expected MinVersion TLS 1.3, got %v", s.http.TLSConfig.MinVersion) 163 } 164 if s.http.TLSConfig.MaxVersion != tls.VersionTLS13 { 165 t.Errorf("expected MaxVersion TLS 1.3, got %v", s.http.TLSConfig.MaxVersion) 166 } 167 if len(s.http.TLSConfig.CipherSuites) != 2 { 168 t.Errorf("expected 2 cipher suites, got %d", len(s.http.TLSConfig.CipherSuites)) 169 } 170 if s.certFile != "/etc/tls/tls.crt" { 171 t.Errorf("expected certFile=/etc/tls/tls.crt, got %s", s.certFile) 172 } 173 if s.keyFile != "/etc/tls/tls.key" { 174 t.Errorf("expected keyFile=/etc/tls/tls.key, got %s", s.keyFile) 175 } 176 } 177 178 func TestPrometheusMTLSFromEnv(t *testing.T) { 179 t.Run("require without client CA file returns error", func(t *testing.T) { 180 t.Setenv(prometheusTLSCertEnvName, "/etc/tls/tls.crt") 181 t.Setenv(prometheusTLSKeyEnvName, "/etc/tls/tls.key") 182 t.Setenv(prometheusTLSClientAuthEnvName, "require") 183 t.Setenv(prometheusTLSClientCAFileEnvName, "") // unset 184 _, err := NewServer() 185 if err == nil { 186 t.Fatal("expected NewServer to fail when client auth is require but client CA file is unset") 187 } 188 if !strings.Contains(err.Error(), "cannot be validated without a CA") { 189 t.Errorf("unexpected error: %v", err) 190 } 191 }) 192 193 t.Run("invalid client auth value returns error", func(t *testing.T) { 194 t.Setenv(prometheusTLSCertEnvName, "/etc/tls/tls.crt") 195 t.Setenv(prometheusTLSKeyEnvName, "/etc/tls/tls.key") 196 t.Setenv(prometheusTLSClientAuthEnvName, "invalid") 197 _, err := NewServer() 198 if err == nil { 199 t.Fatal("expected NewServer to fail with invalid client auth value") 200 } 201 if !strings.Contains(err.Error(), `invalid METRICS_PROMETHEUS_TLS_CLIENT_AUTH`) { 202 t.Errorf("unexpected error: %v", err) 203 } 204 }) 205 206 t.Run("optional with valid client CA file sets ClientAuth and ClientCAs", func(t *testing.T) { 207 caFile := createTempCACertFile(t) 208 t.Setenv(prometheusTLSCertEnvName, "/etc/tls/tls.crt") 209 t.Setenv(prometheusTLSKeyEnvName, "/etc/tls/tls.key") 210 t.Setenv(prometheusTLSClientAuthEnvName, "optional") 211 t.Setenv(prometheusTLSClientCAFileEnvName, caFile) 212 213 s, err := NewServer() 214 if err != nil { 215 t.Fatal("NewServer() =", err) 216 } 217 if s.http.TLSConfig == nil { 218 t.Fatal("expected TLSConfig to be set") 219 } 220 if s.http.TLSConfig.ClientAuth != tls.VerifyClientCertIfGiven { 221 t.Errorf("expected ClientAuth VerifyClientCertIfGiven, got %v", s.http.TLSConfig.ClientAuth) 222 } 223 if s.http.TLSConfig.ClientCAs == nil { 224 t.Error("expected ClientCAs to be set") 225 } 226 }) 227 228 t.Run("require with valid client CA file sets RequireAndVerifyClientCert", func(t *testing.T) { 229 caFile := createTempCACertFile(t) 230 t.Setenv(prometheusTLSCertEnvName, "/etc/tls/tls.crt") 231 t.Setenv(prometheusTLSKeyEnvName, "/etc/tls/tls.key") 232 t.Setenv(prometheusTLSClientAuthEnvName, "require") 233 t.Setenv(prometheusTLSClientCAFileEnvName, caFile) 234 235 s, err := NewServer() 236 if err != nil { 237 t.Fatal("NewServer() =", err) 238 } 239 if s.http.TLSConfig == nil { 240 t.Fatal("expected TLSConfig to be set") 241 } 242 if s.http.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert { 243 t.Errorf("expected ClientAuth RequireAndVerifyClientCert, got %v", s.http.TLSConfig.ClientAuth) 244 } 245 if s.http.TLSConfig.ClientCAs == nil { 246 t.Error("expected ClientCAs to be set") 247 } 248 }) 249 250 t.Run("explicit none does not set client auth", func(t *testing.T) { 251 t.Setenv(prometheusTLSCertEnvName, "/etc/tls/tls.crt") 252 t.Setenv(prometheusTLSKeyEnvName, "/etc/tls/tls.key") 253 t.Setenv(prometheusTLSClientAuthEnvName, "none") 254 t.Setenv(prometheusTLSClientCAFileEnvName, "") 255 256 s, err := NewServer() 257 if err != nil { 258 t.Fatal("NewServer() =", err) 259 } 260 if s.http.TLSConfig == nil { 261 t.Fatal("expected TLSConfig to be set") 262 } 263 if s.http.TLSConfig.ClientAuth != tls.NoClientCert { 264 t.Errorf("expected ClientAuth NoClientCert, got %v", s.http.TLSConfig.ClientAuth) 265 } 266 if s.http.TLSConfig.ClientCAs != nil { 267 t.Error("expected ClientCAs to be nil") 268 } 269 }) 270 271 t.Run("mTLS env vars without TLS enabled returns error", func(t *testing.T) { 272 t.Setenv(prometheusTLSCertEnvName, "") 273 t.Setenv(prometheusTLSKeyEnvName, "") 274 t.Setenv(prometheusTLSClientAuthEnvName, "require") 275 t.Setenv(prometheusTLSClientCAFileEnvName, "/etc/tls/ca.pem") 276 _, err := NewServer() 277 if err == nil { 278 t.Fatal("expected NewServer to fail when mTLS is set but TLS is not enabled") 279 } 280 if !strings.Contains(err.Error(), "require TLS to be enabled") { 281 t.Errorf("unexpected error: %v", err) 282 } 283 }) 284 285 t.Run("client CA file with empty client auth returns error", func(t *testing.T) { 286 t.Setenv(prometheusTLSCertEnvName, "/etc/tls/tls.crt") 287 t.Setenv(prometheusTLSKeyEnvName, "/etc/tls/tls.key") 288 t.Setenv(prometheusTLSClientAuthEnvName, "") 289 t.Setenv(prometheusTLSClientCAFileEnvName, "/etc/tls/ca.pem") 290 _, err := NewServer() 291 if err == nil { 292 t.Fatal("expected NewServer to fail when CA file is set but client auth is unset") 293 } 294 if !strings.Contains(err.Error(), "is set but") { 295 t.Errorf("unexpected error: %v", err) 296 } 297 }) 298 299 t.Run("client CA file with none client auth returns error", func(t *testing.T) { 300 t.Setenv(prometheusTLSCertEnvName, "/etc/tls/tls.crt") 301 t.Setenv(prometheusTLSKeyEnvName, "/etc/tls/tls.key") 302 t.Setenv(prometheusTLSClientAuthEnvName, "none") 303 t.Setenv(prometheusTLSClientCAFileEnvName, "/etc/tls/ca.pem") 304 _, err := NewServer() 305 if err == nil { 306 t.Fatal("expected NewServer to fail when CA file is set but client auth is none") 307 } 308 if !strings.Contains(err.Error(), "is set but") { 309 t.Errorf("unexpected error: %v", err) 310 } 311 }) 312 313 t.Run("client CA file without TLS enabled returns error", func(t *testing.T) { 314 t.Setenv(prometheusTLSCertEnvName, "") 315 t.Setenv(prometheusTLSKeyEnvName, "") 316 t.Setenv(prometheusTLSClientAuthEnvName, "") 317 t.Setenv(prometheusTLSClientCAFileEnvName, "/etc/tls/ca.pem") 318 _, err := NewServer() 319 if err == nil { 320 t.Fatal("expected NewServer to fail when client CA file is set but TLS is not enabled") 321 } 322 if !strings.Contains(err.Error(), "require TLS to be enabled") { 323 t.Errorf("unexpected error: %v", err) 324 } 325 }) 326 } 327 328 // createTempCACertFile writes a minimal self-signed CA cert to a temp file and returns its path. 329 func createTempCACertFile(t *testing.T) string { 330 t.Helper() 331 key, err := rsa.GenerateKey(rand.Reader, 2048) 332 if err != nil { 333 t.Fatal(err) 334 } 335 tmpl := &x509.Certificate{ 336 SerialNumber: big.NewInt(1), 337 NotBefore: time.Now(), 338 NotAfter: time.Now().Add(24 * time.Hour), 339 BasicConstraintsValid: true, 340 IsCA: true, 341 } 342 certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) 343 if err != nil { 344 t.Fatal(err) 345 } 346 f, err := os.CreateTemp("", "prometheus-mtls-ca-*.pem") 347 if err != nil { 348 t.Fatal(err) 349 } 350 defer f.Close() 351 if err := pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: certDER}); err != nil { 352 t.Fatal(err) 353 } 354 name := f.Name() 355 t.Cleanup(func() { _ = os.Remove(name) }) 356 return name 357 }