Add management-integrations (#1227)

This commit is contained in:
Yury Gargay 2023-10-17 17:19:47 +02:00 committed by GitHub
parent 7d8a69cc0c
commit 87cc53b743
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 32 additions and 30 deletions

1
go.mod
View File

@ -51,6 +51,7 @@ require (
github.com/miekg/dns v1.1.43 github.com/miekg/dns v1.1.43
github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/nadoo/ipset v0.5.0 github.com/nadoo/ipset v0.5.0
github.com/netbirdio/management-integrations/integrations v0.0.0-20231017101406-322cbabed3da
github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/okta/okta-sdk-golang/v2 v2.18.0
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pion/logging v0.2.2 github.com/pion/logging v0.2.2

2
go.sum
View File

@ -495,6 +495,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ=
github.com/netbirdio/management-integrations/integrations v0.0.0-20231017101406-322cbabed3da h1:S1RoPhLTw3+IhHGnyfcQlj4aqIIaQdVd3SqaiK+MYFY=
github.com/netbirdio/management-integrations/integrations v0.0.0-20231017101406-322cbabed3da/go.mod h1:KSqjzHcqlodTWiuap5lRXxt5KT3vtYRoksL0KIrTK40=
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g=
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/netbirdio/systray v0.0.0-20221012095658-dc8eda872c0c h1:wK/s4nyZj/GF/kFJQjX6nqNfE0G3gcqd6hhnPCyp4sw= github.com/netbirdio/systray v0.0.0-20221012095658-dc8eda872c0c h1:wK/s4nyZj/GF/kFJQjX6nqNfE0G3gcqd6hhnPCyp4sw=

View File

@ -6,6 +6,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/rs/cors" "github.com/rs/cors"
"github.com/netbirdio/management-integrations/integrations"
s "github.com/netbirdio/netbird/management/server" s "github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/http/middleware" "github.com/netbirdio/netbird/management/server/http/middleware"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
@ -58,6 +59,7 @@ func APIHandler(accountManager s.AccountManager, jwtValidator jwtclaims.JWTValid
AuthCfg: authCfg, AuthCfg: authCfg,
} }
integrations.RegisterHandlers(api.Router, accountManager)
api.addAccountsEndpoint() api.addAccountsEndpoint()
api.addPeersEndpoint() api.addPeersEndpoint()
api.addUsersEndpoint() api.addUsersEndpoint()
@ -73,8 +75,8 @@ func APIHandler(accountManager s.AccountManager, jwtValidator jwtclaims.JWTValid
err := api.Router.Walk(func(route *mux.Route, _ *mux.Router, _ []*mux.Route) error { err := api.Router.Walk(func(route *mux.Route, _ *mux.Router, _ []*mux.Route) error {
methods, err := route.GetMethods() methods, err := route.GetMethods()
if err != nil { if err != nil { // we may have wildcard routes from integrations without methods, skip them for now
return err methods = []string{}
} }
for _, method := range methods { for _, method := range methods {
template, err := route.GetPathTemplate() template, err := route.GetPathTemplate()

View File

@ -57,10 +57,17 @@ func NewAuthMiddleware(getAccountFromPAT GetAccountFromPATFunc, validateAndParse
func (m *AuthMiddleware) Handler(h http.Handler) http.Handler { func (m *AuthMiddleware) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
auth := strings.Split(r.Header.Get("Authorization"), " ") auth := strings.Split(r.Header.Get("Authorization"), " ")
authType := auth[0] authType := strings.ToLower(auth[0])
switch strings.ToLower(authType) {
// 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": case "bearer":
err := m.CheckJWTFromRequest(w, r) err := m.checkJWTFromRequest(w, r, auth)
if err != nil { if err != nil {
log.Errorf("Error when validating JWT claims: %s", err.Error()) log.Errorf("Error when validating JWT claims: %s", err.Error())
util.WriteError(status.Errorf(status.Unauthorized, "token invalid"), w) util.WriteError(status.Errorf(status.Unauthorized, "token invalid"), w)
@ -68,7 +75,7 @@ func (m *AuthMiddleware) Handler(h http.Handler) http.Handler {
} }
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
case "token": case "token":
err := m.CheckPATFromRequest(w, r) err := m.checkPATFromRequest(w, r, auth)
if err != nil { if err != nil {
log.Debugf("Error when validating PAT claims: %s", err.Error()) log.Debugf("Error when validating PAT claims: %s", err.Error())
util.WriteError(status.Errorf(status.Unauthorized, "token invalid"), w) util.WriteError(status.Errorf(status.Unauthorized, "token invalid"), w)
@ -83,9 +90,8 @@ func (m *AuthMiddleware) Handler(h http.Handler) http.Handler {
} }
// CheckJWTFromRequest checks if the JWT is valid // CheckJWTFromRequest checks if the JWT is valid
func (m *AuthMiddleware) CheckJWTFromRequest(w http.ResponseWriter, r *http.Request) error { func (m *AuthMiddleware) checkJWTFromRequest(w http.ResponseWriter, r *http.Request, auth []string) error {
token, err := getTokenFromJWTRequest(auth)
token, err := getTokenFromJWTRequest(r)
// If an error occurs, call the error handler and return an error // If an error occurs, call the error handler and return an error
if err != nil { if err != nil {
@ -110,8 +116,8 @@ func (m *AuthMiddleware) CheckJWTFromRequest(w http.ResponseWriter, r *http.Requ
} }
// CheckPATFromRequest checks if the PAT is valid // CheckPATFromRequest checks if the PAT is valid
func (m *AuthMiddleware) CheckPATFromRequest(w http.ResponseWriter, r *http.Request) error { func (m *AuthMiddleware) checkPATFromRequest(w http.ResponseWriter, r *http.Request, auth []string) error {
token, err := getTokenFromPATRequest(r) token, err := getTokenFromPATRequest(auth)
// If an error occurs, call the error handler and return an error // If an error occurs, call the error handler and return an error
if err != nil { if err != nil {
@ -143,16 +149,9 @@ func (m *AuthMiddleware) CheckPATFromRequest(w http.ResponseWriter, r *http.Requ
return nil return nil
} }
// getTokenFromJWTRequest is a "TokenExtractor" that takes a give request and extracts // getTokenFromJWTRequest is a "TokenExtractor" that takes auth header parts and extracts
// the JWT token from the Authorization header. // the JWT token from the Authorization header.
func getTokenFromJWTRequest(r *http.Request) (string, error) { func getTokenFromJWTRequest(authHeaderParts []string) (string, error) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return "", nil // No error, just no token
}
// TODO: Make this a bit more robust, parsing-wise
authHeaderParts := strings.Fields(authHeader)
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" { if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
return "", errors.New("Authorization header format must be Bearer {token}") return "", errors.New("Authorization header format must be Bearer {token}")
} }
@ -160,16 +159,9 @@ func getTokenFromJWTRequest(r *http.Request) (string, error) {
return authHeaderParts[1], nil return authHeaderParts[1], nil
} }
// getTokenFromPATRequest is a "TokenExtractor" that takes a give request and extracts // getTokenFromPATRequest is a "TokenExtractor" that takes auth header parts and extracts
// the PAT token from the Authorization header. // the PAT token from the Authorization header.
func getTokenFromPATRequest(r *http.Request) (string, error) { func getTokenFromPATRequest(authHeaderParts []string) (string, error) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return "", nil // No error, just no token
}
// TODO: Make this a bit more robust, parsing-wise
authHeaderParts := strings.Fields(authHeader)
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "token" { if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "token" {
return "", errors.New("Authorization header format must be Token {token}") return "", errors.New("Authorization header format must be Token {token}")
} }

View File

@ -19,7 +19,7 @@ const (
domain = "domain" domain = "domain"
userID = "userID" userID = "userID"
tokenID = "tokenID" tokenID = "tokenID"
PAT = "PAT" PAT = "nbp_PAT"
JWT = "JWT" JWT = "JWT"
wrongToken = "wrongToken" wrongToken = "wrongToken"
) )
@ -82,6 +82,11 @@ func TestAuthMiddleware_Handler(t *testing.T) {
authHeader: "Token " + wrongToken, authHeader: "Token " + wrongToken,
expectedStatusCode: 401, expectedStatusCode: 401,
}, },
{
name: "Fallback to PAT Token",
authHeader: "Bearer " + PAT,
expectedStatusCode: 200,
},
{ {
name: "Valid JWT Token", name: "Valid JWT Token",
authHeader: "Bearer " + JWT, authHeader: "Bearer " + JWT,