mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 08:16:39 +00:00
[misc] Move shared components to shared directory (#4286)
Moved the following directories: ``` - management/client → shared/management/client - management/domain → shared/management/domain - management/proto → shared/management/proto - signal/client → shared/signal/client - signal/proto → shared/signal/proto - relay/client → shared/relay/client - relay/auth → shared/relay/auth ``` and adjusted import paths
This commit is contained in:
8
shared/relay/auth/hmac/doc.go
Normal file
8
shared/relay/auth/hmac/doc.go
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
This package uses a similar HMAC method for authentication with the TURN server. The Management server provides the
|
||||
tokens for the peers. The peers manage these tokens in the token store. The token store is a simple thread safe store
|
||||
that keeps the tokens in memory. These tokens are used to authenticate the peers with the Relay server in the hello
|
||||
message.
|
||||
*/
|
||||
|
||||
package hmac
|
||||
44
shared/relay/auth/hmac/store.go
Normal file
44
shared/relay/auth/hmac/store.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package hmac
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
v2 "github.com/netbirdio/netbird/shared/relay/auth/hmac/v2"
|
||||
)
|
||||
|
||||
// TokenStore is a simple in-memory store for token
|
||||
// With this can update the token in thread safe way
|
||||
type TokenStore struct {
|
||||
mu sync.Mutex
|
||||
token []byte
|
||||
}
|
||||
|
||||
func (a *TokenStore) UpdateToken(token *Token) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if token == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sig, err := base64.StdEncoding.DecodeString(token.Signature)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode signature: %w", err)
|
||||
}
|
||||
|
||||
tok := v2.Token{
|
||||
AuthAlgo: v2.AuthAlgoHMACSHA256,
|
||||
Signature: sig,
|
||||
Payload: []byte(token.Payload),
|
||||
}
|
||||
|
||||
a.token = tok.Marshal()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *TokenStore) TokenBinary() []byte {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return a.token
|
||||
}
|
||||
94
shared/relay/auth/hmac/token.go
Normal file
94
shared/relay/auth/hmac/token.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package hmac
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"hash"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Payload string
|
||||
Signature string
|
||||
}
|
||||
|
||||
func unmarshalToken(payload []byte) (Token, error) {
|
||||
var creds Token
|
||||
buffer := bytes.NewBuffer(payload)
|
||||
decoder := gob.NewDecoder(buffer)
|
||||
err := decoder.Decode(&creds)
|
||||
return creds, err
|
||||
}
|
||||
|
||||
// TimedHMAC generates a token with TTL and uses a pre-shared secret known to the relay server
|
||||
type TimedHMAC struct {
|
||||
secret string
|
||||
timeToLive time.Duration
|
||||
}
|
||||
|
||||
// NewTimedHMAC creates a new TimedHMAC instance
|
||||
func NewTimedHMAC(secret string, timeToLive time.Duration) *TimedHMAC {
|
||||
return &TimedHMAC{
|
||||
secret: secret,
|
||||
timeToLive: timeToLive,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateToken generates new time-based secret token - basically Payload is a unix timestamp and Signature is a HMAC
|
||||
// hash of a timestamp with a preshared TURN secret
|
||||
func (m *TimedHMAC) GenerateToken(algo func() hash.Hash) (*Token, error) {
|
||||
timeAuth := time.Now().Add(m.timeToLive).Unix()
|
||||
timeStamp := strconv.FormatInt(timeAuth, 10)
|
||||
|
||||
checksum, err := m.generate(algo, timeStamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Token{
|
||||
Payload: timeStamp,
|
||||
Signature: base64.StdEncoding.EncodeToString(checksum),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Validate checks if the token is valid
|
||||
func (m *TimedHMAC) Validate(algo func() hash.Hash, token Token) error {
|
||||
expectedMAC, err := m.generate(algo, token.Payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
expectedSignature := base64.StdEncoding.EncodeToString(expectedMAC)
|
||||
|
||||
if !hmac.Equal([]byte(expectedSignature), []byte(token.Signature)) {
|
||||
return fmt.Errorf("signature mismatch")
|
||||
}
|
||||
|
||||
timeAuthInt, err := strconv.ParseInt(token.Payload, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid payload: %w", err)
|
||||
}
|
||||
|
||||
if time.Now().Unix() > timeAuthInt {
|
||||
return fmt.Errorf("expired token")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *TimedHMAC) generate(algo func() hash.Hash, payload string) ([]byte, error) {
|
||||
mac := hmac.New(algo, []byte(m.secret))
|
||||
_, err := mac.Write([]byte(payload))
|
||||
if err != nil {
|
||||
log.Debugf("failed to generate token: %s", err)
|
||||
return nil, fmt.Errorf("failed to generate token: %w", err)
|
||||
}
|
||||
|
||||
return mac.Sum(nil), nil
|
||||
}
|
||||
105
shared/relay/auth/hmac/token_test.go
Normal file
105
shared/relay/auth/hmac/token_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package hmac
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGenerateCredentials(t *testing.T) {
|
||||
secret := "secret"
|
||||
timeToLive := 1 * time.Hour
|
||||
v := NewTimedHMAC(secret, timeToLive)
|
||||
|
||||
creds, err := v.GenerateToken(sha1.New)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if creds.Payload == "" {
|
||||
t.Fatalf("expected non-empty payload")
|
||||
}
|
||||
|
||||
_, err = strconv.ParseInt(creds.Payload, 10, 64)
|
||||
if err != nil {
|
||||
t.Fatalf("expected payload to be a valid unix timestamp, got %v", err)
|
||||
}
|
||||
|
||||
_, err = base64.StdEncoding.DecodeString(creds.Signature)
|
||||
if err != nil {
|
||||
t.Fatalf("expected signature to be base64 encoded, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCredentials(t *testing.T) {
|
||||
secret := "supersecret"
|
||||
timeToLive := 1 * time.Hour
|
||||
manager := NewTimedHMAC(secret, timeToLive)
|
||||
|
||||
// Test valid token
|
||||
creds, err := manager.GenerateToken(sha1.New)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if err := manager.Validate(sha1.New, *creds); err != nil {
|
||||
t.Fatalf("expected valid token: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidSignature(t *testing.T) {
|
||||
secret := "supersecret"
|
||||
timeToLive := 1 * time.Hour
|
||||
manager := NewTimedHMAC(secret, timeToLive)
|
||||
|
||||
creds, err := manager.GenerateToken(sha256.New)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
invalidCreds := &Token{
|
||||
Payload: creds.Payload,
|
||||
Signature: "invalidsignature",
|
||||
}
|
||||
|
||||
if err = manager.Validate(sha1.New, *invalidCreds); err == nil {
|
||||
t.Fatalf("expected invalid token due to signature mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpired(t *testing.T) {
|
||||
secret := "supersecret"
|
||||
v := NewTimedHMAC(secret, -1*time.Hour)
|
||||
expiredCreds, err := v.GenerateToken(sha256.New)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if err = v.Validate(sha1.New, *expiredCreds); err == nil {
|
||||
t.Fatalf("expected invalid token due to expiration")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidPayload(t *testing.T) {
|
||||
secret := "supersecret"
|
||||
timeToLive := 1 * time.Hour
|
||||
v := NewTimedHMAC(secret, timeToLive)
|
||||
|
||||
creds, err := v.GenerateToken(sha256.New)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// Test invalid payload
|
||||
invalidPayloadCreds := &Token{
|
||||
Payload: "invalidtimestamp",
|
||||
Signature: creds.Signature,
|
||||
}
|
||||
|
||||
if err = v.Validate(sha1.New, *invalidPayloadCreds); err == nil {
|
||||
t.Fatalf("expected invalid token due to invalid payload")
|
||||
}
|
||||
}
|
||||
40
shared/relay/auth/hmac/v2/algo.go
Normal file
40
shared/relay/auth/hmac/v2/algo.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"hash"
|
||||
)
|
||||
|
||||
const (
|
||||
AuthAlgoUnknown AuthAlgo = iota
|
||||
AuthAlgoHMACSHA256
|
||||
)
|
||||
|
||||
type AuthAlgo uint8
|
||||
|
||||
func (a AuthAlgo) String() string {
|
||||
switch a {
|
||||
case AuthAlgoHMACSHA256:
|
||||
return "HMAC-SHA256"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (a AuthAlgo) New() func() hash.Hash {
|
||||
switch a {
|
||||
case AuthAlgoHMACSHA256:
|
||||
return sha256.New
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a AuthAlgo) Size() int {
|
||||
switch a {
|
||||
case AuthAlgoHMACSHA256:
|
||||
return sha256.Size
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
45
shared/relay/auth/hmac/v2/generator.go
Normal file
45
shared/relay/auth/hmac/v2/generator.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"fmt"
|
||||
"hash"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Generator struct {
|
||||
algo func() hash.Hash
|
||||
algoType AuthAlgo
|
||||
secret []byte
|
||||
timeToLive time.Duration
|
||||
}
|
||||
|
||||
func NewGenerator(algo AuthAlgo, secret []byte, timeToLive time.Duration) (*Generator, error) {
|
||||
algoFunc := algo.New()
|
||||
if algoFunc == nil {
|
||||
return nil, fmt.Errorf("unsupported auth algorithm: %s", algo)
|
||||
}
|
||||
return &Generator{
|
||||
algo: algoFunc,
|
||||
algoType: algo,
|
||||
secret: secret,
|
||||
timeToLive: timeToLive,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *Generator) GenerateToken() (*Token, error) {
|
||||
expirationTime := time.Now().Add(g.timeToLive).Unix()
|
||||
|
||||
payload := []byte(strconv.FormatInt(expirationTime, 10))
|
||||
|
||||
h := hmac.New(g.algo, g.secret)
|
||||
h.Write(payload)
|
||||
signature := h.Sum(nil)
|
||||
|
||||
return &Token{
|
||||
AuthAlgo: g.algoType,
|
||||
Signature: signature,
|
||||
Payload: payload,
|
||||
}, nil
|
||||
}
|
||||
110
shared/relay/auth/hmac/v2/hmac_test.go
Normal file
110
shared/relay/auth/hmac/v2/hmac_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGenerateCredentials(t *testing.T) {
|
||||
secret := "supersecret"
|
||||
timeToLive := 1 * time.Hour
|
||||
g, err := NewGenerator(AuthAlgoHMACSHA256, []byte(secret), timeToLive)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create generator: %v", err)
|
||||
}
|
||||
|
||||
token, err := g.GenerateToken()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if len(token.Payload) == 0 {
|
||||
t.Fatalf("expected non-empty payload")
|
||||
}
|
||||
|
||||
_, err = strconv.ParseInt(string(token.Payload), 10, 64)
|
||||
if err != nil {
|
||||
t.Fatalf("expected payload to be a valid unix timestamp, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCredentials(t *testing.T) {
|
||||
secret := "supersecret"
|
||||
timeToLive := 1 * time.Hour
|
||||
g, err := NewGenerator(AuthAlgoHMACSHA256, []byte(secret), timeToLive)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create generator: %v", err)
|
||||
}
|
||||
|
||||
token, err := g.GenerateToken()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
v := NewValidator([]byte(secret))
|
||||
if err := v.Validate(token.Marshal()); err != nil {
|
||||
t.Fatalf("expected valid token: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidSignature(t *testing.T) {
|
||||
secret := "supersecret"
|
||||
timeToLive := 1 * time.Hour
|
||||
g, err := NewGenerator(AuthAlgoHMACSHA256, []byte(secret), timeToLive)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create generator: %v", err)
|
||||
}
|
||||
|
||||
token, err := g.GenerateToken()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
token.Signature = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
|
||||
v := NewValidator([]byte(secret))
|
||||
if err := v.Validate(token.Marshal()); err == nil {
|
||||
t.Fatalf("expected valid token: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpired(t *testing.T) {
|
||||
secret := "supersecret"
|
||||
timeToLive := -1 * time.Hour
|
||||
g, err := NewGenerator(AuthAlgoHMACSHA256, []byte(secret), timeToLive)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create generator: %v", err)
|
||||
}
|
||||
|
||||
token, err := g.GenerateToken()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
v := NewValidator([]byte(secret))
|
||||
if err := v.Validate(token.Marshal()); err == nil {
|
||||
t.Fatalf("expected valid token: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidPayload(t *testing.T) {
|
||||
secret := "supersecret"
|
||||
timeToLive := 1 * time.Hour
|
||||
g, err := NewGenerator(AuthAlgoHMACSHA256, []byte(secret), timeToLive)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create generator: %v", err)
|
||||
}
|
||||
|
||||
token, err := g.GenerateToken()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
token.Payload = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
|
||||
v := NewValidator([]byte(secret))
|
||||
if err := v.Validate(token.Marshal()); err == nil {
|
||||
t.Fatalf("expected invalid token due to invalid payload")
|
||||
}
|
||||
}
|
||||
39
shared/relay/auth/hmac/v2/token.go
Normal file
39
shared/relay/auth/hmac/v2/token.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package v2
|
||||
|
||||
import "errors"
|
||||
|
||||
type Token struct {
|
||||
AuthAlgo AuthAlgo
|
||||
Signature []byte
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
func (t *Token) Marshal() []byte {
|
||||
size := 1 + len(t.Signature) + len(t.Payload)
|
||||
|
||||
buf := make([]byte, size)
|
||||
|
||||
buf[0] = byte(t.AuthAlgo)
|
||||
copy(buf[1:], t.Signature)
|
||||
copy(buf[1+len(t.Signature):], t.Payload)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func UnmarshalToken(data []byte) (*Token, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, errors.New("invalid token data")
|
||||
}
|
||||
|
||||
algo := AuthAlgo(data[0])
|
||||
sigSize := algo.Size()
|
||||
if len(data) < 1+sigSize {
|
||||
return nil, errors.New("invalid token data: insufficient length")
|
||||
}
|
||||
|
||||
return &Token{
|
||||
AuthAlgo: algo,
|
||||
Signature: data[1 : 1+sigSize],
|
||||
Payload: data[1+sigSize:],
|
||||
}, nil
|
||||
}
|
||||
59
shared/relay/auth/hmac/v2/validator.go
Normal file
59
shared/relay/auth/hmac/v2/validator.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const minLengthUnixTimestamp = 10
|
||||
|
||||
type Validator struct {
|
||||
secret []byte
|
||||
}
|
||||
|
||||
func NewValidator(secret []byte) *Validator {
|
||||
return &Validator{secret: secret}
|
||||
}
|
||||
|
||||
func (v *Validator) Validate(data any) error {
|
||||
d, ok := data.([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid data type")
|
||||
}
|
||||
|
||||
token, err := UnmarshalToken(d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal token: %w", err)
|
||||
}
|
||||
|
||||
if len(token.Payload) < minLengthUnixTimestamp {
|
||||
return errors.New("invalid payload: insufficient length")
|
||||
}
|
||||
|
||||
hashFunc := token.AuthAlgo.New()
|
||||
if hashFunc == nil {
|
||||
return fmt.Errorf("unsupported auth algorithm: %s", token.AuthAlgo)
|
||||
}
|
||||
|
||||
h := hmac.New(hashFunc, v.secret)
|
||||
h.Write(token.Payload)
|
||||
expectedMAC := h.Sum(nil)
|
||||
|
||||
if !hmac.Equal(token.Signature, expectedMAC) {
|
||||
return errors.New("invalid signature")
|
||||
}
|
||||
|
||||
timestamp, err := strconv.ParseInt(string(token.Payload), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid payload: %w", err)
|
||||
}
|
||||
|
||||
if time.Now().Unix() > timestamp {
|
||||
return fmt.Errorf("expired token")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
33
shared/relay/auth/hmac/validator.go
Normal file
33
shared/relay/auth/hmac/validator.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package hmac
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type TimedHMACValidator struct {
|
||||
*TimedHMAC
|
||||
}
|
||||
|
||||
func NewTimedHMACValidator(secret string, duration time.Duration) *TimedHMACValidator {
|
||||
ta := NewTimedHMAC(secret, duration)
|
||||
return &TimedHMACValidator{
|
||||
ta,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *TimedHMACValidator) Validate(credentials any) error {
|
||||
b, ok := credentials.([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid credentials type")
|
||||
}
|
||||
c, err := unmarshalToken(b)
|
||||
if err != nil {
|
||||
log.Debugf("failed to unmarshal token: %s", err)
|
||||
return err
|
||||
}
|
||||
return a.TimedHMAC.Validate(sha256.New, c)
|
||||
}
|
||||
Reference in New Issue
Block a user