mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-25 19:56:46 +00:00
Compare commits
29 Commits
handle-use
...
v0.23.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ad3894a51 | ||
|
|
c81b83b346 | ||
|
|
8c5c6815e0 | ||
|
|
0c470e7838 | ||
|
|
8118d60ffb | ||
|
|
1956ca169e | ||
|
|
830dee1771 | ||
|
|
c08a96770e | ||
|
|
c6bf1c7f26 | ||
|
|
5f499d66b2 | ||
|
|
7c065bd9fc | ||
|
|
ab849f0942 | ||
|
|
aa1d31bde6 | ||
|
|
5b4dc4dd47 | ||
|
|
1324169ebb | ||
|
|
732afd8393 | ||
|
|
da7b6b11ad | ||
|
|
e260270825 | ||
|
|
d4b6d7646c | ||
|
|
8febab4076 | ||
|
|
34e2c6b943 | ||
|
|
0be8c72601 | ||
|
|
c34e53477f | ||
|
|
8d18190c94 | ||
|
|
06bec61be9 | ||
|
|
2135533f1d | ||
|
|
bb791d59f3 | ||
|
|
30f1c54ed1 | ||
|
|
5c8541ef42 |
41
.github/workflows/android-build-validation.yml
vendored
Normal file
41
.github/workflows/android-build-validation.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Android build validation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.20.x"
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v2
|
||||
- name: NDK Cache
|
||||
id: ndk-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /usr/local/lib/android/sdk/ndk
|
||||
key: ndk-cache-23.1.7779620
|
||||
- name: Setup NDK
|
||||
run: /usr/local/lib/android/sdk/tools/bin/sdkmanager --install "ndk;23.1.7779620"
|
||||
- name: install gomobile
|
||||
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20230531173138-3c911d8e3eda
|
||||
- name: gomobile init
|
||||
run: gomobile init
|
||||
- name: build android nebtird lib
|
||||
run: PATH=$PATH:$(go env GOPATH) gomobile bind -o $GITHUB_WORKSPACE/netbird.aar -javapkg=io.netbird.gomobile -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/io.netbird.client/cache/wireguard -X github.com/netbirdio/netbird/version.version=buildtest" $GITHUB_WORKSPACE/client/android
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
ANDROID_NDK_HOME: /usr/local/lib/android/sdk/ndk/23.1.7779620
|
||||
@@ -80,6 +80,7 @@ jobs:
|
||||
CI_NETBIRD_MGMT_IDP: "none"
|
||||
CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id
|
||||
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
||||
CI_NETBIRD_SIGNAL_PORT: 12345
|
||||
|
||||
run: |
|
||||
grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
||||
@@ -91,6 +92,7 @@ jobs:
|
||||
grep NETBIRD_MGMT_API_ENDPOINT docker-compose.yml | grep "$CI_NETBIRD_DOMAIN:33073"
|
||||
grep AUTH_REDIRECT_URI docker-compose.yml | grep $CI_NETBIRD_AUTH_REDIRECT_URI
|
||||
grep AUTH_SILENT_REDIRECT_URI docker-compose.yml | egrep 'AUTH_SILENT_REDIRECT_URI=$'
|
||||
grep $CI_NETBIRD_SIGNAL_PORT docker-compose.yml | grep ':80'
|
||||
grep LETSENCRYPT_DOMAIN docker-compose.yml | egrep 'LETSENCRYPT_DOMAIN=$'
|
||||
grep NETBIRD_TOKEN_SOURCE docker-compose.yml | grep $CI_NETBIRD_TOKEN_SOURCE
|
||||
grep AuthUserIDClaim management.json | grep $CI_NETBIRD_AUTH_USER_ID_CLAIM
|
||||
@@ -120,7 +122,7 @@ jobs:
|
||||
|
||||
- name: test running containers
|
||||
run: |
|
||||
count=$(docker compose ps --format json | jq '.[] | select(.Project | contains("infrastructure_files")) | .State' | grep -c running)
|
||||
count=$(docker compose ps --format json | jq '. | select(.Name | contains("infrastructure_files")) | .State' | grep -c running)
|
||||
test $count -eq 4
|
||||
working-directory: infrastructure_files
|
||||
|
||||
|
||||
@@ -84,10 +84,14 @@ func (a *Auth) SaveConfigIfSSOSupported(listener SSOListener) {
|
||||
func (a *Auth) saveConfigIfSSOSupported() (bool, error) {
|
||||
supportsSSO := true
|
||||
err := a.withBackOff(a.ctx, func() (err error) {
|
||||
_, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
|
||||
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound {
|
||||
_, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
|
||||
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound {
|
||||
_, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
|
||||
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) {
|
||||
_, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
|
||||
s, ok := gstatus.FromError(err)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
if s.Code() == codes.NotFound || s.Code() == codes.Unimplemented {
|
||||
supportsSSO = false
|
||||
err = nil
|
||||
}
|
||||
@@ -189,7 +193,7 @@ func (a *Auth) login(urlOpener URLOpener) error {
|
||||
}
|
||||
|
||||
func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, error) {
|
||||
oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config)
|
||||
oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -82,9 +81,10 @@ var loginCmd = &cobra.Command{
|
||||
client := proto.NewDaemonServiceClient(conn)
|
||||
|
||||
loginRequest := proto.LoginRequest{
|
||||
SetupKey: setupKey,
|
||||
PreSharedKey: preSharedKey,
|
||||
ManagementUrl: managementURL,
|
||||
SetupKey: setupKey,
|
||||
PreSharedKey: preSharedKey,
|
||||
ManagementUrl: managementURL,
|
||||
IsLinuxDesktopClient: isLinuxRunningDesktop(),
|
||||
}
|
||||
|
||||
var loginErr error
|
||||
@@ -165,7 +165,7 @@ func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *internal.C
|
||||
}
|
||||
|
||||
func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *internal.Config) (*auth.TokenInfo, error) {
|
||||
oAuthFlow, err := auth.NewOAuthFlow(ctx, config)
|
||||
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, isLinuxRunningDesktop())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -195,60 +195,17 @@ func openURL(cmd *cobra.Command, verificationURIComplete, userCode string) {
|
||||
codeMsg = fmt.Sprintf("and enter the code %s to authenticate.", userCode)
|
||||
}
|
||||
|
||||
browserAuthMsg := "Please do the SSO login in your browser. \n" +
|
||||
cmd.Println("Please do the SSO login in your browser. \n" +
|
||||
"If your browser didn't open automatically, use this URL to log in:\n\n" +
|
||||
verificationURIComplete + " " + codeMsg
|
||||
|
||||
setupKeyAuthMsg := "\nAlternatively, you may want to use a setup key, see:\n\n" +
|
||||
"https://docs.netbird.io/how-to/register-machines-using-setup-keys"
|
||||
|
||||
authenticateUsingBrowser := func() {
|
||||
cmd.Println(browserAuthMsg)
|
||||
cmd.Println("")
|
||||
if err := open.Run(verificationURIComplete); err != nil {
|
||||
cmd.Println(setupKeyAuthMsg)
|
||||
}
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows", "darwin":
|
||||
authenticateUsingBrowser()
|
||||
case "linux":
|
||||
if isLinuxRunningDesktop() {
|
||||
authenticateUsingBrowser()
|
||||
} else {
|
||||
// If current flow is PKCE, it implies the server is anticipating the redirect to localhost.
|
||||
// Devices lacking browser support are incompatible with this flow.Therefore,
|
||||
// these devices will need to resort to setup keys instead.
|
||||
if isPKCEFlow(verificationURIComplete) {
|
||||
cmd.Println("Please proceed with setting up this device using setup keys, see:\n\n" +
|
||||
"https://docs.netbird.io/how-to/register-machines-using-setup-keys")
|
||||
} else {
|
||||
cmd.Println(browserAuthMsg)
|
||||
}
|
||||
}
|
||||
verificationURIComplete + " " + codeMsg)
|
||||
cmd.Println("")
|
||||
if err := open.Run(verificationURIComplete); err != nil {
|
||||
cmd.Println("\nAlternatively, you may want to use a setup key, see:\n\n" +
|
||||
"https://docs.netbird.io/how-to/register-machines-using-setup-keys")
|
||||
}
|
||||
}
|
||||
|
||||
// isLinuxRunningDesktop checks if a Linux OS is running desktop environment.
|
||||
// isLinuxRunningDesktop checks if a Linux OS is running desktop environment
|
||||
func isLinuxRunningDesktop() bool {
|
||||
for _, env := range os.Environ() {
|
||||
values := strings.Split(env, "=")
|
||||
if len(values) == 2 {
|
||||
key, value := values[0], values[1]
|
||||
if key == "XDG_CURRENT_DESKTOP" && value != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isPKCEFlow determines if the PKCE flow is active or not,
|
||||
// by checking the existence of redirect_uri inside the verification URL.
|
||||
func isPKCEFlow(verificationURL string) bool {
|
||||
if verificationURL == "" {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(verificationURL, "redirect_uri")
|
||||
return os.Getenv("DESKTOP_SESSION") != "" || os.Getenv("XDG_CURRENT_DESKTOP") != ""
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
|
||||
return nil, nil
|
||||
}
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
eventStore, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -141,13 +141,14 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
|
||||
}
|
||||
|
||||
loginRequest := proto.LoginRequest{
|
||||
SetupKey: setupKey,
|
||||
PreSharedKey: preSharedKey,
|
||||
ManagementUrl: managementURL,
|
||||
AdminURL: adminURL,
|
||||
NatExternalIPs: natExternalIPs,
|
||||
CleanNATExternalIPs: natExternalIPs != nil && len(natExternalIPs) == 0,
|
||||
CustomDNSAddress: customDNSAddressConverted,
|
||||
SetupKey: setupKey,
|
||||
PreSharedKey: preSharedKey,
|
||||
ManagementUrl: managementURL,
|
||||
AdminURL: adminURL,
|
||||
NatExternalIPs: natExternalIPs,
|
||||
CleanNATExternalIPs: natExternalIPs != nil && len(natExternalIPs) == 0,
|
||||
CustomDNSAddress: customDNSAddressConverted,
|
||||
IsLinuxDesktopClient: isLinuxRunningDesktop(),
|
||||
}
|
||||
|
||||
var loginErr error
|
||||
|
||||
@@ -93,7 +93,7 @@ func Create(wgIface iFaceMapper, ipv6Supported bool) (*Manager, error) {
|
||||
|
||||
// AddFiltering rule to the firewall
|
||||
//
|
||||
// If comment is empty rule ID is used as comment
|
||||
// Comment will be ignored because some system this feature is not supported
|
||||
func (m *Manager) AddFiltering(
|
||||
ip net.IP,
|
||||
protocol fw.Protocol,
|
||||
@@ -123,9 +123,6 @@ func (m *Manager) AddFiltering(
|
||||
ipsetName = m.transformIPsetName(ipsetName, sPortVal, dPortVal)
|
||||
|
||||
ruleID := uuid.New().String()
|
||||
if comment == "" {
|
||||
comment = ruleID
|
||||
}
|
||||
|
||||
if ipsetName != "" {
|
||||
rs, rsExists := m.rulesets[ipsetName]
|
||||
@@ -157,8 +154,7 @@ func (m *Manager) AddFiltering(
|
||||
// this is new ipset so we need to create firewall rule for it
|
||||
}
|
||||
|
||||
specs := m.filterRuleSpecs("filter", ip, string(protocol), sPortVal, dPortVal,
|
||||
direction, action, comment, ipsetName)
|
||||
specs := m.filterRuleSpecs(ip, string(protocol), sPortVal, dPortVal, direction, action, ipsetName)
|
||||
|
||||
if direction == fw.RuleDirectionOUT {
|
||||
ok, err := client.Exists("filter", ChainOutputFilterName, specs...)
|
||||
@@ -283,7 +279,7 @@ func (m *Manager) AllowNetbird() error {
|
||||
fw.RuleDirectionIN,
|
||||
fw.ActionAccept,
|
||||
"",
|
||||
"allow netbird interface traffic",
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to allow netbird interface traffic: %w", err)
|
||||
@@ -296,7 +292,7 @@ func (m *Manager) AllowNetbird() error {
|
||||
fw.RuleDirectionOUT,
|
||||
fw.ActionAccept,
|
||||
"",
|
||||
"allow netbird interface traffic",
|
||||
"",
|
||||
)
|
||||
return err
|
||||
}
|
||||
@@ -362,9 +358,7 @@ func (m *Manager) reset(client *iptables.IPTables, table string) error {
|
||||
|
||||
// filterRuleSpecs returns the specs of a filtering rule
|
||||
func (m *Manager) filterRuleSpecs(
|
||||
table string, ip net.IP, protocol string, sPort, dPort string,
|
||||
direction fw.RuleDirection, action fw.Action, comment string,
|
||||
ipsetName string,
|
||||
ip net.IP, protocol string, sPort, dPort string, direction fw.RuleDirection, action fw.Action, ipsetName string,
|
||||
) (specs []string) {
|
||||
matchByIP := true
|
||||
// don't use IP matching if IP is ip 0.0.0.0
|
||||
@@ -398,8 +392,7 @@ func (m *Manager) filterRuleSpecs(
|
||||
if dPort != "" {
|
||||
specs = append(specs, "--dport", dPort)
|
||||
}
|
||||
specs = append(specs, "-j", m.actionToStr(action))
|
||||
return append(specs, "-m", "comment", "--comment", comment)
|
||||
return append(specs, "-j", m.actionToStr(action))
|
||||
}
|
||||
|
||||
// rawClient returns corresponding iptables client for the given ip
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !linux
|
||||
//go:build !linux || android
|
||||
|
||||
package acl
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !android
|
||||
|
||||
package acl
|
||||
|
||||
import (
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -57,25 +58,45 @@ func (t TokenInfo) GetTokenToUse() string {
|
||||
return t.AccessToken
|
||||
}
|
||||
|
||||
// NewOAuthFlow initializes and returns the appropriate OAuth flow based on the management configuration.
|
||||
func NewOAuthFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) {
|
||||
log.Debug("loading pkce authorization flow info")
|
||||
|
||||
pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
|
||||
if err == nil {
|
||||
return NewPKCEAuthorizationFlow(pkceFlowInfo.ProviderConfig)
|
||||
// NewOAuthFlow initializes and returns the appropriate OAuth flow based on the management configuration
|
||||
//
|
||||
// It starts by initializing the PKCE.If this process fails, it resorts to the Device Code Flow,
|
||||
// and if that also fails, the authentication process is deemed unsuccessful
|
||||
//
|
||||
// On Linux distros without desktop environment support, it only tries to initialize the Device Code Flow
|
||||
func NewOAuthFlow(ctx context.Context, config *internal.Config, isLinuxDesktopClient bool) (OAuthFlow, error) {
|
||||
if runtime.GOOS == "linux" && !isLinuxDesktopClient {
|
||||
return authenticateWithDeviceCodeFlow(ctx, config)
|
||||
}
|
||||
|
||||
log.Debugf("loading pkce authorization flow info failed with error: %v", err)
|
||||
log.Debugf("falling back to device authorization flow info")
|
||||
pkceFlow, err := authenticateWithPKCEFlow(ctx, config)
|
||||
if err != nil {
|
||||
// fallback to device code flow
|
||||
log.Debugf("failed to initialize pkce authentication with error: %v\n", err)
|
||||
log.Debug("falling back to device code flow")
|
||||
return authenticateWithDeviceCodeFlow(ctx, config)
|
||||
}
|
||||
return pkceFlow, nil
|
||||
}
|
||||
|
||||
// authenticateWithPKCEFlow initializes the Proof Key for Code Exchange flow auth flow
|
||||
func authenticateWithPKCEFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) {
|
||||
pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting pkce authorization flow info failed with error: %v", err)
|
||||
}
|
||||
return NewPKCEAuthorizationFlow(pkceFlowInfo.ProviderConfig)
|
||||
}
|
||||
|
||||
// authenticateWithDeviceCodeFlow initializes the Device Code auth Flow
|
||||
func authenticateWithDeviceCodeFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) {
|
||||
deviceFlowInfo, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
|
||||
if err != nil {
|
||||
s, ok := gstatus.FromError(err)
|
||||
if ok && s.Code() == codes.NotFound {
|
||||
return nil, fmt.Errorf("no SSO provider returned from management. " +
|
||||
"If you are using hosting Netbird see documentation at " +
|
||||
"https://github.com/netbirdio/netbird/tree/main/management for details")
|
||||
"Please proceed with setting up this device using setup keys " +
|
||||
"https://docs.netbird.io/how-to/register-machines-using-setup-keys")
|
||||
} else if ok && s.Code() == codes.Unimplemented {
|
||||
return nil, fmt.Errorf("the management server, %s, does not support SSO providers, "+
|
||||
"please update your server or use Setup Keys to login", config.ManagementURL)
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -80,7 +79,7 @@ func (p *PKCEAuthorizationFlow) GetClientID(_ context.Context) string {
|
||||
}
|
||||
|
||||
// RequestAuthInfo requests a authorization code login flow information.
|
||||
func (p *PKCEAuthorizationFlow) RequestAuthInfo(_ context.Context) (AuthFlowInfo, error) {
|
||||
func (p *PKCEAuthorizationFlow) RequestAuthInfo(ctx context.Context) (AuthFlowInfo, error) {
|
||||
state, err := randomBytesInHex(24)
|
||||
if err != nil {
|
||||
return AuthFlowInfo{}, fmt.Errorf("could not generate random state: %v", err)
|
||||
@@ -114,64 +113,37 @@ func (p *PKCEAuthorizationFlow) WaitToken(ctx context.Context, _ AuthFlowInfo) (
|
||||
tokenChan := make(chan *oauth2.Token, 1)
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
go p.startServer(tokenChan, errChan)
|
||||
parsedURL, err := url.Parse(p.oAuthConfig.RedirectURL)
|
||||
if err != nil {
|
||||
return TokenInfo{}, fmt.Errorf("failed to parse redirect URL: %v", err)
|
||||
}
|
||||
|
||||
server := &http.Server{Addr: fmt.Sprintf(":%s", parsedURL.Port())}
|
||||
defer func() {
|
||||
shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := server.Shutdown(shutdownCtx); err != nil {
|
||||
log.Errorf("failed to close the server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
go p.startServer(server, tokenChan, errChan)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return TokenInfo{}, ctx.Err()
|
||||
case token := <-tokenChan:
|
||||
return p.handleOAuthToken(token)
|
||||
return p.parseOAuthToken(token)
|
||||
case err := <-errChan:
|
||||
return TokenInfo{}, err
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PKCEAuthorizationFlow) startServer(tokenChan chan<- *oauth2.Token, errChan chan<- error) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
parsedURL, err := url.Parse(p.oAuthConfig.RedirectURL)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("failed to parse redirect URL: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
server := http.Server{Addr: fmt.Sprintf(":%s", parsedURL.Port())}
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||
defer wg.Done()
|
||||
|
||||
tokenValidatorFunc := func() (*oauth2.Token, error) {
|
||||
query := req.URL.Query()
|
||||
|
||||
if authError := query.Get(queryError); authError != "" {
|
||||
authErrorDesc := query.Get(queryErrorDesc)
|
||||
return nil, fmt.Errorf("%s.%s", authError, authErrorDesc)
|
||||
}
|
||||
|
||||
// Prevent timing attacks on state
|
||||
if state := query.Get(queryState); subtle.ConstantTimeCompare([]byte(p.state), []byte(state)) == 0 {
|
||||
return nil, fmt.Errorf("invalid state")
|
||||
}
|
||||
|
||||
code := query.Get(queryCode)
|
||||
if code == "" {
|
||||
return nil, fmt.Errorf("missing code")
|
||||
}
|
||||
|
||||
return p.oAuthConfig.Exchange(
|
||||
req.Context(),
|
||||
code,
|
||||
oauth2.SetAuthURLParam("code_verifier", p.codeVerifier),
|
||||
)
|
||||
}
|
||||
|
||||
token, err := tokenValidatorFunc()
|
||||
func (p *PKCEAuthorizationFlow) startServer(server *http.Server, tokenChan chan<- *oauth2.Token, errChan chan<- error) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||
token, err := p.handleRequest(req)
|
||||
if err != nil {
|
||||
renderPKCEFlowTmpl(w, err)
|
||||
errChan <- fmt.Errorf("PKCE authorization flow failed: %v", err)
|
||||
@@ -182,13 +154,38 @@ func (p *PKCEAuthorizationFlow) startServer(tokenChan chan<- *oauth2.Token, errC
|
||||
tokenChan <- token
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
if err := server.Shutdown(context.Background()); err != nil {
|
||||
log.Errorf("error while shutting down pkce flow server: %v", err)
|
||||
server.Handler = mux
|
||||
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
errChan <- err
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PKCEAuthorizationFlow) handleOAuthToken(token *oauth2.Token) (TokenInfo, error) {
|
||||
func (p *PKCEAuthorizationFlow) handleRequest(req *http.Request) (*oauth2.Token, error) {
|
||||
query := req.URL.Query()
|
||||
|
||||
if authError := query.Get(queryError); authError != "" {
|
||||
authErrorDesc := query.Get(queryErrorDesc)
|
||||
return nil, fmt.Errorf("%s.%s", authError, authErrorDesc)
|
||||
}
|
||||
|
||||
// Prevent timing attacks on the state
|
||||
if state := query.Get(queryState); subtle.ConstantTimeCompare([]byte(p.state), []byte(state)) == 0 {
|
||||
return nil, fmt.Errorf("invalid state")
|
||||
}
|
||||
|
||||
code := query.Get(queryCode)
|
||||
if code == "" {
|
||||
return nil, fmt.Errorf("missing code")
|
||||
}
|
||||
|
||||
return p.oAuthConfig.Exchange(
|
||||
req.Context(),
|
||||
code,
|
||||
oauth2.SetAuthURLParam("code_verifier", p.codeVerifier),
|
||||
)
|
||||
}
|
||||
|
||||
func (p *PKCEAuthorizationFlow) parseOAuthToken(token *oauth2.Token) (TokenInfo, error) {
|
||||
tokenInfo := TokenInfo{
|
||||
AccessToken: token.AccessToken,
|
||||
RefreshToken: token.RefreshToken,
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -44,15 +43,14 @@ func isValidAccessToken(token string, audience string) error {
|
||||
}
|
||||
|
||||
// Audience claim of JWT can be a string or an array of strings
|
||||
typ := reflect.TypeOf(claims.Audience)
|
||||
switch typ.Kind() {
|
||||
case reflect.String:
|
||||
if claims.Audience == audience {
|
||||
switch aud := claims.Audience.(type) {
|
||||
case string:
|
||||
if aud == audience {
|
||||
return nil
|
||||
}
|
||||
case reflect.Slice:
|
||||
for _, aud := range claims.Audience.([]interface{}) {
|
||||
if audience == aud {
|
||||
case []interface{}:
|
||||
for _, audItem := range aud {
|
||||
if audStr, ok := audItem.(string); ok && audStr == audience {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
//go:build !linux
|
||||
//go:build !linux || android
|
||||
|
||||
package checkfw
|
||||
|
||||
@@ -1049,7 +1049,7 @@ func startManagement(dataDir string) (*grpc.Server, string, error) {
|
||||
return nil, "", err
|
||||
}
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
eventStore, false)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@ func (p *WGEBPFProxy) proxyToLocal(endpointPort uint16, remoteConn net.Conn) {
|
||||
log.Errorf("failed to read from turn conn (endpoint: :%d): %s", endpointPort, err)
|
||||
}
|
||||
p.removeTurnConn(endpointPort)
|
||||
log.Infof("stop forward turn packages to port: %d. error: %s", endpointPort, err)
|
||||
return
|
||||
}
|
||||
err = p.sendPkg(buf[:n], endpointPort)
|
||||
@@ -158,7 +159,7 @@ func (p *WGEBPFProxy) proxyToRemote() {
|
||||
conn, ok := p.turnConnStore[uint16(addr.Port)]
|
||||
p.turnConnMutex.Unlock()
|
||||
if !ok {
|
||||
log.Errorf("turn conn not found by port: %d", addr.Port)
|
||||
log.Infof("turn conn not found by port: %d", addr.Port)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.21.9
|
||||
// protoc v4.23.4
|
||||
// source: daemon.proto
|
||||
|
||||
package proto
|
||||
@@ -40,8 +40,9 @@ type LoginRequest struct {
|
||||
// cleanNATExternalIPs clean map list of external IPs.
|
||||
// This is needed because the generated code
|
||||
// omits initialized empty slices due to omitempty tags
|
||||
CleanNATExternalIPs bool `protobuf:"varint,6,opt,name=cleanNATExternalIPs,proto3" json:"cleanNATExternalIPs,omitempty"`
|
||||
CustomDNSAddress []byte `protobuf:"bytes,7,opt,name=customDNSAddress,proto3" json:"customDNSAddress,omitempty"`
|
||||
CleanNATExternalIPs bool `protobuf:"varint,6,opt,name=cleanNATExternalIPs,proto3" json:"cleanNATExternalIPs,omitempty"`
|
||||
CustomDNSAddress []byte `protobuf:"bytes,7,opt,name=customDNSAddress,proto3" json:"customDNSAddress,omitempty"`
|
||||
IsLinuxDesktopClient bool `protobuf:"varint,8,opt,name=isLinuxDesktopClient,proto3" json:"isLinuxDesktopClient,omitempty"`
|
||||
}
|
||||
|
||||
func (x *LoginRequest) Reset() {
|
||||
@@ -125,6 +126,13 @@ func (x *LoginRequest) GetCustomDNSAddress() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *LoginRequest) GetIsLinuxDesktopClient() bool {
|
||||
if x != nil {
|
||||
return x.IsLinuxDesktopClient
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -1043,7 +1051,7 @@ var file_daemon_proto_rawDesc = []byte{
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x96, 0x02, 0x0a, 0x0c, 0x4c, 0x6f,
|
||||
0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xca, 0x02, 0x0a, 0x0c, 0x4c, 0x6f,
|
||||
0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65,
|
||||
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65,
|
||||
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61,
|
||||
@@ -1061,128 +1069,131 @@ var file_daemon_proto_rawDesc = []byte{
|
||||
0x6e, 0x61, 0x6c, 0x49, 0x50, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
|
||||
0x44, 0x4e, 0x53, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x44, 0x4e, 0x53, 0x41, 0x64, 0x64, 0x72, 0x65,
|
||||
0x73, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f,
|
||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x65, 0x65,
|
||||
0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73,
|
||||
0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73,
|
||||
0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69,
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49,
|
||||
0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55,
|
||||
0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x31, 0x0a, 0x13, 0x57, 0x61,
|
||||
0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x16, 0x0a,
|
||||
0x14, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x3d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72,
|
||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65,
|
||||
0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
|
||||
0x82, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x0a, 0x66, 0x75,
|
||||
0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12,
|
||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
||||
0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24,
|
||||
0x0a, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a,
|
||||
0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
||||
0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46,
|
||||
0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x22, 0x0a,
|
||||
0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65,
|
||||
0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x05, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x22, 0xcf, 0x02,
|
||||
0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49,
|
||||
0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70,
|
||||
0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62,
|
||||
0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53,
|
||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72,
|
||||
0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65,
|
||||
0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18,
|
||||
0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x34, 0x0a,
|
||||
0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61,
|
||||
0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x6c, 0x6f,
|
||||
0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54,
|
||||
0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65,
|
||||
0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61,
|
||||
0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66,
|
||||
0x71, 0x64, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22,
|
||||
0x76, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
|
||||
0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49,
|
||||
0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x72,
|
||||
0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66,
|
||||
0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x61,
|
||||
0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e,
|
||||
0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e,
|
||||
0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63,
|
||||
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
|
||||
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01, 0x0a, 0x0a, 0x46, 0x75,
|
||||
0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67,
|
||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73,
|
||||
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c,
|
||||
0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61,
|
||||
0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53,
|
||||
0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x65,
|
||||
0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61,
|
||||
0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61,
|
||||
0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53,
|
||||
0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32, 0xf7, 0x02, 0x0a, 0x0d,
|
||||
0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a,
|
||||
0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64,
|
||||
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f,
|
||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57,
|
||||
0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74,
|
||||
0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
||||
0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61,
|
||||
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61,
|
||||
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74,
|
||||
0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04,
|
||||
0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f,
|
||||
0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
||||
0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18,
|
||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||
0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
||||
0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x73, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x69, 0x73, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x44, 0x65, 0x73,
|
||||
0x6b, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x14, 0x69, 0x73, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70,
|
||||
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x65, 0x64,
|
||||
0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1a,
|
||||
0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x65,
|
||||
0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x55, 0x52, 0x49, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x31,
|
||||
0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64,
|
||||
0x65, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69,
|
||||
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c,
|
||||
0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32,
|
||||
0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c, 0x6c,
|
||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
||||
0x75, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
||||
0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11,
|
||||
0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55,
|
||||
0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69,
|
||||
0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69,
|
||||
0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c,
|
||||
0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65,
|
||||
0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72,
|
||||
0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52,
|
||||
0x4c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52,
|
||||
0x4c, 0x22, 0xcf, 0x02, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12,
|
||||
0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53,
|
||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e,
|
||||
0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53,
|
||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63,
|
||||
0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72,
|
||||
0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63,
|
||||
0x74, 0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e,
|
||||
0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64,
|
||||
0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74,
|
||||
0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70,
|
||||
0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49,
|
||||
0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66,
|
||||
0x71, 0x64, 0x6e, 0x22, 0x76, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72,
|
||||
0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a,
|
||||
0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e,
|
||||
0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x53,
|
||||
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52,
|
||||
0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09,
|
||||
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61,
|
||||
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a,
|
||||
0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12,
|
||||
0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01,
|
||||
0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
|
||||
0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69,
|
||||
0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61,
|
||||
0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50,
|
||||
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
|
||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65,
|
||||
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65,
|
||||
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18,
|
||||
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50,
|
||||
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32,
|
||||
0xf7, 0x02, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65,
|
||||
0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69,
|
||||
0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
||||
0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||
0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64,
|
||||
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
|
||||
0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||
0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
||||
0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e,
|
||||
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64,
|
||||
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -51,6 +51,7 @@ message LoginRequest {
|
||||
|
||||
bytes customDNSAddress = 7;
|
||||
|
||||
bool isLinuxDesktopClient = 8;
|
||||
}
|
||||
|
||||
message LoginResponse {
|
||||
|
||||
@@ -208,7 +208,7 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
|
||||
state.Set(internal.StatusConnecting)
|
||||
|
||||
if msg.SetupKey == "" {
|
||||
oAuthFlow, err := auth.NewOAuthFlow(ctx, config)
|
||||
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, msg.IsLinuxDesktopClient)
|
||||
if err != nil {
|
||||
state.Set(internal.StatusLoginFailed)
|
||||
return nil, err
|
||||
|
||||
@@ -36,7 +36,7 @@ services:
|
||||
volumes:
|
||||
- $SIGNAL_VOLUMENAME:/var/lib/netbird
|
||||
ports:
|
||||
- 10000:80
|
||||
- $NETBIRD_SIGNAL_PORT:80
|
||||
# # port and command for Let's Encrypt validation
|
||||
# - 443:443
|
||||
# command: ["--letsencrypt-domain", "$NETBIRD_LETSENCRYPT_DOMAIN", "--log-file", "console"]
|
||||
|
||||
@@ -20,6 +20,7 @@ services:
|
||||
- AUTH_SUPPORTED_SCOPES=$NETBIRD_AUTH_SUPPORTED_SCOPES
|
||||
- AUTH_REDIRECT_URI=$NETBIRD_AUTH_REDIRECT_URI
|
||||
- AUTH_SILENT_REDIRECT_URI=$NETBIRD_AUTH_SILENT_REDIRECT_URI
|
||||
- NETBIRD_TOKEN_SOURCE=$NETBIRD_TOKEN_SOURCE
|
||||
# SSL
|
||||
- NGINX_SSL_PORT=443
|
||||
# Letsencrypt
|
||||
|
||||
@@ -21,4 +21,5 @@ NETBIRD_AUTH_USER_ID_CLAIM="email"
|
||||
NETBIRD_AUTH_DEVICE_AUTH_SCOPE="openid email"
|
||||
NETBIRD_MGMT_IDP=$CI_NETBIRD_MGMT_IDP
|
||||
NETBIRD_IDP_MGMT_CLIENT_ID=$CI_NETBIRD_IDP_MGMT_CLIENT_ID
|
||||
NETBIRD_IDP_MGMT_CLIENT_SECRET=$CI_NETBIRD_IDP_MGMT_CLIENT_SECRET
|
||||
NETBIRD_IDP_MGMT_CLIENT_SECRET=$CI_NETBIRD_IDP_MGMT_CLIENT_SECRET
|
||||
NETBIRD_SIGNAL_PORT=12345
|
||||
@@ -61,7 +61,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
eventStore, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/netbirdio/netbird/encryption"
|
||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/activity/sqlite"
|
||||
httpapi "github.com/netbirdio/netbird/management/server/http"
|
||||
"github.com/netbirdio/netbird/management/server/idp"
|
||||
@@ -142,12 +143,22 @@ var (
|
||||
if disableSingleAccMode {
|
||||
mgmtSingleAccModeDomain = ""
|
||||
}
|
||||
eventStore, err := sqlite.NewSQLiteStore(config.Datadir)
|
||||
eventStore, key, err := initEventStore(config.Datadir, config.DataStoreEncryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to initialize database: %s", err)
|
||||
}
|
||||
|
||||
if config.DataStoreEncryptionKey != key {
|
||||
log.Infof("update config with activity store key")
|
||||
config.DataStoreEncryptionKey = key
|
||||
err := updateMgmtConfig(mgmtConfig, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write out store encryption key: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
|
||||
dnsDomain, eventStore)
|
||||
dnsDomain, eventStore, userDeleteFromIDPEnabled)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build default manager: %v", err)
|
||||
}
|
||||
@@ -287,6 +298,20 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func initEventStore(dataDir string, key string) (activity.Store, string, error) {
|
||||
var err error
|
||||
if key == "" {
|
||||
log.Debugf("generate new activity store encryption key")
|
||||
key, err = sqlite.GenerateKey()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
store, err := sqlite.NewSQLiteStore(dataDir, key)
|
||||
return store, key, err
|
||||
|
||||
}
|
||||
|
||||
func notifyStop(msg string) {
|
||||
select {
|
||||
case stopCh <- 1:
|
||||
@@ -440,6 +465,10 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
||||
return loadedConfig, err
|
||||
}
|
||||
|
||||
func updateMgmtConfig(path string, config *server.Config) error {
|
||||
return util.DirectWriteJson(path, config)
|
||||
}
|
||||
|
||||
// OIDCConfigResponse used for parsing OIDC config response
|
||||
type OIDCConfigResponse struct {
|
||||
Issuer string `json:"issuer"`
|
||||
|
||||
@@ -24,6 +24,7 @@ var (
|
||||
disableMetrics bool
|
||||
disableSingleAccMode bool
|
||||
idpSignKeyRefreshEnabled bool
|
||||
userDeleteFromIDPEnabled bool
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "netbird-mgmt",
|
||||
@@ -56,6 +57,7 @@ func init() {
|
||||
mgmtCmd.Flags().BoolVar(&disableMetrics, "disable-anonymous-metrics", false, "disables push of anonymous usage metrics to NetBird")
|
||||
mgmtCmd.Flags().StringVar(&dnsDomain, "dns-domain", defaultSingleAccModeDomain, fmt.Sprintf("Domain used for peer resolution. This is appended to the peer's name, e.g. pi-server. %s. Max lenght is 192 characters to allow appending to a peer name with up to 63 characters.", defaultSingleAccModeDomain))
|
||||
mgmtCmd.Flags().BoolVar(&idpSignKeyRefreshEnabled, "idp-sign-key-refresh-enabled", false, "Enable cache headers evaluation to determine signing key rotation period. This will refresh the signing key upon expiry.")
|
||||
mgmtCmd.Flags().BoolVar(&userDeleteFromIDPEnabled, "user-delete-from-idp", false, "Allows to delete user from IDP when user is deleted from account")
|
||||
rootCmd.MarkFlagRequired("config") //nolint
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
|
||||
|
||||
@@ -80,26 +80,23 @@ type AccountManager interface {
|
||||
GetUsersFromAccount(accountID, userID string) ([]*UserInfo, error)
|
||||
GetGroup(accountId, groupID string) (*Group, error)
|
||||
SaveGroup(accountID, userID string, group *Group) error
|
||||
UpdateGroup(accountID string, groupID string, operations []GroupUpdateOperation) (*Group, error)
|
||||
DeleteGroup(accountId, userId, groupID string) error
|
||||
ListGroups(accountId string) ([]*Group, error)
|
||||
GroupAddPeer(accountId, groupID, peerID string) error
|
||||
GroupDeletePeer(accountId, groupID, peerKey string) error
|
||||
GroupDeletePeer(accountId, groupID, peerID string) error
|
||||
GroupListPeers(accountId, groupID string) ([]*Peer, error)
|
||||
GetPolicy(accountID, policyID, userID string) (*Policy, error)
|
||||
SavePolicy(accountID, userID string, policy *Policy) error
|
||||
DeletePolicy(accountID, policyID, userID string) error
|
||||
ListPolicies(accountID, userID string) ([]*Policy, error)
|
||||
GetRoute(accountID, routeID, userID string) (*route.Route, error)
|
||||
CreateRoute(accountID string, prefix, peerID, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error)
|
||||
CreateRoute(accountID, prefix, peerID string, peerGroupIDs []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error)
|
||||
SaveRoute(accountID, userID string, route *route.Route) error
|
||||
UpdateRoute(accountID, routeID string, operations []RouteUpdateOperation) (*route.Route, error)
|
||||
DeleteRoute(accountID, routeID, userID string) error
|
||||
ListRoutes(accountID, userID string) ([]*route.Route, error)
|
||||
GetNameServerGroup(accountID, nsGroupID string) (*nbdns.NameServerGroup, error)
|
||||
CreateNameServerGroup(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string) (*nbdns.NameServerGroup, error)
|
||||
SaveNameServerGroup(accountID, userID string, nsGroupToSave *nbdns.NameServerGroup) error
|
||||
UpdateNameServerGroup(accountID, nsGroupID, userID string, operations []NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error)
|
||||
DeleteNameServerGroup(accountID, nsGroupID, userID string) error
|
||||
ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error)
|
||||
GetDNSDomain() string
|
||||
@@ -133,6 +130,9 @@ type DefaultAccountManager struct {
|
||||
// dnsDomain is used for peer resolution. This is appended to the peer's name
|
||||
dnsDomain string
|
||||
peerLoginExpiry Scheduler
|
||||
|
||||
// userDeleteFromIDPEnabled allows to delete user from IDP when user is deleted from account
|
||||
userDeleteFromIDPEnabled bool
|
||||
}
|
||||
|
||||
// Settings represents Account settings structure that can be modified via API and Dashboard
|
||||
@@ -253,22 +253,39 @@ func (a *Account) filterRoutesByGroups(routes []*route.Route, groupListMap looku
|
||||
func (a *Account) getEnabledAndDisabledRoutesByPeer(peerID string) ([]*route.Route, []*route.Route) {
|
||||
var enabledRoutes []*route.Route
|
||||
var disabledRoutes []*route.Route
|
||||
|
||||
takeRoute := func(r *route.Route, id string) {
|
||||
peer := a.GetPeer(peerID)
|
||||
if peer == nil {
|
||||
log.Errorf("route %s has peer %s that doesn't exist under account %s", r.ID, peerID, a.Id)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Enabled {
|
||||
enabledRoutes = append(enabledRoutes, r)
|
||||
return
|
||||
}
|
||||
disabledRoutes = append(disabledRoutes, r)
|
||||
}
|
||||
|
||||
for _, r := range a.Routes {
|
||||
if len(r.PeerGroups) != 0 {
|
||||
for _, groupID := range r.PeerGroups {
|
||||
group := a.GetGroup(groupID)
|
||||
if group == nil {
|
||||
log.Errorf("route %s has peers group %s that doesn't exist under account %s", r.ID, groupID, a.Id)
|
||||
continue
|
||||
}
|
||||
for _, id := range group.Peers {
|
||||
if id == peerID {
|
||||
takeRoute(r, id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.Peer == peerID {
|
||||
// We need to set Peer.Key instead of Peer.ID because this object will be sent to agents as part of a network map.
|
||||
// Ideally we should have a separate field for that, but fine for now.
|
||||
peer := a.GetPeer(peerID)
|
||||
if peer == nil {
|
||||
log.Errorf("route %s has peer %s that doesn't exist under account %s", r.ID, peerID, a.Id)
|
||||
continue
|
||||
}
|
||||
raut := r.Copy()
|
||||
raut.Peer = peer.Key
|
||||
if r.Enabled {
|
||||
enabledRoutes = append(enabledRoutes, raut)
|
||||
continue
|
||||
}
|
||||
disabledRoutes = append(disabledRoutes, raut)
|
||||
takeRoute(r, peerID)
|
||||
}
|
||||
}
|
||||
return enabledRoutes, disabledRoutes
|
||||
@@ -316,8 +333,51 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap {
|
||||
}
|
||||
peersToConnect = append(peersToConnect, p)
|
||||
}
|
||||
// Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
|
||||
routesUpdate := a.getRoutesToSync(peerID, peersToConnect)
|
||||
|
||||
routes := a.getRoutesToSync(peerID, peersToConnect)
|
||||
|
||||
takePeer := func(id string) (*Peer, bool) {
|
||||
peer := a.GetPeer(id)
|
||||
if peer == nil || peer.Meta.GoOS != "linux" {
|
||||
return nil, false
|
||||
}
|
||||
return peer, true
|
||||
}
|
||||
|
||||
// We need to set Peer.Key instead of Peer.ID because this object will be sent to agents as part of a network map.
|
||||
// Ideally we should have a separate field for that, but fine for now.
|
||||
var routesUpdate []*route.Route
|
||||
seenPeers := make(map[string]bool)
|
||||
for _, r := range routes {
|
||||
if r.Peer != "" {
|
||||
peer, valid := takePeer(r.Peer)
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
rCopy := r.Copy()
|
||||
rCopy.Peer = peer.Key // client expects the key
|
||||
routesUpdate = append(routesUpdate, rCopy)
|
||||
continue
|
||||
}
|
||||
for _, groupID := range r.PeerGroups {
|
||||
if group := a.GetGroup(groupID); group != nil {
|
||||
for _, peerId := range group.Peers {
|
||||
peer, valid := takePeer(peerId)
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := seenPeers[peer.ID]; !ok {
|
||||
rCopy := r.Copy()
|
||||
rCopy.ID = r.ID + ":" + peer.ID // we have to provide unit route id when distribute network map
|
||||
rCopy.Peer = peer.Key // client expects the key
|
||||
routesUpdate = append(routesUpdate, rCopy)
|
||||
}
|
||||
seenPeers[peer.ID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dnsManagementStatus := a.getPeerDNSManagementStatus(peerID)
|
||||
dnsUpdate := nbdns.Config{
|
||||
@@ -738,18 +798,19 @@ func (a *Account) UserGroupsRemoveFromPeers(userID string, groups ...string) {
|
||||
|
||||
// BuildManager creates a new DefaultAccountManager with a provided Store
|
||||
func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager,
|
||||
singleAccountModeDomain string, dnsDomain string, eventStore activity.Store,
|
||||
singleAccountModeDomain string, dnsDomain string, eventStore activity.Store, userDeleteFromIDPEnabled bool,
|
||||
) (*DefaultAccountManager, error) {
|
||||
am := &DefaultAccountManager{
|
||||
Store: store,
|
||||
peersUpdateManager: peersUpdateManager,
|
||||
idpManager: idpManager,
|
||||
ctx: context.Background(),
|
||||
cacheMux: sync.Mutex{},
|
||||
cacheLoading: map[string]chan struct{}{},
|
||||
dnsDomain: dnsDomain,
|
||||
eventStore: eventStore,
|
||||
peerLoginExpiry: NewDefaultScheduler(),
|
||||
Store: store,
|
||||
peersUpdateManager: peersUpdateManager,
|
||||
idpManager: idpManager,
|
||||
ctx: context.Background(),
|
||||
cacheMux: sync.Mutex{},
|
||||
cacheLoading: map[string]chan struct{}{},
|
||||
dnsDomain: dnsDomain,
|
||||
eventStore: eventStore,
|
||||
peerLoginExpiry: NewDefaultScheduler(),
|
||||
userDeleteFromIDPEnabled: userDeleteFromIDPEnabled,
|
||||
}
|
||||
allAccounts := store.GetAllAccounts()
|
||||
// enable single account mode only if configured by user and number of existing accounts is not grater than 1
|
||||
@@ -874,33 +935,19 @@ func (am *DefaultAccountManager) peerLoginExpirationJob(accountID string) func()
|
||||
return account.GetNextPeerExpiration()
|
||||
}
|
||||
|
||||
expiredPeers := account.GetExpiredPeers()
|
||||
var peerIDs []string
|
||||
for _, peer := range account.GetExpiredPeers() {
|
||||
if peer.Status.LoginExpired {
|
||||
continue
|
||||
}
|
||||
for _, peer := range expiredPeers {
|
||||
peerIDs = append(peerIDs, peer.ID)
|
||||
peer.MarkLoginExpired(true)
|
||||
account.UpdatePeer(peer)
|
||||
err = am.Store.SavePeerStatus(account.Id, peer.ID, *peer.Status)
|
||||
if err != nil {
|
||||
log.Errorf("failed saving peer status while expiring peer %s", peer.ID)
|
||||
return account.GetNextPeerExpiration()
|
||||
}
|
||||
am.storeEvent(peer.UserID, peer.ID, account.Id, activity.PeerLoginExpired, peer.EventMeta(am.GetDNSDomain()))
|
||||
}
|
||||
|
||||
log.Debugf("discovered %d peers to expire for account %s", len(peerIDs), account.Id)
|
||||
|
||||
if len(peerIDs) != 0 {
|
||||
// this will trigger peer disconnect from the management service
|
||||
am.peersUpdateManager.CloseChannels(peerIDs)
|
||||
err = am.updateAccountPeers(account)
|
||||
if err != nil {
|
||||
log.Errorf("failed updating account peers while expiring peers for account %s", accountID)
|
||||
return account.GetNextPeerExpiration()
|
||||
}
|
||||
if err := am.expireAndUpdatePeers(account, expiredPeers); err != nil {
|
||||
log.Errorf("failed updating account peers while expiring peers for account %s", account.Id)
|
||||
return account.GetNextPeerExpiration()
|
||||
}
|
||||
|
||||
return account.GetNextPeerExpiration()
|
||||
}
|
||||
}
|
||||
@@ -1256,7 +1303,6 @@ func (am *DefaultAccountManager) redeemInvite(account *Account, userID string) e
|
||||
|
||||
// MarkPATUsed marks a personal access token as used
|
||||
func (am *DefaultAccountManager) MarkPATUsed(tokenID string) error {
|
||||
unlock := am.Store.AcquireGlobalLock()
|
||||
|
||||
user, err := am.Store.GetUserByTokenID(tokenID)
|
||||
if err != nil {
|
||||
@@ -1268,8 +1314,7 @@ func (am *DefaultAccountManager) MarkPATUsed(tokenID string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
unlock()
|
||||
unlock = am.Store.AcquireAccountLock(account.Id)
|
||||
unlock := am.Store.AcquireAccountLock(account.Id)
|
||||
defer unlock()
|
||||
|
||||
account, err = am.Store.GetAccountByUser(user.Id)
|
||||
@@ -1605,19 +1650,3 @@ func newAccountWithId(accountID, userID, domain string) *Account {
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
func removeFromList(inputList []string, toRemove []string) []string {
|
||||
toRemoveMap := make(map[string]struct{})
|
||||
for _, item := range toRemove {
|
||||
toRemoveMap[item] = struct{}{}
|
||||
}
|
||||
|
||||
var resultList []string
|
||||
for _, item := range inputList {
|
||||
_, ok := toRemoveMap[item]
|
||||
if !ok {
|
||||
resultList = append(resultList, item)
|
||||
}
|
||||
}
|
||||
return resultList
|
||||
}
|
||||
|
||||
@@ -1385,8 +1385,9 @@ func TestAccount_Copy(t *testing.T) {
|
||||
},
|
||||
Routes: map[string]*route.Route{
|
||||
"route1": {
|
||||
ID: "route1",
|
||||
Groups: []string{"group1"},
|
||||
ID: "route1",
|
||||
PeerGroups: []string{},
|
||||
Groups: []string{"group1"},
|
||||
},
|
||||
},
|
||||
NameServerGroups: map[string]*nbdns.NameServerGroup{
|
||||
@@ -2063,7 +2064,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
return nil, err
|
||||
}
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "netbird.cloud", eventStore)
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "netbird.cloud", eventStore, false)
|
||||
}
|
||||
|
||||
func createStore(t *testing.T) (Store, error) {
|
||||
|
||||
@@ -104,6 +104,8 @@ const (
|
||||
UserBlocked
|
||||
// UserUnblocked indicates that a user unblocked another user
|
||||
UserUnblocked
|
||||
// UserDeleted indicates that a user deleted another user
|
||||
UserDeleted
|
||||
// GroupDeleted indicates that a user deleted group
|
||||
GroupDeleted
|
||||
// UserLoggedInPeer indicates that user logged in their peer with an interactive SSO login
|
||||
@@ -162,6 +164,7 @@ var activityMap = map[Activity]Code{
|
||||
ServiceUserDeleted: {"Service user deleted", "service.user.delete"},
|
||||
UserBlocked: {"User blocked", "user.block"},
|
||||
UserUnblocked: {"User unblocked", "user.unblock"},
|
||||
UserDeleted: {"User deleted", "user.delete"},
|
||||
GroupDeleted: {"Group deleted", "group.delete"},
|
||||
UserLoggedInPeer: {"User logged in peer", "user.peer.login"},
|
||||
PeerLoginExpired: {"Peer login expired", "peer.login.expire"},
|
||||
|
||||
@@ -18,10 +18,15 @@ type Event struct {
|
||||
ID uint64
|
||||
// InitiatorID is the ID of an object that initiated the event (e.g., a user)
|
||||
InitiatorID string
|
||||
// InitiatorName is the name of an object that initiated the event.
|
||||
InitiatorName string
|
||||
// InitiatorEmail is the email address of an object that initiated the event.
|
||||
InitiatorEmail string
|
||||
// TargetID is the ID of an object that was effected by the event (e.g., a peer)
|
||||
TargetID string
|
||||
// AccountID is the ID of an account where the event happened
|
||||
AccountID string
|
||||
|
||||
// Meta of the event, e.g. deleted peer information like name, IP, etc
|
||||
Meta map[string]any
|
||||
}
|
||||
@@ -35,12 +40,14 @@ func (e *Event) Copy() *Event {
|
||||
}
|
||||
|
||||
return &Event{
|
||||
Timestamp: e.Timestamp,
|
||||
Activity: e.Activity,
|
||||
ID: e.ID,
|
||||
InitiatorID: e.InitiatorID,
|
||||
TargetID: e.TargetID,
|
||||
AccountID: e.AccountID,
|
||||
Meta: meta,
|
||||
Timestamp: e.Timestamp,
|
||||
Activity: e.Activity,
|
||||
ID: e.ID,
|
||||
InitiatorID: e.InitiatorID,
|
||||
InitiatorName: e.InitiatorName,
|
||||
InitiatorEmail: e.InitiatorEmail,
|
||||
TargetID: e.TargetID,
|
||||
AccountID: e.AccountID,
|
||||
Meta: meta,
|
||||
}
|
||||
}
|
||||
|
||||
81
management/server/activity/sqlite/crypt.go
Normal file
81
management/server/activity/sqlite/crypt.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var iv = []byte{10, 22, 13, 79, 05, 8, 52, 91, 87, 98, 88, 98, 35, 25, 13, 05}
|
||||
|
||||
type FieldEncrypt struct {
|
||||
block cipher.Block
|
||||
}
|
||||
|
||||
func GenerateKey() (string, error) {
|
||||
key := make([]byte, 32)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
readableKey := base64.StdEncoding.EncodeToString(key)
|
||||
return readableKey, nil
|
||||
}
|
||||
|
||||
func NewFieldEncrypt(key string) (*FieldEncrypt, error) {
|
||||
binKey, err := base64.StdEncoding.DecodeString(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(binKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ec := &FieldEncrypt{
|
||||
block: block,
|
||||
}
|
||||
|
||||
return ec, nil
|
||||
}
|
||||
|
||||
func (ec *FieldEncrypt) Encrypt(payload string) string {
|
||||
plainText := pkcs5Padding([]byte(payload))
|
||||
cipherText := make([]byte, len(plainText))
|
||||
cbc := cipher.NewCBCEncrypter(ec.block, iv)
|
||||
cbc.CryptBlocks(cipherText, plainText)
|
||||
return base64.StdEncoding.EncodeToString(cipherText)
|
||||
}
|
||||
|
||||
func (ec *FieldEncrypt) Decrypt(data string) (string, error) {
|
||||
cipherText, err := base64.StdEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cbc := cipher.NewCBCDecrypter(ec.block, iv)
|
||||
cbc.CryptBlocks(cipherText, cipherText)
|
||||
payload, err := pkcs5UnPadding(cipherText)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(payload), nil
|
||||
}
|
||||
|
||||
func pkcs5Padding(ciphertext []byte) []byte {
|
||||
padding := aes.BlockSize - len(ciphertext)%aes.BlockSize
|
||||
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(ciphertext, padText...)
|
||||
}
|
||||
|
||||
func pkcs5UnPadding(src []byte) ([]byte, error) {
|
||||
srcLen := len(src)
|
||||
paddingLen := int(src[srcLen-1])
|
||||
if paddingLen >= srcLen || paddingLen > aes.BlockSize {
|
||||
return nil, fmt.Errorf("padding size error")
|
||||
}
|
||||
return src[:srcLen-paddingLen], nil
|
||||
}
|
||||
63
management/server/activity/sqlite/crypt_test.go
Normal file
63
management/server/activity/sqlite/crypt_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateKey(t *testing.T) {
|
||||
testData := "exampl@netbird.io"
|
||||
key, err := GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key: %s", err)
|
||||
}
|
||||
ee, err := NewFieldEncrypt(key)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to init email encryption: %s", err)
|
||||
}
|
||||
|
||||
encrypted := ee.Encrypt(testData)
|
||||
if encrypted == "" {
|
||||
t.Fatalf("invalid encrypted text")
|
||||
}
|
||||
|
||||
decrypted, err := ee.Decrypt(encrypted)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decrypt data: %s", err)
|
||||
}
|
||||
|
||||
if decrypted != testData {
|
||||
t.Fatalf("decrypted data is not match with test data: %s, %s", testData, decrypted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorruptKey(t *testing.T) {
|
||||
testData := "exampl@netbird.io"
|
||||
key, err := GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key: %s", err)
|
||||
}
|
||||
ee, err := NewFieldEncrypt(key)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to init email encryption: %s", err)
|
||||
}
|
||||
|
||||
encrypted := ee.Encrypt(testData)
|
||||
if encrypted == "" {
|
||||
t.Fatalf("invalid encrypted text")
|
||||
}
|
||||
|
||||
newKey, err := GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate key: %s", err)
|
||||
}
|
||||
|
||||
ee, err = NewFieldEncrypt(newKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to init email encryption: %s", err)
|
||||
}
|
||||
|
||||
res, _ := ee.Decrypt(encrypted)
|
||||
if res == testData {
|
||||
t.Fatalf("incorrect decryption, the result is: %s", res)
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,14 @@ package sqlite
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
|
||||
// sqlite driver
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,69 +25,122 @@ const (
|
||||
"meta TEXT," +
|
||||
" target_id TEXT);"
|
||||
|
||||
selectDescQuery = "SELECT id, activity, timestamp, initiator_id, target_id, account_id, meta" +
|
||||
" FROM events WHERE account_id = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?;"
|
||||
selectAscQuery = "SELECT id, activity, timestamp, initiator_id, target_id, account_id, meta" +
|
||||
" FROM events WHERE account_id = ? ORDER BY timestamp ASC LIMIT ? OFFSET ?;"
|
||||
creatTableDeletedUsersQuery = `CREATE TABLE IF NOT EXISTS deleted_users (id TEXT NOT NULL, email TEXT NOT NULL, name TEXT);`
|
||||
|
||||
selectDescQuery = `SELECT events.id, activity, timestamp, initiator_id, i.name as "initiator_name", i.email as "initiator_email", target_id, t.name as "target_name", t.email as "target_email", account_id, meta
|
||||
FROM events
|
||||
LEFT JOIN deleted_users i ON events.initiator_id = i.id
|
||||
LEFT JOIN deleted_users t ON events.target_id = t.id
|
||||
WHERE account_id = ?
|
||||
ORDER BY timestamp DESC LIMIT ? OFFSET ?;`
|
||||
|
||||
selectAscQuery = `SELECT events.id, activity, timestamp, initiator_id, i.name as "initiator_name", i.email as "initiator_email", target_id, t.name as "target_name", t.email as "target_email", account_id, meta
|
||||
FROM events
|
||||
LEFT JOIN deleted_users i ON events.initiator_id = i.id
|
||||
LEFT JOIN deleted_users t ON events.target_id = t.id
|
||||
WHERE account_id = ?
|
||||
ORDER BY timestamp ASC LIMIT ? OFFSET ?;`
|
||||
|
||||
insertQuery = "INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id, meta) " +
|
||||
"VALUES(?, ?, ?, ?, ?, ?)"
|
||||
|
||||
insertDeleteUserQuery = `INSERT INTO deleted_users(id, email, name) VALUES(?, ?, ?)`
|
||||
)
|
||||
|
||||
// Store is the implementation of the activity.Store interface backed by SQLite
|
||||
type Store struct {
|
||||
db *sql.DB
|
||||
db *sql.DB
|
||||
fieldEncrypt *FieldEncrypt
|
||||
|
||||
insertStatement *sql.Stmt
|
||||
selectAscStatement *sql.Stmt
|
||||
selectDescStatement *sql.Stmt
|
||||
deleteUserStmt *sql.Stmt
|
||||
}
|
||||
|
||||
// NewSQLiteStore creates a new Store with an event table if not exists.
|
||||
func NewSQLiteStore(dataDir string) (*Store, error) {
|
||||
func NewSQLiteStore(dataDir string, encryptionKey string) (*Store, error) {
|
||||
dbFile := filepath.Join(dataDir, eventSinkDB)
|
||||
db, err := sql.Open("sqlite3", dbFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
crypt, err := NewFieldEncrypt(encryptionKey)
|
||||
if err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = db.Exec(createTableQuery)
|
||||
if err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = db.Exec(creatTableDeletedUsersQuery)
|
||||
if err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = updateDeletedUsersTable(db)
|
||||
if err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
insertStmt, err := db.Prepare(insertQuery)
|
||||
if err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selectDescStmt, err := db.Prepare(selectDescQuery)
|
||||
if err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selectAscStmt, err := db.Prepare(selectAscQuery)
|
||||
if err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Store{
|
||||
deleteUserStmt, err := db.Prepare(insertDeleteUserQuery)
|
||||
if err != nil {
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Store{
|
||||
db: db,
|
||||
fieldEncrypt: crypt,
|
||||
insertStatement: insertStmt,
|
||||
selectDescStatement: selectDescStmt,
|
||||
selectAscStatement: selectAscStmt,
|
||||
}, nil
|
||||
deleteUserStmt: deleteUserStmt,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func processResult(result *sql.Rows) ([]*activity.Event, error) {
|
||||
func (store *Store) processResult(result *sql.Rows) ([]*activity.Event, error) {
|
||||
events := make([]*activity.Event, 0)
|
||||
for result.Next() {
|
||||
var id int64
|
||||
var operation activity.Activity
|
||||
var timestamp time.Time
|
||||
var initiator string
|
||||
var initiatorName *string
|
||||
var initiatorEmail *string
|
||||
var target string
|
||||
var targetUserName *string
|
||||
var targetEmail *string
|
||||
var account string
|
||||
var jsonMeta string
|
||||
err := result.Scan(&id, &operation, ×tamp, &initiator, &target, &account, &jsonMeta)
|
||||
err := result.Scan(&id, &operation, ×tamp, &initiator, &initiatorName, &initiatorEmail, &target, &targetUserName, &targetEmail, &account, &jsonMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -100,7 +153,27 @@ func processResult(result *sql.Rows) ([]*activity.Event, error) {
|
||||
}
|
||||
}
|
||||
|
||||
events = append(events, &activity.Event{
|
||||
if targetUserName != nil {
|
||||
name, err := store.fieldEncrypt.Decrypt(*targetUserName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt username for target id: %s", target)
|
||||
meta["username"] = ""
|
||||
} else {
|
||||
meta["username"] = name
|
||||
}
|
||||
}
|
||||
|
||||
if targetEmail != nil {
|
||||
email, err := store.fieldEncrypt.Decrypt(*targetEmail)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt email address for target id: %s", target)
|
||||
meta["email"] = ""
|
||||
} else {
|
||||
meta["email"] = email
|
||||
}
|
||||
}
|
||||
|
||||
event := &activity.Event{
|
||||
Timestamp: timestamp,
|
||||
Activity: operation,
|
||||
ID: uint64(id),
|
||||
@@ -108,7 +181,27 @@ func processResult(result *sql.Rows) ([]*activity.Event, error) {
|
||||
TargetID: target,
|
||||
AccountID: account,
|
||||
Meta: meta,
|
||||
})
|
||||
}
|
||||
|
||||
if initiatorName != nil {
|
||||
name, err := store.fieldEncrypt.Decrypt(*initiatorName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt username of initiator: %s", initiator)
|
||||
} else {
|
||||
event.InitiatorName = name
|
||||
}
|
||||
}
|
||||
|
||||
if initiatorEmail != nil {
|
||||
email, err := store.fieldEncrypt.Decrypt(*initiatorEmail)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decrypt email address of initiator: %s", initiator)
|
||||
} else {
|
||||
event.InitiatorEmail = email
|
||||
}
|
||||
}
|
||||
|
||||
events = append(events, event)
|
||||
}
|
||||
|
||||
return events, nil
|
||||
@@ -127,13 +220,18 @@ func (store *Store) Get(accountID string, offset, limit int, descending bool) ([
|
||||
}
|
||||
|
||||
defer result.Close() //nolint
|
||||
return processResult(result)
|
||||
return store.processResult(result)
|
||||
}
|
||||
|
||||
// Save an event in the SQLite events table
|
||||
// Save an event in the SQLite events table end encrypt the "email" element in meta map
|
||||
func (store *Store) Save(event *activity.Event) (*activity.Event, error) {
|
||||
var jsonMeta string
|
||||
if event.Meta != nil {
|
||||
meta, err := store.saveDeletedUserEmailAndNameInEncrypted(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if meta != nil {
|
||||
metaBytes, err := json.Marshal(event.Meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -156,6 +254,34 @@ func (store *Store) Save(event *activity.Event) (*activity.Event, error) {
|
||||
return eventCopy, nil
|
||||
}
|
||||
|
||||
// saveDeletedUserEmailAndNameInEncrypted if the meta contains email and name then store it in encrypted way and delete
|
||||
// this item from meta map
|
||||
func (store *Store) saveDeletedUserEmailAndNameInEncrypted(event *activity.Event) (map[string]any, error) {
|
||||
email, ok := event.Meta["email"]
|
||||
if !ok {
|
||||
return event.Meta, nil
|
||||
}
|
||||
|
||||
name, ok := event.Meta["name"]
|
||||
if !ok {
|
||||
return event.Meta, nil
|
||||
}
|
||||
|
||||
encryptedEmail := store.fieldEncrypt.Encrypt(fmt.Sprintf("%s", email))
|
||||
encryptedName := store.fieldEncrypt.Encrypt(fmt.Sprintf("%s", name))
|
||||
_, err := store.deleteUserStmt.Exec(event.TargetID, encryptedEmail, encryptedName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(event.Meta) == 2 {
|
||||
return nil, nil // nolint
|
||||
}
|
||||
delete(event.Meta, "email")
|
||||
delete(event.Meta, "name")
|
||||
return event.Meta, nil
|
||||
}
|
||||
|
||||
// Close the Store
|
||||
func (store *Store) Close() error {
|
||||
if store.db != nil {
|
||||
@@ -163,3 +289,44 @@ func (store *Store) Close() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateDeletedUsersTable(db *sql.DB) error {
|
||||
log.Debugf("check deleted_users table version")
|
||||
rows, err := db.Query(`PRAGMA table_info(deleted_users);`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
found := false
|
||||
for rows.Next() {
|
||||
var (
|
||||
cid int
|
||||
name string
|
||||
dataType string
|
||||
notNull int
|
||||
dfltVal sql.NullString
|
||||
pk int
|
||||
)
|
||||
err := rows.Scan(&cid, &name, &dataType, ¬Null, &dfltVal, &pk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if name == "name" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if found {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("update delted_users table")
|
||||
_, err = db.Exec(`ALTER TABLE deleted_users ADD COLUMN name TEXT;`)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
|
||||
func TestNewSQLiteStore(t *testing.T) {
|
||||
dataDir := t.TempDir()
|
||||
store, err := NewSQLiteStore(dataDir)
|
||||
key, _ := GenerateKey()
|
||||
store, err := NewSQLiteStore(dataDir, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
|
||||
@@ -35,7 +35,8 @@ type Config struct {
|
||||
TURNConfig *TURNConfig
|
||||
Signal *Host
|
||||
|
||||
Datadir string
|
||||
Datadir string
|
||||
DataStoreEncryptionKey string
|
||||
|
||||
HttpConfig *HttpServerConfig
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
return nil, err
|
||||
}
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "netbird.test", eventStore)
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "netbird.test", eventStore, false)
|
||||
}
|
||||
|
||||
func createDNSStore(t *testing.T) (Store, error) {
|
||||
|
||||
@@ -33,26 +33,6 @@ type Group struct {
|
||||
Peers []string
|
||||
}
|
||||
|
||||
const (
|
||||
// UpdateGroupName indicates a name update operation
|
||||
UpdateGroupName GroupUpdateOperationType = iota
|
||||
// InsertPeersToGroup indicates insert peers to group operation
|
||||
InsertPeersToGroup
|
||||
// RemovePeersFromGroup indicates a remove peers from group operation
|
||||
RemovePeersFromGroup
|
||||
// UpdateGroupPeers indicates a replacement of group peers list
|
||||
UpdateGroupPeers
|
||||
)
|
||||
|
||||
// GroupUpdateOperationType operation type
|
||||
type GroupUpdateOperationType int
|
||||
|
||||
// GroupUpdateOperation operation object with type and values to be applied
|
||||
type GroupUpdateOperation struct {
|
||||
Type GroupUpdateOperationType
|
||||
Values []string
|
||||
}
|
||||
|
||||
// EventMeta returns activity event meta related to the group
|
||||
func (g *Group) EventMeta() map[string]any {
|
||||
return map[string]any{"name": g.Name}
|
||||
@@ -165,57 +145,6 @@ func difference(a, b []string) []string {
|
||||
return diff
|
||||
}
|
||||
|
||||
// UpdateGroup updates a group using a list of operations
|
||||
func (am *DefaultAccountManager) UpdateGroup(accountID string,
|
||||
groupID string, operations []GroupUpdateOperation,
|
||||
) (*Group, error) {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groupToUpdate, ok := account.Groups[groupID]
|
||||
if !ok {
|
||||
return nil, status.Errorf(status.NotFound, "group with ID %s no longer exists", groupID)
|
||||
}
|
||||
|
||||
group := groupToUpdate.Copy()
|
||||
|
||||
for _, operation := range operations {
|
||||
switch operation.Type {
|
||||
case UpdateGroupName:
|
||||
group.Name = operation.Values[0]
|
||||
case UpdateGroupPeers:
|
||||
group.Peers = operation.Values
|
||||
case InsertPeersToGroup:
|
||||
sourceList := group.Peers
|
||||
resultList := removeFromList(sourceList, operation.Values)
|
||||
group.Peers = append(resultList, operation.Values...)
|
||||
case RemovePeersFromGroup:
|
||||
sourceList := group.Peers
|
||||
resultList := removeFromList(sourceList, operation.Values)
|
||||
group.Peers = resultList
|
||||
}
|
||||
}
|
||||
|
||||
account.Groups[groupID] = group
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = am.updateAccountPeers(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return group, nil
|
||||
}
|
||||
|
||||
// DeleteGroup object of the peers
|
||||
func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string) error {
|
||||
unlock := am.Store.AcquireAccountLock(accountId)
|
||||
@@ -356,7 +285,7 @@ func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string)
|
||||
}
|
||||
|
||||
// GroupDeletePeer removes peer from the group
|
||||
func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey string) error {
|
||||
func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID string) error {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
@@ -372,7 +301,7 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey str
|
||||
|
||||
account.Network.IncSerial()
|
||||
for i, itemID := range group.Peers {
|
||||
if itemID == peerKey {
|
||||
if itemID == peerID {
|
||||
group.Peers = append(group.Peers[:i], group.Peers[i+1:]...)
|
||||
if err := am.Store.SaveAccount(account); err != nil {
|
||||
return err
|
||||
|
||||
@@ -159,6 +159,11 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi
|
||||
select {
|
||||
// condition when there are some updates
|
||||
case update, open := <-updates:
|
||||
|
||||
if s.appMetrics != nil {
|
||||
s.appMetrics.GRPCMetrics().UpdateChannelQueueLength(len(updates) + 1)
|
||||
}
|
||||
|
||||
if !open {
|
||||
log.Debugf("updates channel for peer %s was closed", peerKey.String())
|
||||
s.cancelPeerRoutines(peer)
|
||||
|
||||
0
management/server/http/api/generate.sh
Normal file → Executable file
0
management/server/http/api/generate.sh
Normal file → Executable file
@@ -745,9 +745,15 @@ components:
|
||||
type: boolean
|
||||
example: true
|
||||
peer:
|
||||
description: Peer Identifier associated with route
|
||||
description: Peer Identifier associated with route. This property can not be set together with `peer_groups`
|
||||
type: string
|
||||
example: chacbco6lnnbn6cg5s91
|
||||
peer_groups:
|
||||
description: Peers Group Identifier associated with route. This property can not be set together with `peer`
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: chacbco6lnnbn6cg5s91
|
||||
network:
|
||||
description: Network range in CIDR format
|
||||
type: string
|
||||
@@ -773,7 +779,9 @@ components:
|
||||
- description
|
||||
- network_id
|
||||
- enabled
|
||||
- peer
|
||||
# Only one property has to be set
|
||||
#- peer
|
||||
#- peer_groups
|
||||
- network
|
||||
- metric
|
||||
- masquerade
|
||||
@@ -922,6 +930,14 @@ components:
|
||||
description: The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
|
||||
type: string
|
||||
example: google-oauth2|123456789012345678901
|
||||
initiator_name:
|
||||
description: The name of the initiator of the event.
|
||||
type: string
|
||||
example: John Doe
|
||||
initiator_email:
|
||||
description: The e-mail address of the initiator of the event. E.g., an e-mail of a user that triggered the event.
|
||||
type: string
|
||||
example: demo@netbird.io
|
||||
target_id:
|
||||
description: The ID of the target of the event. E.g., an ID of the peer that a user removed.
|
||||
type: string
|
||||
@@ -938,6 +954,8 @@ components:
|
||||
- activity
|
||||
- activity_code
|
||||
- initiator_id
|
||||
- initiator_name
|
||||
- initiator_email
|
||||
- target_id
|
||||
- meta
|
||||
responses:
|
||||
@@ -1134,8 +1152,8 @@ paths:
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Block a User
|
||||
description: This method blocks a user from accessing the system, but leaves the IDP user intact.
|
||||
summary: Delete a User
|
||||
description: This method removes a user from accessing the system. For this leaves the IDP user intact unless the `--user-delete-from-idp` is passed to management startup.
|
||||
tags: [ Users ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Package api provides primitives to interact with the openapi HTTP API.
|
||||
//
|
||||
// Code generated by github.com/deepmap/oapi-codegen version v1.11.1-0.20220912230023-4a1477f6a8ba DO NOT EDIT.
|
||||
// Code generated by github.com/deepmap/oapi-codegen version v1.15.0 DO NOT EDIT.
|
||||
package api
|
||||
|
||||
import (
|
||||
@@ -164,9 +164,15 @@ type Event struct {
|
||||
// Id Event unique identifier
|
||||
Id string `json:"id"`
|
||||
|
||||
// InitiatorEmail The e-mail address of the initiator of the event. E.g., an e-mail of a user that triggered the event.
|
||||
InitiatorEmail string `json:"initiator_email"`
|
||||
|
||||
// InitiatorId The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
|
||||
InitiatorId string `json:"initiator_id"`
|
||||
|
||||
// InitiatorName The name of the initiator of the event.
|
||||
InitiatorName string `json:"initiator_name"`
|
||||
|
||||
// Meta The metadata of the event
|
||||
Meta map[string]string `json:"meta"`
|
||||
|
||||
@@ -593,8 +599,11 @@ type Route struct {
|
||||
// NetworkType Network type indicating if it is IPv4 or IPv6
|
||||
NetworkType string `json:"network_type"`
|
||||
|
||||
// Peer Peer Identifier associated with route
|
||||
Peer string `json:"peer"`
|
||||
// Peer Peer Identifier associated with route. This property can not be set together with `peer_groups`
|
||||
Peer *string `json:"peer,omitempty"`
|
||||
|
||||
// PeerGroups Peers Group Identifier associated with route. This property can not be set together with `peer`
|
||||
PeerGroups *[]string `json:"peer_groups,omitempty"`
|
||||
}
|
||||
|
||||
// RouteRequest defines model for RouteRequest.
|
||||
@@ -620,8 +629,11 @@ type RouteRequest struct {
|
||||
// NetworkId Route network identifier, to group HA routes
|
||||
NetworkId string `json:"network_id"`
|
||||
|
||||
// Peer Peer Identifier associated with route
|
||||
Peer string `json:"peer"`
|
||||
// Peer Peer Identifier associated with route. This property can not be set together with `peer_groups`
|
||||
Peer *string `json:"peer,omitempty"`
|
||||
|
||||
// PeerGroups Peers Group Identifier associated with route. This property can not be set together with `peer`
|
||||
PeerGroups *[]string `json:"peer_groups,omitempty"`
|
||||
}
|
||||
|
||||
// Rule defines model for Rule.
|
||||
|
||||
@@ -45,14 +45,66 @@ func (h *EventsHandler) GetAllEvents(w http.ResponseWriter, r *http.Request) {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
}
|
||||
events := make([]*api.Event, 0)
|
||||
for _, e := range accountEvents {
|
||||
events = append(events, toEventResponse(e))
|
||||
events := make([]*api.Event, len(accountEvents))
|
||||
for i, e := range accountEvents {
|
||||
events[i] = toEventResponse(e)
|
||||
}
|
||||
|
||||
err = h.fillEventsWithUserInfo(events, account.Id, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, events)
|
||||
}
|
||||
|
||||
func (h *EventsHandler) fillEventsWithUserInfo(events []*api.Event, accountId, userId string) error {
|
||||
// build email, name maps based on users
|
||||
userInfos, err := h.accountManager.GetUsersFromAccount(accountId, userId)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get users from account: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
emails := make(map[string]string)
|
||||
names := make(map[string]string)
|
||||
for _, ui := range userInfos {
|
||||
emails[ui.ID] = ui.Email
|
||||
names[ui.ID] = ui.Name
|
||||
}
|
||||
|
||||
var ok bool
|
||||
for _, event := range events {
|
||||
// fill initiator
|
||||
if event.InitiatorEmail == "" {
|
||||
event.InitiatorEmail, ok = emails[event.InitiatorId]
|
||||
if !ok {
|
||||
log.Warnf("failed to resolve email for initiator: %s", event.InitiatorId)
|
||||
}
|
||||
}
|
||||
|
||||
if event.InitiatorName == "" {
|
||||
// here to allowed to be empty because in the first release we did not store the name
|
||||
event.InitiatorName = names[event.InitiatorId]
|
||||
}
|
||||
|
||||
// fill target meta
|
||||
email, ok := emails[event.TargetId]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
event.Meta["email"] = email
|
||||
|
||||
username, ok := names[event.TargetId]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
event.Meta["username"] = username
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toEventResponse(event *activity.Event) *api.Event {
|
||||
meta := make(map[string]string)
|
||||
if event.Meta != nil {
|
||||
@@ -60,13 +112,16 @@ func toEventResponse(event *activity.Event) *api.Event {
|
||||
meta[s] = fmt.Sprintf("%v", a)
|
||||
}
|
||||
}
|
||||
return &api.Event{
|
||||
Id: fmt.Sprint(event.ID),
|
||||
InitiatorId: event.InitiatorID,
|
||||
Activity: event.Activity.Message(),
|
||||
ActivityCode: api.EventActivityCode(event.Activity.StringCode()),
|
||||
TargetId: event.TargetID,
|
||||
Timestamp: event.Timestamp,
|
||||
Meta: meta,
|
||||
e := &api.Event{
|
||||
Id: fmt.Sprint(event.ID),
|
||||
InitiatorId: event.InitiatorID,
|
||||
InitiatorName: event.InitiatorName,
|
||||
InitiatorEmail: event.InitiatorEmail,
|
||||
Activity: event.Activity.Message(),
|
||||
ActivityCode: api.EventActivityCode(event.Activity.StringCode()),
|
||||
TargetId: event.TargetID,
|
||||
Timestamp: event.Timestamp,
|
||||
Meta: meta,
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ func initEventsTestData(account string, user *server.User, events ...*activity.E
|
||||
},
|
||||
}, user, nil
|
||||
},
|
||||
GetUsersFromAccountFunc: func(accountID, userID string) ([]*server.UserInfo, error) {
|
||||
return make([]*server.UserInfo, 0), nil
|
||||
},
|
||||
},
|
||||
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
||||
jwtclaims.WithFromRequestContext(func(r *http.Request) jwtclaims.AuthorizationClaims {
|
||||
|
||||
@@ -53,22 +53,6 @@ func initGroupTestData(user *server.User, groups ...*server.Group) *GroupsHandle
|
||||
Issued: server.GroupIssuedAPI,
|
||||
}, nil
|
||||
},
|
||||
UpdateGroupFunc: func(_ string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error) {
|
||||
var group server.Group
|
||||
group.ID = groupID
|
||||
for _, operation := range operations {
|
||||
switch operation.Type {
|
||||
case server.UpdateGroupName:
|
||||
group.Name = operation.Values[0]
|
||||
case server.UpdateGroupPeers, server.InsertPeersToGroup:
|
||||
group.Peers = operation.Values
|
||||
case server.RemovePeersFromGroup:
|
||||
default:
|
||||
return nil, fmt.Errorf("no operation")
|
||||
}
|
||||
}
|
||||
return &group, nil
|
||||
},
|
||||
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
||||
for _, peer := range TestPeers {
|
||||
if peer.IP.String() == peerIP {
|
||||
|
||||
@@ -88,31 +88,6 @@ func initNameserversTestData() *NameserversHandler {
|
||||
}
|
||||
return status.Errorf(status.NotFound, "nameserver group with ID %s was not found", nsGroupToSave.ID)
|
||||
},
|
||||
UpdateNameServerGroupFunc: func(accountID, nsGroupID, _ string, operations []server.NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error) {
|
||||
nsGroupToUpdate := baseExistingNSGroup.Copy()
|
||||
if nsGroupID != nsGroupToUpdate.ID {
|
||||
return nil, status.Errorf(status.NotFound, "nameserver group ID %s no longer exists", nsGroupID)
|
||||
}
|
||||
for _, operation := range operations {
|
||||
switch operation.Type {
|
||||
case server.UpdateNameServerGroupName:
|
||||
nsGroupToUpdate.Name = operation.Values[0]
|
||||
case server.UpdateNameServerGroupDescription:
|
||||
nsGroupToUpdate.Description = operation.Values[0]
|
||||
case server.UpdateNameServerGroupNameServers:
|
||||
var parsedNSList []nbdns.NameServer
|
||||
for _, nsURL := range operation.Values {
|
||||
parsed, err := nbdns.ParseNameServerURL(nsURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsedNSList = append(parsedNSList, parsed)
|
||||
}
|
||||
nsGroupToUpdate.NameServers = parsedNSList
|
||||
}
|
||||
}
|
||||
return nsGroupToUpdate, nil
|
||||
},
|
||||
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
return testingNSAccount, testingAccount.Users["test_user"], nil
|
||||
},
|
||||
|
||||
@@ -82,7 +82,33 @@ func (h *RoutesHandler) CreateRoute(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
newRoute, err := h.accountManager.CreateRoute(account.Id, newPrefix.String(), req.Peer, req.Description, req.NetworkId, req.Masquerade, req.Metric, req.Groups, req.Enabled, user.Id)
|
||||
peerId := ""
|
||||
if req.Peer != nil {
|
||||
peerId = *req.Peer
|
||||
}
|
||||
|
||||
peerGroupIds := []string{}
|
||||
if req.PeerGroups != nil {
|
||||
peerGroupIds = *req.PeerGroups
|
||||
}
|
||||
|
||||
if (peerId != "" && len(peerGroupIds) > 0) || (peerId == "" && len(peerGroupIds) == 0) {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "only one peer or peer_groups should be provided"), w)
|
||||
return
|
||||
}
|
||||
|
||||
// do not allow non Linux peers
|
||||
if peer := account.GetPeer(peerId); peer != nil {
|
||||
if peer.Meta.GoOS != "linux" {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "non-linux peers are non supported as network routes"), w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newRoute, err := h.accountManager.CreateRoute(
|
||||
account.Id, newPrefix.String(), peerId, peerGroupIds,
|
||||
req.Description, req.NetworkId, req.Masquerade, req.Metric, req.Groups, req.Enabled, user.Id,
|
||||
)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -135,19 +161,49 @@ func (h *RoutesHandler) UpdateRoute(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Peer != nil && req.PeerGroups != nil {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "only peer or peers_group should be provided"), w)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Peer == nil && req.PeerGroups == nil {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "either peer or peers_group should be provided"), w)
|
||||
return
|
||||
}
|
||||
|
||||
peerID := ""
|
||||
if req.Peer != nil {
|
||||
peerID = *req.Peer
|
||||
}
|
||||
|
||||
// do not allow non Linux peers
|
||||
if peer := account.GetPeer(peerID); peer != nil {
|
||||
if peer.Meta.GoOS != "linux" {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "non-linux peers are non supported as network routes"), w)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newRoute := &route.Route{
|
||||
ID: routeID,
|
||||
Network: newPrefix,
|
||||
NetID: req.NetworkId,
|
||||
NetworkType: prefixType,
|
||||
Masquerade: req.Masquerade,
|
||||
Peer: req.Peer,
|
||||
Metric: req.Metric,
|
||||
Description: req.Description,
|
||||
Enabled: req.Enabled,
|
||||
Groups: req.Groups,
|
||||
}
|
||||
|
||||
if req.Peer != nil {
|
||||
newRoute.Peer = peerID
|
||||
}
|
||||
|
||||
if req.PeerGroups != nil {
|
||||
newRoute.PeerGroups = *req.PeerGroups
|
||||
}
|
||||
|
||||
err = h.accountManager.SaveRoute(account.Id, user.Id, newRoute)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
@@ -208,16 +264,21 @@ func (h *RoutesHandler) GetRoute(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func toRouteResponse(serverRoute *route.Route) *api.Route {
|
||||
return &api.Route{
|
||||
route := &api.Route{
|
||||
Id: serverRoute.ID,
|
||||
Description: serverRoute.Description,
|
||||
NetworkId: serverRoute.NetID,
|
||||
Enabled: serverRoute.Enabled,
|
||||
Peer: serverRoute.Peer,
|
||||
Peer: &serverRoute.Peer,
|
||||
Network: serverRoute.Network.String(),
|
||||
NetworkType: serverRoute.NetworkType.String(),
|
||||
Masquerade: serverRoute.Masquerade,
|
||||
Metric: serverRoute.Metric,
|
||||
Groups: serverRoute.Groups,
|
||||
}
|
||||
|
||||
if len(serverRoute.PeerGroups) > 0 {
|
||||
route.PeerGroups = &serverRoute.PeerGroups
|
||||
}
|
||||
return route
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
@@ -24,16 +23,23 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
existingRouteID = "existingRouteID"
|
||||
notFoundRouteID = "notFoundRouteID"
|
||||
existingPeerIP = "100.64.0.100"
|
||||
existingPeerID = "peer-id"
|
||||
notFoundPeerID = "nonExistingPeer"
|
||||
existingPeerKey = "existingPeerKey"
|
||||
testAccountID = "test_id"
|
||||
existingGroupID = "testGroup"
|
||||
existingRouteID = "existingRouteID"
|
||||
existingRouteID2 = "existingRouteID2" // for peer_groups test
|
||||
notFoundRouteID = "notFoundRouteID"
|
||||
existingPeerIP1 = "100.64.0.100"
|
||||
existingPeerIP2 = "100.64.0.101"
|
||||
notFoundPeerID = "nonExistingPeer"
|
||||
existingPeerKey = "existingPeerKey"
|
||||
nonLinuxExistingPeerKey = "darwinExistingPeerKey"
|
||||
testAccountID = "test_id"
|
||||
existingGroupID = "testGroup"
|
||||
notFoundGroupID = "nonExistingGroup"
|
||||
)
|
||||
|
||||
var emptyString = ""
|
||||
var existingPeerID = "peer-id"
|
||||
var nonLinuxExistingPeerID = "darwin-peer-id"
|
||||
|
||||
var baseExistingRoute = &route.Route{
|
||||
ID: existingRouteID,
|
||||
Description: "base route",
|
||||
@@ -52,8 +58,19 @@ var testingAccount = &server.Account{
|
||||
Peers: map[string]*server.Peer{
|
||||
existingPeerID: {
|
||||
Key: existingPeerKey,
|
||||
IP: netip.MustParseAddr(existingPeerIP).AsSlice(),
|
||||
IP: netip.MustParseAddr(existingPeerIP1).AsSlice(),
|
||||
ID: existingPeerID,
|
||||
Meta: server.PeerSystemMeta{
|
||||
GoOS: "linux",
|
||||
},
|
||||
},
|
||||
nonLinuxExistingPeerID: {
|
||||
Key: nonLinuxExistingPeerID,
|
||||
IP: netip.MustParseAddr(existingPeerIP2).AsSlice(),
|
||||
ID: nonLinuxExistingPeerID,
|
||||
Meta: server.PeerSystemMeta{
|
||||
GoOS: "darwin",
|
||||
},
|
||||
},
|
||||
},
|
||||
Users: map[string]*server.User{
|
||||
@@ -68,17 +85,26 @@ func initRoutesTestData() *RoutesHandler {
|
||||
if routeID == existingRouteID {
|
||||
return baseExistingRoute, nil
|
||||
}
|
||||
if routeID == existingRouteID2 {
|
||||
route := baseExistingRoute.Copy()
|
||||
route.PeerGroups = []string{existingGroupID}
|
||||
return route, nil
|
||||
}
|
||||
return nil, status.Errorf(status.NotFound, "route with ID %s not found", routeID)
|
||||
},
|
||||
CreateRouteFunc: func(accountID string, network, peerID, description, netID string, masquerade bool, metric int, groups []string, enabled bool, _ string) (*route.Route, error) {
|
||||
CreateRouteFunc: func(accountID, network, peerID string, peerGroups []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, _ string) (*route.Route, error) {
|
||||
if peerID == notFoundPeerID {
|
||||
return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
||||
}
|
||||
if len(peerGroups) > 0 && peerGroups[0] == notFoundGroupID {
|
||||
return nil, status.Errorf(status.InvalidArgument, "peer groups with ID %s not found", peerGroups[0])
|
||||
}
|
||||
networkType, p, _ := route.ParseNetwork(network)
|
||||
return &route.Route{
|
||||
ID: existingRouteID,
|
||||
NetID: netID,
|
||||
Peer: peerID,
|
||||
PeerGroups: peerGroups,
|
||||
Network: p,
|
||||
NetworkType: networkType,
|
||||
Description: description,
|
||||
@@ -108,38 +134,6 @@ func initRoutesTestData() *RoutesHandler {
|
||||
IP: netip.MustParseAddr(existingPeerID).AsSlice(),
|
||||
}, nil
|
||||
},
|
||||
UpdateRouteFunc: func(_ string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error) {
|
||||
routeToUpdate := baseExistingRoute
|
||||
if routeID != routeToUpdate.ID {
|
||||
return nil, status.Errorf(status.NotFound, "route %s no longer exists", routeID)
|
||||
}
|
||||
for _, operation := range operations {
|
||||
switch operation.Type {
|
||||
case server.UpdateRouteNetwork:
|
||||
routeToUpdate.NetworkType, routeToUpdate.Network, _ = route.ParseNetwork(operation.Values[0])
|
||||
case server.UpdateRouteDescription:
|
||||
routeToUpdate.Description = operation.Values[0]
|
||||
case server.UpdateRouteNetworkIdentifier:
|
||||
routeToUpdate.NetID = operation.Values[0]
|
||||
case server.UpdateRoutePeer:
|
||||
routeToUpdate.Peer = operation.Values[0]
|
||||
if routeToUpdate.Peer == notFoundPeerID {
|
||||
return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", routeToUpdate.Peer)
|
||||
}
|
||||
case server.UpdateRouteMetric:
|
||||
routeToUpdate.Metric, _ = strconv.Atoi(operation.Values[0])
|
||||
case server.UpdateRouteMasquerade:
|
||||
routeToUpdate.Masquerade, _ = strconv.ParseBool(operation.Values[0])
|
||||
case server.UpdateRouteEnabled:
|
||||
routeToUpdate.Enabled, _ = strconv.ParseBool(operation.Values[0])
|
||||
case server.UpdateRouteGroups:
|
||||
routeToUpdate.Groups = operation.Values
|
||||
default:
|
||||
return nil, fmt.Errorf("no operation")
|
||||
}
|
||||
}
|
||||
return routeToUpdate, nil
|
||||
},
|
||||
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||
return testingAccount, testingAccount.Users["test_user"], nil
|
||||
},
|
||||
@@ -157,6 +151,9 @@ func initRoutesTestData() *RoutesHandler {
|
||||
}
|
||||
|
||||
func TestRoutesHandlers(t *testing.T) {
|
||||
baseExistingRouteWithPeerGroups := baseExistingRoute.Copy()
|
||||
baseExistingRouteWithPeerGroups.PeerGroups = []string{existingGroupID}
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
expectedStatus int
|
||||
@@ -180,6 +177,14 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
requestPath: "/api/routes/" + notFoundRouteID,
|
||||
expectedStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "Get Existing Route with Peer Groups",
|
||||
requestType: http.MethodGet,
|
||||
requestPath: "/api/routes/" + existingRouteID2,
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: true,
|
||||
expectedRoute: toRouteResponse(baseExistingRouteWithPeerGroups),
|
||||
},
|
||||
{
|
||||
name: "Delete Existing Route",
|
||||
requestType: http.MethodDelete,
|
||||
@@ -206,13 +211,21 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
Description: "Post",
|
||||
NetworkId: "awesomeNet",
|
||||
Network: "192.168.0.0/16",
|
||||
Peer: existingPeerID,
|
||||
Peer: &existingPeerID,
|
||||
NetworkType: route.IPv4NetworkString,
|
||||
Masquerade: false,
|
||||
Enabled: false,
|
||||
Groups: []string{existingGroupID},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "POST Non Linux Peer",
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/routes",
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", nonLinuxExistingPeerID, existingGroupID)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "POST Not Found Peer",
|
||||
requestType: http.MethodPost,
|
||||
@@ -237,6 +250,24 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "POST UnprocessableEntity when both peer and peer_groups are provided",
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/routes",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"peer\":\"%s\",\"peer_groups\":[\"%s\"],\"groups\":[\"%s\"]}", existingPeerID, existingGroupID, existingGroupID))),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "POST UnprocessableEntity when no peer and peer_groups are provided",
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/routes",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"groups\":[\"%s\"]}", existingPeerID))),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "PUT OK",
|
||||
requestType: http.MethodPut,
|
||||
@@ -249,7 +280,27 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
Description: "Post",
|
||||
NetworkId: "awesomeNet",
|
||||
Network: "192.168.0.0/16",
|
||||
Peer: existingPeerID,
|
||||
Peer: &existingPeerID,
|
||||
NetworkType: route.IPv4NetworkString,
|
||||
Masquerade: false,
|
||||
Enabled: false,
|
||||
Groups: []string{existingGroupID},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PUT OK when peer_groups provided",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/routes/" + existingRouteID,
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"peer_groups\":[\"%s\"],\"groups\":[\"%s\"]}", existingGroupID, existingGroupID)),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: true,
|
||||
expectedRoute: &api.Route{
|
||||
Id: existingRouteID,
|
||||
Description: "Post",
|
||||
NetworkId: "awesomeNet",
|
||||
Network: "192.168.0.0/16",
|
||||
Peer: &emptyString,
|
||||
PeerGroups: &[]string{existingGroupID},
|
||||
NetworkType: route.IPv4NetworkString,
|
||||
Masquerade: false,
|
||||
Enabled: false,
|
||||
@@ -272,6 +323,14 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "PUT Non Linux Peer",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/routes/" + existingRouteID,
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", nonLinuxExistingPeerID, existingGroupID)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "PUT Invalid Network Identifier",
|
||||
requestType: http.MethodPut,
|
||||
@@ -288,6 +347,24 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "PUT UnprocessableEntity when both peer and peer_groups are provided",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/routes/" + existingRouteID,
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"peer\":\"%s\",\"peer_groups\":[\"%s\"],\"groups\":[\"%s\"]}", existingPeerID, existingGroupID, existingGroupID))),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
{
|
||||
name: "PUT UnprocessableEntity when no peer and peer_groups are provided",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/routes/" + existingRouteID,
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"groups\":[\"%s\"]}", existingPeerID))),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
}
|
||||
|
||||
p := initRoutesTestData()
|
||||
|
||||
@@ -77,6 +77,7 @@ func WriteErrorResponse(errMsg string, httpStatus int, w http.ResponseWriter) {
|
||||
// WriteError converts an error to an JSON error response.
|
||||
// If it is known internal error of type server.Error then it sets the messages from the error, a generic message otherwise
|
||||
func WriteError(err error, w http.ResponseWriter) {
|
||||
log.Errorf("got a handler error: %s", err.Error())
|
||||
errStatus, ok := status.FromError(err)
|
||||
httpStatus := http.StatusInternalServerError
|
||||
msg := "internal server error"
|
||||
|
||||
@@ -513,7 +513,9 @@ func buildUserExportRequest() (string, error) {
|
||||
return string(str), nil
|
||||
}
|
||||
|
||||
func (am *Auth0Manager) createPostRequest(endpoint string, payloadStr string) (*http.Request, error) {
|
||||
func (am *Auth0Manager) createRequest(
|
||||
method string, endpoint string, body io.Reader,
|
||||
) (*http.Request, error) {
|
||||
jwtToken, err := am.credentials.Authenticate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -521,17 +523,23 @@ func (am *Auth0Manager) createPostRequest(endpoint string, payloadStr string) (*
|
||||
|
||||
reqURL := am.authIssuer + endpoint
|
||||
|
||||
payload := strings.NewReader(payloadStr)
|
||||
|
||||
req, err := http.NewRequest("POST", reqURL, payload)
|
||||
req, err := http.NewRequest(method, reqURL, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (am *Auth0Manager) createPostRequest(endpoint string, payloadStr string) (*http.Request, error) {
|
||||
req, err := am.createRequest("POST", endpoint, strings.NewReader(payloadStr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("content-type", "application/json")
|
||||
|
||||
return req, nil
|
||||
|
||||
}
|
||||
|
||||
// GetAllAccounts gets all registered accounts with corresponding user data.
|
||||
@@ -737,6 +745,38 @@ func (am *Auth0Manager) InviteUserByID(userID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteUser from Auth0
|
||||
func (am *Auth0Manager) DeleteUser(userID string) error {
|
||||
req, err := am.createRequest(http.MethodDelete, "/api/v2/users/"+url.QueryEscape(userID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := am.httpClient.Do(req)
|
||||
if err != nil {
|
||||
log.Debugf("execute delete request: %v", err)
|
||||
if am.appMetrics != nil {
|
||||
am.appMetrics.IDPMetrics().CountRequestError()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
log.Errorf("close delete request body: %v", err)
|
||||
}
|
||||
}()
|
||||
if resp.StatusCode != 204 {
|
||||
if am.appMetrics != nil {
|
||||
am.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||
}
|
||||
return fmt.Errorf("unable to delete user, statusCode %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkExportJobStatus checks the status of the job created at CreateExportUsersJob.
|
||||
// If the status is "completed", then return the downloadLink
|
||||
func (am *Auth0Manager) checkExportJobStatus(jobID string) (bool, string, error) {
|
||||
|
||||
@@ -12,9 +12,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/api/v3"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
)
|
||||
|
||||
// AuthentikManager authentik manager client instance.
|
||||
@@ -453,6 +454,38 @@ func (am *AuthentikManager) InviteUserByID(_ string) error {
|
||||
return fmt.Errorf("method InviteUserByID not implemented")
|
||||
}
|
||||
|
||||
// DeleteUser from Authentik
|
||||
func (am *AuthentikManager) DeleteUser(userID string) error {
|
||||
ctx, err := am.authenticationContext()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userPk, err := strconv.ParseInt(userID, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := am.apiClient.CoreApi.CoreUsersDestroy(ctx, int32(userPk)).Execute()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close() // nolint
|
||||
|
||||
if am.appMetrics != nil {
|
||||
am.appMetrics.IDPMetrics().CountDeleteUser()
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
if am.appMetrics != nil {
|
||||
am.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||
}
|
||||
return fmt.Errorf("unable to delete user %s, statusCode %d", userID, resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *AuthentikManager) authenticationContext() (context.Context, error) {
|
||||
jwtToken, err := am.credentials.Authenticate()
|
||||
if err != nil {
|
||||
|
||||
@@ -454,6 +454,43 @@ func (am *AzureManager) InviteUserByID(_ string) error {
|
||||
return fmt.Errorf("method InviteUserByID not implemented")
|
||||
}
|
||||
|
||||
// DeleteUser from Azure
|
||||
func (am *AzureManager) DeleteUser(userID string) error {
|
||||
jwtToken, err := am.credentials.Authenticate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s/users/%s", am.GraphAPIEndpoint, url.QueryEscape(userID))
|
||||
req, err := http.NewRequest(http.MethodDelete, reqURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
||||
req.Header.Add("content-type", "application/json")
|
||||
|
||||
log.Debugf("delete idp user %s", userID)
|
||||
|
||||
resp, err := am.httpClient.Do(req)
|
||||
if err != nil {
|
||||
if am.appMetrics != nil {
|
||||
am.appMetrics.IDPMetrics().CountRequestError()
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if am.appMetrics != nil {
|
||||
am.appMetrics.IDPMetrics().CountDeleteUser()
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return fmt.Errorf("unable to delete user, statusCode %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *AzureManager) getUserExtensions() ([]azureExtension, error) {
|
||||
q := url.Values{}
|
||||
q.Add("$select", extensionFields)
|
||||
|
||||
@@ -254,6 +254,19 @@ func (gm *GoogleWorkspaceManager) InviteUserByID(_ string) error {
|
||||
return fmt.Errorf("method InviteUserByID not implemented")
|
||||
}
|
||||
|
||||
// DeleteUser from GoogleWorkspace.
|
||||
func (gm *GoogleWorkspaceManager) DeleteUser(userID string) error {
|
||||
if err := gm.usersService.Delete(userID).Do(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gm.appMetrics != nil {
|
||||
gm.appMetrics.IDPMetrics().CountDeleteUser()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getGoogleCredentials retrieves Google credentials based on the provided serviceAccountKey.
|
||||
// It decodes the base64-encoded serviceAccountKey and attempts to obtain credentials using it.
|
||||
// If that fails, it falls back to using the default Google credentials path.
|
||||
|
||||
@@ -18,6 +18,7 @@ type Manager interface {
|
||||
CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error)
|
||||
GetUserByEmail(email string) ([]*UserData, error)
|
||||
InviteUserByID(userID string) error
|
||||
DeleteUser(userID string) error
|
||||
}
|
||||
|
||||
// ClientConfig defines common client configuration for all IdP manager
|
||||
@@ -37,10 +38,10 @@ type Config struct {
|
||||
ManagerType string
|
||||
ClientConfig *ClientConfig
|
||||
ExtraConfig ExtraConfig
|
||||
Auth0ClientCredentials Auth0ClientConfig
|
||||
AzureClientCredentials AzureClientConfig
|
||||
KeycloakClientCredentials KeycloakClientConfig
|
||||
ZitadelClientCredentials ZitadelClientConfig
|
||||
Auth0ClientCredentials *Auth0ClientConfig
|
||||
AzureClientCredentials *AzureClientConfig
|
||||
KeycloakClientCredentials *KeycloakClientConfig
|
||||
ZitadelClientCredentials *ZitadelClientConfig
|
||||
}
|
||||
|
||||
// ManagerCredentials interface that authenticates using the credential of each type of idp
|
||||
@@ -96,7 +97,7 @@ func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error)
|
||||
case "auth0":
|
||||
auth0ClientConfig := config.Auth0ClientCredentials
|
||||
if config.ClientConfig != nil {
|
||||
auth0ClientConfig = Auth0ClientConfig{
|
||||
auth0ClientConfig = &Auth0ClientConfig{
|
||||
Audience: config.ExtraConfig["Audience"],
|
||||
AuthIssuer: config.ClientConfig.Issuer,
|
||||
ClientID: config.ClientConfig.ClientID,
|
||||
@@ -105,11 +106,11 @@ func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error)
|
||||
}
|
||||
}
|
||||
|
||||
return NewAuth0Manager(auth0ClientConfig, appMetrics)
|
||||
return NewAuth0Manager(*auth0ClientConfig, appMetrics)
|
||||
case "azure":
|
||||
azureClientConfig := config.AzureClientCredentials
|
||||
if config.ClientConfig != nil {
|
||||
azureClientConfig = AzureClientConfig{
|
||||
azureClientConfig = &AzureClientConfig{
|
||||
ClientID: config.ClientConfig.ClientID,
|
||||
ClientSecret: config.ClientConfig.ClientSecret,
|
||||
GrantType: config.ClientConfig.GrantType,
|
||||
@@ -119,11 +120,11 @@ func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error)
|
||||
}
|
||||
}
|
||||
|
||||
return NewAzureManager(azureClientConfig, appMetrics)
|
||||
return NewAzureManager(*azureClientConfig, appMetrics)
|
||||
case "keycloak":
|
||||
keycloakClientConfig := config.KeycloakClientCredentials
|
||||
if config.ClientConfig != nil {
|
||||
keycloakClientConfig = KeycloakClientConfig{
|
||||
keycloakClientConfig = &KeycloakClientConfig{
|
||||
ClientID: config.ClientConfig.ClientID,
|
||||
ClientSecret: config.ClientConfig.ClientSecret,
|
||||
GrantType: config.ClientConfig.GrantType,
|
||||
@@ -132,11 +133,11 @@ func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error)
|
||||
}
|
||||
}
|
||||
|
||||
return NewKeycloakManager(keycloakClientConfig, appMetrics)
|
||||
return NewKeycloakManager(*keycloakClientConfig, appMetrics)
|
||||
case "zitadel":
|
||||
zitadelClientConfig := config.ZitadelClientCredentials
|
||||
if config.ClientConfig != nil {
|
||||
zitadelClientConfig = ZitadelClientConfig{
|
||||
zitadelClientConfig = &ZitadelClientConfig{
|
||||
ClientID: config.ClientConfig.ClientID,
|
||||
ClientSecret: config.ClientConfig.ClientSecret,
|
||||
GrantType: config.ClientConfig.GrantType,
|
||||
@@ -145,7 +146,7 @@ func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error)
|
||||
}
|
||||
}
|
||||
|
||||
return NewZitadelManager(zitadelClientConfig, appMetrics)
|
||||
return NewZitadelManager(*zitadelClientConfig, appMetrics)
|
||||
case "authentik":
|
||||
authentikConfig := AuthentikClientConfig{
|
||||
Issuer: config.ClientConfig.Issuer,
|
||||
|
||||
@@ -467,6 +467,47 @@ func (km *KeycloakManager) InviteUserByID(_ string) error {
|
||||
return fmt.Errorf("method InviteUserByID not implemented")
|
||||
}
|
||||
|
||||
// DeleteUser from Keycloack
|
||||
func (km *KeycloakManager) DeleteUser(userID string) error {
|
||||
jwtToken, err := km.credentials.Authenticate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s/users/%s", km.adminEndpoint, url.QueryEscape(userID))
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, reqURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
||||
req.Header.Add("content-type", "application/json")
|
||||
|
||||
if km.appMetrics != nil {
|
||||
km.appMetrics.IDPMetrics().CountDeleteUser()
|
||||
}
|
||||
|
||||
resp, err := km.httpClient.Do(req)
|
||||
if err != nil {
|
||||
if km.appMetrics != nil {
|
||||
km.appMetrics.IDPMetrics().CountRequestError()
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close() // nolint
|
||||
|
||||
// In the docs, they specified 200, but in the endpoints, they return 204
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
|
||||
if km.appMetrics != nil {
|
||||
km.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||
}
|
||||
|
||||
return fmt.Errorf("unable to delete user, statusCode %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildKeycloakCreateUserRequestPayload(email string, name string, appMetadata AppMetadata) (string, error) {
|
||||
attrs := keycloakUserAttributes{}
|
||||
attrs.Set(wtAccountID, appMetadata.WTAccountID)
|
||||
|
||||
@@ -319,6 +319,28 @@ func (om *OktaManager) InviteUserByID(_ string) error {
|
||||
return fmt.Errorf("method InviteUserByID not implemented")
|
||||
}
|
||||
|
||||
// DeleteUser from Okta
|
||||
func (om *OktaManager) DeleteUser(userID string) error {
|
||||
resp, err := om.client.User.DeactivateOrDeleteUser(context.Background(), userID, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if om.appMetrics != nil {
|
||||
om.appMetrics.IDPMetrics().CountDeleteUser()
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if om.appMetrics != nil {
|
||||
om.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||
}
|
||||
return fmt.Errorf("unable to delete user, statusCode %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateUserProfileSchema updates the Okta user schema to include custom fields,
|
||||
// wt_account_id and wt_pending_invite.
|
||||
func updateUserProfileSchema(client *okta.Client) error {
|
||||
|
||||
@@ -13,8 +13,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
)
|
||||
|
||||
// ZitadelManager zitadel manager client instance.
|
||||
@@ -447,6 +448,21 @@ func (zm *ZitadelManager) InviteUserByID(_ string) error {
|
||||
return fmt.Errorf("method InviteUserByID not implemented")
|
||||
}
|
||||
|
||||
// DeleteUser from Zitadel
|
||||
func (zm *ZitadelManager) DeleteUser(userID string) error {
|
||||
resource := fmt.Sprintf("users/%s", userID)
|
||||
if err := zm.delete(resource); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if zm.appMetrics != nil {
|
||||
zm.appMetrics.IDPMetrics().CountDeleteUser()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// getUserMetadata requests user metadata from zitadel via ID.
|
||||
func (zm *ZitadelManager) getUserMetadata(userID string) ([]zitadelMetadata, error) {
|
||||
resource := fmt.Sprintf("users/%s/metadata/_search", userID)
|
||||
@@ -500,6 +516,42 @@ func (zm *ZitadelManager) post(resource string, body string) ([]byte, error) {
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
// delete perform Delete requests.
|
||||
func (zm *ZitadelManager) delete(resource string) error {
|
||||
jwtToken, err := zm.credentials.Authenticate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s/%s", zm.managementEndpoint, resource)
|
||||
req, err := http.NewRequest(http.MethodDelete, reqURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
||||
req.Header.Add("content-type", "application/json")
|
||||
|
||||
resp, err := zm.httpClient.Do(req)
|
||||
if err != nil {
|
||||
if zm.appMetrics != nil {
|
||||
zm.appMetrics.IDPMetrics().CountRequestError()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
if zm.appMetrics != nil {
|
||||
zm.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||
}
|
||||
|
||||
return fmt.Errorf("unable to delete %s, statusCode %d", reqURL, resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// get perform Get requests.
|
||||
func (zm *ZitadelManager) get(resource string, q url.Values) ([]byte, error) {
|
||||
jwtToken, err := zm.credentials.Authenticate()
|
||||
|
||||
@@ -412,7 +412,7 @@ func startManagement(t *testing.T, config *Config) (*grpc.Server, string, error)
|
||||
peersUpdateManager := NewPeersUpdateManager()
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
eventStore, false)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
@@ -503,7 +503,7 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) {
|
||||
peersUpdateManager := server.NewPeersUpdateManager()
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
eventStore, false)
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating a manager: %v", err)
|
||||
}
|
||||
|
||||
@@ -31,11 +31,10 @@ type MockAccountManager struct {
|
||||
AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, *server.NetworkMap, error)
|
||||
GetGroupFunc func(accountID, groupID string) (*server.Group, error)
|
||||
SaveGroupFunc func(accountID, userID string, group *server.Group) error
|
||||
UpdateGroupFunc func(accountID string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error)
|
||||
DeleteGroupFunc func(accountID, userId, groupID string) error
|
||||
ListGroupsFunc func(accountID string) ([]*server.Group, error)
|
||||
GroupAddPeerFunc func(accountID, groupID, peerKey string) error
|
||||
GroupDeletePeerFunc func(accountID, groupID, peerKey string) error
|
||||
GroupAddPeerFunc func(accountID, groupID, peerID string) error
|
||||
GroupDeletePeerFunc func(accountID, groupID, peerID string) error
|
||||
GroupListPeersFunc func(accountID, groupID string) ([]*server.Peer, error)
|
||||
GetRuleFunc func(accountID, ruleID, userID string) (*server.Rule, error)
|
||||
SaveRuleFunc func(accountID, userID string, rule *server.Rule) error
|
||||
@@ -51,10 +50,9 @@ type MockAccountManager struct {
|
||||
UpdatePeerMetaFunc func(peerID string, meta server.PeerSystemMeta) error
|
||||
UpdatePeerSSHKeyFunc func(peerID string, sshKey string) error
|
||||
UpdatePeerFunc func(accountID, userID string, peer *server.Peer) (*server.Peer, error)
|
||||
CreateRouteFunc func(accountID string, prefix, peer, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error)
|
||||
CreateRouteFunc func(accountID, prefix, peer string, peerGroups []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error)
|
||||
GetRouteFunc func(accountID, routeID, userID string) (*route.Route, error)
|
||||
SaveRouteFunc func(accountID, userID string, route *route.Route) error
|
||||
UpdateRouteFunc func(accountID string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error)
|
||||
DeleteRouteFunc func(accountID, routeID, userID string) error
|
||||
ListRoutesFunc func(accountID, userID string) ([]*route.Route, error)
|
||||
SaveSetupKeyFunc func(accountID string, key *server.SetupKey, userID string) (*server.SetupKey, error)
|
||||
@@ -68,7 +66,6 @@ type MockAccountManager struct {
|
||||
GetNameServerGroupFunc func(accountID, nsGroupID string) (*nbdns.NameServerGroup, error)
|
||||
CreateNameServerGroupFunc func(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string) (*nbdns.NameServerGroup, error)
|
||||
SaveNameServerGroupFunc func(accountID, userID string, nsGroupToSave *nbdns.NameServerGroup) error
|
||||
UpdateNameServerGroupFunc func(accountID, nsGroupID, userID string, operations []server.NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error)
|
||||
DeleteNameServerGroupFunc func(accountID, nsGroupID, userID string) error
|
||||
ListNameServerGroupsFunc func(accountID string) ([]*nbdns.NameServerGroup, error)
|
||||
CreateUserFunc func(accountID, userID string, key *server.UserInfo) (*server.UserInfo, error)
|
||||
@@ -267,14 +264,6 @@ func (am *MockAccountManager) SaveGroup(accountID, userID string, group *server.
|
||||
return status.Errorf(codes.Unimplemented, "method SaveGroup is not implemented")
|
||||
}
|
||||
|
||||
// UpdateGroup mock implementation of UpdateGroup from server.AccountManager interface
|
||||
func (am *MockAccountManager) UpdateGroup(accountID string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error) {
|
||||
if am.UpdateGroupFunc != nil {
|
||||
return am.UpdateGroupFunc(accountID, groupID, operations)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateGroup not implemented")
|
||||
}
|
||||
|
||||
// DeleteGroup mock implementation of DeleteGroup from server.AccountManager interface
|
||||
func (am *MockAccountManager) DeleteGroup(accountId, userId, groupID string) error {
|
||||
if am.DeleteGroupFunc != nil {
|
||||
@@ -292,17 +281,17 @@ func (am *MockAccountManager) ListGroups(accountID string) ([]*server.Group, err
|
||||
}
|
||||
|
||||
// GroupAddPeer mock implementation of GroupAddPeer from server.AccountManager interface
|
||||
func (am *MockAccountManager) GroupAddPeer(accountID, groupID, peerKey string) error {
|
||||
func (am *MockAccountManager) GroupAddPeer(accountID, groupID, peerID string) error {
|
||||
if am.GroupAddPeerFunc != nil {
|
||||
return am.GroupAddPeerFunc(accountID, groupID, peerKey)
|
||||
return am.GroupAddPeerFunc(accountID, groupID, peerID)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method GroupAddPeer is not implemented")
|
||||
}
|
||||
|
||||
// GroupDeletePeer mock implementation of GroupDeletePeer from server.AccountManager interface
|
||||
func (am *MockAccountManager) GroupDeletePeer(accountID, groupID, peerKey string) error {
|
||||
func (am *MockAccountManager) GroupDeletePeer(accountID, groupID, peerID string) error {
|
||||
if am.GroupDeletePeerFunc != nil {
|
||||
return am.GroupDeletePeerFunc(accountID, groupID, peerKey)
|
||||
return am.GroupDeletePeerFunc(accountID, groupID, peerID)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method GroupDeletePeer is not implemented")
|
||||
}
|
||||
@@ -412,9 +401,9 @@ func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *server.
|
||||
}
|
||||
|
||||
// CreateRoute mock implementation of CreateRoute from server.AccountManager interface
|
||||
func (am *MockAccountManager) CreateRoute(accountID string, network, peerID, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) {
|
||||
func (am *MockAccountManager) CreateRoute(accountID, network, peerID string, peerGroups []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) {
|
||||
if am.CreateRouteFunc != nil {
|
||||
return am.CreateRouteFunc(accountID, network, peerID, description, netID, masquerade, metric, groups, enabled, userID)
|
||||
return am.CreateRouteFunc(accountID, network, peerID, peerGroups, description, netID, masquerade, metric, groups, enabled, userID)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreateRoute is not implemented")
|
||||
}
|
||||
@@ -435,14 +424,6 @@ func (am *MockAccountManager) SaveRoute(accountID, userID string, route *route.R
|
||||
return status.Errorf(codes.Unimplemented, "method SaveRoute is not implemented")
|
||||
}
|
||||
|
||||
// UpdateRoute mock implementation of UpdateRoute from server.AccountManager interface
|
||||
func (am *MockAccountManager) UpdateRoute(accountID, ruleID string, operations []server.RouteUpdateOperation) (*route.Route, error) {
|
||||
if am.UpdateRouteFunc != nil {
|
||||
return am.UpdateRouteFunc(accountID, ruleID, operations)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateRoute not implemented")
|
||||
}
|
||||
|
||||
// DeleteRoute mock implementation of DeleteRoute from server.AccountManager interface
|
||||
func (am *MockAccountManager) DeleteRoute(accountID, routeID, userID string) error {
|
||||
if am.DeleteRouteFunc != nil {
|
||||
@@ -533,14 +514,6 @@ func (am *MockAccountManager) SaveNameServerGroup(accountID, userID string, nsGr
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateNameServerGroup mocks UpdateNameServerGroup of the AccountManager interface
|
||||
func (am *MockAccountManager) UpdateNameServerGroup(accountID, nsGroupID, userID string, operations []server.NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error) {
|
||||
if am.UpdateNameServerGroupFunc != nil {
|
||||
return am.UpdateNameServerGroupFunc(accountID, nsGroupID, userID, operations)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DeleteNameServerGroup mocks DeleteNameServerGroup of the AccountManager interface
|
||||
func (am *MockAccountManager) DeleteNameServerGroup(accountID, nsGroupID, userID string) error {
|
||||
if am.DeleteNameServerGroupFunc != nil {
|
||||
|
||||
@@ -3,7 +3,6 @@ package server
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
@@ -15,54 +14,7 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
)
|
||||
|
||||
const (
|
||||
// UpdateNameServerGroupName indicates a nameserver group name update operation
|
||||
UpdateNameServerGroupName NameServerGroupUpdateOperationType = iota
|
||||
// UpdateNameServerGroupDescription indicates a nameserver group description update operation
|
||||
UpdateNameServerGroupDescription
|
||||
// UpdateNameServerGroupNameServers indicates a nameserver group nameservers list update operation
|
||||
UpdateNameServerGroupNameServers
|
||||
// UpdateNameServerGroupGroups indicates a nameserver group' groups update operation
|
||||
UpdateNameServerGroupGroups
|
||||
// UpdateNameServerGroupEnabled indicates a nameserver group status update operation
|
||||
UpdateNameServerGroupEnabled
|
||||
// UpdateNameServerGroupPrimary indicates a nameserver group primary status update operation
|
||||
UpdateNameServerGroupPrimary
|
||||
// UpdateNameServerGroupDomains indicates a nameserver group' domains update operation
|
||||
UpdateNameServerGroupDomains
|
||||
|
||||
domainPattern = `^(?i)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$`
|
||||
)
|
||||
|
||||
// NameServerGroupUpdateOperationType operation type
|
||||
type NameServerGroupUpdateOperationType int
|
||||
|
||||
func (t NameServerGroupUpdateOperationType) String() string {
|
||||
switch t {
|
||||
case UpdateNameServerGroupDescription:
|
||||
return "UpdateNameServerGroupDescription"
|
||||
case UpdateNameServerGroupName:
|
||||
return "UpdateNameServerGroupName"
|
||||
case UpdateNameServerGroupNameServers:
|
||||
return "UpdateNameServerGroupNameServers"
|
||||
case UpdateNameServerGroupGroups:
|
||||
return "UpdateNameServerGroupGroups"
|
||||
case UpdateNameServerGroupEnabled:
|
||||
return "UpdateNameServerGroupEnabled"
|
||||
case UpdateNameServerGroupPrimary:
|
||||
return "UpdateNameServerGroupPrimary"
|
||||
case UpdateNameServerGroupDomains:
|
||||
return "UpdateNameServerGroupDomains"
|
||||
default:
|
||||
return "InvalidOperation"
|
||||
}
|
||||
}
|
||||
|
||||
// NameServerGroupUpdateOperation operation object with type and values to be applied
|
||||
type NameServerGroupUpdateOperation struct {
|
||||
Type NameServerGroupUpdateOperationType
|
||||
Values []string
|
||||
}
|
||||
const domainPattern = `^(?i)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$`
|
||||
|
||||
// GetNameServerGroup gets a nameserver group object from account and nameserver group IDs
|
||||
func (am *DefaultAccountManager) GetNameServerGroup(accountID, nsGroupID string) (*nbdns.NameServerGroup, error) {
|
||||
@@ -172,109 +124,6 @@ func (am *DefaultAccountManager) SaveNameServerGroup(accountID, userID string, n
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateNameServerGroup updates existing nameserver group with set of operations
|
||||
func (am *DefaultAccountManager) UpdateNameServerGroup(accountID, nsGroupID, userID string, operations []NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error) {
|
||||
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(operations) == 0 {
|
||||
return nil, status.Errorf(status.InvalidArgument, "operations shouldn't be empty")
|
||||
}
|
||||
|
||||
nsGroupToUpdate, ok := account.NameServerGroups[nsGroupID]
|
||||
if !ok {
|
||||
return nil, status.Errorf(status.NotFound, "nameserver group ID %s no longer exists", nsGroupID)
|
||||
}
|
||||
|
||||
newNSGroup := nsGroupToUpdate.Copy()
|
||||
|
||||
for _, operation := range operations {
|
||||
valuesCount := len(operation.Values)
|
||||
if valuesCount < 1 {
|
||||
return nil, status.Errorf(status.InvalidArgument, "operation %s contains invalid number of values, it should be at least 1", operation.Type.String())
|
||||
}
|
||||
|
||||
for _, value := range operation.Values {
|
||||
if value == "" {
|
||||
return nil, status.Errorf(status.InvalidArgument, "operation %s contains invalid empty string value", operation.Type.String())
|
||||
}
|
||||
}
|
||||
switch operation.Type {
|
||||
case UpdateNameServerGroupDescription:
|
||||
newNSGroup.Description = operation.Values[0]
|
||||
case UpdateNameServerGroupName:
|
||||
if valuesCount > 1 {
|
||||
return nil, status.Errorf(status.InvalidArgument, "failed to parse name values, expected 1 value got %d", valuesCount)
|
||||
}
|
||||
err = validateNSGroupName(operation.Values[0], nsGroupID, account.NameServerGroups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newNSGroup.Name = operation.Values[0]
|
||||
case UpdateNameServerGroupNameServers:
|
||||
var nsList []nbdns.NameServer
|
||||
for _, url := range operation.Values {
|
||||
ns, err := nbdns.ParseNameServerURL(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nsList = append(nsList, ns)
|
||||
}
|
||||
err = validateNSList(nsList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newNSGroup.NameServers = nsList
|
||||
case UpdateNameServerGroupGroups:
|
||||
err = validateGroups(operation.Values, account.Groups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newNSGroup.Groups = operation.Values
|
||||
case UpdateNameServerGroupEnabled:
|
||||
enabled, err := strconv.ParseBool(operation.Values[0])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(status.InvalidArgument, "failed to parse enabled %s, not boolean", operation.Values[0])
|
||||
}
|
||||
newNSGroup.Enabled = enabled
|
||||
case UpdateNameServerGroupPrimary:
|
||||
primary, err := strconv.ParseBool(operation.Values[0])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(status.InvalidArgument, "failed to parse primary status %s, not boolean", operation.Values[0])
|
||||
}
|
||||
newNSGroup.Primary = primary
|
||||
case UpdateNameServerGroupDomains:
|
||||
err = validateDomainInput(false, operation.Values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newNSGroup.Domains = operation.Values
|
||||
}
|
||||
}
|
||||
|
||||
account.NameServerGroups[nsGroupID] = newNSGroup
|
||||
|
||||
account.Network.IncSerial()
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = am.updateAccountPeers(account)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return newNSGroup.Copy(), status.Errorf(status.Internal, "failed to update peers after update nameserver %s", newNSGroup.Name)
|
||||
}
|
||||
|
||||
return newNSGroup.Copy(), nil
|
||||
}
|
||||
|
||||
// DeleteNameServerGroup deletes nameserver group with nsGroupID
|
||||
func (am *DefaultAccountManager) DeleteNameServerGroup(accountID, nsGroupID, userID string) error {
|
||||
|
||||
|
||||
@@ -655,323 +655,6 @@ func TestSaveNameServerGroup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateNameServerGroup(t *testing.T) {
|
||||
nsGroupID := "testingNSGroup"
|
||||
|
||||
existingNSGroup := &nbdns.NameServerGroup{
|
||||
ID: nsGroupID,
|
||||
Name: "super",
|
||||
Description: "super",
|
||||
Primary: true,
|
||||
NameServers: []nbdns.NameServer{
|
||||
{
|
||||
IP: netip.MustParseAddr("1.1.1.1"),
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
{
|
||||
IP: netip.MustParseAddr("1.1.2.2"),
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
},
|
||||
Groups: []string{group1ID},
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
existingNSGroup *nbdns.NameServerGroup
|
||||
nsGroupID string
|
||||
operations []NameServerGroupUpdateOperation
|
||||
shouldCreate bool
|
||||
errFunc require.ErrorAssertionFunc
|
||||
expectedNSGroup *nbdns.NameServerGroup
|
||||
}{
|
||||
{
|
||||
name: "Should Config Single Property",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupName,
|
||||
Values: []string{"superNew"},
|
||||
},
|
||||
},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
expectedNSGroup: &nbdns.NameServerGroup{
|
||||
ID: nsGroupID,
|
||||
Name: "superNew",
|
||||
Description: "super",
|
||||
Primary: true,
|
||||
NameServers: []nbdns.NameServer{
|
||||
{
|
||||
IP: netip.MustParseAddr("1.1.1.1"),
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
{
|
||||
IP: netip.MustParseAddr("1.1.2.2"),
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
},
|
||||
Groups: []string{group1ID},
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should Config Multiple Properties",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupName,
|
||||
Values: []string{"superNew"},
|
||||
},
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupDescription,
|
||||
Values: []string{"superDescription"},
|
||||
},
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupNameServers,
|
||||
Values: []string{"udp://127.0.0.1:53", "udp://8.8.8.8:53"},
|
||||
},
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupGroups,
|
||||
Values: []string{group1ID, group2ID},
|
||||
},
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupEnabled,
|
||||
Values: []string{"false"},
|
||||
},
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupPrimary,
|
||||
Values: []string{"false"},
|
||||
},
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupDomains,
|
||||
Values: []string{validDomain},
|
||||
},
|
||||
},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
expectedNSGroup: &nbdns.NameServerGroup{
|
||||
ID: nsGroupID,
|
||||
Name: "superNew",
|
||||
Description: "superDescription",
|
||||
Primary: false,
|
||||
Domains: []string{validDomain},
|
||||
NameServers: []nbdns.NameServer{
|
||||
{
|
||||
IP: netip.MustParseAddr("127.0.0.1"),
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
{
|
||||
IP: netip.MustParseAddr("8.8.8.8"),
|
||||
NSType: nbdns.UDPNameServerType,
|
||||
Port: nbdns.DefaultDNSPort,
|
||||
},
|
||||
},
|
||||
Groups: []string{group1ID, group2ID},
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Invalid ID",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: "nonExistingNSGroup",
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Empty Operations",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Empty Values",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupName,
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Empty String",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupName,
|
||||
Values: []string{""},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Invalid Name Large String",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupName,
|
||||
Values: []string{"12345678901234567890qwertyuiopqwertyuiop1"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Invalid On Existing Name",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupName,
|
||||
Values: []string{existingNSGroupName},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Invalid On Multiple Name Values",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupName,
|
||||
Values: []string{"nameOne", "nameTwo"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Invalid Boolean",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupEnabled,
|
||||
Values: []string{"yes"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Invalid Nameservers Wrong Schema",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupNameServers,
|
||||
Values: []string{"https://127.0.0.1:53"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Invalid Nameservers Wrong IP",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupNameServers,
|
||||
Values: []string{"udp://8.8.8.300:53"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Large Number Of Nameservers",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupNameServers,
|
||||
Values: []string{"udp://127.0.0.1:53", "udp://8.8.8.8:53", "udp://8.8.4.4:53"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Invalid GroupID",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupGroups,
|
||||
Values: []string{"nonExistingGroupID"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Invalid Domains",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupDomains,
|
||||
Values: []string{invalidDomain},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Should Not Config On Invalid Primary Status",
|
||||
existingNSGroup: existingNSGroup,
|
||||
nsGroupID: existingNSGroup.ID,
|
||||
operations: []NameServerGroupUpdateOperation{
|
||||
NameServerGroupUpdateOperation{
|
||||
Type: UpdateNameServerGroupPrimary,
|
||||
Values: []string{"yes"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
am, err := createNSManager(t)
|
||||
if err != nil {
|
||||
t.Error("failed to create account manager")
|
||||
}
|
||||
|
||||
account, err := initTestNSAccount(t, am)
|
||||
if err != nil {
|
||||
t.Error("failed to init testing account")
|
||||
}
|
||||
|
||||
account.NameServerGroups[testCase.existingNSGroup.ID] = testCase.existingNSGroup
|
||||
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
t.Error("account should be saved")
|
||||
}
|
||||
|
||||
updatedRoute, err := am.UpdateNameServerGroup(account.Id, testCase.nsGroupID, userID, testCase.operations)
|
||||
testCase.errFunc(t, err)
|
||||
|
||||
if !testCase.shouldCreate {
|
||||
return
|
||||
}
|
||||
|
||||
testCase.expectedNSGroup.ID = updatedRoute.ID
|
||||
|
||||
if !testCase.expectedNSGroup.IsEqual(updatedRoute) {
|
||||
t.Errorf("new nameserver group didn't match expected group:\nGot %#v\nExpected:%#v\n", updatedRoute, testCase.expectedNSGroup)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteNameServerGroup(t *testing.T) {
|
||||
nsGroupID := "testingNSGroup"
|
||||
|
||||
@@ -1061,7 +744,7 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
return nil, err
|
||||
}
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore)
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore, false)
|
||||
}
|
||||
|
||||
func createNSStore(t *testing.T) (Store, error) {
|
||||
|
||||
@@ -2,7 +2,6 @@ package server
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/netbirdio/netbird/management/proto"
|
||||
@@ -13,57 +12,6 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// UpdateRouteDescription indicates a route description update operation
|
||||
UpdateRouteDescription RouteUpdateOperationType = iota
|
||||
// UpdateRouteNetwork indicates a route IP update operation
|
||||
UpdateRouteNetwork
|
||||
// UpdateRoutePeer indicates a route peer update operation
|
||||
UpdateRoutePeer
|
||||
// UpdateRouteMetric indicates a route metric update operation
|
||||
UpdateRouteMetric
|
||||
// UpdateRouteMasquerade indicates a route masquerade update operation
|
||||
UpdateRouteMasquerade
|
||||
// UpdateRouteEnabled indicates a route enabled update operation
|
||||
UpdateRouteEnabled
|
||||
// UpdateRouteNetworkIdentifier indicates a route net ID update operation
|
||||
UpdateRouteNetworkIdentifier
|
||||
// UpdateRouteGroups indicates a group list update operation
|
||||
UpdateRouteGroups
|
||||
)
|
||||
|
||||
// RouteUpdateOperationType operation type
|
||||
type RouteUpdateOperationType int
|
||||
|
||||
func (t RouteUpdateOperationType) String() string {
|
||||
switch t {
|
||||
case UpdateRouteDescription:
|
||||
return "UpdateRouteDescription"
|
||||
case UpdateRouteNetwork:
|
||||
return "UpdateRouteNetwork"
|
||||
case UpdateRoutePeer:
|
||||
return "UpdateRoutePeer"
|
||||
case UpdateRouteMetric:
|
||||
return "UpdateRouteMetric"
|
||||
case UpdateRouteMasquerade:
|
||||
return "UpdateRouteMasquerade"
|
||||
case UpdateRouteEnabled:
|
||||
return "UpdateRouteEnabled"
|
||||
case UpdateRouteNetworkIdentifier:
|
||||
return "UpdateRouteNetworkIdentifier"
|
||||
case UpdateRouteGroups:
|
||||
return "UpdateRouteGroups"
|
||||
default:
|
||||
return "InvalidOperation"
|
||||
}
|
||||
}
|
||||
|
||||
// RouteUpdateOperation operation object with type and values to be applied
|
||||
type RouteUpdateOperation struct {
|
||||
Type RouteUpdateOperationType
|
||||
Values []string
|
||||
}
|
||||
|
||||
// GetRoute gets a route object from account and route IDs
|
||||
func (am *DefaultAccountManager) GetRoute(accountID, routeID, userID string) (*route.Route, error) {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
@@ -91,30 +39,82 @@ func (am *DefaultAccountManager) GetRoute(accountID, routeID, userID string) (*r
|
||||
return nil, status.Errorf(status.NotFound, "route with ID %s not found", routeID)
|
||||
}
|
||||
|
||||
// checkPrefixPeerExists checks the combination of prefix and peer id, if it exists returns an error, otherwise returns nil
|
||||
func (am *DefaultAccountManager) checkPrefixPeerExists(accountID, peerID string, prefix netip.Prefix) error {
|
||||
|
||||
if peerID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// checkRoutePrefixExistsForPeers checks if a route with a given prefix exists for a single peer or multiple peer groups.
|
||||
func (am *DefaultAccountManager) checkRoutePrefixExistsForPeers(account *Account, peerID, routeID string, peerGroupIDs []string, prefix netip.Prefix) error {
|
||||
// routes can have both peer and peer_groups
|
||||
routesWithPrefix := account.GetRoutesByPrefix(prefix)
|
||||
|
||||
// lets remember all the peers and the peer groups from routesWithPrefix
|
||||
seenPeers := make(map[string]bool)
|
||||
seenPeerGroups := make(map[string]bool)
|
||||
|
||||
for _, prefixRoute := range routesWithPrefix {
|
||||
if prefixRoute.Peer == peerID {
|
||||
return status.Errorf(status.AlreadyExists, "failed to add route with prefix %s - peer already has this route", prefix.String())
|
||||
// we skip route(s) with the same network ID as we want to allow updating of the existing route
|
||||
// when create a new route routeID is newly generated so nothing will be skipped
|
||||
if routeID == prefixRoute.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
if prefixRoute.Peer != "" {
|
||||
seenPeers[prefixRoute.ID] = true
|
||||
}
|
||||
for _, groupID := range prefixRoute.PeerGroups {
|
||||
seenPeerGroups[groupID] = true
|
||||
|
||||
group := account.GetGroup(groupID)
|
||||
if group == nil {
|
||||
return status.Errorf(
|
||||
status.InvalidArgument, "failed to add route with prefix %s - peer group %s doesn't exist",
|
||||
prefix.String(), groupID)
|
||||
}
|
||||
|
||||
for _, pID := range group.Peers {
|
||||
seenPeers[pID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if peerID != "" {
|
||||
// check that peerID exists and is not in any route as single peer or part of the group
|
||||
peer := account.GetPeer(peerID)
|
||||
if peer == nil {
|
||||
return status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
||||
}
|
||||
if _, ok := seenPeers[peerID]; ok {
|
||||
return status.Errorf(status.AlreadyExists,
|
||||
"failed to add route with prefix %s - peer %s already has this route", prefix.String(), peerID)
|
||||
}
|
||||
}
|
||||
|
||||
// check that peerGroupIDs are not in any route peerGroups list
|
||||
for _, groupID := range peerGroupIDs {
|
||||
group := account.GetGroup(groupID) // we validated the group existent before entering this function, o need to check again.
|
||||
|
||||
if _, ok := seenPeerGroups[groupID]; ok {
|
||||
return status.Errorf(
|
||||
status.AlreadyExists, "failed to add route with prefix %s - peer group %s already has this route",
|
||||
prefix.String(), group.Name)
|
||||
}
|
||||
|
||||
// check that the peers from peerGroupIDs groups are not the same peers we saw in routesWithPrefix
|
||||
for _, id := range group.Peers {
|
||||
if _, ok := seenPeers[id]; ok {
|
||||
peer := account.GetPeer(peerID)
|
||||
if peer == nil {
|
||||
return status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
||||
}
|
||||
return status.Errorf(status.AlreadyExists,
|
||||
"failed to add route with prefix %s - peer %s from the group %s already has this route",
|
||||
prefix.String(), peer.Name, group.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateRoute creates and saves a new route
|
||||
func (am *DefaultAccountManager) CreateRoute(accountID string, network, peerID, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) {
|
||||
func (am *DefaultAccountManager) CreateRoute(accountID, network, peerID string, peerGroupIDs []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
@@ -123,19 +123,29 @@ func (am *DefaultAccountManager) CreateRoute(accountID string, network, peerID,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if peerID != "" {
|
||||
peer := account.GetPeer(peerID)
|
||||
if peer == nil {
|
||||
return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
||||
}
|
||||
if peerID != "" && len(peerGroupIDs) != 0 {
|
||||
return nil, status.Errorf(
|
||||
status.InvalidArgument,
|
||||
"peer with ID %s and peers group %s should not be provided at the same time",
|
||||
peerID, peerGroupIDs)
|
||||
}
|
||||
|
||||
var newRoute route.Route
|
||||
newRoute.ID = xid.New().String()
|
||||
|
||||
prefixType, newPrefix, err := route.ParseNetwork(network)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(status.InvalidArgument, "failed to parse IP %s", network)
|
||||
}
|
||||
err = am.checkPrefixPeerExists(accountID, peerID, newPrefix)
|
||||
|
||||
if len(peerGroupIDs) > 0 {
|
||||
err = validateGroups(peerGroupIDs, account.Groups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = am.checkRoutePrefixExistsForPeers(account, peerID, newRoute.ID, peerGroupIDs, newPrefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -154,7 +164,7 @@ func (am *DefaultAccountManager) CreateRoute(accountID string, network, peerID,
|
||||
}
|
||||
|
||||
newRoute.Peer = peerID
|
||||
newRoute.ID = xid.New().String()
|
||||
newRoute.PeerGroups = peerGroupIDs
|
||||
newRoute.Network = newPrefix
|
||||
newRoute.NetworkType = prefixType
|
||||
newRoute.Description = description
|
||||
@@ -212,13 +222,22 @@ func (am *DefaultAccountManager) SaveRoute(accountID, userID string, routeToSave
|
||||
return err
|
||||
}
|
||||
|
||||
if routeToSave.Peer != "" {
|
||||
peer := account.GetPeer(routeToSave.Peer)
|
||||
if peer == nil {
|
||||
return status.Errorf(status.InvalidArgument, "peer with ID %s not found", routeToSave.Peer)
|
||||
if routeToSave.Peer != "" && len(routeToSave.PeerGroups) != 0 {
|
||||
return status.Errorf(status.InvalidArgument, "peer with ID and peer groups should not be provided at the same time")
|
||||
}
|
||||
|
||||
if len(routeToSave.PeerGroups) > 0 {
|
||||
err = validateGroups(routeToSave.PeerGroups, account.Groups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = am.checkRoutePrefixExistsForPeers(account, routeToSave.Peer, routeToSave.ID, routeToSave.Copy().PeerGroups, routeToSave.Network)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = validateGroups(routeToSave.Groups, account.Groups)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -241,109 +260,6 @@ func (am *DefaultAccountManager) SaveRoute(accountID, userID string, routeToSave
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRoute updates existing route with set of operations
|
||||
func (am *DefaultAccountManager) UpdateRoute(accountID, routeID string, operations []RouteUpdateOperation) (*route.Route, error) {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
account, err := am.Store.GetAccount(accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routeToUpdate, ok := account.Routes[routeID]
|
||||
if !ok {
|
||||
return nil, status.Errorf(status.NotFound, "route %s no longer exists", routeID)
|
||||
}
|
||||
|
||||
newRoute := routeToUpdate.Copy()
|
||||
|
||||
for _, operation := range operations {
|
||||
|
||||
if len(operation.Values) != 1 {
|
||||
return nil, status.Errorf(status.InvalidArgument, "operation %s contains invalid number of values, it should be 1", operation.Type.String())
|
||||
}
|
||||
|
||||
switch operation.Type {
|
||||
case UpdateRouteDescription:
|
||||
newRoute.Description = operation.Values[0]
|
||||
case UpdateRouteNetworkIdentifier:
|
||||
if utf8.RuneCountInString(operation.Values[0]) > route.MaxNetIDChar || operation.Values[0] == "" {
|
||||
return nil, status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d", route.MaxNetIDChar)
|
||||
}
|
||||
newRoute.NetID = operation.Values[0]
|
||||
case UpdateRouteNetwork:
|
||||
prefixType, prefix, err := route.ParseNetwork(operation.Values[0])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(status.InvalidArgument, "failed to parse IP %s", operation.Values[0])
|
||||
}
|
||||
err = am.checkPrefixPeerExists(accountID, routeToUpdate.Peer, prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoute.Network = prefix
|
||||
newRoute.NetworkType = prefixType
|
||||
case UpdateRoutePeer:
|
||||
if operation.Values[0] != "" {
|
||||
peer := account.GetPeer(operation.Values[0])
|
||||
if peer == nil {
|
||||
return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", operation.Values[0])
|
||||
}
|
||||
}
|
||||
|
||||
err = am.checkPrefixPeerExists(accountID, operation.Values[0], routeToUpdate.Network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoute.Peer = operation.Values[0]
|
||||
case UpdateRouteMetric:
|
||||
metric, err := strconv.Atoi(operation.Values[0])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(status.InvalidArgument, "failed to parse metric %s, not int", operation.Values[0])
|
||||
}
|
||||
if metric < route.MinMetric || metric > route.MaxMetric {
|
||||
return nil, status.Errorf(status.InvalidArgument, "failed to parse metric %s, value should be %d > N < %d",
|
||||
operation.Values[0],
|
||||
route.MinMetric,
|
||||
route.MaxMetric,
|
||||
)
|
||||
}
|
||||
newRoute.Metric = metric
|
||||
case UpdateRouteMasquerade:
|
||||
masquerade, err := strconv.ParseBool(operation.Values[0])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(status.InvalidArgument, "failed to parse masquerade %s, not boolean", operation.Values[0])
|
||||
}
|
||||
newRoute.Masquerade = masquerade
|
||||
case UpdateRouteEnabled:
|
||||
enabled, err := strconv.ParseBool(operation.Values[0])
|
||||
if err != nil {
|
||||
return nil, status.Errorf(status.InvalidArgument, "failed to parse enabled %s, not boolean", operation.Values[0])
|
||||
}
|
||||
newRoute.Enabled = enabled
|
||||
case UpdateRouteGroups:
|
||||
err = validateGroups(operation.Values, account.Groups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoute.Groups = operation.Values
|
||||
}
|
||||
}
|
||||
|
||||
account.Routes[routeID] = newRoute
|
||||
|
||||
account.Network.IncSerial()
|
||||
if err = am.Store.SaveAccount(account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = am.updateAccountPeers(account)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(status.Internal, "failed to update account peers")
|
||||
}
|
||||
return newRoute, nil
|
||||
}
|
||||
|
||||
// DeleteRoute deletes route with routeID
|
||||
func (am *DefaultAccountManager) DeleteRoute(accountID, routeID, userID string) error {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
|
||||
@@ -14,24 +14,37 @@ import (
|
||||
const (
|
||||
peer1Key = "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8="
|
||||
peer2Key = "/yF0+vCfv+mRR5k0dca0TrGdO/oiNeAI58gToZm5NyI="
|
||||
peer3Key = "ayF0+vCfv+mRR5k0dca0TrGdO/oiNeAI58gToZm5NaF="
|
||||
peer4Key = "ayF0+vCfv+mRR5k0dca0TrGdO/oiNeAI58gToZm5acc="
|
||||
peer5Key = "ayF0+vCfv+mRR5k0dca0TrGdO/oiNeAI58gToZm5a55="
|
||||
peer1ID = "peer-1-id"
|
||||
peer2ID = "peer-2-id"
|
||||
peer3ID = "peer-3-id"
|
||||
peer4ID = "peer-4-id"
|
||||
peer5ID = "peer-5-id"
|
||||
routeGroup1 = "routeGroup1"
|
||||
routeGroup2 = "routeGroup2"
|
||||
routeGroup3 = "routeGroup3" // for existing route
|
||||
routeGroup4 = "routeGroup4" // for existing route
|
||||
routeGroupHA1 = "routeGroupHA1"
|
||||
routeGroupHA2 = "routeGroupHA2"
|
||||
routeInvalidGroup1 = "routeInvalidGroup1"
|
||||
userID = "testingUser"
|
||||
existingNetwork = "10.10.10.0/24"
|
||||
existingRouteID = "random-id"
|
||||
)
|
||||
|
||||
func TestCreateRoute(t *testing.T) {
|
||||
type input struct {
|
||||
network string
|
||||
netID string
|
||||
peerKey string
|
||||
description string
|
||||
masquerade bool
|
||||
metric int
|
||||
enabled bool
|
||||
groups []string
|
||||
network string
|
||||
netID string
|
||||
peerKey string
|
||||
peerGroupIDs []string
|
||||
description string
|
||||
masquerade bool
|
||||
metric int
|
||||
enabled bool
|
||||
groups []string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
@@ -67,6 +80,48 @@ func TestCreateRoute(t *testing.T) {
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Happy Path Peer Groups",
|
||||
inputArgs: input{
|
||||
network: "192.168.0.0/16",
|
||||
netID: "happy",
|
||||
peerGroupIDs: []string{routeGroupHA1, routeGroupHA2},
|
||||
description: "super",
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1, routeGroup2},
|
||||
},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
expectedRoute: &route.Route{
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
NetworkType: route.IPv4Network,
|
||||
NetID: "happy",
|
||||
PeerGroups: []string{routeGroupHA1, routeGroupHA2},
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1, routeGroup2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Both peer and peer_groups Provided Should Fail",
|
||||
inputArgs: input{
|
||||
network: "192.168.0.0/16",
|
||||
netID: "happy",
|
||||
peerKey: peer1ID,
|
||||
peerGroupIDs: []string{routeGroupHA1},
|
||||
description: "super",
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Bad Prefix Should Fail",
|
||||
inputArgs: input{
|
||||
@@ -97,6 +152,36 @@ func TestCreateRoute(t *testing.T) {
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Bad Peer already has this route",
|
||||
inputArgs: input{
|
||||
network: existingNetwork,
|
||||
netID: "bad",
|
||||
peerKey: peer5ID,
|
||||
description: "super",
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Bad Peers Group already has this route",
|
||||
inputArgs: input{
|
||||
network: existingNetwork,
|
||||
netID: "bad",
|
||||
peerGroupIDs: []string{routeGroup1, routeGroup3},
|
||||
description: "super",
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Empty Peer Should Create",
|
||||
inputArgs: input{
|
||||
@@ -238,13 +323,14 @@ func TestCreateRoute(t *testing.T) {
|
||||
|
||||
account, err := initTestRouteAccount(t, am)
|
||||
if err != nil {
|
||||
t.Error("failed to init testing account")
|
||||
t.Errorf("failed to init testing account: %s", err)
|
||||
}
|
||||
|
||||
outRoute, err := am.CreateRoute(
|
||||
account.Id,
|
||||
testCase.inputArgs.network,
|
||||
testCase.inputArgs.peerKey,
|
||||
testCase.inputArgs.peerGroupIDs,
|
||||
testCase.inputArgs.description,
|
||||
testCase.inputArgs.netID,
|
||||
testCase.inputArgs.masquerade,
|
||||
@@ -272,6 +358,7 @@ func TestCreateRoute(t *testing.T) {
|
||||
|
||||
func TestSaveRoute(t *testing.T) {
|
||||
validPeer := peer2ID
|
||||
validUsedPeer := peer5ID
|
||||
invalidPeer := "nonExisting"
|
||||
validPrefix := netip.MustParsePrefix("192.168.0.0/24")
|
||||
invalidPrefix, _ := netip.ParsePrefix("192.168.0.0/34")
|
||||
@@ -279,11 +366,14 @@ func TestSaveRoute(t *testing.T) {
|
||||
invalidMetric := 99999
|
||||
validNetID := "12345678901234567890qw"
|
||||
invalidNetID := "12345678901234567890qwertyuiopqwertyuiop1"
|
||||
validGroupHA1 := routeGroupHA1
|
||||
validGroupHA2 := routeGroupHA2
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
existingRoute *route.Route
|
||||
newPeer *string
|
||||
newPeerGroups []string
|
||||
newMetric *int
|
||||
newPrefix *netip.Prefix
|
||||
newGroups []string
|
||||
@@ -325,6 +415,55 @@ func TestSaveRoute(t *testing.T) {
|
||||
Groups: []string{routeGroup2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Happy Path Peer Groups",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
NetID: validNetID,
|
||||
NetworkType: route.IPv4Network,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newPeerGroups: []string{validGroupHA1, validGroupHA2},
|
||||
newMetric: &validMetric,
|
||||
newPrefix: &validPrefix,
|
||||
newGroups: []string{routeGroup2},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
expectedRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: validPrefix,
|
||||
NetID: validNetID,
|
||||
NetworkType: route.IPv4Network,
|
||||
PeerGroups: []string{validGroupHA1, validGroupHA2},
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: validMetric,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Both peer and peers_roup Provided Should Fail",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
NetID: validNetID,
|
||||
NetworkType: route.IPv4Network,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newPeer: &validPeer,
|
||||
newPeerGroups: []string{validGroupHA1},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Bad Prefix Should Fail",
|
||||
existingRoute: &route.Route{
|
||||
@@ -461,6 +600,71 @@ func TestSaveRoute(t *testing.T) {
|
||||
newGroups: []string{routeInvalidGroup1},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Allow to modify existing route with new peer",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix(existingNetwork),
|
||||
NetID: validNetID,
|
||||
NetworkType: route.IPv4Network,
|
||||
Peer: peer1ID,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newPeer: &validPeer,
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
expectedRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix(existingNetwork),
|
||||
NetID: validNetID,
|
||||
NetworkType: route.IPv4Network,
|
||||
Peer: validPeer,
|
||||
PeerGroups: []string{},
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Do not allow to modify existing route with a peer from another route",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix(existingNetwork),
|
||||
NetID: validNetID,
|
||||
NetworkType: route.IPv4Network,
|
||||
Peer: peer1ID,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newPeer: &validUsedPeer,
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Do not allow to modify existing route with a peers group from another route",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix(existingNetwork),
|
||||
NetID: validNetID,
|
||||
NetworkType: route.IPv4Network,
|
||||
PeerGroups: []string{routeGroup3},
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newPeerGroups: []string{routeGroup4},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
@@ -488,6 +692,9 @@ func TestSaveRoute(t *testing.T) {
|
||||
if testCase.newPeer != nil {
|
||||
routeToSave.Peer = *testCase.newPeer
|
||||
}
|
||||
if len(testCase.newPeerGroups) != 0 {
|
||||
routeToSave.PeerGroups = testCase.newPeerGroups
|
||||
}
|
||||
if testCase.newMetric != nil {
|
||||
routeToSave.Metric = *testCase.newMetric
|
||||
}
|
||||
@@ -524,265 +731,6 @@ func TestSaveRoute(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRoute(t *testing.T) {
|
||||
routeID := "testingRouteID"
|
||||
|
||||
existingRoute := &route.Route{
|
||||
ID: routeID,
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
NetID: "superRoute",
|
||||
NetworkType: route.IPv4Network,
|
||||
Peer: peer1ID,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
existingRoute *route.Route
|
||||
operations []RouteUpdateOperation
|
||||
shouldCreate bool
|
||||
errFunc require.ErrorAssertionFunc
|
||||
expectedRoute *route.Route
|
||||
}{
|
||||
{
|
||||
name: "Happy Path Single OPS",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRoutePeer,
|
||||
Values: []string{peer2ID},
|
||||
},
|
||||
},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
expectedRoute: &route.Route{
|
||||
ID: routeID,
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
NetID: "superRoute",
|
||||
NetworkType: route.IPv4Network,
|
||||
Peer: peer2ID,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Happy Path Multiple OPS",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteDescription,
|
||||
Values: []string{"great"},
|
||||
},
|
||||
{
|
||||
Type: UpdateRouteNetwork,
|
||||
Values: []string{"192.168.0.0/24"},
|
||||
},
|
||||
{
|
||||
Type: UpdateRoutePeer,
|
||||
Values: []string{peer2ID},
|
||||
},
|
||||
{
|
||||
Type: UpdateRouteMetric,
|
||||
Values: []string{"3030"},
|
||||
},
|
||||
{
|
||||
Type: UpdateRouteMasquerade,
|
||||
Values: []string{"true"},
|
||||
},
|
||||
{
|
||||
Type: UpdateRouteEnabled,
|
||||
Values: []string{"false"},
|
||||
},
|
||||
{
|
||||
Type: UpdateRouteNetworkIdentifier,
|
||||
Values: []string{"megaRoute"},
|
||||
},
|
||||
{
|
||||
Type: UpdateRouteGroups,
|
||||
Values: []string{routeGroup2},
|
||||
},
|
||||
},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
expectedRoute: &route.Route{
|
||||
ID: routeID,
|
||||
Network: netip.MustParsePrefix("192.168.0.0/24"),
|
||||
NetID: "megaRoute",
|
||||
NetworkType: route.IPv4Network,
|
||||
Peer: peer2ID,
|
||||
Description: "great",
|
||||
Masquerade: true,
|
||||
Metric: 3030,
|
||||
Enabled: false,
|
||||
Groups: []string{routeGroup2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Empty Values Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRoutePeer,
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Multiple Values Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRoutePeer,
|
||||
Values: []string{peer2ID, peer1ID},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Bad Prefix Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteNetwork,
|
||||
Values: []string{"192.168.0.0/34"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Bad Peer Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRoutePeer,
|
||||
Values: []string{"non existing Peer"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Empty Peer",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRoutePeer,
|
||||
Values: []string{""},
|
||||
},
|
||||
},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
expectedRoute: &route.Route{
|
||||
ID: routeID,
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
NetID: "superRoute",
|
||||
NetworkType: route.IPv4Network,
|
||||
Peer: "",
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Large Network ID Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteNetworkIdentifier,
|
||||
Values: []string{"12345678901234567890qwertyuiopqwertyuiop1"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Empty Network ID Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteNetworkIdentifier,
|
||||
Values: []string{""},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid Metric Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteMetric,
|
||||
Values: []string{"999999"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid Boolean Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteMasquerade,
|
||||
Values: []string{"yes"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid Group Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteGroups,
|
||||
Values: []string{routeInvalidGroup1},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
am, err := createRouterManager(t)
|
||||
if err != nil {
|
||||
t.Error("failed to create account manager")
|
||||
}
|
||||
|
||||
account, err := initTestRouteAccount(t, am)
|
||||
if err != nil {
|
||||
t.Error("failed to init testing account")
|
||||
}
|
||||
|
||||
account.Routes[testCase.existingRoute.ID] = testCase.existingRoute
|
||||
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
t.Error("account should be saved")
|
||||
}
|
||||
|
||||
updatedRoute, err := am.UpdateRoute(account.Id, testCase.existingRoute.ID, testCase.operations)
|
||||
|
||||
testCase.errFunc(t, err)
|
||||
|
||||
if !testCase.shouldCreate {
|
||||
return
|
||||
}
|
||||
|
||||
testCase.expectedRoute.ID = updatedRoute.ID
|
||||
|
||||
if !testCase.expectedRoute.IsEqual(updatedRoute) {
|
||||
t.Errorf("new route didn't match expected route:\nGot %#v\nExpected:%#v\n", updatedRoute, testCase.expectedRoute)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRoute(t *testing.T) {
|
||||
testingRoute := &route.Route{
|
||||
ID: "testingRoute",
|
||||
@@ -828,6 +776,96 @@ func TestDeleteRoute(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNetworkMap_RouteSyncPeerGroups(t *testing.T) {
|
||||
baseRoute := &route.Route{
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
NetID: "superNet",
|
||||
NetworkType: route.IPv4Network,
|
||||
PeerGroups: []string{routeGroupHA1, routeGroupHA2},
|
||||
Description: "ha route",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1, routeGroup2},
|
||||
}
|
||||
|
||||
am, err := createRouterManager(t)
|
||||
if err != nil {
|
||||
t.Error("failed to create account manager")
|
||||
}
|
||||
|
||||
account, err := initTestRouteAccount(t, am)
|
||||
if err != nil {
|
||||
t.Error("failed to init testing account")
|
||||
}
|
||||
|
||||
newAccountRoutes, err := am.GetNetworkMap(peer1ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, newAccountRoutes.Routes, 0, "new accounts should have no routes")
|
||||
|
||||
newRoute, err := am.CreateRoute(
|
||||
account.Id, baseRoute.Network.String(), baseRoute.Peer, baseRoute.PeerGroups, baseRoute.Description,
|
||||
baseRoute.NetID, baseRoute.Masquerade, baseRoute.Metric, baseRoute.Groups, baseRoute.Enabled, userID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newRoute.Enabled, true)
|
||||
|
||||
peer1Routes, err := am.GetNetworkMap(peer1ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer1Routes.Routes, 3, "HA route should have more than 1 routes")
|
||||
|
||||
peer2Routes, err := am.GetNetworkMap(peer2ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer2Routes.Routes, 3, "HA route should have more than 1 routes")
|
||||
|
||||
peer4Routes, err := am.GetNetworkMap(peer4ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer4Routes.Routes, 3, "HA route should have more than 1 routes")
|
||||
|
||||
groups, err := am.ListGroups(account.Id)
|
||||
require.NoError(t, err)
|
||||
var groupHA1, groupHA2 *Group
|
||||
for _, group := range groups {
|
||||
switch group.Name {
|
||||
case routeGroupHA1:
|
||||
groupHA1 = group
|
||||
case routeGroupHA2:
|
||||
groupHA2 = group
|
||||
}
|
||||
}
|
||||
|
||||
err = am.GroupDeletePeer(account.Id, groupHA1.ID, peer2ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
peer2RoutesAfterDelete, err := am.GetNetworkMap(peer2ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer2RoutesAfterDelete.Routes, 2, "after peer deletion group should have only 2 route")
|
||||
|
||||
err = am.GroupDeletePeer(account.Id, groupHA2.ID, peer4ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
peer2RoutesAfterDelete, err = am.GetNetworkMap(peer2ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer2RoutesAfterDelete.Routes, 1, "after peer deletion group should have only 1 route")
|
||||
|
||||
err = am.GroupAddPeer(account.Id, groupHA2.ID, peer4ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
peer1RoutesAfterAdd, err := am.GetNetworkMap(peer1ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer1RoutesAfterAdd.Routes, 2, "HA route should have more than 1 route")
|
||||
|
||||
peer2RoutesAfterAdd, err := am.GetNetworkMap(peer2ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer2RoutesAfterAdd.Routes, 2, "HA route should have more than 1 route")
|
||||
|
||||
err = am.DeleteRoute(account.Id, newRoute.ID, userID)
|
||||
require.NoError(t, err)
|
||||
|
||||
peer1DeletedRoute, err := am.GetNetworkMap(peer1ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer1DeletedRoute.Routes, 0, "we should receive one route for peer1")
|
||||
}
|
||||
|
||||
func TestGetNetworkMap_RouteSync(t *testing.T) {
|
||||
// no routes for peer in different groups
|
||||
// no routes when route is deleted
|
||||
@@ -858,7 +896,7 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, newAccountRoutes.Routes, 0, "new accounts should have no routes")
|
||||
|
||||
createdRoute, err := am.CreateRoute(account.Id, baseRoute.Network.String(), peer1ID,
|
||||
createdRoute, err := am.CreateRoute(account.Id, baseRoute.Network.String(), peer1ID, []string{},
|
||||
baseRoute.Description, baseRoute.NetID, baseRoute.Masquerade, baseRoute.Metric, baseRoute.Groups, false,
|
||||
userID)
|
||||
require.NoError(t, err)
|
||||
@@ -940,7 +978,7 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
return nil, err
|
||||
}
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore)
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore, false)
|
||||
}
|
||||
|
||||
func createRouterStore(t *testing.T) (Store, error) {
|
||||
@@ -954,6 +992,8 @@ func createRouterStore(t *testing.T) (Store, error) {
|
||||
}
|
||||
|
||||
func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) {
|
||||
t.Helper()
|
||||
|
||||
accountID := "testingAcc"
|
||||
domain := "example.com"
|
||||
|
||||
@@ -1013,6 +1053,81 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
||||
}
|
||||
account.Peers[peer2.ID] = peer2
|
||||
|
||||
ips = account.getTakenIPs()
|
||||
peer3IP, err := AllocatePeerIP(account.Network.Net, ips)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer3 := &Peer{
|
||||
IP: peer3IP,
|
||||
ID: peer3ID,
|
||||
Key: peer3Key,
|
||||
Name: "test-host3@netbird.io",
|
||||
UserID: userID,
|
||||
Meta: PeerSystemMeta{
|
||||
Hostname: "test-host3@netbird.io",
|
||||
GoOS: "darwin",
|
||||
Kernel: "Darwin",
|
||||
Core: "13.4.1",
|
||||
Platform: "arm64",
|
||||
OS: "darwin",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
},
|
||||
}
|
||||
account.Peers[peer3.ID] = peer3
|
||||
|
||||
ips = account.getTakenIPs()
|
||||
peer4IP, err := AllocatePeerIP(account.Network.Net, ips)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer4 := &Peer{
|
||||
IP: peer4IP,
|
||||
ID: peer4ID,
|
||||
Key: peer4Key,
|
||||
Name: "test-host4@netbird.io",
|
||||
UserID: userID,
|
||||
Meta: PeerSystemMeta{
|
||||
Hostname: "test-host4@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
},
|
||||
}
|
||||
account.Peers[peer4.ID] = peer4
|
||||
|
||||
ips = account.getTakenIPs()
|
||||
peer5IP, err := AllocatePeerIP(account.Network.Net, ips)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer5 := &Peer{
|
||||
IP: peer5IP,
|
||||
ID: peer5ID,
|
||||
Key: peer5Key,
|
||||
Name: "test-host4@netbird.io",
|
||||
UserID: userID,
|
||||
Meta: PeerSystemMeta{
|
||||
Hostname: "test-host4@netbird.io",
|
||||
GoOS: "linux",
|
||||
Kernel: "Linux",
|
||||
Core: "21.04",
|
||||
Platform: "x86_64",
|
||||
OS: "Ubuntu",
|
||||
WtVersion: "development",
|
||||
UIVersion: "development",
|
||||
},
|
||||
}
|
||||
account.Peers[peer5.ID] = peer5
|
||||
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1029,24 +1144,57 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newGroup := &Group{
|
||||
ID: routeGroup1,
|
||||
Name: routeGroup1,
|
||||
Peers: []string{peer1.ID},
|
||||
err = am.GroupAddPeer(accountID, groupAll.ID, peer3ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = am.SaveGroup(accountID, userID, newGroup)
|
||||
err = am.GroupAddPeer(accountID, groupAll.ID, peer4ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newGroup = &Group{
|
||||
ID: routeGroup2,
|
||||
Name: routeGroup2,
|
||||
Peers: []string{peer2.ID},
|
||||
newGroup := []*Group{
|
||||
{
|
||||
ID: routeGroup1,
|
||||
Name: routeGroup1,
|
||||
Peers: []string{peer1.ID},
|
||||
},
|
||||
{
|
||||
ID: routeGroup2,
|
||||
Name: routeGroup2,
|
||||
Peers: []string{peer2.ID},
|
||||
},
|
||||
{
|
||||
ID: routeGroup3,
|
||||
Name: routeGroup3,
|
||||
Peers: []string{peer5.ID},
|
||||
},
|
||||
{
|
||||
ID: routeGroup4,
|
||||
Name: routeGroup4,
|
||||
Peers: []string{peer5.ID},
|
||||
},
|
||||
{
|
||||
ID: routeGroupHA1,
|
||||
Name: routeGroupHA1,
|
||||
Peers: []string{peer1.ID, peer2.ID, peer3.ID}, // we have one non Linux peer, see peer3
|
||||
},
|
||||
{
|
||||
ID: routeGroupHA2,
|
||||
Name: routeGroupHA2,
|
||||
Peers: []string{peer1.ID, peer4.ID},
|
||||
},
|
||||
}
|
||||
|
||||
err = am.SaveGroup(accountID, userID, newGroup)
|
||||
for _, group := range newGroup {
|
||||
err = am.SaveGroup(accountID, userID, group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = am.CreateRoute(account.Id, existingNetwork, "", []string{routeGroup3, routeGroup4},
|
||||
"", existingRouteID, false, 1000, []string{groupAll.ID}, true, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ type GRPCMetrics struct {
|
||||
activeStreamsGauge asyncint64.Gauge
|
||||
syncRequestDuration syncint64.Histogram
|
||||
loginRequestDuration syncint64.Histogram
|
||||
channelQueueLength syncint64.Histogram
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
@@ -52,6 +53,18 @@ func NewGRPCMetrics(ctx context.Context, meter metric.Meter) (*GRPCMetrics, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We use histogram here as we have multiple channel at the same time and we want to see a slice at any given time
|
||||
// Then we should be able to extract min, manx, mean and the percentiles.
|
||||
// TODO(yury): This needs custom bucketing as we are interested in the values from 0 to server.channelBufferSize (100)
|
||||
channelQueue, err := meter.SyncInt64().Histogram(
|
||||
"management.grpc.updatechannel.queue",
|
||||
instrument.WithDescription("Number of update messages in the channel queue"),
|
||||
instrument.WithUnit("length"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &GRPCMetrics{
|
||||
meter: meter,
|
||||
syncRequestsCounter: syncRequestsCounter,
|
||||
@@ -60,6 +73,7 @@ func NewGRPCMetrics(ctx context.Context, meter metric.Meter) (*GRPCMetrics, erro
|
||||
activeStreamsGauge: activeStreamsGauge,
|
||||
syncRequestDuration: syncRequestDuration,
|
||||
loginRequestDuration: loginRequestDuration,
|
||||
channelQueueLength: channelQueue,
|
||||
ctx: ctx,
|
||||
}, err
|
||||
}
|
||||
@@ -100,3 +114,8 @@ func (grpcMetrics *GRPCMetrics) RegisterConnectedStreams(producer func() int64)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// UpdateChannelQueueLength update the histogram that keep distribution of the update messages channel queue
|
||||
func (metrics *GRPCMetrics) UpdateChannelQueueLength(len int) {
|
||||
metrics.channelQueueLength.Record(metrics.ctx, int64(len))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package telemetry
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"go.opentelemetry.io/otel/metric/instrument"
|
||||
"go.opentelemetry.io/otel/metric/instrument/syncint64"
|
||||
@@ -13,6 +14,7 @@ type IDPMetrics struct {
|
||||
getUserByEmailCounter syncint64.Counter
|
||||
getAllAccountsCounter syncint64.Counter
|
||||
createUserCounter syncint64.Counter
|
||||
deleteUserCounter syncint64.Counter
|
||||
getAccountCounter syncint64.Counter
|
||||
getUserByIDCounter syncint64.Counter
|
||||
authenticateRequestCounter syncint64.Counter
|
||||
@@ -39,6 +41,10 @@ func NewIDPMetrics(ctx context.Context, meter metric.Meter) (*IDPMetrics, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deleteUserCounter, err := meter.SyncInt64().Counter("management.idp.delete.user.counter", instrument.WithUnit("1"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
getAccountCounter, err := meter.SyncInt64().Counter("management.idp.get.account.counter", instrument.WithUnit("1"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -65,6 +71,7 @@ func NewIDPMetrics(ctx context.Context, meter metric.Meter) (*IDPMetrics, error)
|
||||
getUserByEmailCounter: getUserByEmailCounter,
|
||||
getAllAccountsCounter: getAllAccountsCounter,
|
||||
createUserCounter: createUserCounter,
|
||||
deleteUserCounter: deleteUserCounter,
|
||||
getAccountCounter: getAccountCounter,
|
||||
getUserByIDCounter: getUserByIDCounter,
|
||||
authenticateRequestCounter: authenticateRequestCounter,
|
||||
@@ -88,6 +95,11 @@ func (idpMetrics *IDPMetrics) CountCreateUser() {
|
||||
idpMetrics.createUserCounter.Add(idpMetrics.ctx, 1)
|
||||
}
|
||||
|
||||
// CountDeleteUser ...
|
||||
func (idpMetrics *IDPMetrics) CountDeleteUser() {
|
||||
idpMetrics.deleteUserCounter.Add(idpMetrics.ctx, 1)
|
||||
}
|
||||
|
||||
// CountGetAllAccounts ...
|
||||
func (idpMetrics *IDPMetrics) CountGetAllAccounts() {
|
||||
idpMetrics.getAllAccountsCounter.Add(idpMetrics.ctx, 1)
|
||||
|
||||
@@ -11,37 +11,54 @@ import (
|
||||
|
||||
// StoreMetrics represents all metrics related to the FileStore
|
||||
type StoreMetrics struct {
|
||||
globalLockAcquisitionDuration syncint64.Histogram
|
||||
persistenceDuration syncint64.Histogram
|
||||
ctx context.Context
|
||||
globalLockAcquisitionDurationMicro syncint64.Histogram
|
||||
globalLockAcquisitionDurationMs syncint64.Histogram
|
||||
persistenceDurationMicro syncint64.Histogram
|
||||
persistenceDurationMs syncint64.Histogram
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewStoreMetrics creates an instance of StoreMetrics
|
||||
func NewStoreMetrics(ctx context.Context, meter metric.Meter) (*StoreMetrics, error) {
|
||||
globalLockAcquisitionDuration, err := meter.SyncInt64().Histogram("management.store.global.lock.acquisition.duration.micro",
|
||||
globalLockAcquisitionDurationMicro, err := meter.SyncInt64().Histogram("management.store.global.lock.acquisition.duration.micro",
|
||||
instrument.WithUnit("microseconds"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
persistenceDuration, err := meter.SyncInt64().Histogram("management.store.persistence.duration.micro",
|
||||
|
||||
globalLockAcquisitionDurationMs, err := meter.SyncInt64().Histogram("management.store.global.lock.acquisition.duration.ms")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
persistenceDurationMicro, err := meter.SyncInt64().Histogram("management.store.persistence.duration.micro",
|
||||
instrument.WithUnit("microseconds"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
persistenceDurationMs, err := meter.SyncInt64().Histogram("management.store.persistence.duration.ms")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &StoreMetrics{
|
||||
globalLockAcquisitionDuration: globalLockAcquisitionDuration,
|
||||
persistenceDuration: persistenceDuration,
|
||||
ctx: ctx,
|
||||
globalLockAcquisitionDurationMicro: globalLockAcquisitionDurationMicro,
|
||||
globalLockAcquisitionDurationMs: globalLockAcquisitionDurationMs,
|
||||
persistenceDurationMicro: persistenceDurationMicro,
|
||||
persistenceDurationMs: persistenceDurationMs,
|
||||
ctx: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CountGlobalLockAcquisitionDuration counts the duration of the global lock acquisition
|
||||
func (metrics *StoreMetrics) CountGlobalLockAcquisitionDuration(duration time.Duration) {
|
||||
metrics.globalLockAcquisitionDuration.Record(metrics.ctx, duration.Microseconds())
|
||||
metrics.globalLockAcquisitionDurationMicro.Record(metrics.ctx, duration.Microseconds())
|
||||
metrics.globalLockAcquisitionDurationMs.Record(metrics.ctx, duration.Milliseconds())
|
||||
}
|
||||
|
||||
// CountPersistenceDuration counts the duration of a store persistence operation
|
||||
func (metrics *StoreMetrics) CountPersistenceDuration(duration time.Duration) {
|
||||
metrics.persistenceDuration.Record(metrics.ctx, duration.Microseconds())
|
||||
metrics.persistenceDurationMicro.Record(metrics.ctx, duration.Microseconds())
|
||||
metrics.persistenceDurationMs.Record(metrics.ctx, duration.Milliseconds())
|
||||
}
|
||||
|
||||
@@ -309,6 +309,9 @@ func (am *DefaultAccountManager) GetUser(claims jwtclaims.AuthorizationClaims) (
|
||||
|
||||
// DeleteUser deletes a user from the given account.
|
||||
func (am *DefaultAccountManager) DeleteUser(accountID, initiatorUserID string, targetUserID string) error {
|
||||
if initiatorUserID == targetUserID {
|
||||
return status.Errorf(status.InvalidArgument, "self deletion is not allowed")
|
||||
}
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
@@ -327,15 +330,43 @@ func (am *DefaultAccountManager) DeleteUser(accountID, initiatorUserID string, t
|
||||
return status.Errorf(status.NotFound, "user not found")
|
||||
}
|
||||
if executingUser.Role != UserRoleAdmin {
|
||||
return status.Errorf(status.PermissionDenied, "only admins can delete service users")
|
||||
return status.Errorf(status.PermissionDenied, "only admins can delete users")
|
||||
}
|
||||
|
||||
if !targetUser.IsServiceUser {
|
||||
return status.Errorf(status.PermissionDenied, "regular users can not be deleted")
|
||||
peers, err := account.FindUserPeers(targetUserID)
|
||||
if err != nil {
|
||||
return status.Errorf(status.Internal, "failed to find user peers")
|
||||
}
|
||||
|
||||
meta := map[string]any{"name": targetUser.ServiceUserName}
|
||||
am.storeEvent(initiatorUserID, targetUserID, accountID, activity.ServiceUserDeleted, meta)
|
||||
if err := am.expireAndUpdatePeers(account, peers); err != nil {
|
||||
log.Errorf("failed update deleted peers expiration: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
tuEmail, tuName, err := am.getEmailAndNameOfTargetUser(account.Id, initiatorUserID, targetUserID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to resolve email address: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var meta map[string]any
|
||||
var eventAction activity.Activity
|
||||
if targetUser.IsServiceUser {
|
||||
meta = map[string]any{"name": targetUser.ServiceUserName}
|
||||
eventAction = activity.ServiceUserDeleted
|
||||
} else {
|
||||
meta = map[string]any{"name": tuName, "email": tuEmail}
|
||||
eventAction = activity.UserDeleted
|
||||
}
|
||||
am.storeEvent(initiatorUserID, targetUserID, accountID, eventAction, meta)
|
||||
|
||||
if !targetUser.IsServiceUser && !isNil(am.idpManager) {
|
||||
err := am.deleteUserFromIDP(targetUserID, accountID)
|
||||
if err != nil {
|
||||
log.Debugf("failed to delete user from IDP: %s", targetUserID)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
delete(account.Users, targetUserID)
|
||||
|
||||
@@ -609,23 +640,10 @@ func (am *DefaultAccountManager) SaveUser(accountID, initiatorUserID string, upd
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var peerIDs []string
|
||||
for _, peer := range blockedPeers {
|
||||
peerIDs = append(peerIDs, peer.ID)
|
||||
peer.MarkLoginExpired(true)
|
||||
account.UpdatePeer(peer)
|
||||
err = am.Store.SavePeerStatus(account.Id, peer.ID, *peer.Status)
|
||||
if err != nil {
|
||||
log.Errorf("failed saving peer status while expiring peer %s", peer.ID)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
am.peersUpdateManager.CloseChannels(peerIDs)
|
||||
err = am.updateAccountPeers(account)
|
||||
if err != nil {
|
||||
log.Errorf("failed updating account peers while expiring peers of a blocked user %s", accountID)
|
||||
return nil, err
|
||||
|
||||
if err := am.expireAndUpdatePeers(account, blockedPeers); err != nil {
|
||||
log.Errorf("failed update expired peers: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,6 +832,70 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) (
|
||||
return userInfos, nil
|
||||
}
|
||||
|
||||
// expireAndUpdatePeers expires all peers of the given user and updates them in the account
|
||||
func (am *DefaultAccountManager) expireAndUpdatePeers(account *Account, peers []*Peer) error {
|
||||
var peerIDs []string
|
||||
for _, peer := range peers {
|
||||
if peer.Status.LoginExpired {
|
||||
continue
|
||||
}
|
||||
peerIDs = append(peerIDs, peer.ID)
|
||||
peer.MarkLoginExpired(true)
|
||||
account.UpdatePeer(peer)
|
||||
if err := am.Store.SavePeerStatus(account.Id, peer.ID, *peer.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
am.storeEvent(
|
||||
peer.UserID, peer.ID, account.Id,
|
||||
activity.PeerLoginExpired, peer.EventMeta(am.GetDNSDomain()),
|
||||
)
|
||||
}
|
||||
|
||||
if len(peerIDs) != 0 {
|
||||
// this will trigger peer disconnect from the management service
|
||||
am.peersUpdateManager.CloseChannels(peerIDs)
|
||||
if err := am.updateAccountPeers(account); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) deleteUserFromIDP(targetUserID, accountID string) error {
|
||||
if am.userDeleteFromIDPEnabled {
|
||||
log.Debugf("user %s deleted from IdP", targetUserID)
|
||||
err := am.idpManager.DeleteUser(targetUserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete user %s from IdP: %s", targetUserID, err)
|
||||
}
|
||||
} else {
|
||||
err := am.idpManager.UpdateUserAppMetadata(targetUserID, idp.AppMetadata{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove user %s app metadata in IdP: %s", targetUserID, err)
|
||||
}
|
||||
|
||||
_, err = am.refreshCache(accountID)
|
||||
if err != nil {
|
||||
log.Errorf("refresh account (%q) cache: %v", accountID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) getEmailAndNameOfTargetUser(accountId, initiatorId, targetId string) (string, string, error) {
|
||||
userInfos, err := am.GetUsersFromAccount(accountId, initiatorId)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
for _, ui := range userInfos {
|
||||
if ui.ID == targetId {
|
||||
return ui.Email, ui.Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("user info not found for user: %s", targetId)
|
||||
}
|
||||
|
||||
func findUserInIDPUserdata(userID string, userData []*idp.UserData) (*idp.UserData, bool) {
|
||||
for _, user := range userData {
|
||||
if user.ID == userID {
|
||||
|
||||
@@ -424,7 +424,7 @@ func TestUser_DeleteUser_ServiceUser(t *testing.T) {
|
||||
assert.Nil(t, store.Accounts[mockAccountID].Users[mockServiceUserID])
|
||||
}
|
||||
|
||||
func TestUser_DeleteUser_regularUser(t *testing.T) {
|
||||
func TestUser_DeleteUser_SelfDelete(t *testing.T) {
|
||||
store := newStore(t)
|
||||
account := newAccountWithId(mockAccountID, mockUserID, "")
|
||||
|
||||
@@ -439,8 +439,35 @@ func TestUser_DeleteUser_regularUser(t *testing.T) {
|
||||
}
|
||||
|
||||
err = am.DeleteUser(mockAccountID, mockUserID, mockUserID)
|
||||
if err == nil {
|
||||
t.Fatalf("failed to prevent self deletion")
|
||||
}
|
||||
}
|
||||
|
||||
assert.Errorf(t, err, "Regular users can not be deleted (yet)")
|
||||
func TestUser_DeleteUser_regularUser(t *testing.T) {
|
||||
store := newStore(t)
|
||||
account := newAccountWithId(mockAccountID, mockUserID, "")
|
||||
targetId := "user2"
|
||||
account.Users[targetId] = &User{
|
||||
Id: targetId,
|
||||
IsServiceUser: true,
|
||||
ServiceUserName: "user2username",
|
||||
}
|
||||
|
||||
err := store.SaveAccount(account)
|
||||
if err != nil {
|
||||
t.Fatalf("Error when saving account: %s", err)
|
||||
}
|
||||
|
||||
am := DefaultAccountManager{
|
||||
Store: store,
|
||||
eventStore: &activity.InMemoryEventStore{},
|
||||
}
|
||||
|
||||
err = am.DeleteUser(mockAccountID, mockUserID, targetId)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_GetUser(t *testing.T) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/bin/sh
|
||||
# This code is based on the netbird-installer contribution by physk on GitHub.
|
||||
# Source: https://github.com/physk/netbird-installer
|
||||
set -e
|
||||
@@ -17,6 +16,12 @@ OS_TYPE=""
|
||||
ARCH="$(uname -m)"
|
||||
PACKAGE_MANAGER="bin"
|
||||
INSTALL_DIR=""
|
||||
SUDO=""
|
||||
|
||||
|
||||
if command -v sudo > /dev/null && [ "$(id -u)" -ne 0 ]; then
|
||||
SUDO="sudo"
|
||||
fi
|
||||
|
||||
get_latest_release() {
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
@@ -65,27 +70,35 @@ download_release_binary() {
|
||||
unzip -q -o "$BINARY_NAME"
|
||||
mv "netbird_ui_${OS_TYPE}_${ARCH}" "$INSTALL_DIR"
|
||||
else
|
||||
sudo mkdir -p "$INSTALL_DIR"
|
||||
${SUDO} mkdir -p "$INSTALL_DIR"
|
||||
tar -xzvf "$BINARY_NAME"
|
||||
sudo mv "${1%_"${BINARY_BASE_NAME}"}" "$INSTALL_DIR/"
|
||||
${SUDO} mv "${1%_"${BINARY_BASE_NAME}"}" "$INSTALL_DIR/"
|
||||
fi
|
||||
}
|
||||
|
||||
add_apt_repo() {
|
||||
sudo apt-get update
|
||||
sudo apt-get install ca-certificates gnupg -y
|
||||
${SUDO} apt-get update
|
||||
${SUDO} apt-get install ca-certificates curl gnupg -y
|
||||
|
||||
curl -sSL https://pkgs.wiretrustee.com/debian/public.key \
|
||||
| sudo gpg --dearmor --output /usr/share/keyrings/wiretrustee-archive-keyring.gpg
|
||||
# Remove old keys and repo source files
|
||||
${SUDO} rm -f \
|
||||
/etc/apt/sources.list.d/netbird.list \
|
||||
/etc/apt/sources.list.d/wiretrustee.list \
|
||||
/etc/apt/trusted.gpg.d/wiretrustee.gpg \
|
||||
/usr/share/keyrings/netbird-archive-keyring.gpg \
|
||||
/usr/share/keyrings/wiretrustee-archive-keyring.gpg
|
||||
|
||||
APT_REPO="deb [signed-by=/usr/share/keyrings/wiretrustee-archive-keyring.gpg] https://pkgs.wiretrustee.com/debian stable main"
|
||||
echo "$APT_REPO" | sudo tee /etc/apt/sources.list.d/wiretrustee.list
|
||||
curl -sSL https://pkgs.netbird.io/debian/public.key \
|
||||
| ${SUDO} gpg --dearmor -o /usr/share/keyrings/netbird-archive-keyring.gpg
|
||||
|
||||
sudo apt-get update
|
||||
echo 'deb [signed-by=/usr/share/keyrings/netbird-archive-keyring.gpg] https://pkgs.netbird.io/debian stable main' \
|
||||
| ${SUDO} tee /etc/apt/sources.list.d/netbird.list
|
||||
|
||||
${SUDO} apt-get update
|
||||
}
|
||||
|
||||
add_rpm_repo() {
|
||||
cat <<-EOF | sudo tee /etc/yum.repos.d/netbird.repo
|
||||
cat <<-EOF | ${SUDO} tee /etc/yum.repos.d/netbird.repo
|
||||
[NetBird]
|
||||
name=NetBird
|
||||
baseurl=https://pkgs.netbird.io/yum/
|
||||
@@ -104,7 +117,7 @@ add_aur_repo() {
|
||||
for PKG in $INSTALL_PKGS; do
|
||||
if ! pacman -Q "$PKG" > /dev/null 2>&1; then
|
||||
# Install missing package(s)
|
||||
sudo pacman -S "$PKG" --noconfirm
|
||||
${SUDO} pacman -S "$PKG" --noconfirm
|
||||
|
||||
# Add installed package for clean up later
|
||||
REMOVE_PKGS="$REMOVE_PKGS $PKG"
|
||||
@@ -121,7 +134,7 @@ add_aur_repo() {
|
||||
fi
|
||||
|
||||
# Clean up the installed packages
|
||||
sudo pacman -Rs "$REMOVE_PKGS" --noconfirm
|
||||
${SUDO} pacman -Rs "$REMOVE_PKGS" --noconfirm
|
||||
}
|
||||
|
||||
install_native_binaries() {
|
||||
@@ -181,8 +194,136 @@ install_netbird() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Identify OS name and default package manager
|
||||
if type uname >/dev/null 2>&1; then
|
||||
# Run the installation, if a desktop environment is not detected
|
||||
# only the CLI will be installed
|
||||
case "$PACKAGE_MANAGER" in
|
||||
apt)
|
||||
add_apt_repo
|
||||
${SUDO} apt-get install netbird -y
|
||||
|
||||
if ! $SKIP_UI_APP; then
|
||||
${SUDO} apt-get install netbird-ui -y
|
||||
fi
|
||||
;;
|
||||
yum)
|
||||
add_rpm_repo
|
||||
${SUDO} yum -y install netbird
|
||||
if ! $SKIP_UI_APP; then
|
||||
${SUDO} yum -y install netbird-ui
|
||||
fi
|
||||
;;
|
||||
dnf)
|
||||
add_rpm_repo
|
||||
${SUDO} dnf -y install dnf-plugin-config-manager
|
||||
${SUDO} dnf config-manager --add-repo /etc/yum.repos.d/netbird.repo
|
||||
${SUDO} dnf -y install netbird
|
||||
|
||||
if ! $SKIP_UI_APP; then
|
||||
${SUDO} dnf -y install netbird-ui
|
||||
fi
|
||||
;;
|
||||
pacman)
|
||||
${SUDO} pacman -Syy
|
||||
add_aur_repo
|
||||
;;
|
||||
brew)
|
||||
# Remove Wiretrustee if it had been installed using Homebrew before
|
||||
if brew ls --versions wiretrustee >/dev/null 2>&1; then
|
||||
echo "Removing existing wiretrustee client"
|
||||
|
||||
# Stop and uninstall daemon service:
|
||||
wiretrustee service stop
|
||||
wiretrustee service uninstall
|
||||
|
||||
# Unlik the app
|
||||
brew unlink wiretrustee
|
||||
fi
|
||||
|
||||
brew install netbirdio/tap/netbird
|
||||
if ! $SKIP_UI_APP; then
|
||||
brew install --cask netbirdio/tap/netbird-ui
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if [ "$OS_NAME" = "nixos" ];then
|
||||
echo "Please add NetBird to your NixOS configuration.nix directly:"
|
||||
echo ""
|
||||
echo "services.netbird.enable = true;"
|
||||
|
||||
if ! $SKIP_UI_APP; then
|
||||
echo "environment.systemPackages = [ pkgs.netbird-ui ];"
|
||||
fi
|
||||
|
||||
echo "Build and apply new configuration:"
|
||||
echo ""
|
||||
echo "${SUDO} nixos-rebuild switch"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
install_native_binaries
|
||||
;;
|
||||
esac
|
||||
|
||||
# Add package manager to config
|
||||
${SUDO} mkdir -p "$CONFIG_FOLDER"
|
||||
echo "package_manager=$PACKAGE_MANAGER" | ${SUDO} tee "$CONFIG_FILE" > /dev/null
|
||||
|
||||
# Load and start netbird service
|
||||
if ! ${SUDO} netbird service install 2>&1; then
|
||||
echo "NetBird service has already been loaded"
|
||||
fi
|
||||
if ! ${SUDO} netbird service start 2>&1; then
|
||||
echo "NetBird service has already been started"
|
||||
fi
|
||||
|
||||
|
||||
echo "Installation has been finished. To connect, you need to run NetBird by executing the following command:"
|
||||
echo ""
|
||||
echo "netbird up"
|
||||
}
|
||||
|
||||
version_greater_equal() {
|
||||
printf '%s\n%s\n' "$2" "$1" | sort -V -C
|
||||
}
|
||||
|
||||
is_bin_package_manager() {
|
||||
if ${SUDO} test -f "$1" && ${SUDO} grep -q "package_manager=bin" "$1" ; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
update_netbird() {
|
||||
if is_bin_package_manager "$CONFIG_FILE"; then
|
||||
latest_release=$(get_latest_release)
|
||||
latest_version=${latest_release#v}
|
||||
installed_version=$(netbird version)
|
||||
|
||||
if [ "$latest_version" = "$installed_version" ]; then
|
||||
echo "Installed netbird version ($installed_version) is up-to-date"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if version_greater_equal "$latest_version" "$installed_version"; then
|
||||
echo "NetBird new version ($latest_version) available. Updating..."
|
||||
echo ""
|
||||
echo "Initiating NetBird update. This will stop the netbird service and restart it after the update"
|
||||
|
||||
${SUDO} netbird service stop
|
||||
${SUDO} netbird service uninstall
|
||||
install_native_binaries
|
||||
|
||||
${SUDO} netbird service install
|
||||
${SUDO} netbird service start
|
||||
fi
|
||||
else
|
||||
echo "NetBird installation was done using a package manager. Please use your system's package manager to update"
|
||||
fi
|
||||
}
|
||||
|
||||
# Identify OS name and default package manager
|
||||
if type uname >/dev/null 2>&1; then
|
||||
case "$(uname)" in
|
||||
Linux)
|
||||
OS_NAME="$(. /etc/os-release && echo "$ID")"
|
||||
@@ -233,136 +374,7 @@ install_netbird() {
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Run the installation, if a desktop environment is not detected
|
||||
# only the CLI will be installed
|
||||
case "$PACKAGE_MANAGER" in
|
||||
apt)
|
||||
add_apt_repo
|
||||
sudo apt-get install netbird -y
|
||||
|
||||
if ! $SKIP_UI_APP; then
|
||||
sudo apt-get install netbird-ui -y
|
||||
fi
|
||||
;;
|
||||
yum)
|
||||
add_rpm_repo
|
||||
sudo yum -y install netbird
|
||||
if ! $SKIP_UI_APP; then
|
||||
sudo yum -y install netbird-ui
|
||||
fi
|
||||
;;
|
||||
dnf)
|
||||
add_rpm_repo
|
||||
sudo dnf -y install dnf-plugin-config-manager
|
||||
sudo dnf config-manager --add-repo /etc/yum.repos.d/netbird.repo
|
||||
sudo dnf -y install netbird
|
||||
|
||||
if ! $SKIP_UI_APP; then
|
||||
sudo dnf -y install netbird-ui
|
||||
fi
|
||||
;;
|
||||
pacman)
|
||||
sudo pacman -Syy
|
||||
add_aur_repo
|
||||
;;
|
||||
brew)
|
||||
# Remove Wiretrustee if it had been installed using Homebrew before
|
||||
if brew ls --versions wiretrustee >/dev/null 2>&1; then
|
||||
echo "Removing existing wiretrustee client"
|
||||
|
||||
# Stop and uninstall daemon service:
|
||||
wiretrustee service stop
|
||||
wiretrustee service uninstall
|
||||
|
||||
# Unlik the app
|
||||
brew unlink wiretrustee
|
||||
fi
|
||||
|
||||
brew install netbirdio/tap/netbird
|
||||
if ! $SKIP_UI_APP; then
|
||||
brew install --cask netbirdio/tap/netbird-ui
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if [ "$OS_NAME" = "nixos" ];then
|
||||
echo "Please add NetBird to your NixOS configuration.nix directly:"
|
||||
echo ""
|
||||
echo "services.netbird.enable = true;"
|
||||
|
||||
if ! $SKIP_UI_APP; then
|
||||
echo "environment.systemPackages = [ pkgs.netbird-ui ];"
|
||||
fi
|
||||
|
||||
echo "Build and apply new configuration:"
|
||||
echo ""
|
||||
echo "sudo nixos-rebuild switch"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
install_native_binaries
|
||||
;;
|
||||
esac
|
||||
|
||||
# Add package manager to config
|
||||
sudo mkdir -p "$CONFIG_FOLDER"
|
||||
echo "package_manager=$PACKAGE_MANAGER" | sudo tee "$CONFIG_FILE" > /dev/null
|
||||
|
||||
# Load and start netbird service
|
||||
if ! sudo netbird service install 2>&1; then
|
||||
echo "NetBird service has already been loaded"
|
||||
fi
|
||||
if ! sudo netbird service start 2>&1; then
|
||||
echo "NetBird service has already been started"
|
||||
fi
|
||||
|
||||
|
||||
echo "Installation has been finished. To connect, you need to run NetBird by executing the following command:"
|
||||
echo ""
|
||||
echo "sudo netbird up"
|
||||
}
|
||||
|
||||
version_greater_equal() {
|
||||
printf '%s\n%s\n' "$2" "$1" | sort -V -C
|
||||
}
|
||||
|
||||
is_bin_package_manager() {
|
||||
if sudo test -f "$1" && sudo grep -q "package_manager=bin" "$1" ; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
update_netbird() {
|
||||
if is_bin_package_manager "$CONFIG_FILE"; then
|
||||
latest_release=$(get_latest_release)
|
||||
latest_version=${latest_release#v}
|
||||
installed_version=$(netbird version)
|
||||
|
||||
if [ "$latest_version" = "$installed_version" ]; then
|
||||
echo "Installed netbird version ($installed_version) is up-to-date"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if version_greater_equal "$latest_version" "$installed_version"; then
|
||||
echo "NetBird new version ($latest_version) available. Updating..."
|
||||
echo ""
|
||||
echo "Initiating NetBird update. This will stop the netbird service and restart it after the update"
|
||||
|
||||
sudo netbird service stop
|
||||
sudo netbird service uninstall
|
||||
install_native_binaries
|
||||
|
||||
sudo netbird service install
|
||||
sudo netbird service start
|
||||
fi
|
||||
else
|
||||
echo "NetBird installation was done using a package manager. Please use your system's package manager to update"
|
||||
fi
|
||||
}
|
||||
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
--update)
|
||||
@@ -370,4 +382,4 @@ case "$1" in
|
||||
;;
|
||||
*)
|
||||
install_netbird
|
||||
esac
|
||||
esac
|
||||
|
||||
@@ -70,6 +70,7 @@ type Route struct {
|
||||
NetID string
|
||||
Description string
|
||||
Peer string
|
||||
PeerGroups []string
|
||||
NetworkType NetworkType
|
||||
Masquerade bool
|
||||
Metric int
|
||||
@@ -79,7 +80,7 @@ type Route struct {
|
||||
|
||||
// EventMeta returns activity event meta related to the route
|
||||
func (r *Route) EventMeta() map[string]any {
|
||||
return map[string]any{"name": r.NetID, "network_range": r.Network.String(), "peer_id": r.Peer}
|
||||
return map[string]any{"name": r.NetID, "network_range": r.Network.String(), "peer_id": r.Peer, "peer_groups": r.PeerGroups}
|
||||
}
|
||||
|
||||
// Copy copies a route object
|
||||
@@ -91,12 +92,14 @@ func (r *Route) Copy() *Route {
|
||||
Network: r.Network,
|
||||
NetworkType: r.NetworkType,
|
||||
Peer: r.Peer,
|
||||
PeerGroups: make([]string, len(r.PeerGroups)),
|
||||
Metric: r.Metric,
|
||||
Masquerade: r.Masquerade,
|
||||
Enabled: r.Enabled,
|
||||
Groups: make([]string, len(r.Groups)),
|
||||
}
|
||||
copy(route.Groups, r.Groups)
|
||||
copy(route.PeerGroups, r.PeerGroups)
|
||||
return route
|
||||
}
|
||||
|
||||
@@ -111,7 +114,8 @@ func (r *Route) IsEqual(other *Route) bool {
|
||||
other.Metric == r.Metric &&
|
||||
other.Masquerade == r.Masquerade &&
|
||||
other.Enabled == r.Enabled &&
|
||||
compareGroupsList(r.Groups, other.Groups)
|
||||
compareList(r.Groups, other.Groups) &&
|
||||
compareList(r.PeerGroups, other.PeerGroups)
|
||||
}
|
||||
|
||||
// ParseNetwork Parses a network prefix string and returns a netip.Prefix object and if is invalid, IPv4 or IPv6
|
||||
@@ -134,7 +138,7 @@ func ParseNetwork(networkString string) (NetworkType, netip.Prefix, error) {
|
||||
return IPv4Network, masked, nil
|
||||
}
|
||||
|
||||
func compareGroupsList(list, other []string) bool {
|
||||
func compareList(list, other []string) bool {
|
||||
if len(list) != len(other) {
|
||||
return false
|
||||
}
|
||||
|
||||
64
util/file.go
64
util/file.go
@@ -5,6 +5,8 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// WriteJson writes JSON config object to a file creating parent directories if required
|
||||
@@ -54,6 +56,68 @@ func WriteJson(file string, obj interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DirectWriteJson writes JSON config object to a file creating parent directories if required without creating a temporary file
|
||||
func DirectWriteJson(file string, obj interface{}) error {
|
||||
|
||||
_, _, err := prepareConfigFileDir(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetFile, err := openOrCreateFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = targetFile.Close()
|
||||
if err != nil {
|
||||
log.Errorf("failed to close file %s: %v", file, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// make it pretty
|
||||
bs, err := json.MarshalIndent(obj, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = targetFile.Truncate(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = targetFile.Write(bs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func openOrCreateFile(file string) (*os.File, error) {
|
||||
s, err := os.Stat(file)
|
||||
if err == nil {
|
||||
return os.OpenFile(file, os.O_WRONLY, s.Mode())
|
||||
}
|
||||
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetFile, err := os.Create(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//no:lint
|
||||
err = targetFile.Chmod(0640)
|
||||
if err != nil {
|
||||
_ = targetFile.Close()
|
||||
return nil, err
|
||||
}
|
||||
return targetFile, nil
|
||||
}
|
||||
|
||||
// ReadJson reads JSON config file and maps to a provided interface
|
||||
func ReadJson(file string, res interface{}) (interface{}, error) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user