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  }