package callerauth import ( "context" "encoding/base64" "encoding/json" "errors" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "time" ) func TestKubernetesTokenReviewerClassifiesRejectedTokenAsUnauthenticated(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/apis/authentication.k8s.io/v1/tokenreviews" { t.Fatalf("TokenReview path = %q", r.URL.Path) } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte(`{ "apiVersion": "authentication.k8s.io/v1", "kind": "TokenReview", "status": { "authenticated": false, "error": "invalid bearer token" } }`)) })) defer server.Close() tokenFile := filepath.Join(t.TempDir(), "reviewer-token") if err := os.WriteFile(tokenFile, []byte("reviewer-token\n"), 0o600); err != nil { t.Fatal(err) } reviewer := &KubernetesTokenReviewer{ endpoint: server.URL + "/apis/authentication.k8s.io/v1/tokenreviews", audience: "flex-auth", reviewerTokenFile: tokenFile, client: server.Client(), } _, err := reviewer.Review(context.Background(), "malformed") if !errors.Is(err, ErrUnauthenticated) { t.Fatalf("Review error = %v; want unauthenticated", err) } } func TestKubernetesTokenReviewerCapturesTokenExpiry(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte(`{ "apiVersion": "authentication.k8s.io/v1", "kind": "TokenReview", "status": { "authenticated": true, "audiences": ["flex-auth"], "user": {"username": "system:serviceaccount:secrets-engine:secrets-engine"} } }`)) })) defer server.Close() tokenFile := filepath.Join(t.TempDir(), "reviewer-token") if err := os.WriteFile(tokenFile, []byte("reviewer-token\n"), 0o600); err != nil { t.Fatal(err) } reviewer := &KubernetesTokenReviewer{ endpoint: server.URL + "/apis/authentication.k8s.io/v1/tokenreviews", audience: "flex-auth", reviewerTokenFile: tokenFile, client: server.Client(), } exp := time.Unix(1788730498, 0).UTC() token := unsignedJWT(map[string]any{"exp": exp.Unix(), "sub": "system:serviceaccount:secrets-engine:secrets-engine"}) identity, err := reviewer.Review(context.Background(), token) if err != nil { t.Fatal(err) } if identity.Username != "system:serviceaccount:secrets-engine:secrets-engine" { t.Fatalf("username = %q", identity.Username) } if !identity.NotAfter.Equal(exp) { t.Fatalf("NotAfter = %s; want %s", identity.NotAfter, exp) } } func TestTokenExpiryReadsJWTClaim(t *testing.T) { exp := time.Unix(1788730498, 0).UTC() got, ok := tokenExpiry(unsignedJWT(map[string]any{"exp": exp.Unix()})) if !ok || !got.Equal(exp) { t.Fatalf("tokenExpiry = %s, %v; want %s", got, ok, exp) } if _, ok := tokenExpiry("not-a-jwt"); ok { t.Fatal("opaque token should not yield expiry") } } func unsignedJWT(claims map[string]any) string { header, _ := json.Marshal(map[string]string{"alg": "none", "typ": "JWT"}) payload, _ := json.Marshal(claims) return base64.RawURLEncoding.EncodeToString(header) + "." + base64.RawURLEncoding.EncodeToString(payload) + ".sig" }