mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 07:26:14 -04:00
Compare commits
9 Commits
v0.25.7
...
debug-goog
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f41e2bd13 | ||
|
|
cb3408a10b | ||
|
|
0afd738509 | ||
|
|
e3d038da8a | ||
|
|
cf87f1e702 | ||
|
|
e890fdae54 | ||
|
|
dd14db6478 | ||
|
|
88747e3e01 | ||
|
|
fb30931365 |
@@ -46,24 +46,32 @@ func (u *upstreamResolverIOS) exchange(ctx context.Context, upstream string, r *
|
||||
if err != nil {
|
||||
log.Errorf("error while parsing upstream host: %s", err)
|
||||
}
|
||||
|
||||
timeout := upstreamTimeout
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
timeout = time.Until(deadline)
|
||||
}
|
||||
client.DialTimeout = timeout
|
||||
|
||||
upstreamIP := net.ParseIP(upstreamHost)
|
||||
if u.lNet.Contains(upstreamIP) || net.IP.IsPrivate(upstreamIP) {
|
||||
log.Debugf("using private client to query upstream: %s", upstream)
|
||||
client = u.getClientPrivate()
|
||||
client = u.getClientPrivate(timeout)
|
||||
}
|
||||
|
||||
return client.ExchangeContext(ctx, r, upstream)
|
||||
// Cannot use client.ExchangeContext because it overwrites our Dialer
|
||||
return client.Exchange(r, upstream)
|
||||
}
|
||||
|
||||
// getClientPrivate returns a new DNS client bound to the local IP address of the Netbird interface
|
||||
// This method is needed for iOS
|
||||
func (u *upstreamResolverIOS) getClientPrivate() *dns.Client {
|
||||
func (u *upstreamResolverIOS) getClientPrivate(dialTimeout time.Duration) *dns.Client {
|
||||
dialer := &net.Dialer{
|
||||
LocalAddr: &net.UDPAddr{
|
||||
IP: u.lIP,
|
||||
Port: 0, // Let the OS pick a free port
|
||||
},
|
||||
Timeout: upstreamTimeout,
|
||||
Timeout: dialTimeout,
|
||||
Control: func(network, address string, c syscall.RawConn) error {
|
||||
var operr error
|
||||
fn := func(s uintptr) {
|
||||
|
||||
@@ -130,8 +130,9 @@ type Conn struct {
|
||||
remoteModeCh chan ModeMessage
|
||||
meta meta
|
||||
|
||||
adapter iface.TunAdapter
|
||||
iFaceDiscover stdnet.ExternalIFaceDiscover
|
||||
adapter iface.TunAdapter
|
||||
iFaceDiscover stdnet.ExternalIFaceDiscover
|
||||
sentExtraSrflx bool
|
||||
}
|
||||
|
||||
// meta holds meta information about a connection
|
||||
@@ -464,6 +465,8 @@ func (conn *Conn) cleanup() error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
conn.sentExtraSrflx = false
|
||||
|
||||
var err1, err2, err3 error
|
||||
if conn.agent != nil {
|
||||
err1 = conn.agent.Close()
|
||||
@@ -557,6 +560,30 @@ func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
||||
if err != nil {
|
||||
log.Errorf("failed signaling candidate to the remote peer %s %s", conn.config.Key, err)
|
||||
}
|
||||
|
||||
// sends an extra server reflexive candidate to the remote peer with our related port (usually the wireguard port)
|
||||
// this is useful when network has an existing port forwarding rule for the wireguard port and this peer
|
||||
if !conn.sentExtraSrflx && candidate.Type() == ice.CandidateTypeServerReflexive && candidate.Port() != candidate.RelatedAddress().Port {
|
||||
relatedAdd := candidate.RelatedAddress()
|
||||
extraSrflx, err := ice.NewCandidateServerReflexive(&ice.CandidateServerReflexiveConfig{
|
||||
Network: candidate.NetworkType().String(),
|
||||
Address: candidate.Address(),
|
||||
Port: relatedAdd.Port,
|
||||
Component: candidate.Component(),
|
||||
RelAddr: relatedAdd.Address,
|
||||
RelPort: relatedAdd.Port,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed creating extra server reflexive candidate %s", err)
|
||||
return
|
||||
}
|
||||
err = conn.signalCandidate(extraSrflx)
|
||||
if err != nil {
|
||||
log.Errorf("failed signaling the extra server reflexive candidate to the remote peer %s: %s", conn.config.Key, err)
|
||||
return
|
||||
}
|
||||
conn.sentExtraSrflx = true
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -171,3 +171,5 @@ replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-202
|
||||
replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20240105182236-6c340dd55aed
|
||||
|
||||
replace github.com/cloudflare/circl => github.com/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6
|
||||
|
||||
replace github.com/grpc-ecosystem/go-grpc-middleware/v2 => github.com/surik/go-grpc-middleware/v2 v2.0.0-20240206110057-98a38fc1f86f
|
||||
|
||||
4
go.sum
4
go.sum
@@ -286,8 +286,6 @@ github.com/gopherjs/gopherjs v0.0.0-20220410123724-9e86199038b0 h1:fWY+zXdWhvWnd
|
||||
github.com/gopherjs/gopherjs v0.0.0-20220410123724-9e86199038b0/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240202184442-37827591b26c h1:Kvw2BIua5WGDnknpnODn9K74PYWLhhqt8G3l0chyzEI=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240202184442-37827591b26c/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
||||
@@ -519,6 +517,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/surik/go-grpc-middleware/v2 v2.0.0-20240206110057-98a38fc1f86f h1:J+egXEDkpg/vOYYzPO5IwF8OufGb7g+KcwEF1AWIzhQ=
|
||||
github.com/surik/go-grpc-middleware/v2 v2.0.0-20240206110057-98a38fc1f86f/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
|
||||
github.com/things-go/go-socks5 v0.0.4 h1:jMQjIc+qhD4z9cITOMnBiwo9dDmpGuXmBlkRFrl/qD0=
|
||||
github.com/things-go/go-socks5 v0.0.4/go.mod h1:sh4K6WHrmHZpjxLTCHyYtXYH8OUuD+yZun41NomR1IQ=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -170,21 +171,31 @@ var (
|
||||
|
||||
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||
|
||||
trustedPeers := config.TrustedHTTPProxies
|
||||
if len(trustedPeers) == 0 {
|
||||
trustedPeers = []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0")}
|
||||
trustedPeers := config.ReverseProxy.TrustedPeers
|
||||
defaultTrustedPeers := []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0")}
|
||||
if len(trustedPeers) == 0 || slices.Equal[[]netip.Prefix](trustedPeers, defaultTrustedPeers) {
|
||||
log.Warn("TrustedPeers are configured to default value '0.0.0.0/0', '::/0'. This allows connection IP spoofing.")
|
||||
trustedPeers = defaultTrustedPeers
|
||||
}
|
||||
trustedHTTPProxies := config.ReverseProxy.TrustedHTTPProxies
|
||||
trustedProxiesCount := config.ReverseProxy.TrustedHTTPProxiesCount
|
||||
if len(trustedHTTPProxies) > 0 && trustedProxiesCount > 0 {
|
||||
log.Warn("TrustedHTTPProxies and TrustedHTTPProxiesCount both are configured. " +
|
||||
"This is not recommended way to extract X-Forwarded-For. Consider using one of these options.")
|
||||
}
|
||||
realipOpts := realip.Opts{
|
||||
TrustedPeers: trustedPeers,
|
||||
TrustedProxies: trustedHTTPProxies,
|
||||
TrustedProxiesCount: trustedProxiesCount,
|
||||
Headers: []string{realip.XForwardedFor, realip.XRealIp},
|
||||
}
|
||||
headers := []string{realip.XForwardedFor, realip.XRealIp}
|
||||
gRPCOpts := []grpc.ServerOption{
|
||||
grpc.KeepaliveEnforcementPolicy(kaep),
|
||||
grpc.KeepaliveParams(kasp),
|
||||
grpc.ChainUnaryInterceptor(
|
||||
realip.UnaryServerInterceptor(trustedPeers, headers),
|
||||
),
|
||||
grpc.ChainStreamInterceptor(
|
||||
realip.StreamServerInterceptor(trustedPeers, headers),
|
||||
),
|
||||
grpc.ChainUnaryInterceptor(realip.UnaryServerInterceptorOpts(realipOpts)),
|
||||
grpc.ChainStreamInterceptor(realip.StreamServerInterceptorOpts(realipOpts)),
|
||||
}
|
||||
|
||||
var certManager *autocert.Manager
|
||||
var tlsConfig *tls.Config
|
||||
tlsEnabled := false
|
||||
|
||||
@@ -1223,6 +1223,8 @@ func (am *DefaultAccountManager) lookupUserInCache(userID string, account *Accou
|
||||
}
|
||||
}
|
||||
|
||||
// add extra check on external cache manager. We may get to this point when the user is not yet findable in IDP,
|
||||
// or it didn't have its metadata updated with am.addAccountIDToIDPAppMeta
|
||||
user, err := account.FindUser(userID)
|
||||
if err != nil {
|
||||
log.Errorf("failed finding user %s in account %s", userID, account.Id)
|
||||
@@ -1231,14 +1233,11 @@ func (am *DefaultAccountManager) lookupUserInCache(userID string, account *Accou
|
||||
|
||||
key := user.IntegrationReference.CacheKey(account.Id, userID)
|
||||
ud, err := am.externalCacheManager.Get(am.ctx, key)
|
||||
if err == nil {
|
||||
log.Errorf("failed to get externalCache for key: %s, error: %s", key, err)
|
||||
return ud, status.Errorf(status.NotFound, "user %s not found in the IdP", userID)
|
||||
if err != nil {
|
||||
log.Debugf("failed to get externalCache for key: %s, error: %s", key, err)
|
||||
}
|
||||
|
||||
log.Infof("user %s not found in any cache", userID)
|
||||
|
||||
return nil, nil //nolint:nilnil
|
||||
return ud, nil
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) refreshCache(accountID string) ([]*idp.UserData, error) {
|
||||
|
||||
@@ -41,8 +41,6 @@ type Config struct {
|
||||
|
||||
HttpConfig *HttpServerConfig
|
||||
|
||||
TrustedHTTPProxies []netip.Prefix
|
||||
|
||||
IdpManagerConfig *idp.Config
|
||||
|
||||
DeviceAuthorizationFlow *DeviceAuthorizationFlow
|
||||
@@ -50,6 +48,8 @@ type Config struct {
|
||||
PKCEAuthorizationFlow *PKCEAuthorizationFlow
|
||||
|
||||
StoreConfig StoreConfig
|
||||
|
||||
ReverseProxy ReverseProxy
|
||||
}
|
||||
|
||||
// GetAuthAudiences returns the audience from the http config and device authorization flow config
|
||||
@@ -146,6 +146,27 @@ type StoreConfig struct {
|
||||
Engine StoreEngine
|
||||
}
|
||||
|
||||
// ReverseProxy contains reverse proxy configuration in front of management.
|
||||
type ReverseProxy struct {
|
||||
// TrustedHTTPProxies represents a list of trusted HTTP proxies by their IP prefixes.
|
||||
// When extracting the real IP address from request headers, the middleware will verify
|
||||
// if the peer's address falls within one of these trusted IP prefixes.
|
||||
TrustedHTTPProxies []netip.Prefix
|
||||
|
||||
// TrustedHTTPProxiesCount specifies the count of trusted HTTP proxies between the internet
|
||||
// and the server. When using the trusted proxy count method to extract the real IP address,
|
||||
// the middleware will search the X-Forwarded-For IP list from the rightmost by this count
|
||||
// minus one.
|
||||
TrustedHTTPProxiesCount uint
|
||||
|
||||
// TrustedPeers represents a list of trusted peers by their IP prefixes.
|
||||
// These peers are considered trustworthy by the gRPC server operator,
|
||||
// and the middleware will attempt to extract the real IP address from
|
||||
// request headers if the peer's address falls within one of these
|
||||
// trusted IP prefixes.
|
||||
TrustedPeers []netip.Prefix
|
||||
}
|
||||
|
||||
// validateURL validates input http url
|
||||
func validateURL(httpURL string) bool {
|
||||
_, err := url.ParseRequestURI(httpURL)
|
||||
|
||||
@@ -115,6 +115,13 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, peerID := range newGroup.Peers {
|
||||
if account.Peers[peerID] == nil {
|
||||
return status.Errorf(status.InvalidArgument, "peer with ID \"%s\" not found", peerID)
|
||||
}
|
||||
}
|
||||
|
||||
oldGroup, exists := account.Groups[newGroup.ID]
|
||||
account.Groups[newGroup.ID] = newGroup
|
||||
|
||||
|
||||
@@ -904,7 +904,7 @@ components:
|
||||
nameservers:
|
||||
description: Nameserver list
|
||||
minLength: 1
|
||||
maxLength: 2
|
||||
maxLength: 3
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Nameserver'
|
||||
|
||||
@@ -240,10 +240,9 @@ func (h *GroupsHandler) GetGroup(w http.ResponseWriter, r *http.Request) {
|
||||
func toGroupResponse(account *server.Account, group *server.Group) *api.Group {
|
||||
cache := make(map[string]api.PeerMinimum)
|
||||
gr := api.Group{
|
||||
Id: group.ID,
|
||||
Name: group.Name,
|
||||
PeersCount: len(group.Peers),
|
||||
Issued: &group.Issued,
|
||||
Id: group.ID,
|
||||
Name: group.Name,
|
||||
Issued: &group.Issued,
|
||||
}
|
||||
|
||||
for _, pid := range group.Peers {
|
||||
@@ -261,5 +260,8 @@ func toGroupResponse(account *server.Account, group *server.Group) *api.Group {
|
||||
gr.Peers = append(gr.Peers, peerResp)
|
||||
}
|
||||
}
|
||||
|
||||
gr.PeersCount = len(gr.Peers)
|
||||
|
||||
return &gr
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -150,19 +152,37 @@ func (gm *GoogleWorkspaceManager) GetAllAccounts() (map[string][]*UserData, erro
|
||||
|
||||
// getAllUsers returns all users in a Google Workspace account filtered by customer ID.
|
||||
func (gm *GoogleWorkspaceManager) getAllUsers() ([]*UserData, error) {
|
||||
var usersLimit int64 = 500
|
||||
if maxUsersLimitEnv := os.Getenv("GOOGLE_WORKSPACE_USERS_LIMIT"); maxUsersLimitEnv != "" {
|
||||
maxUsersLimit, err := strconv.Atoi(maxUsersLimitEnv)
|
||||
if err == nil {
|
||||
log.Debugf("GOOGLE_WORKSPACE_USERS_LIMIT env is set using %d as users limit", maxUsersLimit)
|
||||
usersLimit = int64(maxUsersLimit)
|
||||
}
|
||||
} else {
|
||||
log.Debugf("GOOGLE_WORKSPACE_USERS_LIMIT env is not set using default users limit 500")
|
||||
}
|
||||
|
||||
users := make([]*UserData, 0)
|
||||
pageToken := ""
|
||||
for {
|
||||
call := gm.usersService.List().Customer(gm.CustomerID).MaxResults(500)
|
||||
call := gm.usersService.List().Customer(gm.CustomerID).MaxResults(usersLimit)
|
||||
if pageToken != "" {
|
||||
call.PageToken(pageToken)
|
||||
}
|
||||
|
||||
resp, err := call.Do()
|
||||
if err != nil {
|
||||
log.Debugf("failed to retrieve users from workspace error: %s, http status: %d, headers: %v",
|
||||
err.Error(),
|
||||
resp.HTTPStatusCode,
|
||||
resp.Header,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("fetched %d users from workspace", len(resp.Users))
|
||||
|
||||
for _, user := range resp.Users {
|
||||
users = append(users, parseGoogleWorkspaceUser(user))
|
||||
}
|
||||
|
||||
@@ -255,8 +255,8 @@ func validateNSGroupName(name, nsGroupID string, nsGroupMap map[string]*nbdns.Na
|
||||
|
||||
func validateNSList(list []nbdns.NameServer) error {
|
||||
nsListLenght := len(list)
|
||||
if nsListLenght == 0 || nsListLenght > 2 {
|
||||
return status.Errorf(status.InvalidArgument, "the list of nameservers should be 1 or 2, got %d", len(list))
|
||||
if nsListLenght == 0 || nsListLenght > 3 {
|
||||
return status.Errorf(status.InvalidArgument, "the list of nameservers should be 1 or 3, got %d", len(list))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ func TestCreateNameServerGroup(t *testing.T) {
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Create A NS Group With More Than 2 Nameservers Should Fail",
|
||||
name: "Create A NS Group With More Than 3 Nameservers Should Fail",
|
||||
inputArgs: input{
|
||||
name: "super",
|
||||
description: "super",
|
||||
@@ -238,6 +238,11 @@ func TestCreateNameServerGroup(t *testing.T) {
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
{
|
||||
IP: netip.MustParseAddr("1.1.4.4"),
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
},
|
||||
@@ -457,6 +462,11 @@ func TestSaveNameServerGroup(t *testing.T) {
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
{
|
||||
IP: netip.MustParseAddr("1.1.4.4"),
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
}
|
||||
invalidID := "doesntExist"
|
||||
validName := "12345678901234567890qw"
|
||||
|
||||
Reference in New Issue
Block a user