Merge prototype/reverse-proxy with proxy clustering support

Combines token-based authentication from upstream with proxy clustering:
- Session keys for JWT signing (SessionPrivateKey/SessionPublicKey)
- One-time token store for proxy authentication
- Cluster-targeted updates via SendReverseProxyUpdateToCluster
- ProxyCluster field derived from domain
- OIDC validation config support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
mlsmaycon
2026-02-05 14:08:07 +01:00
73 changed files with 5239 additions and 1253 deletions

View File

@@ -7,6 +7,7 @@ import (
"strconv"
"time"
"github.com/netbirdio/netbird/util/crypt"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
@@ -31,6 +32,9 @@ const (
StatusCertificatePending ProxyStatus = "certificate_pending"
StatusCertificateFailed ProxyStatus = "certificate_failed"
StatusError ProxyStatus = "error"
TargetTypePeer = "peer"
TargetTypeResource = "resource"
)
type Target struct {
@@ -58,15 +62,17 @@ type BearerAuthConfig struct {
DistributionGroups []string `json:"distribution_groups,omitempty" gorm:"serializer:json"`
}
type LinkAuthConfig struct {
Enabled bool `json:"enabled"`
}
type AuthConfig struct {
PasswordAuth *PasswordAuthConfig `json:"password_auth,omitempty" gorm:"serializer:json"`
PinAuth *PINAuthConfig `json:"pin_auth,omitempty" gorm:"serializer:json"`
BearerAuth *BearerAuthConfig `json:"bearer_auth,omitempty" gorm:"serializer:json"`
LinkAuth *LinkAuthConfig `json:"link_auth,omitempty" gorm:"serializer:json"`
}
type OIDCValidationConfig struct {
Issuer string
Audiences []string
KeysLocation string
MaxTokenAgeSeconds int64
}
type ReverseProxyMeta struct {
@@ -76,15 +82,17 @@ type ReverseProxyMeta struct {
}
type ReverseProxy struct {
ID string `gorm:"primaryKey"`
AccountID string `gorm:"index"`
Name string
Domain string `gorm:"index"`
ProxyCluster string `gorm:"index"`
Targets []Target `gorm:"serializer:json"`
Enabled bool
Auth AuthConfig `gorm:"serializer:json"`
Meta ReverseProxyMeta `gorm:"embedded;embeddedPrefix:meta_"`
ID string `gorm:"primaryKey"`
AccountID string `gorm:"index"`
Name string
Domain string `gorm:"index"`
ProxyCluster string `gorm:"index"`
Targets []Target `gorm:"serializer:json"`
Enabled bool
Auth AuthConfig `gorm:"serializer:json"`
Meta ReverseProxyMeta `gorm:"embedded;embeddedPrefix:meta_"`
SessionPrivateKey string `gorm:"column:session_private_key"`
SessionPublicKey string `gorm:"column:session_public_key"`
}
func NewReverseProxy(accountID, name, domain, proxyCluster string, targets []Target, enabled bool) *ReverseProxy {
@@ -127,12 +135,6 @@ func (r *ReverseProxy) ToAPIResponse() *api.ReverseProxy {
}
}
if r.Auth.LinkAuth != nil {
authConfig.LinkAuth = &api.LinkAuthConfig{
Enabled: r.Auth.LinkAuth.Enabled,
}
}
// Convert internal targets to API targets
apiTargets := make([]api.ReverseProxyTarget, 0, len(r.Targets))
for _, target := range r.Targets {
@@ -173,7 +175,7 @@ func (r *ReverseProxy) ToAPIResponse() *api.ReverseProxy {
return resp
}
func (r *ReverseProxy) ToProtoMapping(operation Operation, setupKey string) *proto.ProxyMapping {
func (r *ReverseProxy) ToProtoMapping(operation Operation, authToken string, oidcConfig OIDCValidationConfig) *proto.ProxyMapping {
pathMappings := make([]*proto.PathMapping, 0, len(r.Targets))
for _, target := range r.Targets {
if !target.Enabled {
@@ -200,7 +202,10 @@ func (r *ReverseProxy) ToProtoMapping(operation Operation, setupKey string) *pro
})
}
auth := &proto.Authentication{}
auth := &proto.Authentication{
SessionKey: r.SessionPublicKey,
MaxSessionAgeSeconds: int64((time.Hour * 24).Seconds()),
}
if r.Auth.PasswordAuth != nil && r.Auth.PasswordAuth.Enabled {
auth.Password = true
@@ -211,13 +216,7 @@ func (r *ReverseProxy) ToProtoMapping(operation Operation, setupKey string) *pro
}
if r.Auth.BearerAuth != nil && r.Auth.BearerAuth.Enabled {
auth.Oidc = &proto.OIDC{
DistributionGroups: r.Auth.BearerAuth.DistributionGroups,
}
}
if r.Auth.LinkAuth != nil && r.Auth.LinkAuth.Enabled {
auth.Link = true
auth.Oidc = true
}
return &proto.ProxyMapping{
@@ -225,7 +224,7 @@ func (r *ReverseProxy) ToProtoMapping(operation Operation, setupKey string) *pro
Id: r.ID,
Domain: r.Domain,
Path: pathMappings,
SetupKey: setupKey,
AuthToken: authToken,
Auth: auth,
AccountId: r.AccountID,
}
@@ -289,13 +288,6 @@ func (r *ReverseProxy) FromAPIRequest(req *api.ReverseProxyRequest, accountID st
}
r.Auth.BearerAuth = bearerAuth
}
if req.Auth.LinkAuth != nil {
r.Auth.LinkAuth = &LinkAuthConfig{
Enabled: req.Auth.LinkAuth.Enabled,
}
}
}
func (r *ReverseProxy) Validate() error {
@@ -320,3 +312,54 @@ func (r *ReverseProxy) Validate() error {
func (r *ReverseProxy) EventMeta() map[string]any {
return map[string]any{"name": r.Name, "domain": r.Domain, "proxy_cluster": r.ProxyCluster}
}
func (r *ReverseProxy) Copy() *ReverseProxy {
targets := make([]Target, len(r.Targets))
copy(targets, r.Targets)
return &ReverseProxy{
ID: r.ID,
AccountID: r.AccountID,
Name: r.Name,
Domain: r.Domain,
ProxyCluster: r.ProxyCluster,
Targets: targets,
Enabled: r.Enabled,
Auth: r.Auth,
Meta: r.Meta,
SessionPrivateKey: r.SessionPrivateKey,
SessionPublicKey: r.SessionPublicKey,
}
}
func (r *ReverseProxy) EncryptSensitiveData(enc *crypt.FieldEncrypt) error {
if enc == nil {
return nil
}
if r.SessionPrivateKey != "" {
var err error
r.SessionPrivateKey, err = enc.Encrypt(r.SessionPrivateKey)
if err != nil {
return err
}
}
return nil
}
func (r *ReverseProxy) DecryptSensitiveData(enc *crypt.FieldEncrypt) error {
if enc == nil {
return nil
}
if r.SessionPrivateKey != "" {
var err error
r.SessionPrivateKey, err = enc.Decrypt(r.SessionPrivateKey)
if err != nil {
return err
}
}
return nil
}