Fonctionnalités fondamentales de la bibliothèque Go Kit

Fonctionnalités principales

Go Kit fournit plusieurs composants modulaires pour construire des services robustes :

  • auth - Gestion de l'authentification
  • circuitbreaker - Disjoncteur pour la résilience
  • endpoint - Définitoin des points d'accès aux services
  • log - Système de journalisation
  • metrics - Collecte de métriques
  • ratelimit - Limitation du débit
  • sd - Découverte de services
  • tracing - Traçabilité distribuée
  • transport - Couche de transport des requêtes

Middleware d'authentification

Authentification Basic Auth


func BasicAuthValidator(user, pass, realm string) endpoint.Middleware {
    hashUser := convertToHash([]byte(user))
    hashPass := convertToHash([]byte(pass))
    
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, req interface{}) (interface{}, error) {
            authHeader, exists := ctx.Value(httptransport.ContextKeyRequestAuthorization).(string)
            if !exists {
                return nil, AuthFailure{realm}
            }
            
            parsedUser, parsedPass, valid := extractBasicAuth(authHeader)
            if !valid {
                return nil, AuthFailure{realm}
            }
            
            hashParsedUser := convertToHash(parsedUser)
            hashParsedPass := convertToHash(parsedPass)
            
            if subtle.ConstantTimeCompare(hashParsedUser, hashUser) == 0 ||
               subtle.ConstantTimeCompare(hashParsedPass, hashPass) == 0 {
                return nil, AuthFailure{realm}
            }
            
            return next(ctx, req)
        }
    }
}

Authentification basée sur les attributs avec Casbin


func CasbinAuthorizationMiddleware(
    subject string, resource interface{}, permission string,
) endpoint.Middleware {
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, req interface{}) (resp interface{}, err error) {
            policyModel := ctx.Value(PolicyModelContextKey)
            accessPolicy := ctx.Value(AccessPolicyContextKey)
            
            enforcer, initErr := casbin.NewEnforcer(policyModel, accessPolicy)
            if initErr != nil {
                return nil, initErr
            }
            
            updatedCtx := context.WithValue(ctx, CasbinInstanceKey, enforcer)
            
            authorized, checkErr := enforcer.Enforce(subject, resource, permission)
            if checkErr != nil {
                return nil, checkErr
            }
            if !authorized {
                return nil, AccessDeniedError
            }
            
            return next(updatedCtx, req)
        }
    }
}

Génération et validation de tokens JWT


func JWTTokenGenerator(keyID string, secret []byte, algorithm jwt.SigningMethod, userClaims jwt.Claims) endpoint.Middleware {
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, req interface{}) (resp interface{}, err error) {
            token := jwt.NewWithClaims(algorithm, userClaims)
            token.Header["key_id"] = keyID
            
            signedToken, signErr := token.SignedString(secret)
            if signErr != nil {
                return nil, signErr
            }
            
            ctxWithToken := context.WithValue(ctx, JWTTokenKey, signedToken)
            return next(ctxWithToken, req)
        }
    }
}

func JWTTokenValidator(keyLookup jwt.Keyfunc, expectedAlg jwt.SigningMethod, claimsFactory ClaimsFactory) endpoint.Middleware {
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, req interface{}) (resp interface{}, err error) {
            tokenValue, exists := ctx.Value(JWTTokenKey).(string)
            if !exists {
                return nil, MissingTokenError
            }
            
            parsedToken, parseErr := jwt.ParseWithClaims(tokenValue, claimsFactory(), func(token *jwt.Token) (interface{}, error) {
                if token.Method != expectedAlg {
                    return nil, InvalidAlgorithmError
                }
                return keyLookup(token)
            })
            
            if parseErr != nil {
                validationErr, isValidation := parseErr.(*jwt.ValidationError)
                if isValidation {
                    switch {
                    case validationErr.Errors&jwt.ValidationErrorMalformed != 0:
                        return nil, MalformedTokenError
                    case validationErr.Errors&jwt.ValidationErrorExpired != 0:
                        return nil, ExpiredTokenError
                    case validationErr.Errors&jwt.ValidationErrorNotValidYet != 0:
                        return nil, InactiveTokenError
                    case validationErr.Inner != nil:
                        return nil, validationErr.Inner
                    }
                }
                return nil, parseErr
            }
            
            if !parsedToken.Valid {
                return nil, InvalidTokenError
            }
            
            ctxWithClaims := context.WithValue(ctx, JWTClaimsKey, parsedToken.Claims)
            return next(ctxWithClaims, req)
        }
    }
}

Protection par disjoncteur

Intégration avec GoBreaker


func GoBreakerMiddleware(cb *gobreaker.CircuitBreaker) endpoint.Middleware {
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, req interface{}) (interface{}, error) {
            return cb.Execute(func() (interface{}, error) {
                return next(ctx, req)
            })
        }
    }
}

Disjoncteur personnalisé


func CustomBreakerMiddleware(cb breaker.Breaker) endpoint.Middleware {
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, req interface{}) (result interface{}, err error) {
            if !cb.Authorize() {
                return nil, breaker.ErrCircuitOpen
            }
            
            start := time.Now()
            defer func() {
                duration := time.Since(start)
                if err == nil {
                    cb.RecordSuccess(duration)
                } else {
                    cb.RecordFailure(duration)
                }
            }()
            
            result, err = next(ctx, req)
            return
        }
    }
}

Intégration avec Hystrix


func HystrixMiddleware(cmdName string) endpoint.Middleware {
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, req interface{}) (resp interface{}, err error) {
            var result interface{}
            
            execErr := hystrix.Do(cmdName, func() error {
                var innerErr error
                result, innerErr = next(ctx, req)
                return innerErr
            }, nil)
            
            if execErr != nil {
                return nil, execErr
            }
            return result, nil
        }
    }
}

Limitation de débit

Limiter avec délai


func DelayingRateLimiter(waiter Waiter) endpoint.Middleware {
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, req interface{}) (interface{}, error) {
            waitErr := waiter.Wait(ctx)
            if waitErr != nil {
                return nil, waitErr
            }
            return next(ctx, req)
        }
    }
}

Limiter avec rejet d'erreur


func RejectingRateLimiter(allowance Allower) endpoint.Middleware {
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, req interface{}) (interface{}, error) {
            if !allowance.IsAllowed() {
                return nil, RateLimitExceeded
            }
            return next(ctx, req)
        }
    }
}

Étiquettes: go-kit middleware authentication circuit-breaker rate-limiting

Publié le 23 juillet à 21h38