Complete and refine OAuth + Traeffik Restriction
This commit is contained in:
@@ -27,8 +27,12 @@ type AuthConnector interface {
|
||||
RevokeToken(token string, clientID string) error
|
||||
RefreshToken(refreshToken string, clientID string) (*TokenResponse, error)
|
||||
|
||||
// Forward auth
|
||||
CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string, external bool) bool
|
||||
// CheckAuthForward validates the token and permissions for a forward auth request.
|
||||
// Returns an HTTP status code:
|
||||
// 200 — token active and permissions granted
|
||||
// 401 — token missing, invalid, or inactive → caller should redirect to login
|
||||
// 403 — token valid but permissions denied → caller should return forbidden
|
||||
CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string, external bool) int
|
||||
}
|
||||
|
||||
// Token is the unified token response returned to clients
|
||||
|
||||
@@ -299,61 +299,61 @@ func (h *HydraConnector) RefreshToken(refreshToken string, clientID string) (*To
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// CheckAuthForward validates a JWT token for forward auth (Traefik integration)
|
||||
// It introspects the token via Hydra and checks permissions from the token's extra claims
|
||||
func (h *HydraConnector) CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string, external bool) bool {
|
||||
// CheckAuthForward validates a JWT token for forward auth (Traefik integration).
|
||||
// It introspects the token via Hydra then checks permissions via Keto.
|
||||
// Only requests from our own peer (external == false) are accepted.
|
||||
// Returns 200 (OK), 401 (token inactive/invalid → redirect to login), or 403 (permission denied).
|
||||
func (h *HydraConnector) CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string, external bool) int {
|
||||
if forward == "" || method == "" {
|
||||
return false
|
||||
return http.StatusUnauthorized
|
||||
}
|
||||
// Defense in depth: only SELF peer requests are allowed.
|
||||
if external {
|
||||
return http.StatusUnauthorized
|
||||
}
|
||||
logger := oclib.GetLogger()
|
||||
|
||||
// Introspect the token via Hydra to get claims
|
||||
// Introspect the token via Hydra.
|
||||
// An inactive or invalid token means the user must re-authenticate → 401.
|
||||
result, err := h.Introspect(reqToken)
|
||||
if err != nil || !result.Active {
|
||||
if err != nil {
|
||||
logger.Error().Msg("Forward auth introspect failed: " + err.Error())
|
||||
}
|
||||
return false
|
||||
return http.StatusUnauthorized
|
||||
}
|
||||
|
||||
// Extract claims from the introspection result's extra data
|
||||
// Hydra puts consent session's access_token data in the "ext" field of introspection
|
||||
// Build session claims from Hydra's introspection "ext" field.
|
||||
// Hydra injects the consent session's access_token data there.
|
||||
var sessionClaims claims.Claims
|
||||
sessionClaims.Session.AccessToken = make(map[string]interface{})
|
||||
sessionClaims.Session.IDToken = make(map[string]interface{})
|
||||
if result.Extra != nil {
|
||||
sessionClaims.Session.AccessToken = make(map[string]interface{})
|
||||
sessionClaims.Session.IDToken = make(map[string]interface{})
|
||||
for k, v := range result.Extra {
|
||||
sessionClaims.Session.AccessToken[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Also try to get id_token claims from the token if it's a JWT
|
||||
// For now, use the introspected extra claims and the peer signature verification
|
||||
if sessionClaims.Session.IDToken == nil {
|
||||
sessionClaims.Session.IDToken = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// Get self peer for signature verification
|
||||
// For SELF peer requests skip the signature check (internal traffic).
|
||||
pp := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), "", "", []string{}, nil).Search(nil, fmt.Sprintf("%v", peer.SELF.EnumIndex()), false)
|
||||
if len(pp.Data) > 0 {
|
||||
p := pp.Data[0].(*peer.Peer)
|
||||
// Re-sign for local verification if this is our own peer
|
||||
if !external && p.PublicKey == publicKey {
|
||||
if p.PublicKey == publicKey {
|
||||
sessionClaims.Session.IDToken["signature"] = ""
|
||||
// For internal requests, skip signature check by using the claims decoder directly
|
||||
ok, err := claims.GetClaims().DecodeClaimsInToken(host, method, forward, sessionClaims, publicKey, external)
|
||||
if err != nil {
|
||||
logger.Error().Msg("Failed to decode claims: " + err.Error())
|
||||
}
|
||||
return ok
|
||||
}
|
||||
}
|
||||
|
||||
// Check permissions via Keto.
|
||||
// A valid token with insufficient permissions → 403 (authenticated, not authorized).
|
||||
ok, err := claims.GetClaims().DecodeClaimsInToken(host, method, forward, sessionClaims, publicKey, external)
|
||||
if err != nil {
|
||||
logger.Error().Msg("Failed to decode claims: " + err.Error())
|
||||
logger.Error().Msg("Failed to decode claims in forward auth: " + err.Error())
|
||||
return http.StatusForbidden
|
||||
}
|
||||
return ok
|
||||
if !ok {
|
||||
return http.StatusForbidden
|
||||
}
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
// extractBearerToken extracts the token from a "Bearer xxx" Authorization header value
|
||||
|
||||
@@ -370,17 +370,20 @@ func (k KetoConnector) createRelationShip(object string, relation string, subjec
|
||||
log.Error().Msgf("createRelationShip unmarshal error: %s, err=%v", string(b), err)
|
||||
return nil, 500, err
|
||||
}
|
||||
perm := &Permission{
|
||||
Object: data["object"].(string),
|
||||
Relation: data["relation"].(string),
|
||||
Subject: data["subject_id"].(string),
|
||||
}
|
||||
if data["subject_set"] != nil {
|
||||
sub := data["subject_set"].(map[string]interface{})
|
||||
perm.SubPermission = &Permission{
|
||||
Object: sub["object"].(string),
|
||||
Relation: sub["relation"].(string),
|
||||
Subject: sub["subject_id"].(string),
|
||||
perm := &Permission{}
|
||||
if data != nil {
|
||||
perm = &Permission{
|
||||
Object: data["object"].(string),
|
||||
Relation: data["relation"].(string),
|
||||
Subject: data["subject_id"].(string),
|
||||
}
|
||||
if data["subject_set"] != nil {
|
||||
sub := data["subject_set"].(map[string]interface{})
|
||||
perm.SubPermission = &Permission{
|
||||
Object: sub["object"].(string),
|
||||
Relation: sub["relation"].(string),
|
||||
Subject: sub["subject_id"].(string),
|
||||
}
|
||||
}
|
||||
}
|
||||
return perm, 200, nil
|
||||
|
||||
Reference in New Issue
Block a user