@@ -2,6 +2,7 @@ package e2e_test
22
33import (
44 "context"
5+ "encoding/base64"
56 "encoding/json"
67 "io"
78 "net/http"
@@ -13,6 +14,16 @@ import (
1314 "github.com/stretchr/testify/require"
1415)
1516
17+ // makeTestJWT builds a minimal unsigned JWT (alg:none) with the given claims.
18+ // This is sufficient for proxy mode testing where the oauth2-proxy has already
19+ // validated the token and the backend only parses claims without verification.
20+ func makeTestJWT (claims map [string ]any ) string {
21+ header := base64 .RawURLEncoding .EncodeToString ([]byte (`{"alg":"none","typ":"JWT"}` ))
22+ payload , _ := json .Marshal (claims )
23+ payloadB64 := base64 .RawURLEncoding .EncodeToString (payload )
24+ return header + "." + payloadB64 + "."
25+ }
26+
1627// CurrentUserResponse mirrors the response from GET /api/me
1728type CurrentUserResponse struct {
1829 User string `json:"user"`
@@ -31,19 +42,19 @@ func kagentURL() string {
3142}
3243
3344// detectAuthMode probes /api/me to determine if the deployment is in proxy or unsecure mode.
45+ // Sends a JWT Bearer token; in proxy mode the backend parses the JWT and returns the sub claim.
46+ // In unsecure mode the backend ignores the Bearer token and returns the default user.
3447// Returns "proxy" if proxy mode, "unsecure" otherwise.
3548func detectAuthMode (t * testing.T ) string {
3649 t .Helper ()
3750
3851 ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Second )
3952 defer cancel ()
4053
41- // Create request with X-Forwarded-User but no X-User-Id
42- // In proxy mode: will return the forwarded user
43- // In unsecure mode: will return default user (ignores X-Forwarded-User)
54+ token := makeTestJWT (map [string ]any {"sub" : "probe-user" })
4455 req , err := http .NewRequestWithContext (ctx , "GET" , kagentURL ()+ "/api/me" , nil )
4556 require .NoError (t , err )
46- req .Header .Set ("X-Forwarded-User " , "probe-user" )
57+ req .Header .Set ("Authorization " , "Bearer " + token )
4758
4859 resp , err := http .DefaultClient .Do (req )
4960 require .NoError (t , err )
@@ -173,13 +184,16 @@ func TestE2EAuthProxyMode(t *testing.T) {
173184 t .Skip ("Skipping proxy mode tests - deployment is in unsecure mode" )
174185 }
175186
176- t .Run ("full_headers" , func (t * testing.T ) {
177- // GET /api/me with all X-Forwarded-* headers
187+ t .Run ("full_claims" , func (t * testing.T ) {
188+ // JWT with all standard claims
189+ token := makeTestJWT (map [string ]any {
190+ "sub" : "john" ,
191+ "email" : "john@example.com" ,
192+ "name" : "John Doe" ,
193+ "groups" : []string {"admin" , "developers" },
194+ })
178195 resp , body := makeAuthRequest (t , map [string ]string {
179- "X-Forwarded-User" : "john" ,
180- "X-Forwarded-Email" : "john@example.com" ,
181- "X-Forwarded-Preferred-Username" : "John Doe" ,
182- "X-Forwarded-Groups" : "admin,developers" ,
196+ "Authorization" : "Bearer " + token ,
183197 }, nil )
184198 require .Equal (t , http .StatusOK , resp .StatusCode )
185199
@@ -190,10 +204,13 @@ func TestE2EAuthProxyMode(t *testing.T) {
190204 require .ElementsMatch (t , []string {"admin" , "developers" }, userResp .Groups )
191205 })
192206
193- t .Run ("minimal_headers" , func (t * testing.T ) {
194- // GET /api/me with only required X-Forwarded-User header
207+ t .Run ("minimal_claims" , func (t * testing.T ) {
208+ // JWT with only sub claim
209+ token := makeTestJWT (map [string ]any {
210+ "sub" : "jane" ,
211+ })
195212 resp , body := makeAuthRequest (t , map [string ]string {
196- "X-Forwarded-User " : "jane" ,
213+ "Authorization " : "Bearer " + token ,
197214 }, nil )
198215 require .Equal (t , http .StatusOK , resp .StatusCode )
199216
@@ -204,35 +221,56 @@ func TestE2EAuthProxyMode(t *testing.T) {
204221 require .Empty (t , userResp .Groups )
205222 })
206223
207- t .Run ("missing_required_header_returns_401" , func (t * testing.T ) {
208- // GET /api/me without X-Forwarded-User should return 401
224+ t .Run ("missing_sub_claim_returns_401" , func (t * testing.T ) {
225+ // JWT without sub claim should return 401
226+ token := makeTestJWT (map [string ]any {
227+ "email" : "test@example.com" ,
228+ })
209229 resp , _ := makeAuthRequest (t , map [string ]string {
210- "X-Forwarded-Email " : "test@example.com" , // email but no user
230+ "Authorization " : "Bearer " + token ,
211231 }, nil )
212232 require .Equal (t , http .StatusUnauthorized , resp .StatusCode )
213233 })
214234
215- t .Run ("group_parsing_trims_whitespace" , func (t * testing.T ) {
216- // Groups with whitespace around commas should be trimmed
235+ t .Run ("no_bearer_token_returns_401" , func (t * testing.T ) {
236+ // No Authorization header and no agent identity should return 401
237+ resp , _ := makeAuthRequest (t , nil , nil )
238+ require .Equal (t , http .StatusUnauthorized , resp .StatusCode )
239+ })
240+
241+ t .Run ("comma_separated_groups_string" , func (t * testing.T ) {
242+ // Groups as comma-separated string should be parsed and trimmed
243+ token := makeTestJWT (map [string ]any {
244+ "sub" : "user" ,
245+ "groups" : "admin, dev , qa " ,
246+ })
217247 resp , body := makeAuthRequest (t , map [string ]string {
218- "X-Forwarded-User" : "user" ,
219- "X-Forwarded-Groups" : "admin, dev , qa " ,
248+ "Authorization" : "Bearer " + token ,
220249 }, nil )
221250 require .Equal (t , http .StatusOK , resp .StatusCode )
222251
223252 userResp := parseUserResponse (t , body )
224253 require .ElementsMatch (t , []string {"admin" , "dev" , "qa" }, userResp .Groups )
225254 })
226255
227- t .Run ("empty_groups_header " , func (t * testing.T ) {
228- // Empty groups header should result in empty groups slice
256+ t .Run ("agent_fallback_with_user_id " , func (t * testing.T ) {
257+ // Agent callback: X-Agent-Name + user_id query param (no Bearer token)
229258 resp , body := makeAuthRequest (t , map [string ]string {
230- "X-Forwarded-User" : "user" ,
231- "X-Forwarded-Groups" : "" ,
232- }, nil )
259+ "X-Agent-Name" : "kagent/test-agent" ,
260+ }, map [string ]string {
261+ "user_id" : "owner@example.com" ,
262+ })
233263 require .Equal (t , http .StatusOK , resp .StatusCode )
234264
235265 userResp := parseUserResponse (t , body )
236- require .Empty (t , userResp .Groups )
266+ require .Equal (t , "owner@example.com" , userResp .User )
267+ })
268+
269+ t .Run ("fallback_without_agent_name_returns_401" , func (t * testing.T ) {
270+ // user_id query param without X-Agent-Name should return 401
271+ resp , _ := makeAuthRequest (t , nil , map [string ]string {
272+ "user_id" : "owner@example.com" ,
273+ })
274+ require .Equal (t , http .StatusUnauthorized , resp .StatusCode )
237275 })
238276}
0 commit comments