flex-auth/internal/callerauth/tokenreview_test.go
tegwick ca070df32d
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 1m0s
Record authenticated caller in the decision envelope.
FLEX-WP-0023-T04: provenance.caller is additive (mode required;
principal/audience/not_after when a token was reviewed). TokenReview
keeps the JWT exp. request_digest is unchanged because the caller is
not binding material.

Assistant: grok
Assistant-Session: 01a09dc1-b21e-77e1-919e-fcad2f82b267
2026-09-14 04:44:07 +02:00

108 lines
3.3 KiB
Go

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"
}