Author SHA1 Message Date
Admin Bot 45254a48b0 fix: gateway authentik port from 80 to 9000
CI / CI (pull_request) Successful in 3m9s
NetworkPolicy allows gateway→iam only on ports 9000/9443, but config
used port 80 for JWKS fetch and token endpoints. This caused
'operation not permitted' errors and JWKS refresh failures.

Affects:
- auth.jwksUrl: uses port 9000 (Authentik HTTP)
- auth.tokenUrl: uses port 9000 for token exchange
- iam adapter upstream: routes to port 9000

Fixes: Gateway unable to validate JWT tokens, all chat/inference requests
returned 401 with 'token is unverifiable' error.
2026-09-13 08:48:08 +09:00
3 changed files with 12 additions and 30 deletions
+9 -9
View File
@@ -52,18 +52,18 @@ func StripIncoming(r *http.Request) {
func Inject(r *http.Request, claims jwt.MapClaims) { func Inject(r *http.Request, claims jwt.MapClaims) {
r.Header.Set(HeaderAuthVerified, "true") r.Header.Set(HeaderAuthVerified, "true")
if sub := ClaimString(claims, "sub"); sub != "" { if sub := claimString(claims, "sub"); sub != "" {
r.Header.Set(HeaderUser, sub) r.Header.Set(HeaderUser, sub)
} }
if roles := ClaimStringSlice(claims, "roles"); len(roles) > 0 { if roles := claimStringSlice(claims, "roles"); len(roles) > 0 {
r.Header.Set(HeaderRoles, strings.Join(roles, ",")) r.Header.Set(HeaderRoles, strings.Join(roles, ","))
} else if perms := ClaimStringSlice(claims, "permissions"); len(perms) > 0 { } else if perms := claimStringSlice(claims, "permissions"); len(perms) > 0 {
r.Header.Set(HeaderRoles, strings.Join(perms, ",")) r.Header.Set(HeaderRoles, strings.Join(perms, ","))
} }
if azp := ClaimString(claims, "azp"); azp != "" { if azp := claimString(claims, "azp"); azp != "" {
sub := ClaimString(claims, "sub") sub := claimString(claims, "sub")
// Only set acting-service when azp differs from sub // Only set acting-service when azp differs from sub
// (i.e., a service account acting, not the user themselves) // (i.e., a service account acting, not the user themselves)
if azp != sub { if azp != sub {
@@ -72,9 +72,9 @@ func Inject(r *http.Request, claims jwt.MapClaims) {
} }
} }
// ClaimString extracts a string value from claims, returning "" if // claimString extracts a string value from claims, returning "" if
// the key is missing or not a string. // the key is missing or not a string.
func ClaimString(claims jwt.MapClaims, key string) string { func claimString(claims jwt.MapClaims, key string) string {
val, ok := claims[key] val, ok := claims[key]
if !ok || val == nil { if !ok || val == nil {
return "" return ""
@@ -86,10 +86,10 @@ func ClaimString(claims jwt.MapClaims, key string) string {
return s return s
} }
// ClaimStringSlice extracts a []string from claims. JWT libraries // claimStringSlice extracts a []string from claims. JWT libraries
// deserialize JSON arrays as []interface{}, so each element is // deserialize JSON arrays as []interface{}, so each element is
// type-asserted individually. Non-string elements are skipped. // type-asserted individually. Non-string elements are skipped.
func ClaimStringSlice(claims jwt.MapClaims, key string) []string { func claimStringSlice(claims jwt.MapClaims, key string) []string {
val, ok := claims[key] val, ok := claims[key]
if !ok || val == nil { if !ok || val == nil {
return nil return nil
-18
View File
@@ -359,24 +359,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Inject identity headers for downstream services // Inject identity headers for downstream services
identity.Inject(r, claims) identity.Inject(r, claims)
// Audit trail: log successful JWT authentication
auditFields := map[string]string{
"path": r.URL.Path,
"method": r.Method,
}
if sub := identity.ClaimString(claims, "sub"); sub != "" {
auditFields["subject"] = sub
}
if azp := identity.ClaimString(claims, "azp"); azp != "" {
auditFields["acting_party"] = azp
}
if roles := identity.ClaimStringSlice(claims, "roles"); len(roles) > 0 {
auditFields["roles"] = strings.Join(roles, ",")
} else if perms := identity.ClaimStringSlice(claims, "permissions"); len(perms) > 0 {
auditFields["permissions"] = strings.Join(perms, ",")
}
logging.Infof("auth ok", auditFields)
// Check required capability if configured // Check required capability if configured
if h.config.Auth.RequiredCapability != "" { if h.config.Auth.RequiredCapability != "" {
if !h.jwtValidator.CheckPermissions(claims, h.config.Auth.RequiredCapability, "*") { if !h.jwtValidator.CheckPermissions(claims, h.config.Auth.RequiredCapability, "*") {
+3 -3
View File
@@ -15,9 +15,9 @@ data:
enabled: true enabled: true
issuer: "https://authentik.riotpiao.com/application/o/api-gw/" issuer: "https://authentik.riotpiao.com/application/o/api-gw/"
audience: "api-gw" audience: "api-gw"
jwksUrl: "http://authentik-server.iam.svc.cluster.local/application/o/api-gw/jwks/" jwksUrl: "http://authentik-server.iam.svc.cluster.local:9000/application/o/api-gw/jwks/"
requiredCapability: "llm:inference" requiredCapability: "llm:inference"
tokenUrl: "http://authentik-server.iam.svc.cluster.local/application/o/token/" tokenUrl: "http://authentik-server.iam.svc.cluster.local:9000/application/o/token/"
clientId: "api-gw" clientId: "api-gw"
# Routes: standard HTTP proxy routes (not LLM-specific) # Routes: standard HTTP proxy routes (not LLM-specific)
@@ -137,7 +137,7 @@ data:
- serviceName: iam - serviceName: iam
upstream: upstream:
url: http://authentik-server.iam.svc.cluster.local:80 url: http://authentik-server.iam.svc.cluster.local:9000
timeoutSeconds: 30 timeoutSeconds: 30
auth: auth:
required: false required: false