package agones import ( "crypto/rand" "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "math/big" "net" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "time" ) func TestKubernetesHTTPClientTrustsCAAddsAndRotatesBearerToken(t *testing.T) { var seen []string server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { seen = append(seen, r.Header.Get("Authorization")) w.WriteHeader(http.StatusNoContent) })) server.TLS = testTLSConfig(t) server.StartTLS() defer server.Close() directory := t.TempDir() caPath := filepath.Join(directory, "ca.crt") tokenPath := filepath.Join(directory, "token") certificate := server.Certificate() if err := os.WriteFile(caPath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate.Raw}), 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(tokenPath, []byte("first-token\n"), 0o600); err != nil { t.Fatal(err) } client, err := NewKubernetesHTTPClient(server.URL, tokenPath, caPath, time.Second) if err != nil { t.Fatal(err) } for _, token := range []string{"first-token", "rotated-token"} { if err := os.WriteFile(tokenPath, []byte(token), 0o600); err != nil { t.Fatal(err) } response, err := client.Get(server.URL) if err != nil { t.Fatal(err) } response.Body.Close() } if len(seen) != 2 || seen[0] != "Bearer first-token" || seen[1] != "Bearer rotated-token" { t.Fatalf("authorization headers = %v", seen) } } func TestKubernetesHTTPClientRejectsInvalidConfigurationAndToken(t *testing.T) { directory := t.TempDir() caPath := filepath.Join(directory, "ca.crt") tokenPath := filepath.Join(directory, "token") if _, err := NewKubernetesHTTPClient("http://kubernetes.default.svc", tokenPath, caPath, time.Second); err == nil { t.Fatal("non-TLS API origin accepted") } if _, err := NewKubernetesHTTPClient("https://kubernetes.default.svc", tokenPath, caPath, time.Second); err == nil { t.Fatal("missing CA accepted") } if err := os.WriteFile(caPath, []byte("not a certificate"), 0o600); err != nil { t.Fatal(err) } if _, err := NewKubernetesHTTPClient("https://kubernetes.default.svc", tokenPath, caPath, time.Second); err == nil { t.Fatal("invalid CA accepted") } } func TestKubernetesHTTPClientDoesNotForwardCredentialAcrossOrigins(t *testing.T) { server := httptest.NewTLSServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) defer server.Close() directory := t.TempDir() caPath := filepath.Join(directory, "ca.crt") tokenPath := filepath.Join(directory, "token") if err := os.WriteFile(caPath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: server.Certificate().Raw}), 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(tokenPath, []byte("secret-token"), 0o600); err != nil { t.Fatal(err) } client, err := NewKubernetesHTTPClient(server.URL, tokenPath, caPath, time.Second) if err != nil { t.Fatal(err) } if _, err := client.Get("https://example.invalid/"); err == nil { t.Fatal("credentialed request to another origin was not rejected") } } func testTLSConfig(t *testing.T) *tls.Config { t.Helper() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatal(err) } template := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "127.0.0.1"}, NotBefore: time.Now().Add(-time.Minute), NotAfter: time.Now().Add(time.Hour), KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, } der, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) if err != nil { t.Fatal(err) } certificate, err := tls.X509KeyPair( pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}), ) if err != nil { t.Fatal(err) } return &tls.Config{Certificates: []tls.Certificate{certificate}, MinVersion: tls.VersionTLS12} }