浏览代码

node token: embed permissions directly in JWT

Signed-off-by: Nicola Murino <[email protected]>
Nicola Murino 1 月之前
父节点
当前提交
a5dd529d88
共有 4 个文件被更改,包括 54 次插入20 次删除
  1. 36 15
      internal/dataprovider/node.go
  2. 3 1
      internal/httpd/api_user.go
  3. 5 2
      internal/httpd/api_utils.go
  4. 10 2
      internal/httpd/middleware.go

+ 36 - 15
internal/dataprovider/node.go

@@ -132,17 +132,17 @@ func (n *Node) validate() error {
 	return n.Data.validate()
 }
 
-func (n *Node) authenticate(token string) (string, string, error) {
+func (n *Node) authenticate(token string) (string, string, []string, error) {
 	if err := n.Data.Key.TryDecrypt(); err != nil {
 		providerLog(logger.LevelError, "unable to decrypt node key: %v", err)
-		return "", "", err
+		return "", "", nil, err
 	}
 	if token == "" {
-		return "", "", ErrInvalidCredentials
+		return "", "", nil, ErrInvalidCredentials
 	}
 	t, err := jwt.Parse([]byte(token), jwt.WithKey(jwa.HS256, []byte(n.Data.Key.GetPayload())), jwt.WithValidate(true))
 	if err != nil {
-		return "", "", fmt.Errorf("unable to parse and validate token: %v", err)
+		return "", "", nil, fmt.Errorf("unable to parse and validate token: %v", err)
 	}
 	var adminUsername, role string
 	if admin, ok := t.Get("admin"); ok {
@@ -151,14 +151,16 @@ func (n *Node) authenticate(token string) (string, string, error) {
 		}
 	}
 	if adminUsername == "" {
-		return "", "", errors.New("no admin username associated with node token")
+		return "", "", nil, errors.New("no admin username associated with node token")
 	}
 	if r, ok := t.Get("role"); ok {
 		if val, ok := r.(string); ok && val != "" {
 			role = val
 		}
 	}
-	return adminUsername, role, nil
+	perms := getPermsFromToken(t)
+
+	return adminUsername, role, perms, nil
 }
 
 // getBaseURL returns the base URL for this node
@@ -175,7 +177,7 @@ func (n *Node) getBaseURL() string {
 }
 
 // generateAuthToken generates a new auth token
-func (n *Node) generateAuthToken(username, role string) (string, error) {
+func (n *Node) generateAuthToken(username, role string, permissions []string) (string, error) {
 	if err := n.Data.Key.TryDecrypt(); err != nil {
 		return "", fmt.Errorf("unable to decrypt node key: %w", err)
 	}
@@ -184,6 +186,7 @@ func (n *Node) generateAuthToken(username, role string) (string, error) {
 	t := jwt.New()
 	t.Set("admin", username)                          //nolint:errcheck
 	t.Set("role", role)                               //nolint:errcheck
+	t.Set("perms", permissions)                       //nolint:errcheck
 	t.Set(jwt.IssuedAtKey, now)                       //nolint:errcheck
 	t.Set(jwt.JwtIDKey, xid.New().String())           //nolint:errcheck
 	t.Set(jwt.NotBeforeKey, now.Add(-30*time.Second)) //nolint:errcheck
@@ -197,14 +200,14 @@ func (n *Node) generateAuthToken(username, role string) (string, error) {
 }
 
 func (n *Node) prepareRequest(ctx context.Context, username, role, relativeURL, method string,
-	body io.Reader,
+	permissions []string, body io.Reader,
 ) (*http.Request, error) {
 	url := fmt.Sprintf("%s%s", n.getBaseURL(), relativeURL)
 	req, err := http.NewRequestWithContext(ctx, method, url, body)
 	if err != nil {
 		return nil, err
 	}
-	token, err := n.generateAuthToken(username, role)
+	token, err := n.generateAuthToken(username, role, permissions)
 	if err != nil {
 		return nil, err
 	}
@@ -214,11 +217,11 @@ func (n *Node) prepareRequest(ctx context.Context, username, role, relativeURL,
 
 // SendGetRequest sends an HTTP GET request to this node.
 // The responseHolder must be a pointer
-func (n *Node) SendGetRequest(username, role, relativeURL string, responseHolder any) error {
+func (n *Node) SendGetRequest(username, role, relativeURL string, permissions []string, responseHolder any) error {
 	ctx, cancel := context.WithTimeout(context.Background(), nodeReqTimeout)
 	defer cancel()
 
-	req, err := n.prepareRequest(ctx, username, role, relativeURL, http.MethodGet, nil)
+	req, err := n.prepareRequest(ctx, username, role, relativeURL, http.MethodGet, permissions, nil)
 	if err != nil {
 		return err
 	}
@@ -246,11 +249,11 @@ func (n *Node) SendGetRequest(username, role, relativeURL string, responseHolder
 }
 
 // SendDeleteRequest sends an HTTP DELETE request to this node
-func (n *Node) SendDeleteRequest(username, role, relativeURL string) error {
+func (n *Node) SendDeleteRequest(username, role, relativeURL string, permissions []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), nodeReqTimeout)
 	defer cancel()
 
-	req, err := n.prepareRequest(ctx, username, role, relativeURL, http.MethodDelete, nil)
+	req, err := n.prepareRequest(ctx, username, role, relativeURL, http.MethodDelete, permissions, nil)
 	if err != nil {
 		return err
 	}
@@ -270,9 +273,9 @@ func (n *Node) SendDeleteRequest(username, role, relativeURL string) error {
 }
 
 // AuthenticateNodeToken check the validity of the provided token
-func AuthenticateNodeToken(token string) (string, string, error) {
+func AuthenticateNodeToken(token string) (string, string, []string, error) {
 	if currentNode == nil {
-		return "", "", errNoClusterNodes
+		return "", "", nil, errNoClusterNodes
 	}
 	return currentNode.authenticate(token)
 }
@@ -284,3 +287,21 @@ func GetNodeName() string {
 	}
 	return currentNode.Name
 }
+
+func getPermsFromToken(t jwt.Token) []string {
+	var perms []string
+	if p, ok := t.Get("perms"); ok {
+		switch v := p.(type) {
+		case []any:
+			for _, elem := range v {
+				switch elemValue := elem.(type) {
+				case string:
+					perms = append(perms, elemValue)
+				}
+			}
+		case []string:
+			perms = v
+		}
+	}
+	return perms
+}

+ 3 - 1
internal/httpd/api_user.go

@@ -264,7 +264,9 @@ func disconnectUser(username, admin, role string) {
 				logger.Warn(logSender, "", "unable to disconnect user %q, error getting node %q: %v", username, stat.Node, err)
 				continue
 			}
-			if err := n.SendDeleteRequest(admin, role, fmt.Sprintf("%s/%s", activeConnectionsPath, stat.ConnectionID)); err != nil {
+			perms := []string{dataprovider.PermAdminCloseConnections}
+			uri := fmt.Sprintf("%s/%s", activeConnectionsPath, stat.ConnectionID)
+			if err := n.SendDeleteRequest(admin, role, uri, perms); err != nil {
 				logger.Warn(logSender, "", "unable to disconnect user %q from node %q, error: %v", username, n.Name, err)
 			}
 		}

+ 5 - 2
internal/httpd/api_utils.go

@@ -217,7 +217,9 @@ func handleCloseConnection(w http.ResponseWriter, r *http.Request) {
 		sendAPIResponse(w, r, nil, http.StatusText(status), status)
 		return
 	}
-	if err := n.SendDeleteRequest(claims.Username, claims.Role, fmt.Sprintf("%s/%s", activeConnectionsPath, connectionID)); err != nil {
+	perms := []string{dataprovider.PermAdminCloseConnections}
+	uri := fmt.Sprintf("%s/%s", activeConnectionsPath, connectionID)
+	if err := n.SendDeleteRequest(claims.Username, claims.Role, uri, perms); err != nil {
 		logger.Warn(logSender, "", "unable to delete connection id %q from node %q: %v", connectionID, n.Name, err)
 		sendAPIResponse(w, r, nil, "Not Found", http.StatusNotFound)
 		return
@@ -243,7 +245,8 @@ func getNodesConnections(admin, role string) []common.ConnectionStatus {
 			defer wg.Done()
 
 			var stats []common.ConnectionStatus
-			if err := node.SendGetRequest(admin, role, activeConnectionsPath, &stats); err != nil {
+			perms := []string{dataprovider.PermAdminViewConnections}
+			if err := node.SendGetRequest(admin, role, activeConnectionsPath, perms, &stats); err != nil {
 				logger.Warn(logSender, "", "unable to get connections from node %s: %v", node.Name, err)
 				return
 			}

+ 10 - 2
internal/httpd/middleware.go

@@ -22,6 +22,7 @@ import (
 	"net/url"
 	"slices"
 	"strings"
+	"time"
 
 	"github.com/go-chi/jwtauth/v5"
 	"github.com/rs/xid"
@@ -369,15 +370,22 @@ func checkNodeToken(tokenAuth *jwtauth.JWTAuth) func(next http.Handler) http.Han
 			if len(token) > 7 && strings.ToUpper(token[0:6]) == "BEARER" {
 				token = token[7:]
 			}
-			admin, role, err := dataprovider.AuthenticateNodeToken(token)
+			if invalidatedJWTTokens.Get(token) {
+				logger.Debug(logSender, "", "the node token has been invalidated")
+				sendAPIResponse(w, r, fmt.Errorf("the provided token is not valid"), "", http.StatusUnauthorized)
+				return
+			}
+			admin, role, perms, err := dataprovider.AuthenticateNodeToken(token)
 			if err != nil {
 				logger.Debug(logSender, "", "unable to authenticate node token %q: %v", token, err)
 				sendAPIResponse(w, r, fmt.Errorf("the provided token cannot be authenticated"), "", http.StatusUnauthorized)
 				return
 			}
+			defer invalidatedJWTTokens.Add(token, time.Now().Add(2*time.Minute).UTC())
+
 			c := jwtTokenClaims{
 				Username:    admin,
-				Permissions: []string{dataprovider.PermAdminViewConnections, dataprovider.PermAdminCloseConnections},
+				Permissions: perms,
 				NodeID:      dataprovider.GetNodeName(),
 				Role:        role,
 			}