mirror of
https://github.com/netbirdio/netbird.git
synced 2025-06-11 04:16:49 +02:00
* Refactor setup key handling to use store methods Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * add lock to get account groups Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * add check for regular user Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * get only required groups for auto-group validation Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * add account lock and return auto groups map on validation Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * refactor account peers update Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor groups to use store methods Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * refactor GetGroupByID and add NewGroupNotFoundError Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add AddPeer and RemovePeer methods to Group struct Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Preserve store engine in SqlStore transactions Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Run groups ops in transaction Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix missing group removed from setup key activity Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix merge Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor posture checks to remove get and save account Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix refactor Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix merge Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix sonar Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Change setup key log level to debug for missing group Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Retrieve modified peers once for group events Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor policy get and save account to use store methods Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Retrieve policy groups and posture checks once for validation Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix typo Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add policy tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor anyGroupHasPeers to retrieve all groups once Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor dns settings to use store methods Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add account locking and merge group deletion methods Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor name server groups to use store methods Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add peer store methods Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor ephemeral peers Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add lock for peer store methods Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor peer handlers Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor peer to use store methods Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix typo Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add locks and remove log Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * run peer ops in transaction Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * remove duplicate store method Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix peer fields updated after save Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * add tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Use update strength and simplify check Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * prevent changing ruleID when not empty Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * prevent duplicate rules during updates Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix lint Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor auth middleware Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor account methods and mock Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor user and PAT handling Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Remove db query context and fix get user by id Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix database transaction locking issue Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Use UTC time in test Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add account locks Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix prevent users from creating PATs for other users Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add store locks and prevent fetching setup keys peers when retrieving user peers with empty userID Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add missing tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor test names and remove duplicate TestPostgresql_SavePeerStatus Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add account locks and remove redundant ephemeral check Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Retrieve all groups for peers and restrict groups for regular users Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix merge Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix merge Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix merge Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix store tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * use account object to get validated peers Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix merge Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Improve peer performance Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Get account direct from store without buffer Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Add get peer groups tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Adjust benchmarks Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Adjust benchmarks Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * [management] Update benchmark workflow (#3181) * update local benchmark expectations * update cloud expectations * Add status error for generic result error Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Use integrated validator direct Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * update expectations * update expectations * update expectations * Refactor peer scheduler to retry every 3 seconds on errors Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * update expectations * fix validator * fix validator * fix validator * update timeouts * Refactor ToGroupsInfo to process slices of groups Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * update expectations * update expectations * update expectations * Bump integrations version Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor GetValidatedPeers Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * go mod tidy Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Use peers and groups map for peers validation Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * remove mysql from api benchmark tests * Fix merge Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix blocked db calls on user auto groups update Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * update expectations Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * update expectations Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Skip user check for system initiated peer deletion Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Remove context in db calls Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * update expectations Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * [management] Improve group peer/resource counting (#3192) * Fix sonar Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Adjust bench expectations Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Rename GetAccountInfoFromPAT to GetTokenInfo Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fix tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Remove global account lock for ListUsers Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * build userinfo after updating users in db Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * [management] Optimize user bulk deletion (#3315) * refactor building user infos Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * fix tests Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * remove unused code Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Refactor GetUsersFromAccount to return a map of UserInfo instead of a slice Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Export BuildUserInfosForAccount to account manager Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Fetch account user info once for bulk users save Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Update user deletion expectations Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Set max open conns for activity store Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> * Update bench expectations Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> --------- Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> --------- Signed-off-by: bcmmbaga <bethuelmbaga12@gmail.com> Co-authored-by: Pascal Fischer <32096965+pascal-fischer@users.noreply.github.com> Co-authored-by: Pascal Fischer <pascal@netbird.io> Co-authored-by: Pedro Costa <550684+pnmcosta@users.noreply.github.com>
203 lines
7.0 KiB
Go
203 lines
7.0 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
nbContext "github.com/netbirdio/netbird/management/server/context"
|
|
"github.com/netbirdio/netbird/management/server/http/middleware/bypass"
|
|
"github.com/netbirdio/netbird/management/server/http/util"
|
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
|
"github.com/netbirdio/netbird/management/server/status"
|
|
"github.com/netbirdio/netbird/management/server/types"
|
|
)
|
|
|
|
// GetAccountInfoFromPATFunc function
|
|
type GetAccountInfoFromPATFunc func(ctx context.Context, token string) (user *types.User, pat *types.PersonalAccessToken, domain string, category string, err error)
|
|
|
|
// ValidateAndParseTokenFunc function
|
|
type ValidateAndParseTokenFunc func(ctx context.Context, token string) (*jwt.Token, error)
|
|
|
|
// MarkPATUsedFunc function
|
|
type MarkPATUsedFunc func(ctx context.Context, token string) error
|
|
|
|
// CheckUserAccessByJWTGroupsFunc function
|
|
type CheckUserAccessByJWTGroupsFunc func(ctx context.Context, claims jwtclaims.AuthorizationClaims) error
|
|
|
|
// AuthMiddleware middleware to verify personal access tokens (PAT) and JWT tokens
|
|
type AuthMiddleware struct {
|
|
getAccountInfoFromPAT GetAccountInfoFromPATFunc
|
|
validateAndParseToken ValidateAndParseTokenFunc
|
|
markPATUsed MarkPATUsedFunc
|
|
checkUserAccessByJWTGroups CheckUserAccessByJWTGroupsFunc
|
|
claimsExtractor *jwtclaims.ClaimsExtractor
|
|
audience string
|
|
userIDClaim string
|
|
}
|
|
|
|
const (
|
|
userProperty = "user"
|
|
)
|
|
|
|
// NewAuthMiddleware instance constructor
|
|
func NewAuthMiddleware(getAccountInfoFromPAT GetAccountInfoFromPATFunc, validateAndParseToken ValidateAndParseTokenFunc,
|
|
markPATUsed MarkPATUsedFunc, checkUserAccessByJWTGroups CheckUserAccessByJWTGroupsFunc, claimsExtractor *jwtclaims.ClaimsExtractor,
|
|
audience string, userIdClaim string) *AuthMiddleware {
|
|
if userIdClaim == "" {
|
|
userIdClaim = jwtclaims.UserIDClaim
|
|
}
|
|
|
|
return &AuthMiddleware{
|
|
getAccountInfoFromPAT: getAccountInfoFromPAT,
|
|
validateAndParseToken: validateAndParseToken,
|
|
markPATUsed: markPATUsed,
|
|
checkUserAccessByJWTGroups: checkUserAccessByJWTGroups,
|
|
claimsExtractor: claimsExtractor,
|
|
audience: audience,
|
|
userIDClaim: userIdClaim,
|
|
}
|
|
}
|
|
|
|
// Handler method of the middleware which authenticates a user either by JWT claims or by PAT
|
|
func (m *AuthMiddleware) Handler(h http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if bypass.ShouldBypass(r.URL.Path, h, w, r) {
|
|
return
|
|
}
|
|
|
|
auth := strings.Split(r.Header.Get("Authorization"), " ")
|
|
authType := strings.ToLower(auth[0])
|
|
|
|
// fallback to token when receive pat as bearer
|
|
if len(auth) >= 2 && authType == "bearer" && strings.HasPrefix(auth[1], "nbp_") {
|
|
authType = "token"
|
|
auth[0] = authType
|
|
}
|
|
|
|
switch authType {
|
|
case "bearer":
|
|
err := m.checkJWTFromRequest(w, r, auth)
|
|
if err != nil {
|
|
log.WithContext(r.Context()).Errorf("Error when validating JWT claims: %s", err.Error())
|
|
util.WriteError(r.Context(), status.Errorf(status.Unauthorized, "token invalid"), w)
|
|
return
|
|
}
|
|
case "token":
|
|
err := m.checkPATFromRequest(w, r, auth)
|
|
if err != nil {
|
|
log.WithContext(r.Context()).Debugf("Error when validating PAT claims: %s", err.Error())
|
|
util.WriteError(r.Context(), status.Errorf(status.Unauthorized, "token invalid"), w)
|
|
return
|
|
}
|
|
default:
|
|
util.WriteError(r.Context(), status.Errorf(status.Unauthorized, "no valid authentication provided"), w)
|
|
return
|
|
}
|
|
claims := m.claimsExtractor.FromRequestContext(r)
|
|
//nolint
|
|
ctx := context.WithValue(r.Context(), nbContext.UserIDKey, claims.UserId)
|
|
//nolint
|
|
ctx = context.WithValue(ctx, nbContext.AccountIDKey, claims.AccountId)
|
|
h.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
|
|
// CheckJWTFromRequest checks if the JWT is valid
|
|
func (m *AuthMiddleware) checkJWTFromRequest(w http.ResponseWriter, r *http.Request, auth []string) error {
|
|
token, err := getTokenFromJWTRequest(auth)
|
|
|
|
// If an error occurs, call the error handler and return an error
|
|
if err != nil {
|
|
return fmt.Errorf("Error extracting token: %w", err)
|
|
}
|
|
|
|
validatedToken, err := m.validateAndParseToken(r.Context(), token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if validatedToken == nil {
|
|
return nil
|
|
}
|
|
|
|
if err := m.verifyUserAccess(r.Context(), validatedToken); err != nil {
|
|
return err
|
|
}
|
|
|
|
// If we get here, everything worked and we can set the
|
|
// user property in context.
|
|
newRequest := r.WithContext(context.WithValue(r.Context(), userProperty, validatedToken)) //nolint
|
|
// Update the current request with the new context information.
|
|
*r = *newRequest
|
|
return nil
|
|
}
|
|
|
|
// verifyUserAccess checks if a user, based on a validated JWT token,
|
|
// is allowed access, particularly in cases where the admin enabled JWT
|
|
// group propagation and designated certain groups with access permissions.
|
|
func (m *AuthMiddleware) verifyUserAccess(ctx context.Context, validatedToken *jwt.Token) error {
|
|
authClaims := m.claimsExtractor.FromToken(validatedToken)
|
|
return m.checkUserAccessByJWTGroups(ctx, authClaims)
|
|
}
|
|
|
|
// CheckPATFromRequest checks if the PAT is valid
|
|
func (m *AuthMiddleware) checkPATFromRequest(w http.ResponseWriter, r *http.Request, auth []string) error {
|
|
token, err := getTokenFromPATRequest(auth)
|
|
if err != nil {
|
|
return fmt.Errorf("error extracting token: %w", err)
|
|
}
|
|
|
|
user, pat, accDomain, accCategory, err := m.getAccountInfoFromPAT(r.Context(), token)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid Token: %w", err)
|
|
}
|
|
if time.Now().After(pat.GetExpirationDate()) {
|
|
return fmt.Errorf("token expired")
|
|
}
|
|
|
|
err = m.markPATUsed(r.Context(), pat.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
claimMaps := jwt.MapClaims{}
|
|
claimMaps[m.userIDClaim] = user.Id
|
|
claimMaps[m.audience+jwtclaims.AccountIDSuffix] = user.AccountID
|
|
claimMaps[m.audience+jwtclaims.DomainIDSuffix] = accDomain
|
|
claimMaps[m.audience+jwtclaims.DomainCategorySuffix] = accCategory
|
|
claimMaps[jwtclaims.IsToken] = true
|
|
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claimMaps)
|
|
newRequest := r.WithContext(context.WithValue(r.Context(), jwtclaims.TokenUserProperty, jwtToken)) //nolint
|
|
// Update the current request with the new context information.
|
|
*r = *newRequest
|
|
return nil
|
|
}
|
|
|
|
// getTokenFromJWTRequest is a "TokenExtractor" that takes auth header parts and extracts
|
|
// the JWT token from the Authorization header.
|
|
func getTokenFromJWTRequest(authHeaderParts []string) (string, error) {
|
|
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
|
|
return "", errors.New("Authorization header format must be Bearer {token}")
|
|
}
|
|
|
|
return authHeaderParts[1], nil
|
|
}
|
|
|
|
// getTokenFromPATRequest is a "TokenExtractor" that takes auth header parts and extracts
|
|
// the PAT token from the Authorization header.
|
|
func getTokenFromPATRequest(authHeaderParts []string) (string, error) {
|
|
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "token" {
|
|
return "", errors.New("Authorization header format must be Token {token}")
|
|
}
|
|
|
|
return authHeaderParts[1], nil
|
|
}
|