diff --git a/management/internals/modules/reverseproxy/manager/manager.go b/management/internals/modules/reverseproxy/manager/manager.go index f504fb055..f42e07398 100644 --- a/management/internals/modules/reverseproxy/manager/manager.go +++ b/management/internals/modules/reverseproxy/manager/manager.go @@ -148,6 +148,10 @@ func (m *managerImpl) CreateReverseProxy(ctx context.Context, accountID, userID reverseProxy.AccountID = accountID reverseProxy.ProxyCluster = proxyCluster reverseProxy.InitNewRecord() + err = reverseProxy.Auth.HashSecrets() + if err != nil { + return nil, fmt.Errorf("hash secrets: %w", err) + } // Generate session JWT signing keys keyPair, err := sessionkey.GenerateKeyPair() @@ -215,6 +219,11 @@ func (m *managerImpl) UpdateReverseProxy(ctx context.Context, accountID, userID var domainChanged bool var reverseProxyEnabledChanged bool + err = reverseProxy.Auth.HashSecrets() + if err != nil { + return nil, fmt.Errorf("hash secrets: %w", err) + } + err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error { existingReverseProxy, err := transaction.GetReverseProxyByID(ctx, store.LockingStrengthUpdate, accountID, reverseProxy.ID) if err != nil { @@ -246,6 +255,18 @@ func (m *managerImpl) UpdateReverseProxy(ctx context.Context, accountID, userID reverseProxy.ProxyCluster = existingReverseProxy.ProxyCluster } + if reverseProxy.Auth.PasswordAuth != nil && reverseProxy.Auth.PasswordAuth.Enabled && + existingReverseProxy.Auth.PasswordAuth != nil && existingReverseProxy.Auth.PasswordAuth.Enabled && + reverseProxy.Auth.PasswordAuth.Password == "" { + reverseProxy.Auth.PasswordAuth = existingReverseProxy.Auth.PasswordAuth + } + + if reverseProxy.Auth.PinAuth != nil && reverseProxy.Auth.PinAuth.Enabled && + existingReverseProxy.Auth.PinAuth != nil && existingReverseProxy.Auth.PinAuth.Enabled && + reverseProxy.Auth.PinAuth.Pin == "" { + reverseProxy.Auth.PinAuth = existingReverseProxy.Auth.PinAuth + } + reverseProxy.Meta = existingReverseProxy.Meta reverseProxy.SessionPrivateKey = existingReverseProxy.SessionPrivateKey reverseProxy.SessionPublicKey = existingReverseProxy.SessionPublicKey diff --git a/management/internals/modules/reverseproxy/reverseproxy.go b/management/internals/modules/reverseproxy/reverseproxy.go index 502de0d4a..9708139f7 100644 --- a/management/internals/modules/reverseproxy/reverseproxy.go +++ b/management/internals/modules/reverseproxy/reverseproxy.go @@ -10,6 +10,7 @@ import ( "github.com/rs/xid" log "github.com/sirupsen/logrus" + "golang.org/x/crypto/bcrypt" "github.com/netbirdio/netbird/util/crypt" @@ -75,6 +76,35 @@ type AuthConfig struct { BearerAuth *BearerAuthConfig `json:"bearer_auth,omitempty" gorm:"serializer:json"` } +func (a *AuthConfig) HashSecrets() error { + if a.PasswordAuth != nil && a.PasswordAuth.Enabled && a.PasswordAuth.Password != "" { + hash, err := bcrypt.GenerateFromPassword([]byte(a.PasswordAuth.Password), 12) + if err != nil { + return err + } + a.PasswordAuth.Password = string(hash) + } + + if a.PinAuth != nil && a.PinAuth.Enabled && a.PinAuth.Pin != "" { + hash, err := bcrypt.GenerateFromPassword([]byte(a.PinAuth.Pin), 12) + if err != nil { + return err + } + a.PinAuth.Pin = string(hash) + } + + return nil +} + +func (a *AuthConfig) ClearSecrets() { + if a.PasswordAuth != nil { + a.PasswordAuth.Password = "" + } + if a.PinAuth != nil { + a.PinAuth.Pin = "" + } +} + type OIDCValidationConfig struct { Issuer string Audiences []string @@ -133,6 +163,8 @@ func (r *ReverseProxy) InitNewRecord() { } func (r *ReverseProxy) ToAPIResponse() *api.ReverseProxy { + r.Auth.ClearSecrets() + authConfig := api.ReverseProxyAuthConfig{} if r.Auth.PasswordAuth != nil { diff --git a/management/internals/shared/grpc/proxy.go b/management/internals/shared/grpc/proxy.go index 7d64eee2f..235110927 100644 --- a/management/internals/shared/grpc/proxy.go +++ b/management/internals/shared/grpc/proxy.go @@ -4,7 +4,6 @@ import ( "context" "crypto/hmac" "crypto/sha256" - "crypto/subtle" "encoding/base64" "encoding/hex" "errors" @@ -17,6 +16,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" log "github.com/sirupsen/logrus" + "golang.org/x/crypto/bcrypt" "golang.org/x/oauth2" "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" @@ -441,7 +441,16 @@ func (s *ProxyServiceServer) Authenticate(ctx context.Context, req *proto.Authen // Break here and use the default authenticated == false. break } - authenticated = subtle.ConstantTimeCompare([]byte(auth.Pin), []byte(v.Pin.GetPin())) == 1 + err = bcrypt.CompareHashAndPassword([]byte(auth.Pin), []byte(v.Pin.GetPin())) + if err != nil { + if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { + log.WithContext(ctx).Tracef("PIN authentication failed: invalid PIN") + } else { + log.WithContext(ctx).Errorf("PIN authentication error: %v", err) + } + break + } + authenticated = true userId = "pin-user" method = proxyauth.MethodPIN case *proto.AuthenticateRequest_Password: @@ -451,7 +460,16 @@ func (s *ProxyServiceServer) Authenticate(ctx context.Context, req *proto.Authen // Break here and use the default authenticated == false. break } - authenticated = subtle.ConstantTimeCompare([]byte(auth.Password), []byte(v.Password.GetPassword())) == 1 + err = bcrypt.CompareHashAndPassword([]byte(auth.Password), []byte(v.Password.GetPassword())) + if err != nil { + if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { + log.WithContext(ctx).Tracef("Password authentication failed: invalid password") + } else { + log.WithContext(ctx).Errorf("Password authentication error: %v", err) + } + break + } + authenticated = true userId = "password-user" method = proxyauth.MethodPassword }