Complete and refine OAuth + Traeffik Restriction
This commit is contained in:
@@ -374,48 +374,104 @@ func (o *OAuthController) Introspect() {
|
||||
o.ServeJSON()
|
||||
}
|
||||
|
||||
// whitelist lists path segments of oc-auth's own challenge endpoints.
|
||||
// These require no token — the challenge is passed as a query parameter by Hydra.
|
||||
var whitelist = []string{
|
||||
"/public/",
|
||||
"/version",
|
||||
"/status",
|
||||
"/login",
|
||||
"/logout",
|
||||
"/refresh",
|
||||
"/introspect",
|
||||
"/consent",
|
||||
}
|
||||
|
||||
// @Title AuthForward
|
||||
// @Description Forward auth for Traefik — validates JWT via Hydra introspection
|
||||
// @Description Forward auth for Traefik — validates JWT via Hydra introspection.
|
||||
// Only requests from our own peer (SELF) are authorized.
|
||||
// Routes in pathWhitelist bypass all checks (with or without token).
|
||||
// Routes in whitelist bypass the token check (oc-auth own challenge endpoints).
|
||||
// On missing/invalid token: 302 to Hydra authorization URL (restart OAuth2 flow).
|
||||
// On wrong peer: 401 (network/config issue, no redirect).
|
||||
// On valid token but insufficient permissions: 403.
|
||||
// On success: 200 so Traefik forwards the request to the target route.
|
||||
// @Param Authorization header string false "Bearer token"
|
||||
// @Success 200 {string}
|
||||
// @router /forward [get]
|
||||
func (o *OAuthController) InternalAuthForward() {
|
||||
fmt.Println("InternalAuthForward")
|
||||
uri := o.Ctx.Request.Header.Get("X-Forwarded-Uri")
|
||||
for _, w := range whitelist {
|
||||
if strings.Contains(uri, w) {
|
||||
fmt.Println("WHITELIST", w)
|
||||
o.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
origin, publicKey, external := o.extractOrigin(o.Ctx.Request)
|
||||
|
||||
reqToken := o.Ctx.Request.Header.Get("Authorization")
|
||||
if reqToken == "" {
|
||||
for _, w := range whitelist {
|
||||
if strings.Contains(o.Ctx.Request.Header.Get("X-Forwarded-Uri"), w) {
|
||||
o.Ctx.ResponseWriter.WriteHeader(200)
|
||||
o.ServeJSON()
|
||||
return
|
||||
}
|
||||
}
|
||||
o.Ctx.ResponseWriter.WriteHeader(401)
|
||||
o.ServeJSON()
|
||||
// Step 1: no token — allow oc-auth's own challenge endpoints (no token needed).
|
||||
// No token and not a whitelisted path → restart OAuth2 flow.
|
||||
fmt.Println("NO TOKEN")
|
||||
o.redirectToLogin(origin)
|
||||
return
|
||||
}
|
||||
|
||||
// Step 2: extract Bearer token — malformed header treated as missing token.
|
||||
splitToken := strings.Split(reqToken, "Bearer ")
|
||||
if len(splitToken) < 2 {
|
||||
reqToken = ""
|
||||
} else {
|
||||
reqToken = splitToken[1]
|
||||
if len(splitToken) < 2 || splitToken[1] == "" {
|
||||
fmt.Println("MALFORMED BEARER")
|
||||
o.redirectToLogin(origin)
|
||||
return
|
||||
}
|
||||
origin, publicKey, external := o.extractOrigin(o.Ctx.Request)
|
||||
if !infrastructure.GetAuthConnector().CheckAuthForward(
|
||||
reqToken = splitToken[1]
|
||||
|
||||
// Step 3: resolve the calling peer — only our own peer (SELF) is authorized.
|
||||
// A non-SELF or unknown peer is a network/config issue, not a login problem → 401.
|
||||
if external || origin == "" || publicKey == "" {
|
||||
fmt.Println("Unauthorized", external, origin, publicKey)
|
||||
o.Ctx.ResponseWriter.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Step 4: introspect via Hydra then check permissions via Keto.
|
||||
// 401 → token inactive/invalid, user must re-authenticate → restart OAuth2 flow.
|
||||
// 403 → token valid, but permissions denied → forbidden.
|
||||
// 200 → all good, let Traefik forward to the target route.
|
||||
switch infrastructure.GetAuthConnector().CheckAuthForward(
|
||||
reqToken, publicKey, origin,
|
||||
o.Ctx.Request.Header.Get("X-Forwarded-Method"),
|
||||
o.Ctx.Request.Header.Get("X-Forwarded-Uri"), external) && origin != "" && publicKey != "" {
|
||||
o.Ctx.ResponseWriter.WriteHeader(401)
|
||||
o.ServeJSON()
|
||||
return
|
||||
uri, external) {
|
||||
case http.StatusOK:
|
||||
fmt.Println("OK")
|
||||
o.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
|
||||
case http.StatusForbidden:
|
||||
fmt.Println("StatusForbidden")
|
||||
o.Ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
default:
|
||||
fmt.Println("redirectToLogin UNAUTHORIZED")
|
||||
// 401 or unexpected status → token likely expired, restart the OAuth2 flow.
|
||||
o.redirectToLogin(origin)
|
||||
}
|
||||
o.ServeJSON()
|
||||
}
|
||||
|
||||
// redirectToLogin redirects the client to Hydra's authorization endpoint to start a fresh
|
||||
// OAuth2 flow. The original request URL is passed as the state parameter so the frontend
|
||||
// can redirect back after successful authentication.
|
||||
func (o *OAuthController) redirectToLogin(origin string) {
|
||||
cfg := conf.GetConfig()
|
||||
|
||||
if strings.Contains(origin, cfg.AdminOrigin) {
|
||||
o.Ctx.ResponseWriter.Header().Set("Location", cfg.OAdminAuthRedirectURI)
|
||||
} else {
|
||||
o.Ctx.ResponseWriter.Header().Set("Location", cfg.OAuthRedirectURI)
|
||||
}
|
||||
|
||||
o.Ctx.ResponseWriter.WriteHeader(http.StatusFound)
|
||||
}
|
||||
|
||||
func (o *OAuthController) extractOrigin(request *http.Request) (string, string, bool) {
|
||||
|
||||
Reference in New Issue
Block a user