Record authenticated caller in the decision envelope.
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

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:
tegwick 2026-09-14 04:44:07 +02:00
parent e62c0cfc36
commit ca070df32d
15 changed files with 344 additions and 35 deletions

View file

@ -5,6 +5,7 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"io"
@ -106,5 +107,33 @@ func (r *KubernetesTokenReviewer) Review(ctx context.Context, callerToken string
if !review.Status.Authenticated {
return Identity{}, nil
}
return Identity{Username: review.Status.User.Username, Audiences: review.Status.Audiences}, nil
identity := Identity{Username: review.Status.User.Username, Audiences: review.Status.Audiences}
if exp, ok := tokenExpiry(callerToken); ok {
identity.NotAfter = exp
}
return identity, nil
}
// tokenExpiry reads exp from a JWT payload. TokenReview already validated the
// token, including expiry; the claim is captured here because the review
// response does not return it.
func tokenExpiry(token string) (time.Time, bool) {
parts := strings.Split(token, ".")
if len(parts) != 3 {
return time.Time{}, false
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
payload, err = base64.URLEncoding.DecodeString(parts[1])
if err != nil {
return time.Time{}, false
}
}
var claims struct {
Exp int64 `json:"exp"`
}
if err := json.Unmarshal(payload, &claims); err != nil || claims.Exp <= 0 {
return time.Time{}, false
}
return time.Unix(claims.Exp, 0).UTC(), true
}