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
This commit is contained in:
parent
e62c0cfc36
commit
ca070df32d
15 changed files with 344 additions and 35 deletions
|
|
@ -2,12 +2,15 @@ package callerauth
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestKubernetesTokenReviewerClassifiesRejectedTokenAsUnauthenticated(t *testing.T) {
|
||||
|
|
@ -44,3 +47,62 @@ func TestKubernetesTokenReviewerClassifiesRejectedTokenAsUnauthenticated(t *test
|
|||
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"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue