mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-30 19:41:30 +02:00
Replace VNC JWT auth with a Noise_IK handshake bound to ACL-pushed pubkeys
This commit is contained in:
@@ -1,68 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
gojwt "github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// ErrTokenExpired signals that the iat-based token age check failed. Callers
|
||||
// use errors.Is to branch on it when they want to surface a stable machine-
|
||||
// readable reason (e.g. so a dashboard can prompt for re-login).
|
||||
var ErrTokenExpired = errors.New("token expired")
|
||||
|
||||
// CheckTokenAge validates that a JWT token's iat claim is within the given
|
||||
// maxAge duration. Returns an error if the claims are unparsable, the iat
|
||||
// claim is missing, or the token is too old.
|
||||
func CheckTokenAge(token *gojwt.Token, maxAge time.Duration) error {
|
||||
if token == nil {
|
||||
return fmt.Errorf("token is nil")
|
||||
}
|
||||
claims, ok := token.Claims.(gojwt.MapClaims)
|
||||
if !ok {
|
||||
return fmt.Errorf("token has invalid claims format (user=%s)", UserIDFromToken(token))
|
||||
}
|
||||
|
||||
iat, ok := claims["iat"].(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("token missing iat claim (user=%s)", UserIDFromToken(token))
|
||||
}
|
||||
|
||||
issuedAt := time.Unix(int64(iat), 0)
|
||||
tokenAge := time.Since(issuedAt)
|
||||
if tokenAge > maxAge {
|
||||
return fmt.Errorf("%w for user=%s: age=%v, max=%v", ErrTokenExpired, userIDFromClaims(claims), tokenAge, maxAge)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserIDFromToken extracts a human-readable user identifier from a JWT token
|
||||
// for use in error messages. Returns "unknown" if the token or claims are nil.
|
||||
func UserIDFromToken(token *gojwt.Token) string {
|
||||
if token == nil {
|
||||
return "unknown"
|
||||
}
|
||||
claims, ok := token.Claims.(gojwt.MapClaims)
|
||||
if !ok {
|
||||
return "unknown"
|
||||
}
|
||||
return userIDFromClaims(claims)
|
||||
}
|
||||
|
||||
// userIDFromClaims extracts a user identifier from JWT claims, trying sub,
|
||||
// user_id, and email in order.
|
||||
func userIDFromClaims(claims gojwt.MapClaims) string {
|
||||
if sub, ok := claims["sub"].(string); ok && sub != "" {
|
||||
return sub
|
||||
}
|
||||
if userID, ok := claims["user_id"].(string); ok && userID != "" {
|
||||
return userID
|
||||
}
|
||||
if email, ok := claims["email"].(string); ok && email != "" {
|
||||
return email
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
@@ -1007,6 +1007,10 @@ components:
|
||||
items:
|
||||
type: string
|
||||
example: "tcp/80"
|
||||
session_pub_key:
|
||||
description: Ephemeral Ed25519 public key the requester will sign session-binding challenges with. Required for VNC rules; ignored for SSH and L4.
|
||||
type: string
|
||||
example: "n0r3pL4c3h0ld3rK3y=="
|
||||
required:
|
||||
- name
|
||||
- wg_pub_key
|
||||
@@ -1028,10 +1032,15 @@ components:
|
||||
items:
|
||||
type: string
|
||||
example: "tcp/80"
|
||||
target_pub_key:
|
||||
description: Identity public key of the destination peer the temporary access was requested for. Used by the requester to verify the destination daemon's identity before transmitting credentials.
|
||||
type: string
|
||||
example: "n0r3pL4c3h0ld3rK3y=="
|
||||
required:
|
||||
- name
|
||||
- id
|
||||
- rules
|
||||
- target_pub_key
|
||||
AccessiblePeer:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/PeerMinimum'
|
||||
|
||||
@@ -3391,6 +3391,9 @@ type PeerTemporaryAccessRequest struct {
|
||||
// Rules List of temporary access rules
|
||||
Rules []string `json:"rules"`
|
||||
|
||||
// SessionPubKey Ephemeral Ed25519 public key the requester will sign session-binding challenges with. Required for VNC rules; ignored for SSH and L4.
|
||||
SessionPubKey *string `json:"session_pub_key,omitempty"`
|
||||
|
||||
// WgPubKey Peer's WireGuard public key
|
||||
WgPubKey string `json:"wg_pub_key"`
|
||||
}
|
||||
@@ -3405,6 +3408,9 @@ type PeerTemporaryAccessResponse struct {
|
||||
|
||||
// Rules List of temporary access rules
|
||||
Rules []string `json:"rules"`
|
||||
|
||||
// TargetPubKey Identity public key of the destination peer the temporary access was requested for. Used by the requester to verify the destination daemon's identity before transmitting credentials.
|
||||
TargetPubKey string `json:"target_pub_key"`
|
||||
}
|
||||
|
||||
// PersonalAccessToken defines model for PersonalAccessToken.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -428,9 +428,6 @@ message MachineUserIndexes {
|
||||
|
||||
// VNCAuth represents VNC authorization configuration for a peer.
|
||||
message VNCAuth {
|
||||
// UserIDClaim is the JWT claim to be used to get the users ID
|
||||
string UserIDClaim = 1;
|
||||
|
||||
// AuthorizedUsers is a list of hashed user IDs authorized to access this peer via VNC
|
||||
repeated bytes AuthorizedUsers = 2;
|
||||
|
||||
@@ -438,6 +435,24 @@ message VNCAuth {
|
||||
// Used in session mode to determine which OS user to create the virtual session as.
|
||||
// The wildcard "*" allows any OS user.
|
||||
map<string, MachineUserIndexes> machine_users = 3;
|
||||
|
||||
// SessionPubKeys are short-lived X25519 static keypairs the dashboard
|
||||
// (or other temporary-access clients) registers per session. The
|
||||
// daemon runs a Noise_IK handshake against the matching pubkey to
|
||||
// authenticate the connection and resolve the pubkey back to a user.
|
||||
repeated SessionPubKey session_pub_keys = 4;
|
||||
}
|
||||
|
||||
// SessionPubKey binds an ephemeral X25519 static public key to a hashed
|
||||
// user identity so the daemon can authorize VNC connections that
|
||||
// complete a Noise_IK handshake with the matching private key.
|
||||
message SessionPubKey {
|
||||
// PubKey is the 32-byte X25519 static public key.
|
||||
bytes pub_key = 1;
|
||||
|
||||
// UserIDHash is the BLAKE2b-128 hash of the user ID this session
|
||||
// belongs to, matching the entries in VNCAuth.AuthorizedUsers.
|
||||
bytes user_id_hash = 2;
|
||||
}
|
||||
|
||||
// RemotePeerConfig represents a configuration of a remote peer.
|
||||
|
||||
Reference in New Issue
Block a user