mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-27 04:36:37 +00:00
Compare commits
52 Commits
handle-use
...
test/dns-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65052e5cba | ||
|
|
64084ca130 | ||
|
|
79f60b86c4 | ||
|
|
de46393a7c | ||
|
|
c4c59ed3a7 | ||
|
|
7f958e9338 | ||
|
|
91b45eab98 | ||
|
|
ec8eb76b42 | ||
|
|
8b8e4bbc6a | ||
|
|
1219006a6e | ||
|
|
4791e41004 | ||
|
|
9131069d12 | ||
|
|
26bbc33e7a | ||
|
|
35bc493cc3 | ||
|
|
e26ec0b937 | ||
|
|
a952e7c72f | ||
|
|
22f69d7852 | ||
|
|
b23011fbe8 | ||
|
|
6ad3894a51 | ||
|
|
c81b83b346 | ||
|
|
8c5c6815e0 | ||
|
|
0c470e7838 | ||
|
|
8118d60ffb | ||
|
|
1956ca169e | ||
|
|
830dee1771 | ||
|
|
c08a96770e | ||
|
|
c6bf1c7f26 | ||
|
|
5f499d66b2 | ||
|
|
7c065bd9fc | ||
|
|
ab849f0942 | ||
|
|
aa1d31bde6 | ||
|
|
5b4dc4dd47 | ||
|
|
e733cdcf33 | ||
|
|
1324169ebb | ||
|
|
732afd8393 | ||
|
|
da7b6b11ad | ||
|
|
e260270825 | ||
|
|
cdbe9c4eef | ||
|
|
8653c32367 | ||
|
|
d4b6d7646c | ||
|
|
8febab4076 | ||
|
|
34e2c6b943 | ||
|
|
0be8c72601 | ||
|
|
c34e53477f | ||
|
|
8d18190c94 | ||
|
|
06bec61be9 | ||
|
|
2135533f1d | ||
|
|
bb791d59f3 | ||
|
|
30f1c54ed1 | ||
|
|
5c8541ef42 | ||
|
|
6743054451 | ||
|
|
7f7e10121d |
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
|
||||||
25
.github/workflows/test-infrastructure-files.yml
vendored
25
.github/workflows/test-infrastructure-files.yml
vendored
@@ -80,6 +80,7 @@ jobs:
|
|||||||
CI_NETBIRD_MGMT_IDP: "none"
|
CI_NETBIRD_MGMT_IDP: "none"
|
||||||
CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id
|
CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id
|
||||||
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
||||||
|
CI_NETBIRD_SIGNAL_PORT: 12345
|
||||||
|
|
||||||
run: |
|
run: |
|
||||||
grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
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 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_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 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 LETSENCRYPT_DOMAIN docker-compose.yml | egrep 'LETSENCRYPT_DOMAIN=$'
|
||||||
grep NETBIRD_TOKEN_SOURCE docker-compose.yml | grep $CI_NETBIRD_TOKEN_SOURCE
|
grep NETBIRD_TOKEN_SOURCE docker-compose.yml | grep $CI_NETBIRD_TOKEN_SOURCE
|
||||||
grep AuthUserIDClaim management.json | grep $CI_NETBIRD_AUTH_USER_ID_CLAIM
|
grep AuthUserIDClaim management.json | grep $CI_NETBIRD_AUTH_USER_ID_CLAIM
|
||||||
@@ -110,6 +112,27 @@ jobs:
|
|||||||
grep -A 6 PKCEAuthorizationFlow management.json | grep -A 5 ProviderConfig | grep TokenEndpoint | grep $CI_NETBIRD_AUTH_TOKEN_ENDPOINT
|
grep -A 6 PKCEAuthorizationFlow management.json | grep -A 5 ProviderConfig | grep TokenEndpoint | grep $CI_NETBIRD_AUTH_TOKEN_ENDPOINT
|
||||||
grep -A 7 PKCEAuthorizationFlow management.json | grep -A 6 ProviderConfig | grep Scope | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES"
|
grep -A 7 PKCEAuthorizationFlow management.json | grep -A 6 ProviderConfig | grep Scope | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES"
|
||||||
|
|
||||||
|
- name: Install modules
|
||||||
|
run: go mod tidy
|
||||||
|
|
||||||
|
- name: Build management binary
|
||||||
|
working-directory: management
|
||||||
|
run: CGO_ENABLED=1 go build -o netbird-mgmt main.go
|
||||||
|
|
||||||
|
- name: Build management docker image
|
||||||
|
working-directory: management
|
||||||
|
run: |
|
||||||
|
docker build -t netbirdio/management:latest .
|
||||||
|
|
||||||
|
- name: Build signal binary
|
||||||
|
working-directory: signal
|
||||||
|
run: CGO_ENABLED=0 go build -o netbird-signal main.go
|
||||||
|
|
||||||
|
- name: Build signal docker image
|
||||||
|
working-directory: signal
|
||||||
|
run: |
|
||||||
|
docker build -t netbirdio/signal:latest .
|
||||||
|
|
||||||
- name: run docker compose up
|
- name: run docker compose up
|
||||||
working-directory: infrastructure_files
|
working-directory: infrastructure_files
|
||||||
run: |
|
run: |
|
||||||
@@ -120,7 +143,7 @@ jobs:
|
|||||||
|
|
||||||
- name: test running containers
|
- name: test running containers
|
||||||
run: |
|
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
|
test $count -eq 4
|
||||||
working-directory: infrastructure_files
|
working-directory: infrastructure_files
|
||||||
|
|
||||||
|
|||||||
@@ -84,10 +84,14 @@ func (a *Auth) SaveConfigIfSSOSupported(listener SSOListener) {
|
|||||||
func (a *Auth) saveConfigIfSSOSupported() (bool, error) {
|
func (a *Auth) saveConfigIfSSOSupported() (bool, error) {
|
||||||
supportsSSO := true
|
supportsSSO := true
|
||||||
err := a.withBackOff(a.ctx, func() (err error) {
|
err := a.withBackOff(a.ctx, func() (err error) {
|
||||||
_, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
|
_, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
|
||||||
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound {
|
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) {
|
||||||
_, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
|
_, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
|
||||||
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound {
|
s, ok := gstatus.FromError(err)
|
||||||
|
if !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s.Code() == codes.NotFound || s.Code() == codes.Unimplemented {
|
||||||
supportsSSO = false
|
supportsSSO = false
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
@@ -189,7 +193,7 @@ func (a *Auth) login(urlOpener URLOpener) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -82,9 +81,10 @@ var loginCmd = &cobra.Command{
|
|||||||
client := proto.NewDaemonServiceClient(conn)
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
|
||||||
loginRequest := proto.LoginRequest{
|
loginRequest := proto.LoginRequest{
|
||||||
SetupKey: setupKey,
|
SetupKey: setupKey,
|
||||||
PreSharedKey: preSharedKey,
|
PreSharedKey: preSharedKey,
|
||||||
ManagementUrl: managementURL,
|
ManagementUrl: managementURL,
|
||||||
|
IsLinuxDesktopClient: isLinuxRunningDesktop(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var loginErr error
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
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" +
|
"If your browser didn't open automatically, use this URL to log in:\n\n" +
|
||||||
verificationURIComplete + " " + codeMsg
|
verificationURIComplete + " " + codeMsg)
|
||||||
|
cmd.Println("")
|
||||||
setupKeyAuthMsg := "\nAlternatively, you may want to use a setup key, see:\n\n" +
|
if err := open.Run(verificationURIComplete); err != nil {
|
||||||
"https://docs.netbird.io/how-to/register-machines-using-setup-keys"
|
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")
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isLinuxRunningDesktop checks if a Linux OS is running desktop environment.
|
// isLinuxRunningDesktop checks if a Linux OS is running desktop environment
|
||||||
func isLinuxRunningDesktop() bool {
|
func isLinuxRunningDesktop() bool {
|
||||||
for _, env := range os.Environ() {
|
return os.Getenv("DESKTOP_SESSION") != "" || os.Getenv("XDG_CURRENT_DESKTOP") != ""
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
|
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||||
eventStore)
|
eventStore, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,13 +141,14 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loginRequest := proto.LoginRequest{
|
loginRequest := proto.LoginRequest{
|
||||||
SetupKey: setupKey,
|
SetupKey: setupKey,
|
||||||
PreSharedKey: preSharedKey,
|
PreSharedKey: preSharedKey,
|
||||||
ManagementUrl: managementURL,
|
ManagementUrl: managementURL,
|
||||||
AdminURL: adminURL,
|
AdminURL: adminURL,
|
||||||
NatExternalIPs: natExternalIPs,
|
NatExternalIPs: natExternalIPs,
|
||||||
CleanNATExternalIPs: natExternalIPs != nil && len(natExternalIPs) == 0,
|
CleanNATExternalIPs: natExternalIPs != nil && len(natExternalIPs) == 0,
|
||||||
CustomDNSAddress: customDNSAddressConverted,
|
CustomDNSAddress: customDNSAddressConverted,
|
||||||
|
IsLinuxDesktopClient: isLinuxRunningDesktop(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var loginErr error
|
var loginErr error
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func Create(wgIface iFaceMapper, ipv6Supported bool) (*Manager, error) {
|
|||||||
|
|
||||||
// AddFiltering rule to the firewall
|
// 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(
|
func (m *Manager) AddFiltering(
|
||||||
ip net.IP,
|
ip net.IP,
|
||||||
protocol fw.Protocol,
|
protocol fw.Protocol,
|
||||||
@@ -123,9 +123,6 @@ func (m *Manager) AddFiltering(
|
|||||||
ipsetName = m.transformIPsetName(ipsetName, sPortVal, dPortVal)
|
ipsetName = m.transformIPsetName(ipsetName, sPortVal, dPortVal)
|
||||||
|
|
||||||
ruleID := uuid.New().String()
|
ruleID := uuid.New().String()
|
||||||
if comment == "" {
|
|
||||||
comment = ruleID
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipsetName != "" {
|
if ipsetName != "" {
|
||||||
rs, rsExists := m.rulesets[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
|
// this is new ipset so we need to create firewall rule for it
|
||||||
}
|
}
|
||||||
|
|
||||||
specs := m.filterRuleSpecs("filter", ip, string(protocol), sPortVal, dPortVal,
|
specs := m.filterRuleSpecs(ip, string(protocol), sPortVal, dPortVal, direction, action, ipsetName)
|
||||||
direction, action, comment, ipsetName)
|
|
||||||
|
|
||||||
if direction == fw.RuleDirectionOUT {
|
if direction == fw.RuleDirectionOUT {
|
||||||
ok, err := client.Exists("filter", ChainOutputFilterName, specs...)
|
ok, err := client.Exists("filter", ChainOutputFilterName, specs...)
|
||||||
@@ -283,7 +279,7 @@ func (m *Manager) AllowNetbird() error {
|
|||||||
fw.RuleDirectionIN,
|
fw.RuleDirectionIN,
|
||||||
fw.ActionAccept,
|
fw.ActionAccept,
|
||||||
"",
|
"",
|
||||||
"allow netbird interface traffic",
|
"",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to allow netbird interface traffic: %w", err)
|
return fmt.Errorf("failed to allow netbird interface traffic: %w", err)
|
||||||
@@ -296,7 +292,7 @@ func (m *Manager) AllowNetbird() error {
|
|||||||
fw.RuleDirectionOUT,
|
fw.RuleDirectionOUT,
|
||||||
fw.ActionAccept,
|
fw.ActionAccept,
|
||||||
"",
|
"",
|
||||||
"allow netbird interface traffic",
|
"",
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -362,9 +358,7 @@ func (m *Manager) reset(client *iptables.IPTables, table string) error {
|
|||||||
|
|
||||||
// filterRuleSpecs returns the specs of a filtering rule
|
// filterRuleSpecs returns the specs of a filtering rule
|
||||||
func (m *Manager) filterRuleSpecs(
|
func (m *Manager) filterRuleSpecs(
|
||||||
table string, ip net.IP, protocol string, sPort, dPort string,
|
ip net.IP, protocol string, sPort, dPort string, direction fw.RuleDirection, action fw.Action, ipsetName string,
|
||||||
direction fw.RuleDirection, action fw.Action, comment string,
|
|
||||||
ipsetName string,
|
|
||||||
) (specs []string) {
|
) (specs []string) {
|
||||||
matchByIP := true
|
matchByIP := true
|
||||||
// don't use IP matching if IP is ip 0.0.0.0
|
// don't use IP matching if IP is ip 0.0.0.0
|
||||||
@@ -398,8 +392,7 @@ func (m *Manager) filterRuleSpecs(
|
|||||||
if dPort != "" {
|
if dPort != "" {
|
||||||
specs = append(specs, "--dport", dPort)
|
specs = append(specs, "--dport", dPort)
|
||||||
}
|
}
|
||||||
specs = append(specs, "-j", m.actionToStr(action))
|
return append(specs, "-j", m.actionToStr(action))
|
||||||
return append(specs, "-m", "comment", "--comment", comment)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rawClient returns corresponding iptables client for the given ip
|
// rawClient returns corresponding iptables client for the given ip
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !linux
|
//go:build !linux || android
|
||||||
|
|
||||||
package acl
|
package acl
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !android
|
||||||
|
|
||||||
package acl
|
package acl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -57,25 +58,45 @@ func (t TokenInfo) GetTokenToUse() string {
|
|||||||
return t.AccessToken
|
return t.AccessToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOAuthFlow initializes and returns the appropriate OAuth flow based on the management configuration.
|
// 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")
|
// 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
|
||||||
pkceFlowInfo, err := internal.GetPKCEAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
|
//
|
||||||
if err == nil {
|
// On Linux distros without desktop environment support, it only tries to initialize the Device Code Flow
|
||||||
return NewPKCEAuthorizationFlow(pkceFlowInfo.ProviderConfig)
|
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)
|
pkceFlow, err := authenticateWithPKCEFlow(ctx, config)
|
||||||
log.Debugf("falling back to device authorization flow info")
|
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)
|
deviceFlowInfo, err := internal.GetDeviceAuthorizationFlowInfo(ctx, config.PrivateKey, config.ManagementURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s, ok := gstatus.FromError(err)
|
s, ok := gstatus.FromError(err)
|
||||||
if ok && s.Code() == codes.NotFound {
|
if ok && s.Code() == codes.NotFound {
|
||||||
return nil, fmt.Errorf("no SSO provider returned from management. " +
|
return nil, fmt.Errorf("no SSO provider returned from management. " +
|
||||||
"If you are using hosting Netbird see documentation at " +
|
"Please proceed with setting up this device using setup keys " +
|
||||||
"https://github.com/netbirdio/netbird/tree/main/management for details")
|
"https://docs.netbird.io/how-to/register-machines-using-setup-keys")
|
||||||
} else if ok && s.Code() == codes.Unimplemented {
|
} else if ok && s.Code() == codes.Unimplemented {
|
||||||
return nil, fmt.Errorf("the management server, %s, does not support SSO providers, "+
|
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)
|
"please update your server or use Setup Keys to login", config.ManagementURL)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -80,7 +79,7 @@ func (p *PKCEAuthorizationFlow) GetClientID(_ context.Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RequestAuthInfo requests a authorization code login flow information.
|
// 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)
|
state, err := randomBytesInHex(24)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AuthFlowInfo{}, fmt.Errorf("could not generate random state: %v", err)
|
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)
|
tokenChan := make(chan *oauth2.Token, 1)
|
||||||
errChan := make(chan error, 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 {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return TokenInfo{}, ctx.Err()
|
return TokenInfo{}, ctx.Err()
|
||||||
case token := <-tokenChan:
|
case token := <-tokenChan:
|
||||||
return p.handleOAuthToken(token)
|
return p.parseOAuthToken(token)
|
||||||
case err := <-errChan:
|
case err := <-errChan:
|
||||||
return TokenInfo{}, err
|
return TokenInfo{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PKCEAuthorizationFlow) startServer(tokenChan chan<- *oauth2.Token, errChan chan<- error) {
|
func (p *PKCEAuthorizationFlow) startServer(server *http.Server, tokenChan chan<- *oauth2.Token, errChan chan<- error) {
|
||||||
var wg sync.WaitGroup
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
parsedURL, err := url.Parse(p.oAuthConfig.RedirectURL)
|
token, err := p.handleRequest(req)
|
||||||
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()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderPKCEFlowTmpl(w, err)
|
renderPKCEFlowTmpl(w, err)
|
||||||
errChan <- fmt.Errorf("PKCE authorization flow failed: %v", 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
|
tokenChan <- token
|
||||||
})
|
})
|
||||||
|
|
||||||
wg.Wait()
|
server.Handler = mux
|
||||||
if err := server.Shutdown(context.Background()); err != nil {
|
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Errorf("error while shutting down pkce flow server: %v", err)
|
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{
|
tokenInfo := TokenInfo{
|
||||||
AccessToken: token.AccessToken,
|
AccessToken: token.AccessToken,
|
||||||
RefreshToken: token.RefreshToken,
|
RefreshToken: token.RefreshToken,
|
||||||
@@ -200,7 +197,13 @@ func (p *PKCEAuthorizationFlow) handleOAuthToken(token *oauth2.Token) (TokenInfo
|
|||||||
tokenInfo.IDToken = idToken
|
tokenInfo.IDToken = idToken
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := isValidAccessToken(tokenInfo.GetTokenToUse(), p.providerConfig.Audience); err != nil {
|
// if a provider doesn't support an audience, use the Client ID for token verification
|
||||||
|
audience := p.providerConfig.Audience
|
||||||
|
if audience == "" {
|
||||||
|
audience = p.providerConfig.ClientID
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := isValidAccessToken(tokenInfo.GetTokenToUse(), audience); err != nil {
|
||||||
return TokenInfo{}, fmt.Errorf("validate access token failed with error: %v", err)
|
return TokenInfo{}, fmt.Errorf("validate access token failed with error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"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
|
// Audience claim of JWT can be a string or an array of strings
|
||||||
typ := reflect.TypeOf(claims.Audience)
|
switch aud := claims.Audience.(type) {
|
||||||
switch typ.Kind() {
|
case string:
|
||||||
case reflect.String:
|
if aud == audience {
|
||||||
if claims.Audience == audience {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case reflect.Slice:
|
case []interface{}:
|
||||||
for _, aud := range claims.Audience.([]interface{}) {
|
for _, audItem := range aud {
|
||||||
if audience == aud {
|
if audStr, ok := audItem.(string); ok && audStr == audience {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
//go:build !linux
|
//go:build !linux || android
|
||||||
|
|
||||||
package checkfw
|
package checkfw
|
||||||
|
|||||||
@@ -42,6 +42,16 @@ func RunClientMobile(ctx context.Context, config *Config, statusRecorder *peer.S
|
|||||||
return runClient(ctx, config, statusRecorder, mobileDependency)
|
return runClient(ctx, config, statusRecorder, mobileDependency)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RunClientiOS(ctx context.Context, config *Config, statusRecorder *peer.Status, fileDescriptor int32, routeListener routemanager.RouteListener, dnsManager dns.IosDnsManager, interfaceName string) error {
|
||||||
|
mobileDependency := MobileDependency{
|
||||||
|
FileDescriptor: fileDescriptor,
|
||||||
|
InterfaceName: interfaceName,
|
||||||
|
RouteListener: routeListener,
|
||||||
|
DnsManager: dnsManager,
|
||||||
|
}
|
||||||
|
return runClient(ctx, config, statusRecorder, mobileDependency)
|
||||||
|
}
|
||||||
|
|
||||||
func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status, mobileDependency MobileDependency) error {
|
func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status, mobileDependency MobileDependency) error {
|
||||||
backOff := &backoff.ExponentialBackOff{
|
backOff := &backoff.ExponentialBackOff{
|
||||||
InitialInterval: time.Second,
|
InitialInterval: time.Second,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (f *fileConfigurator) supportCustomPort() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fileConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||||
backupFileExist := false
|
backupFileExist := false
|
||||||
_, err := os.Stat(fileDefaultResolvConfBackupLocation)
|
_, err := os.Stat(fileDefaultResolvConfBackupLocation)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type hostManager interface {
|
type hostManager interface {
|
||||||
applyDNSConfig(config hostDNSConfig) error
|
applyDNSConfig(config HostDNSConfig) error
|
||||||
restoreHostDNS() error
|
restoreHostDNS() error
|
||||||
supportCustomPort() bool
|
supportCustomPort() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type hostDNSConfig struct {
|
type HostDNSConfig struct {
|
||||||
domains []domainConfig
|
domains []domainConfig
|
||||||
routeAll bool
|
routeAll bool
|
||||||
serverIP string
|
serverIP string
|
||||||
@@ -27,12 +27,12 @@ type domainConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type mockHostConfigurator struct {
|
type mockHostConfigurator struct {
|
||||||
applyDNSConfigFunc func(config hostDNSConfig) error
|
applyDNSConfigFunc func(config HostDNSConfig) error
|
||||||
restoreHostDNSFunc func() error
|
restoreHostDNSFunc func() error
|
||||||
supportCustomPortFunc func() bool
|
supportCustomPortFunc func() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockHostConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
func (m *mockHostConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||||
if m.applyDNSConfigFunc != nil {
|
if m.applyDNSConfigFunc != nil {
|
||||||
return m.applyDNSConfigFunc(config)
|
return m.applyDNSConfigFunc(config)
|
||||||
}
|
}
|
||||||
@@ -55,14 +55,14 @@ func (m *mockHostConfigurator) supportCustomPort() bool {
|
|||||||
|
|
||||||
func newNoopHostMocker() hostManager {
|
func newNoopHostMocker() hostManager {
|
||||||
return &mockHostConfigurator{
|
return &mockHostConfigurator{
|
||||||
applyDNSConfigFunc: func(config hostDNSConfig) error { return nil },
|
applyDNSConfigFunc: func(config HostDNSConfig) error { return nil },
|
||||||
restoreHostDNSFunc: func() error { return nil },
|
restoreHostDNSFunc: func() error { return nil },
|
||||||
supportCustomPortFunc: func() bool { return true },
|
supportCustomPortFunc: func() bool { return true },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dnsConfigToHostDNSConfig(dnsConfig nbdns.Config, ip string, port int) hostDNSConfig {
|
func dnsConfigToHostDNSConfig(dnsConfig nbdns.Config, ip string, port int) HostDNSConfig {
|
||||||
config := hostDNSConfig{
|
config := HostDNSConfig{
|
||||||
routeAll: false,
|
routeAll: false,
|
||||||
serverIP: ip,
|
serverIP: ip,
|
||||||
serverPort: port,
|
serverPort: port,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ func newHostManager(wgInterface WGIface) (hostManager, error) {
|
|||||||
return &androidHostManager{}, nil
|
return &androidHostManager{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a androidHostManager) applyDNSConfig(config hostDNSConfig) error {
|
func (a androidHostManager) applyDNSConfig(config HostDNSConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build !ios
|
||||||
|
|
||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -42,7 +44,7 @@ func (s *systemConfigurator) supportCustomPort() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *systemConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if config.routeAll {
|
if config.routeAll {
|
||||||
|
|||||||
48
client/internal/dns/host_ios.go
Normal file
48
client/internal/dns/host_ios.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type iosHostManager struct {
|
||||||
|
dnsManager IosDnsManager
|
||||||
|
config HostDNSConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHostManager(wgInterface WGIface, dnsManager IosDnsManager) (hostManager, error) {
|
||||||
|
return &iosHostManager{
|
||||||
|
dnsManager: dnsManager,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a iosHostManager) applyDNSConfig(config HostDNSConfig) error {
|
||||||
|
var configAsString []string
|
||||||
|
configAsString = append(configAsString, config.serverIP)
|
||||||
|
configAsString = append(configAsString, strconv.Itoa(config.serverPort))
|
||||||
|
configAsString = append(configAsString, strconv.FormatBool(config.routeAll))
|
||||||
|
var domainConfigAsString []string
|
||||||
|
for _, domain := range config.domains {
|
||||||
|
var domainAsString []string
|
||||||
|
domainAsString = append(domainAsString, strconv.FormatBool(domain.disabled))
|
||||||
|
domainAsString = append(domainAsString, domain.domain)
|
||||||
|
domainAsString = append(domainAsString, strconv.FormatBool(domain.matchOnly))
|
||||||
|
domainConfigAsString = append(domainConfigAsString, strings.Join(domainAsString, "|"))
|
||||||
|
}
|
||||||
|
domainConfig := strings.Join(domainConfigAsString, ";")
|
||||||
|
configAsString = append(configAsString, domainConfig)
|
||||||
|
outputString := strings.Join(configAsString, ",")
|
||||||
|
log.Debug("applyDNSConfig: " + outputString)
|
||||||
|
a.dnsManager.ApplyDns(outputString)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a iosHostManager) restoreHostDNS() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a iosHostManager) supportCustomPort() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -45,7 +45,7 @@ func (s *registryConfigurator) supportCustomPort() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *registryConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||||
var err error
|
var err error
|
||||||
if config.routeAll {
|
if config.routeAll {
|
||||||
err = r.addDNSSetupForAll(config.serverIP)
|
err = r.addDNSSetupForAll(config.serverIP)
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func (n *networkManagerDbusConfigurator) supportCustomPort() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *networkManagerDbusConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
func (n *networkManagerDbusConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||||
connSettings, configVersion, err := n.getAppliedConnectionSettings()
|
connSettings, configVersion, err := n.getAppliedConnectionSettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("got an error while retrieving the applied connection settings, error: %s", err)
|
return fmt.Errorf("got an error while retrieving the applied connection settings, error: %s", err)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (r *resolvconf) supportCustomPort() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resolvconf) applyDNSConfig(config hostDNSConfig) error {
|
func (r *resolvconf) applyDNSConfig(config HostDNSConfig) error {
|
||||||
var err error
|
var err error
|
||||||
if !config.routeAll {
|
if !config.routeAll {
|
||||||
err = r.restoreHostDNS()
|
err = r.restoreHostDNS()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@@ -18,9 +19,14 @@ type ReadyListener interface {
|
|||||||
OnReady()
|
OnReady()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IosDnsManager is a dns manager interface for iosß
|
||||||
|
type IosDnsManager interface {
|
||||||
|
ApplyDns(string)
|
||||||
|
}
|
||||||
|
|
||||||
// Server is a dns server interface
|
// Server is a dns server interface
|
||||||
type Server interface {
|
type Server interface {
|
||||||
Initialize() error
|
Initialize(manager IosDnsManager) error
|
||||||
Stop()
|
Stop()
|
||||||
DnsIP() string
|
DnsIP() string
|
||||||
UpdateDNSServer(serial uint64, update nbdns.Config) error
|
UpdateDNSServer(serial uint64, update nbdns.Config) error
|
||||||
@@ -41,12 +47,15 @@ type DefaultServer struct {
|
|||||||
hostManager hostManager
|
hostManager hostManager
|
||||||
updateSerial uint64
|
updateSerial uint64
|
||||||
previousConfigHash uint64
|
previousConfigHash uint64
|
||||||
currentConfig hostDNSConfig
|
currentConfig HostDNSConfig
|
||||||
|
|
||||||
// permanent related properties
|
// permanent related properties
|
||||||
permanent bool
|
permanent bool
|
||||||
hostsDnsList []string
|
hostsDnsList []string
|
||||||
hostsDnsListLock sync.Mutex
|
hostsDnsListLock sync.Mutex
|
||||||
|
|
||||||
|
interfaceName string
|
||||||
|
wgAddr string
|
||||||
}
|
}
|
||||||
|
|
||||||
type handlerWithStop interface {
|
type handlerWithStop interface {
|
||||||
@@ -60,7 +69,7 @@ type muxUpdate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultServer returns a new dns server
|
// NewDefaultServer returns a new dns server
|
||||||
func NewDefaultServer(ctx context.Context, wgInterface WGIface, customAddress string) (*DefaultServer, error) {
|
func NewDefaultServer(ctx context.Context, wgInterface WGIface, customAddress string, interfaceName string, wgAddr string) (*DefaultServer, error) {
|
||||||
var addrPort *netip.AddrPort
|
var addrPort *netip.AddrPort
|
||||||
if customAddress != "" {
|
if customAddress != "" {
|
||||||
parsedAddrPort, err := netip.ParseAddrPort(customAddress)
|
parsedAddrPort, err := netip.ParseAddrPort(customAddress)
|
||||||
@@ -77,13 +86,13 @@ func NewDefaultServer(ctx context.Context, wgInterface WGIface, customAddress st
|
|||||||
dnsService = newServiceViaListener(wgInterface, addrPort)
|
dnsService = newServiceViaListener(wgInterface, addrPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newDefaultServer(ctx, wgInterface, dnsService), nil
|
return newDefaultServer(ctx, wgInterface, dnsService, interfaceName, wgAddr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultServerPermanentUpstream returns a new dns server. It optimized for mobile systems
|
// NewDefaultServerPermanentUpstream returns a new dns server. It optimized for mobile systems
|
||||||
func NewDefaultServerPermanentUpstream(ctx context.Context, wgInterface WGIface, hostsDnsList []string) *DefaultServer {
|
func NewDefaultServerPermanentUpstream(ctx context.Context, wgInterface WGIface, hostsDnsList []string) *DefaultServer {
|
||||||
log.Debugf("host dns address list is: %v", hostsDnsList)
|
log.Debugf("host dns address list is: %v", hostsDnsList)
|
||||||
ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface))
|
ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface), "", "")
|
||||||
ds.permanent = true
|
ds.permanent = true
|
||||||
ds.hostsDnsList = hostsDnsList
|
ds.hostsDnsList = hostsDnsList
|
||||||
ds.addHostRootZone()
|
ds.addHostRootZone()
|
||||||
@@ -91,7 +100,7 @@ func NewDefaultServerPermanentUpstream(ctx context.Context, wgInterface WGIface,
|
|||||||
return ds
|
return ds
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService service) *DefaultServer {
|
func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService service, interfaceName string, wgAddr string) *DefaultServer {
|
||||||
ctx, stop := context.WithCancel(ctx)
|
ctx, stop := context.WithCancel(ctx)
|
||||||
defaultServer := &DefaultServer{
|
defaultServer := &DefaultServer{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -101,14 +110,16 @@ func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService servi
|
|||||||
localResolver: &localResolver{
|
localResolver: &localResolver{
|
||||||
registeredMap: make(registrationMap),
|
registeredMap: make(registrationMap),
|
||||||
},
|
},
|
||||||
wgInterface: wgInterface,
|
wgInterface: wgInterface,
|
||||||
|
interfaceName: interfaceName,
|
||||||
|
wgAddr: wgAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultServer
|
return defaultServer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize instantiate host manager and the dns service
|
// Initialize instantiate host manager and the dns service
|
||||||
func (s *DefaultServer) Initialize() (err error) {
|
func (s *DefaultServer) Initialize(manager IosDnsManager) (err error) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
@@ -123,7 +134,11 @@ func (s *DefaultServer) Initialize() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.hostManager, err = newHostManager(s.wgInterface)
|
if runtime.GOOS == "ios" {
|
||||||
|
s.hostManager, err = newHostManager(nil, manager)
|
||||||
|
} else {
|
||||||
|
s.hostManager, err = newHostManager(s.wgInterface, nil)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,7 +300,7 @@ func (s *DefaultServer) buildUpstreamHandlerUpdate(nameServerGroups []*nbdns.Nam
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := newUpstreamResolver(s.ctx)
|
handler := newUpstreamResolver(s.ctx, s.interfaceName, s.wgAddr)
|
||||||
for _, ns := range nsGroup.NameServers {
|
for _, ns := range nsGroup.NameServers {
|
||||||
if ns.NSType != nbdns.UDPNameServerType {
|
if ns.NSType != nbdns.UDPNameServerType {
|
||||||
log.Warnf("skiping nameserver %s with type %s, this peer supports only %s",
|
log.Warnf("skiping nameserver %s with type %s, this peer supports only %s",
|
||||||
@@ -458,7 +473,7 @@ func (s *DefaultServer) upstreamCallbacks(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultServer) addHostRootZone() {
|
func (s *DefaultServer) addHostRootZone() {
|
||||||
handler := newUpstreamResolver(s.ctx)
|
handler := newUpstreamResolver(s.ctx, s.interfaceName, s.wgAddr)
|
||||||
handler.upstreamServers = make([]string, len(s.hostsDnsList))
|
handler.upstreamServers = make([]string, len(s.hostsDnsList))
|
||||||
for n, ua := range s.hostsDnsList {
|
for n, ua := range s.hostsDnsList {
|
||||||
handler.upstreamServers[n] = fmt.Sprintf("%s:53", ua)
|
handler.upstreamServers[n] = fmt.Sprintf("%s:53", ua)
|
||||||
|
|||||||
@@ -527,7 +527,7 @@ func TestDNSServerUpstreamDeactivateCallback(t *testing.T) {
|
|||||||
registeredMap: make(registrationMap),
|
registeredMap: make(registrationMap),
|
||||||
},
|
},
|
||||||
hostManager: hostManager,
|
hostManager: hostManager,
|
||||||
currentConfig: hostDNSConfig{
|
currentConfig: HostDNSConfig{
|
||||||
domains: []domainConfig{
|
domains: []domainConfig{
|
||||||
{false, "domain0", false},
|
{false, "domain0", false},
|
||||||
{false, "domain1", false},
|
{false, "domain1", false},
|
||||||
@@ -537,7 +537,7 @@ func TestDNSServerUpstreamDeactivateCallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var domainsUpdate string
|
var domainsUpdate string
|
||||||
hostManager.applyDNSConfigFunc = func(config hostDNSConfig) error {
|
hostManager.applyDNSConfigFunc = func(config HostDNSConfig) error {
|
||||||
domains := []string{}
|
domains := []string{}
|
||||||
for _, item := range config.domains {
|
for _, item := range config.domains {
|
||||||
if item.disabled {
|
if item.disabled {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func (s *systemdDbusConfigurator) supportCustomPort() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *systemdDbusConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
func (s *systemdDbusConfigurator) applyDNSConfig(config HostDNSConfig) error {
|
||||||
parsedIP, err := netip.ParseAddr(config.serverIP)
|
parsedIP, err := netip.ParseAddr(config.serverIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to parse ip address, error: %s", err)
|
return fmt.Errorf("unable to parse ip address, error: %s", err)
|
||||||
|
|||||||
@@ -5,13 +5,16 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -35,23 +38,93 @@ type upstreamResolver struct {
|
|||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
reactivatePeriod time.Duration
|
reactivatePeriod time.Duration
|
||||||
upstreamTimeout time.Duration
|
upstreamTimeout time.Duration
|
||||||
|
lIP net.IP
|
||||||
|
lName string
|
||||||
|
iIndex int
|
||||||
|
|
||||||
deactivate func()
|
deactivate func()
|
||||||
reactivate func()
|
reactivate func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUpstreamResolver(parentCTX context.Context) *upstreamResolver {
|
// func newUpstreamResolver(parentCTX context.Context) *upstreamResolver {
|
||||||
|
// ctx, cancel := context.WithCancel(parentCTX)
|
||||||
|
// return &upstreamResolver{
|
||||||
|
// ctx: ctx,
|
||||||
|
// cancel: cancel,
|
||||||
|
// upstreamClient: &dns.Client{},
|
||||||
|
// upstreamTimeout: upstreamTimeout,
|
||||||
|
// reactivatePeriod: reactivatePeriod,
|
||||||
|
// failsTillDeact: failsTillDeact,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
func getInterfaceIndex(interfaceName string) (int, error) {
|
||||||
|
iface, err := net.InterfaceByName(interfaceName)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to get interface by name error: %s", err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return iface.Index, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUpstreamResolver(parentCTX context.Context, interfaceName string, wgAddr string) *upstreamResolver {
|
||||||
ctx, cancel := context.WithCancel(parentCTX)
|
ctx, cancel := context.WithCancel(parentCTX)
|
||||||
|
|
||||||
|
// Specify the local IP address you want to bind to
|
||||||
|
localIP, _, err := net.ParseCIDR(wgAddr) // Should be our interface IP
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error while parsing CIDR: %s", err)
|
||||||
|
}
|
||||||
|
index, err := getInterfaceIndex(interfaceName)
|
||||||
|
log.Debugf("UpstreamResolver interface name: %s, index: %d, ip: %s", interfaceName, index, localIP)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("unable to get interface index for %s: %s", interfaceName, err)
|
||||||
|
}
|
||||||
|
localIFaceIndex := index // Should be our interface index
|
||||||
|
|
||||||
return &upstreamResolver{
|
return &upstreamResolver{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
upstreamClient: &dns.Client{},
|
|
||||||
upstreamTimeout: upstreamTimeout,
|
upstreamTimeout: upstreamTimeout,
|
||||||
reactivatePeriod: reactivatePeriod,
|
reactivatePeriod: reactivatePeriod,
|
||||||
failsTillDeact: failsTillDeact,
|
failsTillDeact: failsTillDeact,
|
||||||
|
lIP: localIP,
|
||||||
|
iIndex: localIFaceIndex,
|
||||||
|
lName: interfaceName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *upstreamResolver) getClient() *dns.Client {
|
||||||
|
dialer := &net.Dialer{
|
||||||
|
LocalAddr: &net.UDPAddr{
|
||||||
|
IP: u.lIP,
|
||||||
|
Port: 0, // Let the OS pick a free port
|
||||||
|
},
|
||||||
|
Timeout: upstreamTimeout,
|
||||||
|
Control: func(network, address string, c syscall.RawConn) error {
|
||||||
|
var operr error
|
||||||
|
fn := func(s uintptr) {
|
||||||
|
operr = unix.SetsockoptInt(int(s), unix.IPPROTO_IP, unix.IP_BOUND_IF, u.iIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Control(fn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if operr != nil {
|
||||||
|
log.Errorf("error while setting socket option: %s", operr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return operr
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := &dns.Client{
|
||||||
|
Dialer: dialer,
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
func (u *upstreamResolver) stop() {
|
func (u *upstreamResolver) stop() {
|
||||||
log.Debugf("stoping serving DNS for upstreams %s", u.upstreamServers)
|
log.Debugf("stoping serving DNS for upstreams %s", u.upstreamServers)
|
||||||
u.cancel()
|
u.cancel()
|
||||||
@@ -61,7 +134,7 @@ func (u *upstreamResolver) stop() {
|
|||||||
func (u *upstreamResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
func (u *upstreamResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
defer u.checkUpstreamFails()
|
defer u.checkUpstreamFails()
|
||||||
|
|
||||||
log.WithField("question", r.Question[0]).Trace("received an upstream question")
|
//log.WithField("question", r.Question[0]).Debug("received an upstream question")
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-u.ctx.Done():
|
case <-u.ctx.Done():
|
||||||
@@ -70,10 +143,20 @@ func (u *upstreamResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, upstream := range u.upstreamServers {
|
for _, upstream := range u.upstreamServers {
|
||||||
ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout)
|
var (
|
||||||
rm, t, err := u.upstreamClient.ExchangeContext(ctx, r, upstream)
|
err error
|
||||||
|
t time.Duration
|
||||||
cancel()
|
rm *dns.Msg
|
||||||
|
)
|
||||||
|
upstreamExchangeClient := u.getClient()
|
||||||
|
if runtime.GOOS != "ios" {
|
||||||
|
ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout)
|
||||||
|
rm, t, err = upstreamExchangeClient.ExchangeContext(ctx, r, upstream)
|
||||||
|
cancel()
|
||||||
|
} else {
|
||||||
|
log.Debugf("ios upstream resolver: %s", upstream)
|
||||||
|
rm, t, err = upstreamExchangeClient.Exchange(r, upstream)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == context.DeadlineExceeded || isTimeout(err) {
|
if err == context.DeadlineExceeded || isTimeout(err) {
|
||||||
@@ -83,11 +166,11 @@ func (u *upstreamResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
}
|
}
|
||||||
u.failsCount.Add(1)
|
u.failsCount.Add(1)
|
||||||
log.WithError(err).WithField("upstream", upstream).
|
log.WithError(err).WithField("upstream", upstream).
|
||||||
Error("got an error while querying the upstream")
|
Error("got other error while querying the upstream")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Tracef("took %s to query the upstream %s", t, upstream)
|
log.Debugf("took %s to query the upstream %s", t, upstream)
|
||||||
|
|
||||||
err = w.WriteMsg(rm)
|
err = w.WriteMsg(rm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -118,10 +201,11 @@ func (u *upstreamResolver) checkUpstreamFails() {
|
|||||||
case <-u.ctx.Done():
|
case <-u.ctx.Done():
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
log.Warnf("upstream resolving is disabled for %v", reactivatePeriod)
|
//todo test the deactivation logic, it seems to affect the client
|
||||||
u.deactivate()
|
//log.Warnf("upstream resolving is disabled for %v", reactivatePeriod)
|
||||||
u.disabled = true
|
//u.deactivate()
|
||||||
go u.waitUntilResponse()
|
//u.disabled = true
|
||||||
|
//go u.waitUntilResponse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ func (e *Engine) Start() error {
|
|||||||
} else {
|
} else {
|
||||||
// todo fix custom address
|
// todo fix custom address
|
||||||
if e.dnsServer == nil {
|
if e.dnsServer == nil {
|
||||||
e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress)
|
e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress, e.mobileDep.InterfaceName, wgAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.close()
|
e.close()
|
||||||
return err
|
return err
|
||||||
@@ -214,16 +214,22 @@ func (e *Engine) Start() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("Initial routes contain %d routes", len(routes))
|
||||||
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder, routes)
|
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder, routes)
|
||||||
|
e.mobileDep.RouteListener.SetInterfaceIP(wgAddr)
|
||||||
|
|
||||||
e.routeManager.SetRouteChangeListener(e.mobileDep.RouteListener)
|
e.routeManager.SetRouteChangeListener(e.mobileDep.RouteListener)
|
||||||
|
|
||||||
if runtime.GOOS != "android" {
|
switch runtime.GOOS {
|
||||||
err = e.wgInterface.Create()
|
case "android":
|
||||||
} else {
|
err = e.wgInterface.CreateOnAndroid(iface.MobileIFaceArguments{
|
||||||
err = e.wgInterface.CreateOnMobile(iface.MobileIFaceArguments{
|
|
||||||
Routes: e.routeManager.InitialRouteRange(),
|
Routes: e.routeManager.InitialRouteRange(),
|
||||||
Dns: e.dnsServer.DnsIP(),
|
Dns: e.dnsServer.DnsIP(),
|
||||||
})
|
})
|
||||||
|
case "ios":
|
||||||
|
err = e.wgInterface.CreateOniOS(e.mobileDep.FileDescriptor)
|
||||||
|
default:
|
||||||
|
err = e.wgInterface.Create()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed creating tunnel interface %s: [%s]", wgIFaceName, err.Error())
|
log.Errorf("failed creating tunnel interface %s: [%s]", wgIFaceName, err.Error())
|
||||||
@@ -264,7 +270,11 @@ func (e *Engine) Start() error {
|
|||||||
e.acl = acl
|
e.acl = acl
|
||||||
}
|
}
|
||||||
|
|
||||||
err = e.dnsServer.Initialize()
|
if runtime.GOOS == "ios" {
|
||||||
|
err = e.dnsServer.Initialize(e.mobileDep.DnsManager)
|
||||||
|
} else {
|
||||||
|
err = e.dnsServer.Initialize(nil)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e.close()
|
e.close()
|
||||||
return err
|
return err
|
||||||
@@ -466,7 +476,7 @@ func (e *Engine) updateSSH(sshConf *mgmProto.SSHConfig) error {
|
|||||||
}
|
}
|
||||||
// start SSH server if it wasn't running
|
// start SSH server if it wasn't running
|
||||||
if isNil(e.sshServer) {
|
if isNil(e.sshServer) {
|
||||||
//nil sshServer means it has not yet been started
|
// nil sshServer means it has not yet been started
|
||||||
var err error
|
var err error
|
||||||
e.sshServer, err = e.sshServerFunc(e.config.SSHKey,
|
e.sshServer, err = e.sshServerFunc(e.config.SSHKey,
|
||||||
fmt.Sprintf("%s:%d", e.wgInterface.Address().IP.String(), nbssh.DefaultSSHPort))
|
fmt.Sprintf("%s:%d", e.wgInterface.Address().IP.String(), nbssh.DefaultSSHPort))
|
||||||
|
|||||||
@@ -1049,7 +1049,7 @@ func startManagement(dataDir string) (*grpc.Server, string, error) {
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
|
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||||
eventStore)
|
eventStore, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,4 +14,7 @@ type MobileDependency struct {
|
|||||||
RouteListener routemanager.RouteListener
|
RouteListener routemanager.RouteListener
|
||||||
HostDNSAddresses []string
|
HostDNSAddresses []string
|
||||||
DnsReadyListener dns.ReadyListener
|
DnsReadyListener dns.ReadyListener
|
||||||
|
DnsManager dns.IosDnsManager
|
||||||
|
FileDescriptor int32
|
||||||
|
InterfaceName string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,9 +106,6 @@ func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL
|
|||||||
|
|
||||||
func isPKCEProviderConfigValid(config PKCEAuthProviderConfig) error {
|
func isPKCEProviderConfigValid(config PKCEAuthProviderConfig) error {
|
||||||
errorMSGFormat := "invalid provider configuration received from management: %s value is empty. Contact your NetBird administrator"
|
errorMSGFormat := "invalid provider configuration received from management: %s value is empty. Contact your NetBird administrator"
|
||||||
if config.Audience == "" {
|
|
||||||
return fmt.Errorf(errorMSGFormat, "Audience")
|
|
||||||
}
|
|
||||||
if config.ClientID == "" {
|
if config.ClientID == "" {
|
||||||
return fmt.Errorf(errorMSGFormat, "Client ID")
|
return fmt.Errorf(errorMSGFormat, "Client ID")
|
||||||
}
|
}
|
||||||
|
|||||||
31
client/internal/routemanager/firewall_ios.go
Normal file
31
client/internal/routemanager/firewall_ios.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//go:build ios
|
||||||
|
|
||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newFirewall returns a nil manager
|
||||||
|
func newFirewall(context.Context) (firewallManager, error) {
|
||||||
|
return iOSFirewallManager{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type iOSFirewallManager struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i iOSFirewallManager) RestoreOrCreateContainers() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i iOSFirewallManager) InsertRoutingRules(pair routerPair) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i iOSFirewallManager) RemoveRoutingRules(pair routerPair) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i iOSFirewallManager) CleanRoutingRules() {
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
//go:build !linux
|
//go:build !linux && !ios
|
||||||
// +build !linux
|
// +build !linux,!ios
|
||||||
|
|
||||||
package routemanager
|
package routemanager
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,19 @@ package routemanager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RouteListener is a callback interface for mobile system
|
// RouteListener is a callback interface for mobile system
|
||||||
type RouteListener interface {
|
type RouteListener interface {
|
||||||
// OnNewRouteSetting invoke when new route setting has been arrived
|
// OnNewRouteSetting invoke when new route setting has been arrived
|
||||||
OnNewRouteSetting()
|
OnNewRouteSetting(string)
|
||||||
|
SetInterfaceIP(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type notifier struct {
|
type notifier struct {
|
||||||
@@ -55,9 +59,6 @@ func (n *notifier) onNewRoutes(idMap map[string][]*route.Route) {
|
|||||||
|
|
||||||
n.routeRangers = newNets
|
n.routeRangers = newNets
|
||||||
|
|
||||||
if !n.hasDiff(n.initialRouteRangers, newNets) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n.notify()
|
n.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +70,8 @@ func (n *notifier) notify() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func(l RouteListener) {
|
go func(l RouteListener) {
|
||||||
l.OnNewRouteSetting()
|
log.Debugf("notifying route listener with route ranges: %s", strings.Join(n.routeRangers, ","))
|
||||||
|
l.OnNewRouteSetting(strings.Join(n.routeRangers, ","))
|
||||||
}(n.routeListener)
|
}(n.routeListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build android
|
||||||
|
|
||||||
package routemanager
|
package routemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
15
client/internal/routemanager/systemops_ios.go
Normal file
15
client/internal/routemanager/systemops_ios.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//go:build ios
|
||||||
|
|
||||||
|
package routemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !android
|
//go:build !android && !ios
|
||||||
|
|
||||||
package routemanager
|
package routemanager
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
log.Errorf("failed to read from turn conn (endpoint: :%d): %s", endpointPort, err)
|
||||||
}
|
}
|
||||||
p.removeTurnConn(endpointPort)
|
p.removeTurnConn(endpointPort)
|
||||||
|
log.Infof("stop forward turn packages to port: %d. error: %s", endpointPort, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = p.sendPkg(buf[:n], endpointPort)
|
err = p.sendPkg(buf[:n], endpointPort)
|
||||||
@@ -158,7 +159,7 @@ func (p *WGEBPFProxy) proxyToRemote() {
|
|||||||
conn, ok := p.turnConnStore[uint16(addr.Port)]
|
conn, ok := p.turnConnStore[uint16(addr.Port)]
|
||||||
p.turnConnMutex.Unlock()
|
p.turnConnMutex.Unlock()
|
||||||
if !ok {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
200
client/ios/NetBirdSDK/client.go
Normal file
200
client/ios/NetBirdSDK/client.go
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
package NetBirdSDK
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/auth"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||||
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
"github.com/netbirdio/netbird/formatter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RouteListener export internal RouteListener for mobile
|
||||||
|
type RouteListener interface {
|
||||||
|
routemanager.RouteListener
|
||||||
|
}
|
||||||
|
|
||||||
|
// DnsManager export internal dns Manager for mobile
|
||||||
|
type DnsManager interface {
|
||||||
|
dns.IosDnsManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomLogger export internal CustomLogger for mobile
|
||||||
|
type CustomLogger interface {
|
||||||
|
Debug(message string)
|
||||||
|
Info(message string)
|
||||||
|
Error(message string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
formatter.SetLogcatFormatter(log.StandardLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client struct manage the life circle of background service
|
||||||
|
type Client struct {
|
||||||
|
cfgFile string
|
||||||
|
recorder *peer.Status
|
||||||
|
ctxCancel context.CancelFunc
|
||||||
|
ctxCancelLock *sync.Mutex
|
||||||
|
deviceName string
|
||||||
|
osName string
|
||||||
|
osVersion string
|
||||||
|
routeListener routemanager.RouteListener
|
||||||
|
onHostDnsFn func([]string)
|
||||||
|
dnsManager dns.IosDnsManager
|
||||||
|
loginComplete bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient instantiate a new Client
|
||||||
|
func NewClient(cfgFile, deviceName string, osVersion string, osName string, routeListener RouteListener, dnsManager DnsManager) *Client {
|
||||||
|
return &Client{
|
||||||
|
cfgFile: cfgFile,
|
||||||
|
deviceName: deviceName,
|
||||||
|
osName: osName,
|
||||||
|
osVersion: osVersion,
|
||||||
|
recorder: peer.NewRecorder(""),
|
||||||
|
ctxCancelLock: &sync.Mutex{},
|
||||||
|
routeListener: routeListener,
|
||||||
|
dnsManager: dnsManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run start the internal client. It is a blocker function
|
||||||
|
func (c *Client) Run(fd int32, interfaceName string) error {
|
||||||
|
log.Infof("Starting NetBird client")
|
||||||
|
log.Debugf("Tunnel uses interface: %s", interfaceName)
|
||||||
|
cfg, err := internal.UpdateOrCreateConfig(internal.ConfigInput{
|
||||||
|
ConfigPath: c.cfgFile,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.recorder.UpdateManagementAddress(cfg.ManagementURL.String())
|
||||||
|
|
||||||
|
var ctx context.Context
|
||||||
|
//nolint
|
||||||
|
ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName)
|
||||||
|
ctxWithValues = context.WithValue(ctxWithValues, system.OsNameCtxKey, c.osName)
|
||||||
|
ctxWithValues = context.WithValue(ctxWithValues, system.OsVersionCtxKey, c.osVersion)
|
||||||
|
c.ctxCancelLock.Lock()
|
||||||
|
ctx, c.ctxCancel = context.WithCancel(ctxWithValues)
|
||||||
|
defer c.ctxCancel()
|
||||||
|
c.ctxCancelLock.Unlock()
|
||||||
|
|
||||||
|
auth := NewAuthWithConfig(ctx, cfg)
|
||||||
|
err = auth.Login()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Auth successful")
|
||||||
|
// todo do not throw error in case of cancelled context
|
||||||
|
ctx = internal.CtxInitState(ctx)
|
||||||
|
c.onHostDnsFn = func([]string) {}
|
||||||
|
return internal.RunClientiOS(ctx, cfg, c.recorder, fd, c.routeListener, c.dnsManager, interfaceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the internal client and free the resources
|
||||||
|
func (c *Client) Stop() {
|
||||||
|
c.ctxCancelLock.Lock()
|
||||||
|
defer c.ctxCancelLock.Unlock()
|
||||||
|
if c.ctxCancel == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ctxCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ÏSetTraceLogLevel configure the logger to trace level
|
||||||
|
func (c *Client) SetTraceLogLevel() {
|
||||||
|
log.SetLevel(log.TraceLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStatusDetails return with the list of the PeerInfos
|
||||||
|
func (c *Client) GetStatusDetails() *StatusDetails {
|
||||||
|
|
||||||
|
fullStatus := c.recorder.GetFullStatus()
|
||||||
|
|
||||||
|
peerInfos := make([]PeerInfo, len(fullStatus.Peers))
|
||||||
|
for n, p := range fullStatus.Peers {
|
||||||
|
pi := PeerInfo{
|
||||||
|
p.IP,
|
||||||
|
p.FQDN,
|
||||||
|
p.ConnStatus.String(),
|
||||||
|
}
|
||||||
|
peerInfos[n] = pi
|
||||||
|
}
|
||||||
|
return &StatusDetails{items: peerInfos, fqdn: fullStatus.LocalPeerState.FQDN, ip: fullStatus.LocalPeerState.IP}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetManagementStatus() bool {
|
||||||
|
return c.recorder.GetFullStatus().ManagementState.Connected
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) IsLoginRequired() bool {
|
||||||
|
var ctx context.Context
|
||||||
|
ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName)
|
||||||
|
c.ctxCancelLock.Lock()
|
||||||
|
defer c.ctxCancelLock.Unlock()
|
||||||
|
ctx, c.ctxCancel = context.WithCancel(ctxWithValues)
|
||||||
|
|
||||||
|
cfg, _ := internal.UpdateOrCreateConfig(internal.ConfigInput{
|
||||||
|
ConfigPath: c.cfgFile,
|
||||||
|
})
|
||||||
|
|
||||||
|
needsLogin, _ := internal.IsLoginRequired(ctx, cfg.PrivateKey, cfg.ManagementURL, cfg.SSHKey)
|
||||||
|
return needsLogin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) LoginForMobile() string {
|
||||||
|
var ctx context.Context
|
||||||
|
ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName)
|
||||||
|
c.ctxCancelLock.Lock()
|
||||||
|
defer c.ctxCancelLock.Unlock()
|
||||||
|
ctx, c.ctxCancel = context.WithCancel(ctxWithValues)
|
||||||
|
|
||||||
|
cfg, _ := internal.UpdateOrCreateConfig(internal.ConfigInput{
|
||||||
|
ConfigPath: c.cfgFile,
|
||||||
|
})
|
||||||
|
|
||||||
|
oAuthFlow, err := auth.NewOAuthFlow(ctx, cfg, false)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
flowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// This could cause a potential race condition with loading the extension which need to be handled on swift side
|
||||||
|
go func() {
|
||||||
|
waitTimeout := time.Duration(flowInfo.ExpiresIn)
|
||||||
|
waitCTX, cancel := context.WithTimeout(ctx, waitTimeout*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jwtToken := tokenInfo.GetTokenToUse()
|
||||||
|
_ = internal.Login(ctx, cfg, "", jwtToken)
|
||||||
|
c.loginComplete = true
|
||||||
|
}()
|
||||||
|
|
||||||
|
return flowInfo.VerificationURIComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) IsLoginComplete() bool {
|
||||||
|
return c.loginComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ClearLoginComplete() {
|
||||||
|
c.loginComplete = false
|
||||||
|
}
|
||||||
5
client/ios/NetBirdSDK/gomobile.go
Normal file
5
client/ios/NetBirdSDK/gomobile.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package NetBirdSDK
|
||||||
|
|
||||||
|
import _ "golang.org/x/mobile/bind"
|
||||||
|
|
||||||
|
// to keep our CI/CD that checks go.mod and go.sum files happy, we need to import the package above
|
||||||
16
client/ios/NetBirdSDK/logger.go
Normal file
16
client/ios/NetBirdSDK/logger.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package NetBirdSDK
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logFile *os.File
|
||||||
|
|
||||||
|
// InitializeLog initializes the log file.
|
||||||
|
func InitializeLog(logLevel string, filePath string) error {
|
||||||
|
var err error
|
||||||
|
err = util.InitLog(logLevel, filePath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
184
client/ios/NetBirdSDK/login.go
Normal file
184
client/ios/NetBirdSDK/login.go
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
package NetBirdSDK
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
gstatus "google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/cmd"
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/auth"
|
||||||
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SSOListener is async listener for mobile framework
|
||||||
|
type SSOListener interface {
|
||||||
|
OnSuccess(bool)
|
||||||
|
OnError(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrListener is async listener for mobile framework
|
||||||
|
type ErrListener interface {
|
||||||
|
OnSuccess()
|
||||||
|
OnError(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLOpener it is a callback interface. The Open function will be triggered if
|
||||||
|
// the backend want to show an url for the user
|
||||||
|
type URLOpener interface {
|
||||||
|
Open(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth can register or login new client
|
||||||
|
type Auth struct {
|
||||||
|
ctx context.Context
|
||||||
|
config *internal.Config
|
||||||
|
cfgPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuth instantiate Auth struct and validate the management URL
|
||||||
|
func NewAuth(cfgPath string, mgmURL string) (*Auth, error) {
|
||||||
|
inputCfg := internal.ConfigInput{
|
||||||
|
ManagementURL: mgmURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.CreateInMemoryConfig(inputCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Auth{
|
||||||
|
ctx: context.Background(),
|
||||||
|
config: cfg,
|
||||||
|
cfgPath: cfgPath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuthWithConfig instantiate Auth based on existing config
|
||||||
|
func NewAuthWithConfig(ctx context.Context, config *internal.Config) *Auth {
|
||||||
|
return &Auth{
|
||||||
|
ctx: ctx,
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfigIfSSOSupported test the connectivity with the management server by retrieving the server device flow info.
|
||||||
|
// If it returns a flow info than save the configuration and return true. If it gets a codes.NotFound, it means that SSO
|
||||||
|
// is not supported and returns false without saving the configuration. For other errors return false.
|
||||||
|
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 {
|
||||||
|
supportsSSO = false
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if !supportsSSO {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("backoff cycle failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = internal.WriteOutConfig(a.cfgPath, a.config)
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginWithSetupKeyAndSaveConfig test the connectivity with the management server with the setup key.
|
||||||
|
func (a *Auth) LoginWithSetupKeyAndSaveConfig(setupKey string, deviceName string) error {
|
||||||
|
//nolint
|
||||||
|
ctxWithValues := context.WithValue(a.ctx, system.DeviceNameCtxKey, deviceName)
|
||||||
|
|
||||||
|
err := a.withBackOff(a.ctx, func() error {
|
||||||
|
backoffErr := internal.Login(ctxWithValues, a.config, setupKey, "")
|
||||||
|
if s, ok := gstatus.FromError(backoffErr); ok && (s.Code() == codes.PermissionDenied) {
|
||||||
|
// we got an answer from management, exit backoff earlier
|
||||||
|
return backoff.Permanent(backoffErr)
|
||||||
|
}
|
||||||
|
return backoffErr
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("backoff cycle failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return internal.WriteOutConfig(a.cfgPath, a.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) Login() error {
|
||||||
|
var needsLogin bool
|
||||||
|
|
||||||
|
// check if we need to generate JWT token
|
||||||
|
err := a.withBackOff(a.ctx, func() (err error) {
|
||||||
|
needsLogin, err = internal.IsLoginRequired(a.ctx, a.config.PrivateKey, a.config.ManagementURL, a.config.SSHKey)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("backoff cycle failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtToken := ""
|
||||||
|
if needsLogin {
|
||||||
|
return fmt.Errorf("Not authenticated")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.withBackOff(a.ctx, func() error {
|
||||||
|
err := internal.Login(a.ctx, a.config, "", jwtToken)
|
||||||
|
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.InvalidArgument || s.Code() == codes.PermissionDenied) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("backoff cycle failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, error) {
|
||||||
|
oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
flowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting a request OAuth flow info failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go urlOpener.Open(flowInfo.VerificationURIComplete)
|
||||||
|
|
||||||
|
waitTimeout := time.Duration(flowInfo.ExpiresIn)
|
||||||
|
waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("waiting for browser login failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tokenInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) withBackOff(ctx context.Context, bf func() error) error {
|
||||||
|
return backoff.RetryNotify(
|
||||||
|
bf,
|
||||||
|
backoff.WithContext(cmd.CLIBackOffSettings, ctx),
|
||||||
|
func(err error, duration time.Duration) {
|
||||||
|
log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
50
client/ios/NetBirdSDK/peer_notifier.go
Normal file
50
client/ios/NetBirdSDK/peer_notifier.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package NetBirdSDK
|
||||||
|
|
||||||
|
// PeerInfo describe information about the peers. It designed for the UI usage
|
||||||
|
type PeerInfo struct {
|
||||||
|
IP string
|
||||||
|
FQDN string
|
||||||
|
ConnStatus string // Todo replace to enum
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeerInfoCollection made for Java layer to get non default types as collection
|
||||||
|
type PeerInfoCollection interface {
|
||||||
|
Add(s string) PeerInfoCollection
|
||||||
|
Get(i int) string
|
||||||
|
Size() int
|
||||||
|
GetFQDN() string
|
||||||
|
GetIP() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusDetails is the implementation of the PeerInfoCollection
|
||||||
|
type StatusDetails struct {
|
||||||
|
items []PeerInfo
|
||||||
|
fqdn string
|
||||||
|
ip string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new PeerInfo to the collection
|
||||||
|
func (array StatusDetails) Add(s PeerInfo) StatusDetails {
|
||||||
|
array.items = append(array.items, s)
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get return an element of the collection
|
||||||
|
func (array StatusDetails) Get(i int) *PeerInfo {
|
||||||
|
return &array.items[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size return with the size of the collection
|
||||||
|
func (array StatusDetails) Size() int {
|
||||||
|
return len(array.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFQDN return with the FQDN of the local peer
|
||||||
|
func (array StatusDetails) GetFQDN() string {
|
||||||
|
return array.fqdn
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIP return with the IP of the local peer
|
||||||
|
func (array StatusDetails) GetIP() string {
|
||||||
|
return array.ip
|
||||||
|
}
|
||||||
78
client/ios/NetBirdSDK/preferences.go
Normal file
78
client/ios/NetBirdSDK/preferences.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package NetBirdSDK
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Preferences export a subset of the internal config for gomobile
|
||||||
|
type Preferences struct {
|
||||||
|
configInput internal.ConfigInput
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPreferences create new Preferences instance
|
||||||
|
func NewPreferences(configPath string) *Preferences {
|
||||||
|
ci := internal.ConfigInput{
|
||||||
|
ConfigPath: configPath,
|
||||||
|
}
|
||||||
|
return &Preferences{ci}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetManagementURL read url from config file
|
||||||
|
func (p *Preferences) GetManagementURL() (string, error) {
|
||||||
|
if p.configInput.ManagementURL != "" {
|
||||||
|
return p.configInput.ManagementURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg.ManagementURL.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetManagementURL store the given url and wait for commit
|
||||||
|
func (p *Preferences) SetManagementURL(url string) {
|
||||||
|
p.configInput.ManagementURL = url
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdminURL read url from config file
|
||||||
|
func (p *Preferences) GetAdminURL() (string, error) {
|
||||||
|
if p.configInput.AdminURL != "" {
|
||||||
|
return p.configInput.AdminURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg.AdminURL.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdminURL store the given url and wait for commit
|
||||||
|
func (p *Preferences) SetAdminURL(url string) {
|
||||||
|
p.configInput.AdminURL = url
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPreSharedKey read preshared key from config file
|
||||||
|
func (p *Preferences) GetPreSharedKey() (string, error) {
|
||||||
|
if p.configInput.PreSharedKey != nil {
|
||||||
|
return *p.configInput.PreSharedKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cfg.PreSharedKey, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPreSharedKey store the given key and wait for commit
|
||||||
|
func (p *Preferences) SetPreSharedKey(key string) {
|
||||||
|
p.configInput.PreSharedKey = &key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit write out the changes into config file
|
||||||
|
func (p *Preferences) Commit() error {
|
||||||
|
_, err := internal.UpdateOrCreateConfig(p.configInput)
|
||||||
|
return err
|
||||||
|
}
|
||||||
120
client/ios/NetBirdSDK/preferences_test.go
Normal file
120
client/ios/NetBirdSDK/preferences_test.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package NetBirdSDK
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPreferences_DefaultValues(t *testing.T) {
|
||||||
|
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
|
||||||
|
p := NewPreferences(cfgFile)
|
||||||
|
defaultVar, err := p.GetAdminURL()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read default value: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if defaultVar != internal.DefaultAdminURL {
|
||||||
|
t.Errorf("invalid default admin url: %s", defaultVar)
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultVar, err = p.GetManagementURL()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read default management URL: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if defaultVar != internal.DefaultManagementURL {
|
||||||
|
t.Errorf("invalid default management url: %s", defaultVar)
|
||||||
|
}
|
||||||
|
|
||||||
|
var preSharedKey string
|
||||||
|
preSharedKey, err = p.GetPreSharedKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read default preshared key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if preSharedKey != "" {
|
||||||
|
t.Errorf("invalid preshared key: %s", preSharedKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreferences_ReadUncommitedValues(t *testing.T) {
|
||||||
|
exampleString := "exampleString"
|
||||||
|
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
|
||||||
|
p := NewPreferences(cfgFile)
|
||||||
|
|
||||||
|
p.SetAdminURL(exampleString)
|
||||||
|
resp, err := p.GetAdminURL()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read admin url: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != exampleString {
|
||||||
|
t.Errorf("unexpected admin url: %s", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.SetManagementURL(exampleString)
|
||||||
|
resp, err = p.GetManagementURL()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read managmenet url: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != exampleString {
|
||||||
|
t.Errorf("unexpected managemenet url: %s", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.SetPreSharedKey(exampleString)
|
||||||
|
resp, err = p.GetPreSharedKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read preshared key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != exampleString {
|
||||||
|
t.Errorf("unexpected preshared key: %s", resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreferences_Commit(t *testing.T) {
|
||||||
|
exampleURL := "https://myurl.com:443"
|
||||||
|
examplePresharedKey := "topsecret"
|
||||||
|
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
|
||||||
|
p := NewPreferences(cfgFile)
|
||||||
|
|
||||||
|
p.SetAdminURL(exampleURL)
|
||||||
|
p.SetManagementURL(exampleURL)
|
||||||
|
p.SetPreSharedKey(examplePresharedKey)
|
||||||
|
|
||||||
|
err := p.Commit()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to save changes: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p = NewPreferences(cfgFile)
|
||||||
|
resp, err := p.GetAdminURL()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read admin url: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != exampleURL {
|
||||||
|
t.Errorf("unexpected admin url: %s", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = p.GetManagementURL()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read managmenet url: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != exampleURL {
|
||||||
|
t.Errorf("unexpected managemenet url: %s", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = p.GetPreSharedKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read preshared key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != examplePresharedKey {
|
||||||
|
t.Errorf("unexpected preshared key: %s", resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.21.9
|
// protoc v4.23.4
|
||||||
// source: daemon.proto
|
// source: daemon.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -40,8 +40,9 @@ type LoginRequest struct {
|
|||||||
// cleanNATExternalIPs clean map list of external IPs.
|
// cleanNATExternalIPs clean map list of external IPs.
|
||||||
// This is needed because the generated code
|
// This is needed because the generated code
|
||||||
// omits initialized empty slices due to omitempty tags
|
// omits initialized empty slices due to omitempty tags
|
||||||
CleanNATExternalIPs bool `protobuf:"varint,6,opt,name=cleanNATExternalIPs,proto3" json:"cleanNATExternalIPs,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"`
|
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() {
|
func (x *LoginRequest) Reset() {
|
||||||
@@ -125,6 +126,13 @@ func (x *LoginRequest) GetCustomDNSAddress() []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *LoginRequest) GetIsLinuxDesktopClient() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsLinuxDesktopClient
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type LoginResponse struct {
|
type LoginResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
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,
|
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,
|
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,
|
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,
|
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, 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,
|
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,
|
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,
|
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,
|
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,
|
0x73, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x69, 0x73, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x44, 0x65, 0x73,
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f,
|
0x6b, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x65, 0x65,
|
0x52, 0x14, 0x69, 0x73, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70,
|
||||||
0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73,
|
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
||||||
0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73,
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x65, 0x64,
|
||||||
0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69,
|
0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1a,
|
||||||
0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49,
|
0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x65,
|
||||||
0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x18, 0x03, 0x20,
|
||||||
0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55,
|
0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
|
||||||
0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x31, 0x0a, 0x13, 0x57, 0x61,
|
0x6e, 0x55, 0x52, 0x49, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||||
0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18,
|
||||||
0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20,
|
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x16, 0x0a,
|
0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x31,
|
||||||
0x14, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73,
|
0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64,
|
||||||
0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64,
|
||||||
0x22, 0x3d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
0x65, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69,
|
||||||
0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72,
|
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52,
|
||||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70,
|
||||||
0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
|
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
|
||||||
0x82, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c,
|
||||||
0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01,
|
0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
|
||||||
0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x0a, 0x66, 0x75,
|
0x52, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61,
|
||||||
0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12,
|
0x74, 0x75, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
|
||||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||||
0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32,
|
||||||
0x0a, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
|
0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72,
|
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c, 0x6c,
|
||||||
0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75,
|
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
||||||
0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x75, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43,
|
0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e,
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a,
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52,
|
||||||
0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x01,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11,
|
||||||
0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c,
|
0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46,
|
0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55,
|
||||||
0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x03,
|
0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x22, 0x0a,
|
0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x04, 0x20,
|
0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65,
|
0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69,
|
||||||
0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x05, 0x20,
|
0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x22, 0xcf, 0x02,
|
0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65,
|
||||||
0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49,
|
0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72,
|
||||||
0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70,
|
0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52,
|
||||||
0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62,
|
0x4c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52,
|
||||||
0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75,
|
0x4c, 0x22, 0xcf, 0x02, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
|
||||||
0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61,
|
0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12,
|
||||||
0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75,
|
0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53,
|
||||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e,
|
||||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53,
|
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, 0x12, 0x18, 0x0a, 0x07, 0x72,
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||||
0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65,
|
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18,
|
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63,
|
||||||
0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x34, 0x0a,
|
0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12,
|
||||||
0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61,
|
0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
|
||||||
0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x6c, 0x6f,
|
0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72,
|
||||||
0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54,
|
0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63,
|
||||||
0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65,
|
0x74, 0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e,
|
||||||
0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20,
|
0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61,
|
0x52, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64,
|
||||||
0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66,
|
0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74,
|
||||||
0x71, 0x64, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22,
|
0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70,
|
||||||
0x76, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
|
0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49,
|
||||||
0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49,
|
0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12,
|
||||||
0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
|
0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66,
|
||||||
0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x72,
|
0x71, 0x64, 0x6e, 0x22, 0x76, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72,
|
||||||
0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01,
|
0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66,
|
0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18,
|
||||||
0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28,
|
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a,
|
||||||
0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x61,
|
0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65,
|
||||||
0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20,
|
0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e,
|
0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18,
|
||||||
0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e,
|
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x53,
|
||||||
0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52,
|
||||||
0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c,
|
0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63,
|
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
|
0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61,
|
||||||
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01, 0x0a, 0x0a, 0x46, 0x75,
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a,
|
||||||
0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61,
|
0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12,
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67,
|
0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01,
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x6e, 0x61,
|
0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f,
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73,
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18,
|
||||||
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
|
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d,
|
||||||
0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c,
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f,
|
||||||
0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61,
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
|
||||||
0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53,
|
0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02,
|
||||||
0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x65,
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69,
|
||||||
0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61,
|
0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61,
|
||||||
0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61,
|
0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50,
|
||||||
0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
|
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
|
||||||
0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53,
|
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65,
|
||||||
0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32, 0xf7, 0x02, 0x0a, 0x0d,
|
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65,
|
||||||
0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a,
|
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18,
|
||||||
0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64,
|
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32,
|
||||||
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0xf7, 0x02, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f,
|
0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57,
|
0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52,
|
||||||
0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69,
|
||||||
0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
||||||
0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52,
|
||||||
0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70,
|
||||||
0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61,
|
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64,
|
||||||
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||||
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74,
|
0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04,
|
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
|
||||||
0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f,
|
0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
|
||||||
0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||||
0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18,
|
0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
||||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e,
|
||||||
0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43,
|
||||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
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 (
|
var (
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ message LoginRequest {
|
|||||||
|
|
||||||
bytes customDNSAddress = 7;
|
bytes customDNSAddress = 7;
|
||||||
|
|
||||||
|
bool isLinuxDesktopClient = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LoginResponse {
|
message LoginResponse {
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
|
|||||||
state.Set(internal.StatusConnecting)
|
state.Set(internal.StatusConnecting)
|
||||||
|
|
||||||
if msg.SetupKey == "" {
|
if msg.SetupKey == "" {
|
||||||
oAuthFlow, err := auth.NewOAuthFlow(ctx, config)
|
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, msg.IsLinuxDesktopClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.Set(internal.StatusLoginFailed)
|
state.Set(internal.StatusLoginFailed)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ import (
|
|||||||
// DeviceNameCtxKey context key for device name
|
// DeviceNameCtxKey context key for device name
|
||||||
const DeviceNameCtxKey = "deviceName"
|
const DeviceNameCtxKey = "deviceName"
|
||||||
|
|
||||||
|
// OsVersionCtxKey context key for operating system version
|
||||||
|
const OsVersionCtxKey = "OsVersion"
|
||||||
|
|
||||||
|
// OsNameCtxKey context key for operating system name
|
||||||
|
const OsNameCtxKey = "OsName"
|
||||||
|
|
||||||
// Info is an object that contains machine information
|
// Info is an object that contains machine information
|
||||||
// Most of the code is taken from https://github.com/matishsiao/goInfo
|
// Most of the code is taken from https://github.com/matishsiao/goInfo
|
||||||
type Info struct {
|
type Info struct {
|
||||||
@@ -52,6 +58,24 @@ func extractDeviceName(ctx context.Context, defaultName string) string {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extractOsVersion extracts operating system version from context or returns the default
|
||||||
|
func extractOsVersion(ctx context.Context, defaultName string) string {
|
||||||
|
v, ok := ctx.Value(OsVersionCtxKey).(string)
|
||||||
|
if !ok {
|
||||||
|
return defaultName
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractOsName extracts operating system name from context or returns the default
|
||||||
|
func extractOsName(ctx context.Context, defaultName string) string {
|
||||||
|
v, ok := ctx.Value(OsNameCtxKey).(string)
|
||||||
|
if !ok {
|
||||||
|
return defaultName
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
// GetDesktopUIUserAgent returns the Desktop ui user agent
|
// GetDesktopUIUserAgent returns the Desktop ui user agent
|
||||||
func GetDesktopUIUserAgent() string {
|
func GetDesktopUIUserAgent() string {
|
||||||
return "netbird-desktop-ui/" + version.NetbirdVersion()
|
return "netbird-desktop-ui/" + version.NetbirdVersion()
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
//go:build !ios
|
||||||
|
// +build !ios
|
||||||
|
|
||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
27
client/system/info_ios.go
Normal file
27
client/system/info_ios.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//go:build ios
|
||||||
|
// +build ios
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetInfo retrieves and parses the system information
|
||||||
|
func GetInfo(ctx context.Context) *Info {
|
||||||
|
|
||||||
|
// Convert fixed-size byte arrays to Go strings
|
||||||
|
sysName := extractOsName(ctx, "sysName")
|
||||||
|
swVersion := extractOsVersion(ctx, "swVersion")
|
||||||
|
|
||||||
|
gio := &Info{Kernel: sysName, OSVersion: swVersion, Core: swVersion, Platform: "unknown", OS: sysName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
||||||
|
// systemHostname, _ := os.Hostname()
|
||||||
|
gio.Hostname = extractDeviceName(ctx, "hostname")
|
||||||
|
gio.WiretrusteeVersion = version.NetbirdVersion()
|
||||||
|
gio.UIVersion = extractUserAgent(ctx)
|
||||||
|
|
||||||
|
return gio
|
||||||
|
}
|
||||||
1
go.mod
1
go.mod
@@ -29,6 +29,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
fyne.io/fyne/v2 v2.1.4
|
fyne.io/fyne/v2 v2.1.4
|
||||||
|
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible
|
||||||
github.com/c-robinson/iplib v1.0.3
|
github.com/c-robinson/iplib v1.0.3
|
||||||
github.com/cilium/ebpf v0.10.0
|
github.com/cilium/ebpf v0.10.0
|
||||||
github.com/coreos/go-iptables v0.7.0
|
github.com/coreos/go-iptables v0.7.0
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -61,6 +61,8 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
|
|||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible h1:hqcTK6ZISdip65SR792lwYJTa/axESA0889D3UlZbLo=
|
||||||
|
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible/go.mod h1:6B1nuc1MUs6c62ODZDl7hVE5Pv7O2XGSkgg2olnq34I=
|
||||||
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10=
|
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10=
|
||||||
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY=
|
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY=
|
||||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
|
|||||||
@@ -28,14 +28,20 @@ func NewWGIFace(ifaceName string, address string, mtu int, tunAdapter TunAdapter
|
|||||||
return wgIFace, nil
|
return wgIFace, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOnMobile creates a new Wireguard interface, sets a given IP and brings it up.
|
// CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up.
|
||||||
// Will reuse an existing one.
|
// Will reuse an existing one.
|
||||||
func (w *WGIface) CreateOnMobile(mIFaceArgs MobileIFaceArguments) error {
|
func (w *WGIface) CreateOnAndroid(mIFaceArgs MobileIFaceArguments) error {
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
return w.tun.Create(mIFaceArgs)
|
return w.tun.Create(mIFaceArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateOniOS creates a new Wireguard interface, sets a given IP and brings it up.
|
||||||
|
// Will reuse an existing one.
|
||||||
|
func (w *WGIface) CreateOniOS(tunFd int32) error {
|
||||||
|
return fmt.Errorf("this function has not implemented on mobile")
|
||||||
|
}
|
||||||
|
|
||||||
// Create this function make sense on mobile only
|
// Create this function make sense on mobile only
|
||||||
func (w *WGIface) Create() error {
|
func (w *WGIface) Create() error {
|
||||||
return fmt.Errorf("this function has not implemented on mobile")
|
return fmt.Errorf("this function has not implemented on mobile")
|
||||||
|
|||||||
51
iface/iface_ios.go
Normal file
51
iface/iface_ios.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
//go:build ios
|
||||||
|
// +build ios
|
||||||
|
|
||||||
|
package iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pion/transport/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewWGIFace Creates a new WireGuard interface instance
|
||||||
|
func NewWGIFace(ifaceName string, address string, mtu int, tunAdapter TunAdapter, transportNet transport.Net) (*WGIface, error) {
|
||||||
|
wgIFace := &WGIface{
|
||||||
|
mu: sync.Mutex{},
|
||||||
|
}
|
||||||
|
|
||||||
|
wgAddress, err := parseWGAddress(address)
|
||||||
|
if err != nil {
|
||||||
|
return wgIFace, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tun := newTunDevice(wgAddress, mtu, tunAdapter, transportNet)
|
||||||
|
wgIFace.tun = tun
|
||||||
|
|
||||||
|
wgIFace.configurer = newWGConfigurer(tun)
|
||||||
|
|
||||||
|
wgIFace.userspaceBind = !WireGuardModuleIsLoaded()
|
||||||
|
|
||||||
|
return wgIFace, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOniOS creates a new Wireguard interface, sets a given IP and brings it up.
|
||||||
|
// Will reuse an existing one.
|
||||||
|
func (w *WGIface) CreateOniOS(tunFd int32) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
return w.tun.Create(tunFd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up.
|
||||||
|
// Will reuse an existing one.
|
||||||
|
func (w *WGIface) CreateOnAndroid(mIFaceArgs MobileIFaceArguments) error {
|
||||||
|
return fmt.Errorf("this function has not implemented on mobile")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create this function make sense on mobile only
|
||||||
|
func (w *WGIface) Create() error {
|
||||||
|
return fmt.Errorf("this function has not implemented on mobile")
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build !android
|
//go:build !android && !ios
|
||||||
|
// +build !android,!ios
|
||||||
|
|
||||||
package iface
|
package iface
|
||||||
|
|
||||||
@@ -27,8 +28,13 @@ func NewWGIFace(iFaceName string, address string, mtu int, tunAdapter TunAdapter
|
|||||||
return wgIFace, nil
|
return wgIFace, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOnMobile this function make sense on mobile only
|
// CreateOnAndroid this function make sense on mobile only
|
||||||
func (w *WGIface) CreateOnMobile(mIFaceArgs MobileIFaceArguments) error {
|
func (w *WGIface) CreateOnAndroid(mIFaceArgs MobileIFaceArguments) error {
|
||||||
|
return fmt.Errorf("this function has not implemented on non mobile")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOniOS this function make sense on mobile only
|
||||||
|
func (w *WGIface) CreateOniOS(tunFd int32) error {
|
||||||
return fmt.Errorf("this function has not implemented on non mobile")
|
return fmt.Errorf("this function has not implemented on non mobile")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
//go:build android
|
||||||
|
// +build android
|
||||||
|
|
||||||
package iface
|
package iface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
63
iface/ipc_parser_ios.go
Normal file
63
iface/ipc_parser_ios.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
//go:build ios
|
||||||
|
// +build ios
|
||||||
|
|
||||||
|
package iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toWgUserspaceString(wgCfg wgtypes.Config) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
if wgCfg.PrivateKey != nil {
|
||||||
|
hexKey := hex.EncodeToString(wgCfg.PrivateKey[:])
|
||||||
|
sb.WriteString(fmt.Sprintf("private_key=%s\n", hexKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
if wgCfg.ListenPort != nil {
|
||||||
|
sb.WriteString(fmt.Sprintf("listen_port=%d\n", *wgCfg.ListenPort))
|
||||||
|
}
|
||||||
|
|
||||||
|
if wgCfg.ReplacePeers {
|
||||||
|
sb.WriteString("replace_peers=true\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if wgCfg.FirewallMark != nil {
|
||||||
|
sb.WriteString(fmt.Sprintf("fwmark=%d\n", *wgCfg.FirewallMark))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range wgCfg.Peers {
|
||||||
|
hexKey := hex.EncodeToString(p.PublicKey[:])
|
||||||
|
sb.WriteString(fmt.Sprintf("public_key=%s\n", hexKey))
|
||||||
|
|
||||||
|
if p.PresharedKey != nil {
|
||||||
|
preSharedHexKey := hex.EncodeToString(p.PresharedKey[:])
|
||||||
|
sb.WriteString(fmt.Sprintf("preshared_key=%s\n", preSharedHexKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Remove {
|
||||||
|
sb.WriteString("remove=true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ReplaceAllowedIPs {
|
||||||
|
sb.WriteString("replace_allowed_ips=true\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, aip := range p.AllowedIPs {
|
||||||
|
sb.WriteString(fmt.Sprintf("allowed_ip=%s\n", aip.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Endpoint != nil {
|
||||||
|
sb.WriteString(fmt.Sprintf("endpoint=%s\n", p.Endpoint.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.PersistentKeepaliveInterval != nil {
|
||||||
|
sb.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", int(p.PersistentKeepaliveInterval.Seconds())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
//go:build android
|
||||||
|
// +build android
|
||||||
|
|
||||||
package iface
|
package iface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -55,7 +58,7 @@ func (t *tunDevice) Create(mIFaceArgs MobileIFaceArguments) error {
|
|||||||
t.device = device.NewDevice(t.wrapper, t.iceBind, device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
|
t.device = device.NewDevice(t.wrapper, t.iceBind, device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
|
||||||
// without this property mobile devices can discover remote endpoints if the configured one was wrong.
|
// without this property mobile devices can discover remote endpoints if the configured one was wrong.
|
||||||
// this helps with support for the older NetBird clients that had a hardcoded direct mode
|
// this helps with support for the older NetBird clients that had a hardcoded direct mode
|
||||||
//t.device.DisableSomeRoamingForBrokenMobileSemantics()
|
// t.device.DisableSomeRoamingForBrokenMobileSemantics()
|
||||||
|
|
||||||
err = t.device.Up()
|
err = t.device.Up()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
//go:build !ios
|
||||||
|
// +build !ios
|
||||||
|
|
||||||
package iface
|
package iface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
105
iface/tun_ios.go
Normal file
105
iface/tun_ios.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
//go:build ios
|
||||||
|
// +build ios
|
||||||
|
|
||||||
|
package iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pion/transport/v2"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/iface/bind"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tunDevice struct {
|
||||||
|
address WGAddress
|
||||||
|
mtu int
|
||||||
|
tunAdapter TunAdapter
|
||||||
|
iceBind *bind.ICEBind
|
||||||
|
|
||||||
|
fd int
|
||||||
|
name string
|
||||||
|
device *device.Device
|
||||||
|
wrapper *DeviceWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTunDevice(address WGAddress, mtu int, tunAdapter TunAdapter, transportNet transport.Net) *tunDevice {
|
||||||
|
return &tunDevice{
|
||||||
|
address: address,
|
||||||
|
mtu: mtu,
|
||||||
|
tunAdapter: tunAdapter,
|
||||||
|
iceBind: bind.NewICEBind(transportNet),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) Create(tunFd int32) error {
|
||||||
|
log.Infof("create tun interface")
|
||||||
|
|
||||||
|
dupTunFd, err := unix.Dup(int(tunFd))
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to dup tun fd: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = unix.SetNonblock(dupTunFd, true)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to set tun fd as non blocking: %v", err)
|
||||||
|
unix.Close(dupTunFd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tun, err := tun.CreateTUNFromFile(os.NewFile(uintptr(dupTunFd), "/dev/tun"), 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to create new tun device from fd: %v", err)
|
||||||
|
unix.Close(dupTunFd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.wrapper = newDeviceWrapper(tun)
|
||||||
|
log.Debug("Attaching to interface")
|
||||||
|
t.device = device.NewDevice(t.wrapper, t.iceBind, device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
|
||||||
|
// without this property mobile devices can discover remote endpoints if the configured one was wrong.
|
||||||
|
// this helps with support for the older NetBird clients that had a hardcoded direct mode
|
||||||
|
// t.device.DisableSomeRoamingForBrokenMobileSemantics()
|
||||||
|
|
||||||
|
err = t.device.Up()
|
||||||
|
if err != nil {
|
||||||
|
t.device.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf("device is ready to use: %s", t.name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) Device() *device.Device {
|
||||||
|
return t.device
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) DeviceName() string {
|
||||||
|
return t.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) WgAddress() WGAddress {
|
||||||
|
return t.address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) UpdateAddr(addr WGAddress) error {
|
||||||
|
// todo implement
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) Close() (err error) {
|
||||||
|
if t.device != nil {
|
||||||
|
t.device.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tunDevice) routesToString(routes []string) string {
|
||||||
|
return strings.Join(routes, ";")
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build (linux || darwin) && !android
|
//go:build (linux || darwin) && !android && !ios
|
||||||
|
|
||||||
package iface
|
package iface
|
||||||
|
|
||||||
|
|||||||
165
iface/wg_configurer_ios.go
Normal file
165
iface/wg_configurer_ios.go
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
//go:build ios
|
||||||
|
// +build ios
|
||||||
|
|
||||||
|
package iface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errFuncNotImplemented = errors.New("function not implemented")
|
||||||
|
)
|
||||||
|
|
||||||
|
type wGConfigurer struct {
|
||||||
|
tunDevice *tunDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWGConfigurer(tunDevice *tunDevice) wGConfigurer {
|
||||||
|
return wGConfigurer{
|
||||||
|
tunDevice: tunDevice,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *wGConfigurer) configureInterface(privateKey string, port int) error {
|
||||||
|
log.Debugf("adding Wireguard private key")
|
||||||
|
key, err := wgtypes.ParseKey(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fwmark := 0
|
||||||
|
config := wgtypes.Config{
|
||||||
|
PrivateKey: &key,
|
||||||
|
ReplacePeers: true,
|
||||||
|
FirewallMark: &fwmark,
|
||||||
|
ListenPort: &port,
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.tunDevice.Device().IpcSet(toWgUserspaceString(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *wGConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
||||||
|
// parse allowed ips
|
||||||
|
_, ipNet, err := net.ParseCIDR(allowedIps)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
peer := wgtypes.PeerConfig{
|
||||||
|
PublicKey: peerKeyParsed,
|
||||||
|
ReplaceAllowedIPs: true,
|
||||||
|
AllowedIPs: []net.IPNet{*ipNet},
|
||||||
|
PersistentKeepaliveInterval: &keepAlive,
|
||||||
|
PresharedKey: preSharedKey,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
config := wgtypes.Config{
|
||||||
|
Peers: []wgtypes.PeerConfig{peer},
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.tunDevice.Device().IpcSet(toWgUserspaceString(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *wGConfigurer) removePeer(peerKey string) error {
|
||||||
|
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peer := wgtypes.PeerConfig{
|
||||||
|
PublicKey: peerKeyParsed,
|
||||||
|
Remove: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
config := wgtypes.Config{
|
||||||
|
Peers: []wgtypes.PeerConfig{peer},
|
||||||
|
}
|
||||||
|
return c.tunDevice.Device().IpcSet(toWgUserspaceString(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *wGConfigurer) addAllowedIP(peerKey string, allowedIP string) error {
|
||||||
|
_, ipNet, err := net.ParseCIDR(allowedIP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
peer := wgtypes.PeerConfig{
|
||||||
|
PublicKey: peerKeyParsed,
|
||||||
|
UpdateOnly: true,
|
||||||
|
ReplaceAllowedIPs: false,
|
||||||
|
AllowedIPs: []net.IPNet{*ipNet},
|
||||||
|
}
|
||||||
|
|
||||||
|
config := wgtypes.Config{
|
||||||
|
Peers: []wgtypes.PeerConfig{peer},
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.tunDevice.Device().IpcSet(toWgUserspaceString(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *wGConfigurer) removeAllowedIP(peerKey string, ip string) error {
|
||||||
|
ipc, err := c.tunDevice.Device().IpcGet()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
|
||||||
|
hexKey := hex.EncodeToString(peerKeyParsed[:])
|
||||||
|
|
||||||
|
lines := strings.Split(ipc, "\n")
|
||||||
|
|
||||||
|
output := ""
|
||||||
|
foundPeer := false
|
||||||
|
removedAllowedIP := false
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
|
||||||
|
// If we're within the details of the found peer and encounter another public key,
|
||||||
|
// this means we're starting another peer's details. So, reset the flag.
|
||||||
|
if strings.HasPrefix(line, "public_key=") && foundPeer {
|
||||||
|
foundPeer = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify the peer with the specific public key
|
||||||
|
if line == fmt.Sprintf("public_key=%s", hexKey) {
|
||||||
|
foundPeer = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're within the details of the found peer and find the specific allowed IP, skip this line
|
||||||
|
if foundPeer && line == "allowed_ip="+ip {
|
||||||
|
removedAllowedIP = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the line to the output string
|
||||||
|
if strings.HasPrefix(line, "private_key=") || strings.HasPrefix(line, "listen_port=") ||
|
||||||
|
strings.HasPrefix(line, "public_key=") || strings.HasPrefix(line, "preshared_key=") ||
|
||||||
|
strings.HasPrefix(line, "endpoint=") || strings.HasPrefix(line, "persistent_keepalive_interval=") ||
|
||||||
|
strings.HasPrefix(line, "allowed_ip=") {
|
||||||
|
output += line + "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !removedAllowedIP {
|
||||||
|
return fmt.Errorf("allowedIP not found")
|
||||||
|
} else {
|
||||||
|
return c.tunDevice.Device().IpcSet(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !android
|
//go:build !android && !ios
|
||||||
|
|
||||||
package iface
|
package iface
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ func (c *wGConfigurer) configureInterface(privateKey string, port int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *wGConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
func (c *wGConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
|
||||||
//parse allowed ips
|
// parse allowed ips
|
||||||
_, ipNet, err := net.ParseCIDR(allowedIps)
|
_, ipNet, err := net.ParseCIDR(allowedIps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken}
|
|||||||
# PKCE authorization flow
|
# PKCE authorization flow
|
||||||
NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS=${NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS:-"53000"}
|
NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS=${NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS:-"53000"}
|
||||||
NETBIRD_AUTH_PKCE_USE_ID_TOKEN=${NETBIRD_AUTH_PKCE_USE_ID_TOKEN:-false}
|
NETBIRD_AUTH_PKCE_USE_ID_TOKEN=${NETBIRD_AUTH_PKCE_USE_ID_TOKEN:-false}
|
||||||
|
NETBIRD_AUTH_PKCE_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
||||||
|
|
||||||
|
# Dashboard
|
||||||
|
|
||||||
|
# The default setting is to transmit the audience to the IDP during authorization. However,
|
||||||
|
# if your IDP does not have this capability, you can turn this off by setting it to false.
|
||||||
|
NETBIRD_DASH_AUTH_USE_AUDIENCE=${NETBIRD_DASH_AUTH_USE_AUDIENCE:-true}
|
||||||
|
NETBIRD_DASH_AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
||||||
|
|
||||||
# exports
|
# exports
|
||||||
export NETBIRD_DOMAIN
|
export NETBIRD_DOMAIN
|
||||||
@@ -87,3 +95,6 @@ export NETBIRD_AUTH_DEVICE_AUTH_SCOPE
|
|||||||
export NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN
|
export NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN
|
||||||
export NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT
|
export NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT
|
||||||
export NETBIRD_AUTH_PKCE_USE_ID_TOKEN
|
export NETBIRD_AUTH_PKCE_USE_ID_TOKEN
|
||||||
|
export NETBIRD_AUTH_PKCE_AUDIENCE
|
||||||
|
export NETBIRD_DASH_AUTH_USE_AUDIENCE
|
||||||
|
export NETBIRD_DASH_AUTH_AUDIENCE
|
||||||
@@ -164,6 +164,12 @@ done
|
|||||||
|
|
||||||
export NETBIRD_AUTH_PKCE_REDIRECT_URLS=${REDIRECT_URLS%,}
|
export NETBIRD_AUTH_PKCE_REDIRECT_URLS=${REDIRECT_URLS%,}
|
||||||
|
|
||||||
|
# Remove audience for providers that do not support it
|
||||||
|
if [ "$NETBIRD_DASH_AUTH_USE_AUDIENCE" = "false" ]; then
|
||||||
|
export NETBIRD_DASH_AUTH_AUDIENCE=none
|
||||||
|
export NETBIRD_AUTH_PKCE_AUDIENCE=
|
||||||
|
fi
|
||||||
|
|
||||||
env | grep NETBIRD
|
env | grep NETBIRD
|
||||||
|
|
||||||
envsubst <docker-compose.yml.tmpl >docker-compose.yml
|
envsubst <docker-compose.yml.tmpl >docker-compose.yml
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ services:
|
|||||||
- NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
- NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
||||||
- NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
- NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
||||||
# OIDC
|
# OIDC
|
||||||
- AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
- AUTH_AUDIENCE=$NETBIRD_DASH_AUTH_AUDIENCE
|
||||||
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
||||||
- AUTH_CLIENT_SECRET=$NETBIRD_AUTH_CLIENT_SECRET
|
- AUTH_CLIENT_SECRET=$NETBIRD_AUTH_CLIENT_SECRET
|
||||||
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
||||||
@@ -36,7 +36,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- $SIGNAL_VOLUMENAME:/var/lib/netbird
|
- $SIGNAL_VOLUMENAME:/var/lib/netbird
|
||||||
ports:
|
ports:
|
||||||
- 10000:80
|
- $NETBIRD_SIGNAL_PORT:80
|
||||||
# # port and command for Let's Encrypt validation
|
# # port and command for Let's Encrypt validation
|
||||||
# - 443:443
|
# - 443:443
|
||||||
# command: ["--letsencrypt-domain", "$NETBIRD_LETSENCRYPT_DOMAIN", "--log-file", "console"]
|
# command: ["--letsencrypt-domain", "$NETBIRD_LETSENCRYPT_DOMAIN", "--log-file", "console"]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ services:
|
|||||||
- NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
- NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
||||||
- NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
- NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
||||||
# OIDC
|
# OIDC
|
||||||
- AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
- AUTH_AUDIENCE=$NETBIRD_DASH_AUTH_AUDIENCE
|
||||||
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
||||||
- AUTH_CLIENT_SECRET=$NETBIRD_AUTH_CLIENT_SECRET
|
- AUTH_CLIENT_SECRET=$NETBIRD_AUTH_CLIENT_SECRET
|
||||||
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
||||||
@@ -20,6 +20,7 @@ services:
|
|||||||
- AUTH_SUPPORTED_SCOPES=$NETBIRD_AUTH_SUPPORTED_SCOPES
|
- AUTH_SUPPORTED_SCOPES=$NETBIRD_AUTH_SUPPORTED_SCOPES
|
||||||
- AUTH_REDIRECT_URI=$NETBIRD_AUTH_REDIRECT_URI
|
- AUTH_REDIRECT_URI=$NETBIRD_AUTH_REDIRECT_URI
|
||||||
- AUTH_SILENT_REDIRECT_URI=$NETBIRD_AUTH_SILENT_REDIRECT_URI
|
- AUTH_SILENT_REDIRECT_URI=$NETBIRD_AUTH_SILENT_REDIRECT_URI
|
||||||
|
- NETBIRD_TOKEN_SOURCE=$NETBIRD_TOKEN_SOURCE
|
||||||
# SSL
|
# SSL
|
||||||
- NGINX_SSL_PORT=443
|
- NGINX_SSL_PORT=443
|
||||||
# Letsencrypt
|
# Letsencrypt
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
},
|
},
|
||||||
"PKCEAuthorizationFlow": {
|
"PKCEAuthorizationFlow": {
|
||||||
"ProviderConfig": {
|
"ProviderConfig": {
|
||||||
"Audience": "$NETBIRD_AUTH_AUDIENCE",
|
"Audience": "$NETBIRD_AUTH_PKCE_AUDIENCE",
|
||||||
"ClientID": "$NETBIRD_AUTH_CLIENT_ID",
|
"ClientID": "$NETBIRD_AUTH_CLIENT_ID",
|
||||||
"ClientSecret": "$NETBIRD_AUTH_CLIENT_SECRET",
|
"ClientSecret": "$NETBIRD_AUTH_CLIENT_SECRET",
|
||||||
"AuthorizationEndpoint": "$NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT",
|
"AuthorizationEndpoint": "$NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT",
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ NETBIRD_DOMAIN=""
|
|||||||
# e.g., https://example.eu.auth0.com/.well-known/openid-configuration
|
# e.g., https://example.eu.auth0.com/.well-known/openid-configuration
|
||||||
# -------------------------------------------
|
# -------------------------------------------
|
||||||
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT=""
|
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT=""
|
||||||
|
# The default setting is to transmit the audience to the IDP during authorization. However,
|
||||||
|
# if your IDP does not have this capability, you can turn this off by setting it to false.
|
||||||
|
#NETBIRD_DASH_AUTH_USE_AUDIENCE=false
|
||||||
NETBIRD_AUTH_AUDIENCE=""
|
NETBIRD_AUTH_AUDIENCE=""
|
||||||
# e.g. netbird-client
|
# e.g. netbird-client
|
||||||
NETBIRD_AUTH_CLIENT_ID=""
|
NETBIRD_AUTH_CLIENT_ID=""
|
||||||
|
|||||||
@@ -22,3 +22,4 @@ NETBIRD_AUTH_DEVICE_AUTH_SCOPE="openid email"
|
|||||||
NETBIRD_MGMT_IDP=$CI_NETBIRD_MGMT_IDP
|
NETBIRD_MGMT_IDP=$CI_NETBIRD_MGMT_IDP
|
||||||
NETBIRD_IDP_MGMT_CLIENT_ID=$CI_NETBIRD_IDP_MGMT_CLIENT_ID
|
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()
|
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
|
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||||
eventStore)
|
eventStore, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
"github.com/netbirdio/netbird/management/server/activity/sqlite"
|
"github.com/netbirdio/netbird/management/server/activity/sqlite"
|
||||||
httpapi "github.com/netbirdio/netbird/management/server/http"
|
httpapi "github.com/netbirdio/netbird/management/server/http"
|
||||||
"github.com/netbirdio/netbird/management/server/idp"
|
"github.com/netbirdio/netbird/management/server/idp"
|
||||||
@@ -142,12 +143,22 @@ var (
|
|||||||
if disableSingleAccMode {
|
if disableSingleAccMode {
|
||||||
mgmtSingleAccModeDomain = ""
|
mgmtSingleAccModeDomain = ""
|
||||||
}
|
}
|
||||||
eventStore, err := sqlite.NewSQLiteStore(config.Datadir)
|
eventStore, key, err := initEventStore(config.Datadir, config.DataStoreEncryptionKey)
|
||||||
if err != nil {
|
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,
|
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
|
||||||
dnsDomain, eventStore)
|
dnsDomain, eventStore, userDeleteFromIDPEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to build default manager: %v", err)
|
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) {
|
func notifyStop(msg string) {
|
||||||
select {
|
select {
|
||||||
case stopCh <- 1:
|
case stopCh <- 1:
|
||||||
@@ -440,6 +465,10 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
|||||||
return loadedConfig, err
|
return loadedConfig, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateMgmtConfig(path string, config *server.Config) error {
|
||||||
|
return util.DirectWriteJson(path, config)
|
||||||
|
}
|
||||||
|
|
||||||
// OIDCConfigResponse used for parsing OIDC config response
|
// OIDCConfigResponse used for parsing OIDC config response
|
||||||
type OIDCConfigResponse struct {
|
type OIDCConfigResponse struct {
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ var (
|
|||||||
disableMetrics bool
|
disableMetrics bool
|
||||||
disableSingleAccMode bool
|
disableSingleAccMode bool
|
||||||
idpSignKeyRefreshEnabled bool
|
idpSignKeyRefreshEnabled bool
|
||||||
|
userDeleteFromIDPEnabled bool
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "netbird-mgmt",
|
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().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().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(&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.MarkFlagRequired("config") //nolint
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
|
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
|
||||||
|
|||||||
@@ -62,12 +62,9 @@ type AccountManager interface {
|
|||||||
GetAccountFromPAT(pat string) (*Account, *User, *PersonalAccessToken, error)
|
GetAccountFromPAT(pat string) (*Account, *User, *PersonalAccessToken, error)
|
||||||
MarkPATUsed(tokenID string) error
|
MarkPATUsed(tokenID string) error
|
||||||
GetUser(claims jwtclaims.AuthorizationClaims) (*User, error)
|
GetUser(claims jwtclaims.AuthorizationClaims) (*User, error)
|
||||||
AccountExists(accountId string) (*bool, error)
|
|
||||||
GetPeerByKey(peerKey string) (*Peer, error)
|
|
||||||
GetPeers(accountID, userID string) ([]*Peer, error)
|
GetPeers(accountID, userID string) ([]*Peer, error)
|
||||||
MarkPeerConnected(peerKey string, connected bool) error
|
MarkPeerConnected(peerKey string, connected bool) error
|
||||||
DeletePeer(accountID, peerID, userID string) (*Peer, error)
|
DeletePeer(accountID, peerID, userID string) error
|
||||||
GetPeerByIP(accountId string, peerIP string) (*Peer, error)
|
|
||||||
UpdatePeer(accountID, userID string, peer *Peer) (*Peer, error)
|
UpdatePeer(accountID, userID string, peer *Peer) (*Peer, error)
|
||||||
GetNetworkMap(peerID string) (*NetworkMap, error)
|
GetNetworkMap(peerID string) (*NetworkMap, error)
|
||||||
GetPeerNetwork(peerID string) (*Network, error)
|
GetPeerNetwork(peerID string) (*Network, error)
|
||||||
@@ -80,26 +77,22 @@ type AccountManager interface {
|
|||||||
GetUsersFromAccount(accountID, userID string) ([]*UserInfo, error)
|
GetUsersFromAccount(accountID, userID string) ([]*UserInfo, error)
|
||||||
GetGroup(accountId, groupID string) (*Group, error)
|
GetGroup(accountId, groupID string) (*Group, error)
|
||||||
SaveGroup(accountID, userID string, group *Group) error
|
SaveGroup(accountID, userID string, group *Group) error
|
||||||
UpdateGroup(accountID string, groupID string, operations []GroupUpdateOperation) (*Group, error)
|
|
||||||
DeleteGroup(accountId, userId, groupID string) error
|
DeleteGroup(accountId, userId, groupID string) error
|
||||||
ListGroups(accountId string) ([]*Group, error)
|
ListGroups(accountId string) ([]*Group, error)
|
||||||
GroupAddPeer(accountId, groupID, peerID string) 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)
|
GetPolicy(accountID, policyID, userID string) (*Policy, error)
|
||||||
SavePolicy(accountID, userID string, policy *Policy) error
|
SavePolicy(accountID, userID string, policy *Policy) error
|
||||||
DeletePolicy(accountID, policyID, userID string) error
|
DeletePolicy(accountID, policyID, userID string) error
|
||||||
ListPolicies(accountID, userID string) ([]*Policy, error)
|
ListPolicies(accountID, userID string) ([]*Policy, error)
|
||||||
GetRoute(accountID, routeID, userID string) (*route.Route, 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
|
SaveRoute(accountID, userID string, route *route.Route) error
|
||||||
UpdateRoute(accountID, routeID string, operations []RouteUpdateOperation) (*route.Route, error)
|
|
||||||
DeleteRoute(accountID, routeID, userID string) error
|
DeleteRoute(accountID, routeID, userID string) error
|
||||||
ListRoutes(accountID, userID string) ([]*route.Route, error)
|
ListRoutes(accountID, userID string) ([]*route.Route, error)
|
||||||
GetNameServerGroup(accountID, nsGroupID string) (*nbdns.NameServerGroup, 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)
|
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
|
SaveNameServerGroup(accountID, userID string, nsGroupToSave *nbdns.NameServerGroup) error
|
||||||
UpdateNameServerGroup(accountID, nsGroupID, userID string, operations []NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error)
|
|
||||||
DeleteNameServerGroup(accountID, nsGroupID, userID string) error
|
DeleteNameServerGroup(accountID, nsGroupID, userID string) error
|
||||||
ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error)
|
ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error)
|
||||||
GetDNSDomain() string
|
GetDNSDomain() string
|
||||||
@@ -133,6 +126,9 @@ type DefaultAccountManager struct {
|
|||||||
// dnsDomain is used for peer resolution. This is appended to the peer's name
|
// dnsDomain is used for peer resolution. This is appended to the peer's name
|
||||||
dnsDomain string
|
dnsDomain string
|
||||||
peerLoginExpiry Scheduler
|
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
|
// Settings represents Account settings structure that can be modified via API and Dashboard
|
||||||
@@ -253,22 +249,39 @@ func (a *Account) filterRoutesByGroups(routes []*route.Route, groupListMap looku
|
|||||||
func (a *Account) getEnabledAndDisabledRoutesByPeer(peerID string) ([]*route.Route, []*route.Route) {
|
func (a *Account) getEnabledAndDisabledRoutesByPeer(peerID string) ([]*route.Route, []*route.Route) {
|
||||||
var enabledRoutes []*route.Route
|
var enabledRoutes []*route.Route
|
||||||
var disabledRoutes []*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 {
|
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 {
|
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.
|
takeRoute(r, peerID)
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return enabledRoutes, disabledRoutes
|
return enabledRoutes, disabledRoutes
|
||||||
@@ -286,17 +299,6 @@ func (a *Account) GetRoutesByPrefix(prefix netip.Prefix) []*route.Route {
|
|||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerByIP returns peer by it's IP if exists under account or nil otherwise
|
|
||||||
func (a *Account) GetPeerByIP(peerIP string) *Peer {
|
|
||||||
for _, peer := range a.Peers {
|
|
||||||
if peerIP == peer.IP.String() {
|
|
||||||
return peer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroup returns a group by ID if exists, nil otherwise
|
// GetGroup returns a group by ID if exists, nil otherwise
|
||||||
func (a *Account) GetGroup(groupID string) *Group {
|
func (a *Account) GetGroup(groupID string) *Group {
|
||||||
return a.Groups[groupID]
|
return a.Groups[groupID]
|
||||||
@@ -316,8 +318,51 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap {
|
|||||||
}
|
}
|
||||||
peersToConnect = append(peersToConnect, p)
|
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)
|
dnsManagementStatus := a.getPeerDNSManagementStatus(peerID)
|
||||||
dnsUpdate := nbdns.Config{
|
dnsUpdate := nbdns.Config{
|
||||||
@@ -577,8 +622,8 @@ func (a *Account) Copy() *Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
routes := map[string]*route.Route{}
|
routes := map[string]*route.Route{}
|
||||||
for id, route := range a.Routes {
|
for id, r := range a.Routes {
|
||||||
routes[id] = route.Copy()
|
routes[id] = r.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
nsGroups := map[string]*nbdns.NameServerGroup{}
|
nsGroups := map[string]*nbdns.NameServerGroup{}
|
||||||
@@ -738,18 +783,19 @@ func (a *Account) UserGroupsRemoveFromPeers(userID string, groups ...string) {
|
|||||||
|
|
||||||
// BuildManager creates a new DefaultAccountManager with a provided Store
|
// BuildManager creates a new DefaultAccountManager with a provided Store
|
||||||
func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager,
|
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) {
|
) (*DefaultAccountManager, error) {
|
||||||
am := &DefaultAccountManager{
|
am := &DefaultAccountManager{
|
||||||
Store: store,
|
Store: store,
|
||||||
peersUpdateManager: peersUpdateManager,
|
peersUpdateManager: peersUpdateManager,
|
||||||
idpManager: idpManager,
|
idpManager: idpManager,
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
cacheMux: sync.Mutex{},
|
cacheMux: sync.Mutex{},
|
||||||
cacheLoading: map[string]chan struct{}{},
|
cacheLoading: map[string]chan struct{}{},
|
||||||
dnsDomain: dnsDomain,
|
dnsDomain: dnsDomain,
|
||||||
eventStore: eventStore,
|
eventStore: eventStore,
|
||||||
peerLoginExpiry: NewDefaultScheduler(),
|
peerLoginExpiry: NewDefaultScheduler(),
|
||||||
|
userDeleteFromIDPEnabled: userDeleteFromIDPEnabled,
|
||||||
}
|
}
|
||||||
allAccounts := store.GetAllAccounts()
|
allAccounts := store.GetAllAccounts()
|
||||||
// enable single account mode only if configured by user and number of existing accounts is not grater than 1
|
// enable single account mode only if configured by user and number of existing accounts is not grater than 1
|
||||||
@@ -874,33 +920,19 @@ func (am *DefaultAccountManager) peerLoginExpirationJob(accountID string) func()
|
|||||||
return account.GetNextPeerExpiration()
|
return account.GetNextPeerExpiration()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expiredPeers := account.GetExpiredPeers()
|
||||||
var peerIDs []string
|
var peerIDs []string
|
||||||
for _, peer := range account.GetExpiredPeers() {
|
for _, peer := range expiredPeers {
|
||||||
if peer.Status.LoginExpired {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
peerIDs = append(peerIDs, peer.ID)
|
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)
|
log.Debugf("discovered %d peers to expire for account %s", len(peerIDs), account.Id)
|
||||||
|
|
||||||
if len(peerIDs) != 0 {
|
if err := am.expireAndUpdatePeers(account, expiredPeers); err != nil {
|
||||||
// this will trigger peer disconnect from the management service
|
log.Errorf("failed updating account peers while expiring peers for account %s", account.Id)
|
||||||
am.peersUpdateManager.CloseChannels(peerIDs)
|
return account.GetNextPeerExpiration()
|
||||||
err = am.updateAccountPeers(account)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed updating account peers while expiring peers for account %s", accountID)
|
|
||||||
return account.GetNextPeerExpiration()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return account.GetNextPeerExpiration()
|
return account.GetNextPeerExpiration()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -941,6 +973,27 @@ func (am *DefaultAccountManager) warmupIDPCache() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the Identity Provider does not support writing AppMetadata,
|
||||||
|
// in cases like this, we expect it to return all users in an "unset" field.
|
||||||
|
// We iterate over the users in the "unset" field, look up their AccountID in our store, and
|
||||||
|
// update their AppMetadata with the AccountID.
|
||||||
|
if unsetData, ok := userData[idp.UnsetAccountID]; ok {
|
||||||
|
for _, user := range unsetData {
|
||||||
|
accountID, err := am.Store.GetAccountByUser(user.ID)
|
||||||
|
if err == nil {
|
||||||
|
data := userData[accountID.Id]
|
||||||
|
if data == nil {
|
||||||
|
data = make([]*idp.UserData, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
user.AppMetadata.WTAccountID = accountID.Id
|
||||||
|
|
||||||
|
userData[accountID.Id] = append(data, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(userData, idp.UnsetAccountID)
|
||||||
|
|
||||||
for accountID, users := range userData {
|
for accountID, users := range userData {
|
||||||
err = am.cacheManager.Set(am.ctx, accountID, users, cacheStore.WithExpiration(cacheEntryExpiration()))
|
err = am.cacheManager.Set(am.ctx, accountID, users, cacheStore.WithExpiration(cacheEntryExpiration()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1007,7 +1060,36 @@ func (am *DefaultAccountManager) addAccountIDToIDPAppMeta(userID string, account
|
|||||||
|
|
||||||
func (am *DefaultAccountManager) loadAccount(_ context.Context, accountID interface{}) ([]*idp.UserData, error) {
|
func (am *DefaultAccountManager) loadAccount(_ context.Context, accountID interface{}) ([]*idp.UserData, error) {
|
||||||
log.Debugf("account %s not found in cache, reloading", accountID)
|
log.Debugf("account %s not found in cache, reloading", accountID)
|
||||||
return am.idpManager.GetAccount(fmt.Sprintf("%v", accountID))
|
accountIDString := fmt.Sprintf("%v", accountID)
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountIDString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userData, err := am.idpManager.GetAccount(accountIDString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dataMap := make(map[string]*idp.UserData, len(userData))
|
||||||
|
for _, datum := range userData {
|
||||||
|
dataMap[datum.ID] = datum
|
||||||
|
}
|
||||||
|
|
||||||
|
matchedUserData := make([]*idp.UserData, 0)
|
||||||
|
for _, user := range account.Users {
|
||||||
|
if user.IsServiceUser {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
datum, ok := dataMap[user.Id]
|
||||||
|
if !ok {
|
||||||
|
log.Warnf("user %s not found in IDP", user.Id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matchedUserData = append(matchedUserData, datum)
|
||||||
|
}
|
||||||
|
return matchedUserData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) lookupUserInCacheByEmail(email string, accountID string) (*idp.UserData, error) {
|
func (am *DefaultAccountManager) lookupUserInCacheByEmail(email string, accountID string) (*idp.UserData, error) {
|
||||||
@@ -1256,7 +1338,6 @@ func (am *DefaultAccountManager) redeemInvite(account *Account, userID string) e
|
|||||||
|
|
||||||
// MarkPATUsed marks a personal access token as used
|
// MarkPATUsed marks a personal access token as used
|
||||||
func (am *DefaultAccountManager) MarkPATUsed(tokenID string) error {
|
func (am *DefaultAccountManager) MarkPATUsed(tokenID string) error {
|
||||||
unlock := am.Store.AcquireGlobalLock()
|
|
||||||
|
|
||||||
user, err := am.Store.GetUserByTokenID(tokenID)
|
user, err := am.Store.GetUserByTokenID(tokenID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1268,8 +1349,7 @@ func (am *DefaultAccountManager) MarkPATUsed(tokenID string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
unlock()
|
unlock := am.Store.AcquireAccountLock(account.Id)
|
||||||
unlock = am.Store.AcquireAccountLock(account.Id)
|
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
account, err = am.Store.GetAccountByUser(user.Id)
|
account, err = am.Store.GetAccountByUser(user.Id)
|
||||||
@@ -1396,9 +1476,7 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat
|
|||||||
if err := am.Store.SaveAccount(account); err != nil {
|
if err := am.Store.SaveAccount(account); err != nil {
|
||||||
log.Errorf("failed to save account: %v", err)
|
log.Errorf("failed to save account: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if err := am.updateAccountPeers(account); err != nil {
|
am.updateAccountPeers(account)
|
||||||
log.Errorf("failed updating account peers while updating user %s", account.Id)
|
|
||||||
}
|
|
||||||
for _, g := range addNewGroups {
|
for _, g := range addNewGroups {
|
||||||
if group := account.GetGroup(g); group != nil {
|
if group := account.GetGroup(g); group != nil {
|
||||||
am.storeEvent(user.Id, user.Id, account.Id, activity.GroupAddedToUser,
|
am.storeEvent(user.Id, user.Id, account.Id, activity.GroupAddedToUser,
|
||||||
@@ -1509,26 +1587,6 @@ func isDomainValid(domain string) bool {
|
|||||||
return re.Match([]byte(domain))
|
return re.Match([]byte(domain))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountExists checks whether account exists (returns true) or not (returns false)
|
|
||||||
func (am *DefaultAccountManager) AccountExists(accountID string) (*bool, error) {
|
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
|
||||||
defer unlock()
|
|
||||||
|
|
||||||
var res bool
|
|
||||||
_, err := am.Store.GetAccount(accountID)
|
|
||||||
if err != nil {
|
|
||||||
if s, ok := status.FromError(err); ok && s.Type() == status.NotFound {
|
|
||||||
res = false
|
|
||||||
return &res, nil
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res = true
|
|
||||||
return &res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDNSDomain returns the configured dnsDomain
|
// GetDNSDomain returns the configured dnsDomain
|
||||||
func (am *DefaultAccountManager) GetDNSDomain() string {
|
func (am *DefaultAccountManager) GetDNSDomain() string {
|
||||||
return am.dnsDomain
|
return am.dnsDomain
|
||||||
@@ -1605,19 +1663,3 @@ func newAccountWithId(accountID, userID, domain string) *Account {
|
|||||||
}
|
}
|
||||||
return acc
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -706,30 +706,6 @@ func createAccount(am *DefaultAccountManager, accountID, userID, domain string)
|
|||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountManager_AccountExists(t *testing.T) {
|
|
||||||
manager, err := createManager(t)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedId := "test_account"
|
|
||||||
userId := "account_creator"
|
|
||||||
_, err = createAccount(manager, expectedId, userId, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
exists, err := manager.AccountExists(expectedId)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !*exists {
|
|
||||||
t.Errorf("expected account to exist after creation, got false")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccountManager_GetAccount(t *testing.T) {
|
func TestAccountManager_GetAccount(t *testing.T) {
|
||||||
manager, err := createManager(t)
|
manager, err := createManager(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1062,7 +1038,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if _, err := manager.DeletePeer(account.Id, peer3.ID, userID); err != nil {
|
if err := manager.DeletePeer(account.Id, peer3.ID, userID); err != nil {
|
||||||
t.Errorf("delete peer: %v", err)
|
t.Errorf("delete peer: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1129,7 +1105,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = manager.DeletePeer(account.Id, peerKey, userID)
|
err = manager.DeletePeer(account.Id, peerKey, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1385,8 +1361,9 @@ func TestAccount_Copy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Routes: map[string]*route.Route{
|
Routes: map[string]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
Groups: []string{"group1"},
|
PeerGroups: []string{},
|
||||||
|
Groups: []string{"group1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NameServerGroups: map[string]*nbdns.NameServerGroup{
|
NameServerGroups: map[string]*nbdns.NameServerGroup{
|
||||||
@@ -2063,7 +2040,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
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) {
|
func createStore(t *testing.T) (Store, error) {
|
||||||
|
|||||||
@@ -104,6 +104,8 @@ const (
|
|||||||
UserBlocked
|
UserBlocked
|
||||||
// UserUnblocked indicates that a user unblocked another user
|
// UserUnblocked indicates that a user unblocked another user
|
||||||
UserUnblocked
|
UserUnblocked
|
||||||
|
// UserDeleted indicates that a user deleted another user
|
||||||
|
UserDeleted
|
||||||
// GroupDeleted indicates that a user deleted group
|
// GroupDeleted indicates that a user deleted group
|
||||||
GroupDeleted
|
GroupDeleted
|
||||||
// UserLoggedInPeer indicates that user logged in their peer with an interactive SSO login
|
// 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"},
|
ServiceUserDeleted: {"Service user deleted", "service.user.delete"},
|
||||||
UserBlocked: {"User blocked", "user.block"},
|
UserBlocked: {"User blocked", "user.block"},
|
||||||
UserUnblocked: {"User unblocked", "user.unblock"},
|
UserUnblocked: {"User unblocked", "user.unblock"},
|
||||||
|
UserDeleted: {"User deleted", "user.delete"},
|
||||||
GroupDeleted: {"Group deleted", "group.delete"},
|
GroupDeleted: {"Group deleted", "group.delete"},
|
||||||
UserLoggedInPeer: {"User logged in peer", "user.peer.login"},
|
UserLoggedInPeer: {"User logged in peer", "user.peer.login"},
|
||||||
PeerLoginExpired: {"Peer login expired", "peer.login.expire"},
|
PeerLoginExpired: {"Peer login expired", "peer.login.expire"},
|
||||||
|
|||||||
@@ -18,10 +18,15 @@ type Event struct {
|
|||||||
ID uint64
|
ID uint64
|
||||||
// InitiatorID is the ID of an object that initiated the event (e.g., a user)
|
// InitiatorID is the ID of an object that initiated the event (e.g., a user)
|
||||||
InitiatorID string
|
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 is the ID of an object that was effected by the event (e.g., a peer)
|
||||||
TargetID string
|
TargetID string
|
||||||
// AccountID is the ID of an account where the event happened
|
// AccountID is the ID of an account where the event happened
|
||||||
AccountID string
|
AccountID string
|
||||||
|
|
||||||
// Meta of the event, e.g. deleted peer information like name, IP, etc
|
// Meta of the event, e.g. deleted peer information like name, IP, etc
|
||||||
Meta map[string]any
|
Meta map[string]any
|
||||||
}
|
}
|
||||||
@@ -35,12 +40,14 @@ func (e *Event) Copy() *Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Event{
|
return &Event{
|
||||||
Timestamp: e.Timestamp,
|
Timestamp: e.Timestamp,
|
||||||
Activity: e.Activity,
|
Activity: e.Activity,
|
||||||
ID: e.ID,
|
ID: e.ID,
|
||||||
InitiatorID: e.InitiatorID,
|
InitiatorID: e.InitiatorID,
|
||||||
TargetID: e.TargetID,
|
InitiatorName: e.InitiatorName,
|
||||||
AccountID: e.AccountID,
|
InitiatorEmail: e.InitiatorEmail,
|
||||||
Meta: meta,
|
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 (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
|
||||||
|
|
||||||
// sqlite driver
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -25,69 +25,122 @@ const (
|
|||||||
"meta TEXT," +
|
"meta TEXT," +
|
||||||
" target_id TEXT);"
|
" target_id TEXT);"
|
||||||
|
|
||||||
selectDescQuery = "SELECT id, activity, timestamp, initiator_id, target_id, account_id, meta" +
|
creatTableDeletedUsersQuery = `CREATE TABLE IF NOT EXISTS deleted_users (id TEXT NOT NULL, email TEXT NOT NULL, name TEXT);`
|
||||||
" FROM events WHERE account_id = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?;"
|
|
||||||
selectAscQuery = "SELECT id, activity, timestamp, initiator_id, target_id, account_id, meta" +
|
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 WHERE account_id = ? ORDER BY timestamp ASC LIMIT ? OFFSET ?;"
|
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) " +
|
insertQuery = "INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id, meta) " +
|
||||||
"VALUES(?, ?, ?, ?, ?, ?)"
|
"VALUES(?, ?, ?, ?, ?, ?)"
|
||||||
|
|
||||||
|
insertDeleteUserQuery = `INSERT INTO deleted_users(id, email, name) VALUES(?, ?, ?)`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Store is the implementation of the activity.Store interface backed by SQLite
|
// Store is the implementation of the activity.Store interface backed by SQLite
|
||||||
type Store struct {
|
type Store struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
|
fieldEncrypt *FieldEncrypt
|
||||||
|
|
||||||
insertStatement *sql.Stmt
|
insertStatement *sql.Stmt
|
||||||
selectAscStatement *sql.Stmt
|
selectAscStatement *sql.Stmt
|
||||||
selectDescStatement *sql.Stmt
|
selectDescStatement *sql.Stmt
|
||||||
|
deleteUserStmt *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSQLiteStore creates a new Store with an event table if not exists.
|
// 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)
|
dbFile := filepath.Join(dataDir, eventSinkDB)
|
||||||
db, err := sql.Open("sqlite3", dbFile)
|
db, err := sql.Open("sqlite3", dbFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crypt, err := NewFieldEncrypt(encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
_, err = db.Exec(createTableQuery)
|
_, err = db.Exec(createTableQuery)
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
insertStmt, err := db.Prepare(insertQuery)
|
insertStmt, err := db.Prepare(insertQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
selectDescStmt, err := db.Prepare(selectDescQuery)
|
selectDescStmt, err := db.Prepare(selectDescQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
selectAscStmt, err := db.Prepare(selectAscQuery)
|
selectAscStmt, err := db.Prepare(selectAscQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Store{
|
deleteUserStmt, err := db.Prepare(insertDeleteUserQuery)
|
||||||
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Store{
|
||||||
db: db,
|
db: db,
|
||||||
|
fieldEncrypt: crypt,
|
||||||
insertStatement: insertStmt,
|
insertStatement: insertStmt,
|
||||||
selectDescStatement: selectDescStmt,
|
selectDescStatement: selectDescStmt,
|
||||||
selectAscStatement: selectAscStmt,
|
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)
|
events := make([]*activity.Event, 0)
|
||||||
for result.Next() {
|
for result.Next() {
|
||||||
var id int64
|
var id int64
|
||||||
var operation activity.Activity
|
var operation activity.Activity
|
||||||
var timestamp time.Time
|
var timestamp time.Time
|
||||||
var initiator string
|
var initiator string
|
||||||
|
var initiatorName *string
|
||||||
|
var initiatorEmail *string
|
||||||
var target string
|
var target string
|
||||||
|
var targetUserName *string
|
||||||
|
var targetEmail *string
|
||||||
var account string
|
var account string
|
||||||
var jsonMeta 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 {
|
if err != nil {
|
||||||
return nil, err
|
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,
|
Timestamp: timestamp,
|
||||||
Activity: operation,
|
Activity: operation,
|
||||||
ID: uint64(id),
|
ID: uint64(id),
|
||||||
@@ -108,7 +181,27 @@ func processResult(result *sql.Rows) ([]*activity.Event, error) {
|
|||||||
TargetID: target,
|
TargetID: target,
|
||||||
AccountID: account,
|
AccountID: account,
|
||||||
Meta: meta,
|
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
|
return events, nil
|
||||||
@@ -127,13 +220,18 @@ func (store *Store) Get(accountID string, offset, limit int, descending bool) ([
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer result.Close() //nolint
|
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) {
|
func (store *Store) Save(event *activity.Event) (*activity.Event, error) {
|
||||||
var jsonMeta string
|
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)
|
metaBytes, err := json.Marshal(event.Meta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -156,6 +254,34 @@ func (store *Store) Save(event *activity.Event) (*activity.Event, error) {
|
|||||||
return eventCopy, nil
|
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
|
// Close the Store
|
||||||
func (store *Store) Close() error {
|
func (store *Store) Close() error {
|
||||||
if store.db != nil {
|
if store.db != nil {
|
||||||
@@ -163,3 +289,44 @@ func (store *Store) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
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) {
|
func TestNewSQLiteStore(t *testing.T) {
|
||||||
dataDir := t.TempDir()
|
dataDir := t.TempDir()
|
||||||
store, err := NewSQLiteStore(dataDir)
|
key, _ := GenerateKey()
|
||||||
|
store, err := NewSQLiteStore(dataDir, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ type Config struct {
|
|||||||
TURNConfig *TURNConfig
|
TURNConfig *TURNConfig
|
||||||
Signal *Host
|
Signal *Host
|
||||||
|
|
||||||
Datadir string
|
Datadir string
|
||||||
|
DataStoreEncryptionKey string
|
||||||
|
|
||||||
HttpConfig *HttpServerConfig
|
HttpConfig *HttpServerConfig
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,9 @@ func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string
|
|||||||
am.storeEvent(userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta)
|
am.storeEvent(userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
return am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig {
|
func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig {
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
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) {
|
func createDNSStore(t *testing.T) (Store, error) {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ func (e *EphemeralManager) cleanup() {
|
|||||||
|
|
||||||
for id, p := range deletePeers {
|
for id, p := range deletePeers {
|
||||||
log.Debugf("delete ephemeral peer: %s", id)
|
log.Debugf("delete ephemeral peer: %s", id)
|
||||||
_, err := e.accountManager.DeletePeer(p.account.Id, id, activity.SystemInitiator)
|
err := e.accountManager.DeletePeer(p.account.Id, id, activity.SystemInitiator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracef("failed to delete ephemeral peer: %s", err)
|
log.Tracef("failed to delete ephemeral peer: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ type MocAccountManager struct {
|
|||||||
store *MockStore
|
store *MockStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a MocAccountManager) DeletePeer(accountID, peerID, userID string) (*Peer, error) {
|
func (a MocAccountManager) DeletePeer(accountID, peerID, userID string) error {
|
||||||
delete(a.store.account.Peers, peerID)
|
delete(a.store.account.Peers, peerID)
|
||||||
return nil, nil //nolint:nilnil
|
return nil //nolint:nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewManager(t *testing.T) {
|
func TestNewManager(t *testing.T) {
|
||||||
|
|||||||
@@ -33,26 +33,6 @@ type Group struct {
|
|||||||
Peers []string
|
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
|
// EventMeta returns activity event meta related to the group
|
||||||
func (g *Group) EventMeta() map[string]any {
|
func (g *Group) EventMeta() map[string]any {
|
||||||
return map[string]any{"name": g.Name}
|
return map[string]any{"name": g.Name}
|
||||||
@@ -104,10 +84,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// the following snippet tracks the activity and stores the group events in the event store.
|
// the following snippet tracks the activity and stores the group events in the event store.
|
||||||
// It has to happen after all the operations have been successfully performed.
|
// It has to happen after all the operations have been successfully performed.
|
||||||
@@ -165,57 +142,6 @@ func difference(a, b []string) []string {
|
|||||||
return diff
|
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
|
// DeleteGroup object of the peers
|
||||||
func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string) error {
|
func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string) error {
|
||||||
unlock := am.Store.AcquireAccountLock(accountId)
|
unlock := am.Store.AcquireAccountLock(accountId)
|
||||||
@@ -300,7 +226,9 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string)
|
|||||||
|
|
||||||
am.storeEvent(userId, groupID, accountId, activity.GroupDeleted, g.EventMeta())
|
am.storeEvent(userId, groupID, accountId, activity.GroupDeleted, g.EventMeta())
|
||||||
|
|
||||||
return am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListGroups objects of the peers
|
// ListGroups objects of the peers
|
||||||
@@ -352,11 +280,13 @@ func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupDeletePeer removes peer from the group
|
// 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)
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
@@ -372,7 +302,7 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey str
|
|||||||
|
|
||||||
account.Network.IncSerial()
|
account.Network.IncSerial()
|
||||||
for i, itemID := range group.Peers {
|
for i, itemID := range group.Peers {
|
||||||
if itemID == peerKey {
|
if itemID == peerID {
|
||||||
group.Peers = append(group.Peers[:i], group.Peers[i+1:]...)
|
group.Peers = append(group.Peers[:i], group.Peers[i+1:]...)
|
||||||
if err := am.Store.SaveAccount(account); err != nil {
|
if err := am.Store.SaveAccount(account); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -380,31 +310,7 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
}
|
|
||||||
|
return nil
|
||||||
// GroupListPeers returns list of the peers from the group
|
|
||||||
func (am *DefaultAccountManager) GroupListPeers(accountID, groupID string) ([]*Peer, error) {
|
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
|
||||||
defer unlock()
|
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(status.NotFound, "account not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
group, ok := account.Groups[groupID]
|
|
||||||
if !ok {
|
|
||||||
return nil, status.Errorf(status.NotFound, "group with ID %s not found", groupID)
|
|
||||||
}
|
|
||||||
|
|
||||||
peers := make([]*Peer, 0, len(account.Groups))
|
|
||||||
for _, peerID := range group.Peers {
|
|
||||||
p, ok := account.Peers[peerID]
|
|
||||||
if ok {
|
|
||||||
peers = append(peers, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return peers, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,11 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi
|
|||||||
select {
|
select {
|
||||||
// condition when there are some updates
|
// condition when there are some updates
|
||||||
case update, open := <-updates:
|
case update, open := <-updates:
|
||||||
|
|
||||||
|
if s.appMetrics != nil {
|
||||||
|
s.appMetrics.GRPCMetrics().UpdateChannelQueueLength(len(updates) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
if !open {
|
if !open {
|
||||||
log.Debugf("updates channel for peer %s was closed", peerKey.String())
|
log.Debugf("updates channel for peer %s was closed", peerKey.String())
|
||||||
s.cancelPeerRoutines(peer)
|
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
|
type: boolean
|
||||||
example: true
|
example: true
|
||||||
peer:
|
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
|
type: string
|
||||||
example: chacbco6lnnbn6cg5s91
|
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:
|
network:
|
||||||
description: Network range in CIDR format
|
description: Network range in CIDR format
|
||||||
type: string
|
type: string
|
||||||
@@ -773,7 +779,9 @@ components:
|
|||||||
- description
|
- description
|
||||||
- network_id
|
- network_id
|
||||||
- enabled
|
- enabled
|
||||||
- peer
|
# Only one property has to be set
|
||||||
|
#- peer
|
||||||
|
#- peer_groups
|
||||||
- network
|
- network
|
||||||
- metric
|
- metric
|
||||||
- masquerade
|
- 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.
|
description: The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
|
||||||
type: string
|
type: string
|
||||||
example: google-oauth2|123456789012345678901
|
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:
|
target_id:
|
||||||
description: The ID of the target of the event. E.g., an ID of the peer that a user removed.
|
description: The ID of the target of the event. E.g., an ID of the peer that a user removed.
|
||||||
type: string
|
type: string
|
||||||
@@ -938,6 +954,8 @@ components:
|
|||||||
- activity
|
- activity
|
||||||
- activity_code
|
- activity_code
|
||||||
- initiator_id
|
- initiator_id
|
||||||
|
- initiator_name
|
||||||
|
- initiator_email
|
||||||
- target_id
|
- target_id
|
||||||
- meta
|
- meta
|
||||||
responses:
|
responses:
|
||||||
@@ -1134,8 +1152,8 @@ paths:
|
|||||||
'500':
|
'500':
|
||||||
"$ref": "#/components/responses/internal_error"
|
"$ref": "#/components/responses/internal_error"
|
||||||
delete:
|
delete:
|
||||||
summary: Block a User
|
summary: Delete a User
|
||||||
description: This method blocks a user from accessing the system, but leaves the IDP user intact.
|
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 ]
|
tags: [ Users ]
|
||||||
security:
|
security:
|
||||||
- BearerAuth: [ ]
|
- BearerAuth: [ ]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package api provides primitives to interact with the openapi HTTP API.
|
// 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
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -164,9 +164,15 @@ type Event struct {
|
|||||||
// Id Event unique identifier
|
// Id Event unique identifier
|
||||||
Id string `json:"id"`
|
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 The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
|
||||||
InitiatorId string `json:"initiator_id"`
|
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 The metadata of the event
|
||||||
Meta map[string]string `json:"meta"`
|
Meta map[string]string `json:"meta"`
|
||||||
|
|
||||||
@@ -593,8 +599,11 @@ type Route struct {
|
|||||||
// NetworkType Network type indicating if it is IPv4 or IPv6
|
// NetworkType Network type indicating if it is IPv4 or IPv6
|
||||||
NetworkType string `json:"network_type"`
|
NetworkType string `json:"network_type"`
|
||||||
|
|
||||||
// Peer Peer Identifier associated with route
|
// Peer Peer Identifier associated with route. This property can not be set together with `peer_groups`
|
||||||
Peer string `json:"peer"`
|
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.
|
// RouteRequest defines model for RouteRequest.
|
||||||
@@ -620,8 +629,11 @@ type RouteRequest struct {
|
|||||||
// NetworkId Route network identifier, to group HA routes
|
// NetworkId Route network identifier, to group HA routes
|
||||||
NetworkId string `json:"network_id"`
|
NetworkId string `json:"network_id"`
|
||||||
|
|
||||||
// Peer Peer Identifier associated with route
|
// Peer Peer Identifier associated with route. This property can not be set together with `peer_groups`
|
||||||
Peer string `json:"peer"`
|
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.
|
// Rule defines model for Rule.
|
||||||
|
|||||||
@@ -45,14 +45,66 @@ func (h *EventsHandler) GetAllEvents(w http.ResponseWriter, r *http.Request) {
|
|||||||
util.WriteError(err, w)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
events := make([]*api.Event, 0)
|
events := make([]*api.Event, len(accountEvents))
|
||||||
for _, e := range accountEvents {
|
for i, e := range accountEvents {
|
||||||
events = append(events, toEventResponse(e))
|
events[i] = toEventResponse(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.fillEventsWithUserInfo(events, account.Id, user.Id)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
util.WriteJSONObject(w, events)
|
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 {
|
func toEventResponse(event *activity.Event) *api.Event {
|
||||||
meta := make(map[string]string)
|
meta := make(map[string]string)
|
||||||
if event.Meta != nil {
|
if event.Meta != nil {
|
||||||
@@ -60,13 +112,16 @@ func toEventResponse(event *activity.Event) *api.Event {
|
|||||||
meta[s] = fmt.Sprintf("%v", a)
|
meta[s] = fmt.Sprintf("%v", a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &api.Event{
|
e := &api.Event{
|
||||||
Id: fmt.Sprint(event.ID),
|
Id: fmt.Sprint(event.ID),
|
||||||
InitiatorId: event.InitiatorID,
|
InitiatorId: event.InitiatorID,
|
||||||
Activity: event.Activity.Message(),
|
InitiatorName: event.InitiatorName,
|
||||||
ActivityCode: api.EventActivityCode(event.Activity.StringCode()),
|
InitiatorEmail: event.InitiatorEmail,
|
||||||
TargetId: event.TargetID,
|
Activity: event.Activity.Message(),
|
||||||
Timestamp: event.Timestamp,
|
ActivityCode: api.EventActivityCode(event.Activity.StringCode()),
|
||||||
Meta: meta,
|
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
|
}, user, nil
|
||||||
},
|
},
|
||||||
|
GetUsersFromAccountFunc: func(accountID, userID string) ([]*server.UserInfo, error) {
|
||||||
|
return make([]*server.UserInfo, 0), nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
||||||
jwtclaims.WithFromRequestContext(func(r *http.Request) jwtclaims.AuthorizationClaims {
|
jwtclaims.WithFromRequestContext(func(r *http.Request) jwtclaims.AuthorizationClaims {
|
||||||
|
|||||||
@@ -53,30 +53,6 @@ func initGroupTestData(user *server.User, groups ...*server.Group) *GroupsHandle
|
|||||||
Issued: server.GroupIssuedAPI,
|
Issued: server.GroupIssuedAPI,
|
||||||
}, nil
|
}, 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 {
|
|
||||||
return peer, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("peer not found")
|
|
||||||
},
|
|
||||||
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
return &server.Account{
|
return &server.Account{
|
||||||
Id: claims.AccountId,
|
Id: claims.AccountId,
|
||||||
|
|||||||
@@ -88,31 +88,6 @@ func initNameserversTestData() *NameserversHandler {
|
|||||||
}
|
}
|
||||||
return status.Errorf(status.NotFound, "nameserver group with ID %s was not found", nsGroupToSave.ID)
|
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) {
|
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
return testingNSAccount, testingAccount.Users["test_user"], nil
|
return testingNSAccount, testingAccount.Users["test_user"], nil
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *PeersHandler) deletePeer(accountID, userID string, peerID string, w http.ResponseWriter) {
|
func (h *PeersHandler) deletePeer(accountID, userID string, peerID string, w http.ResponseWriter) {
|
||||||
_, err := h.accountManager.DeletePeer(accountID, peerID, userID)
|
err := h.accountManager.DeletePeer(accountID, peerID, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteError(err, w)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -82,7 +82,33 @@ func (h *RoutesHandler) CreateRoute(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
util.WriteError(err, w)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
@@ -135,19 +161,49 @@ func (h *RoutesHandler) UpdateRoute(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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{
|
newRoute := &route.Route{
|
||||||
ID: routeID,
|
ID: routeID,
|
||||||
Network: newPrefix,
|
Network: newPrefix,
|
||||||
NetID: req.NetworkId,
|
NetID: req.NetworkId,
|
||||||
NetworkType: prefixType,
|
NetworkType: prefixType,
|
||||||
Masquerade: req.Masquerade,
|
Masquerade: req.Masquerade,
|
||||||
Peer: req.Peer,
|
|
||||||
Metric: req.Metric,
|
Metric: req.Metric,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
Enabled: req.Enabled,
|
Enabled: req.Enabled,
|
||||||
Groups: req.Groups,
|
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)
|
err = h.accountManager.SaveRoute(account.Id, user.Id, newRoute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteError(err, w)
|
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 {
|
func toRouteResponse(serverRoute *route.Route) *api.Route {
|
||||||
return &api.Route{
|
route := &api.Route{
|
||||||
Id: serverRoute.ID,
|
Id: serverRoute.ID,
|
||||||
Description: serverRoute.Description,
|
Description: serverRoute.Description,
|
||||||
NetworkId: serverRoute.NetID,
|
NetworkId: serverRoute.NetID,
|
||||||
Enabled: serverRoute.Enabled,
|
Enabled: serverRoute.Enabled,
|
||||||
Peer: serverRoute.Peer,
|
Peer: &serverRoute.Peer,
|
||||||
Network: serverRoute.Network.String(),
|
Network: serverRoute.Network.String(),
|
||||||
NetworkType: serverRoute.NetworkType.String(),
|
NetworkType: serverRoute.NetworkType.String(),
|
||||||
Masquerade: serverRoute.Masquerade,
|
Masquerade: serverRoute.Masquerade,
|
||||||
Metric: serverRoute.Metric,
|
Metric: serverRoute.Metric,
|
||||||
Groups: serverRoute.Groups,
|
Groups: serverRoute.Groups,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(serverRoute.PeerGroups) > 0 {
|
||||||
|
route.PeerGroups = &serverRoute.PeerGroups
|
||||||
|
}
|
||||||
|
return route
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
@@ -24,16 +23,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
existingRouteID = "existingRouteID"
|
existingRouteID = "existingRouteID"
|
||||||
notFoundRouteID = "notFoundRouteID"
|
existingRouteID2 = "existingRouteID2" // for peer_groups test
|
||||||
existingPeerIP = "100.64.0.100"
|
notFoundRouteID = "notFoundRouteID"
|
||||||
existingPeerID = "peer-id"
|
existingPeerIP1 = "100.64.0.100"
|
||||||
notFoundPeerID = "nonExistingPeer"
|
existingPeerIP2 = "100.64.0.101"
|
||||||
existingPeerKey = "existingPeerKey"
|
notFoundPeerID = "nonExistingPeer"
|
||||||
testAccountID = "test_id"
|
existingPeerKey = "existingPeerKey"
|
||||||
existingGroupID = "testGroup"
|
nonLinuxExistingPeerKey = "darwinExistingPeerKey"
|
||||||
|
testAccountID = "test_id"
|
||||||
|
existingGroupID = "testGroup"
|
||||||
|
notFoundGroupID = "nonExistingGroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var emptyString = ""
|
||||||
|
var existingPeerID = "peer-id"
|
||||||
|
var nonLinuxExistingPeerID = "darwin-peer-id"
|
||||||
|
|
||||||
var baseExistingRoute = &route.Route{
|
var baseExistingRoute = &route.Route{
|
||||||
ID: existingRouteID,
|
ID: existingRouteID,
|
||||||
Description: "base route",
|
Description: "base route",
|
||||||
@@ -52,8 +58,19 @@ var testingAccount = &server.Account{
|
|||||||
Peers: map[string]*server.Peer{
|
Peers: map[string]*server.Peer{
|
||||||
existingPeerID: {
|
existingPeerID: {
|
||||||
Key: existingPeerKey,
|
Key: existingPeerKey,
|
||||||
IP: netip.MustParseAddr(existingPeerIP).AsSlice(),
|
IP: netip.MustParseAddr(existingPeerIP1).AsSlice(),
|
||||||
ID: existingPeerID,
|
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{
|
Users: map[string]*server.User{
|
||||||
@@ -68,17 +85,26 @@ func initRoutesTestData() *RoutesHandler {
|
|||||||
if routeID == existingRouteID {
|
if routeID == existingRouteID {
|
||||||
return baseExistingRoute, nil
|
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)
|
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 {
|
if peerID == notFoundPeerID {
|
||||||
return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
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)
|
networkType, p, _ := route.ParseNetwork(network)
|
||||||
return &route.Route{
|
return &route.Route{
|
||||||
ID: existingRouteID,
|
ID: existingRouteID,
|
||||||
NetID: netID,
|
NetID: netID,
|
||||||
Peer: peerID,
|
Peer: peerID,
|
||||||
|
PeerGroups: peerGroups,
|
||||||
Network: p,
|
Network: p,
|
||||||
NetworkType: networkType,
|
NetworkType: networkType,
|
||||||
Description: description,
|
Description: description,
|
||||||
@@ -99,47 +125,6 @@ func initRoutesTestData() *RoutesHandler {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
|
||||||
if peerIP != existingPeerID {
|
|
||||||
return nil, status.Errorf(status.NotFound, "Peer with ID %s not found", peerIP)
|
|
||||||
}
|
|
||||||
return &server.Peer{
|
|
||||||
Key: existingPeerKey,
|
|
||||||
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) {
|
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
return testingAccount, testingAccount.Users["test_user"], nil
|
return testingAccount, testingAccount.Users["test_user"], nil
|
||||||
},
|
},
|
||||||
@@ -157,6 +142,9 @@ func initRoutesTestData() *RoutesHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRoutesHandlers(t *testing.T) {
|
func TestRoutesHandlers(t *testing.T) {
|
||||||
|
baseExistingRouteWithPeerGroups := baseExistingRoute.Copy()
|
||||||
|
baseExistingRouteWithPeerGroups.PeerGroups = []string{existingGroupID}
|
||||||
|
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
name string
|
name string
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
@@ -180,6 +168,14 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
requestPath: "/api/routes/" + notFoundRouteID,
|
requestPath: "/api/routes/" + notFoundRouteID,
|
||||||
expectedStatus: http.StatusNotFound,
|
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",
|
name: "Delete Existing Route",
|
||||||
requestType: http.MethodDelete,
|
requestType: http.MethodDelete,
|
||||||
@@ -206,13 +202,21 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
Description: "Post",
|
Description: "Post",
|
||||||
NetworkId: "awesomeNet",
|
NetworkId: "awesomeNet",
|
||||||
Network: "192.168.0.0/16",
|
Network: "192.168.0.0/16",
|
||||||
Peer: existingPeerID,
|
Peer: &existingPeerID,
|
||||||
NetworkType: route.IPv4NetworkString,
|
NetworkType: route.IPv4NetworkString,
|
||||||
Masquerade: false,
|
Masquerade: false,
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
Groups: []string{existingGroupID},
|
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",
|
name: "POST Not Found Peer",
|
||||||
requestType: http.MethodPost,
|
requestType: http.MethodPost,
|
||||||
@@ -237,6 +241,24 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
expectedStatus: http.StatusUnprocessableEntity,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
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",
|
name: "PUT OK",
|
||||||
requestType: http.MethodPut,
|
requestType: http.MethodPut,
|
||||||
@@ -249,7 +271,27 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
Description: "Post",
|
Description: "Post",
|
||||||
NetworkId: "awesomeNet",
|
NetworkId: "awesomeNet",
|
||||||
Network: "192.168.0.0/16",
|
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,
|
NetworkType: route.IPv4NetworkString,
|
||||||
Masquerade: false,
|
Masquerade: false,
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
@@ -272,6 +314,14 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
expectedStatus: http.StatusUnprocessableEntity,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
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",
|
name: "PUT Invalid Network Identifier",
|
||||||
requestType: http.MethodPut,
|
requestType: http.MethodPut,
|
||||||
@@ -288,6 +338,24 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
expectedStatus: http.StatusUnprocessableEntity,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
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()
|
p := initRoutesTestData()
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ func WriteErrorResponse(errMsg string, httpStatus int, w http.ResponseWriter) {
|
|||||||
// WriteError converts an error to an JSON error response.
|
// 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
|
// 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) {
|
func WriteError(err error, w http.ResponseWriter) {
|
||||||
|
log.Errorf("got a handler error: %s", err.Error())
|
||||||
errStatus, ok := status.FromError(err)
|
errStatus, ok := status.FromError(err)
|
||||||
httpStatus := http.StatusInternalServerError
|
httpStatus := http.StatusInternalServerError
|
||||||
msg := "internal server error"
|
msg := "internal server error"
|
||||||
|
|||||||
@@ -513,7 +513,9 @@ func buildUserExportRequest() (string, error) {
|
|||||||
return string(str), nil
|
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()
|
jwtToken, err := am.credentials.Authenticate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -521,17 +523,23 @@ func (am *Auth0Manager) createPostRequest(endpoint string, payloadStr string) (*
|
|||||||
|
|
||||||
reqURL := am.authIssuer + endpoint
|
reqURL := am.authIssuer + endpoint
|
||||||
|
|
||||||
payload := strings.NewReader(payloadStr)
|
req, err := http.NewRequest(method, reqURL, body)
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", reqURL, payload)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
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")
|
req.Header.Add("content-type", "application/json")
|
||||||
|
|
||||||
return req, nil
|
return req, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllAccounts gets all registered accounts with corresponding user data.
|
// GetAllAccounts gets all registered accounts with corresponding user data.
|
||||||
@@ -737,6 +745,38 @@ func (am *Auth0Manager) InviteUserByID(userID string) error {
|
|||||||
return nil
|
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.
|
// checkExportJobStatus checks the status of the job created at CreateExportUsersJob.
|
||||||
// If the status is "completed", then return the downloadLink
|
// If the status is "completed", then return the downloadLink
|
||||||
func (am *Auth0Manager) checkExportJobStatus(jobID string) (bool, string, error) {
|
func (am *Auth0Manager) checkExportJobStatus(jobID string) (bool, string, error) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user