Compare commits

...

39 Commits

Author SHA1 Message Date
Maycon Santos
2d350b2522 update protocol 2024-03-14 15:34:40 +01:00
Maycon Santos
d18d2db9ee use config struct 2024-03-13 15:37:56 +01:00
Maycon Santos
c3a1e1ca2c refactor function 2024-03-12 23:58:16 +01:00
Maycon Santos
c9acd2f880 Send ssh and rosenpass config meta 2024-03-12 23:44:27 +01:00
Viktor Liu
4a1aee1ae0 Add routes and dns servers to status command (#1680)
* Add routes (client and server) to status command
* Add DNS servers to status output
2024-03-12 19:06:16 +01:00
Krzysztof Nazarewski
ba33572ec9 add --service/-s flag for specifying system service name (#1691) 2024-03-12 18:29:19 +01:00
Maycon Santos
9d213e0b54 Add fallback retry to daemon (#1690)
This change adds a fallback retry to the daemon service.

this retry has a larger interval with a shorter max retry run time
then others retries
2024-03-12 18:05:41 +01:00
Maycon Santos
5dde044fa5 Check for record not found when searching the store (#1686)
This change returns status.NotFound only on gorm.ErrRecordNotFound and status.Internal on every other DB error
2024-03-10 19:09:45 +01:00
Bethuel Mmbaga
5a3d9e401f Send terminal notification on peer session expiry (#1660)
Send notification through terminal on user session expiration in Linux and macOS, 
unless UI application is installed to handle it instead.
2024-03-08 18:28:13 +01:00
Maycon Santos
fde1a2196c add ansible collections contributions (#1675) 2024-03-06 23:30:16 +01:00
Maycon Santos
0aeb87742a Return 1s when next expiration is too low (#1672)
using the login expired issue could cause problems with ticker used in the scheduler

This change makes 1s the minimum number returned when rescheduling the peer expiration task
2024-03-06 15:18:53 +01:00
Deniz Adrian
6d747b2f83 Do not fail on virtualized windows systems (#1669)
this handles virtualized systems without Win32_ComputerSystemProduct entries by returning 'unknown' for system product name

Co-authored-by: Bjoern Brauer <zaubernerd@zaubernerd.de>
2024-03-06 14:32:34 +01:00
Viktor Liu
199bf73103 Remove usage stats (#1665) 2024-03-05 09:45:32 +01:00
Maycon Santos
17f5abc653 Handle canceling schedule and avoid recursive call (#1636)
Using time.Ticker allows us to avoid recursive calls that may end up in schedule running and possible deadlock if no routine is listening for cancel calls
2024-03-03 10:35:01 +01:00
Maycon Santos
aa935bdae3 Register creation time for peer, user and account (#1654)
This change register creation time for new peers, users and accounts
2024-03-02 13:49:40 +01:00
Bethuel Mmbaga
452419c4c3 Send UI notification on peer connection session expiry (#1646)
notify the user when the peer connection session expires after it switches from connected 
to disconnected and the status is login required
2024-03-01 17:57:17 +01:00
Viktor Liu
17b1099032 Enhance DNS failover reliability (#1637)
* Fix using wrong array index in log to avoid potential panic

* Increase gRPC connection timeout and add the timeout resolv.conf option

This makes sure the dns client is able to failover to a second
configured nameserver, if present. That is the case then when using the
dns `file` manager and a resolv.conf file generated for netbird.

* On file backup restore, remove the first NS if it's the netbird NS

* Bump dns mangager discovery message from debug to info to ease debugging
2024-03-01 15:17:35 +01:00
pascal-fischer
a4b9e93217 add environment meta from grpc to store (#1651) 2024-03-01 15:15:56 +01:00
Maycon Santos
63d7957140 Log version when starting management and signal (#1649)
Output log version at service startup
2024-02-29 16:05:47 +01:00
Maycon Santos
9a6814deff Enable review linter with exported docs check (#1647)
Exported function and methods should have comments. This warns on undocumented exported functions and methods.
2024-02-29 16:05:31 +01:00
Misha Bragin
190698bcf2 Add open-source network security image (#1643) 2024-02-29 14:04:32 +00:00
pascal-fischer
468fa2940b add quotes to all timestamps in openapi spec (#1642) 2024-02-29 13:09:43 +01:00
Maycon Santos
79a0647a26 Update bug-issue-report.md label (#1640) 2024-02-29 10:21:00 +01:00
Maycon Santos
17ceb3bde8 Add reverse proxy settings to management.json template (#1639)
Add grpc_set_header to the nginx example template
2024-02-28 20:24:28 +01:00
Yury Gargay
5a8f1763a6 Implement Auth0 IdP get all connections method (#1613) 2024-02-28 16:57:35 +01:00
Bethuel Mmbaga
f64e73ca70 Fix invalid cross-device link when moving geolocation databases (#1638)
* Fix invalid cross-device link when move geonames db

* Add test for geolocation databases in workflow

This step checks the existence and proper functioning of geolocation databases, including GeoLite2-City.mmdb and Geonames.db. It will help us ensure that geolocation databases are loaded correctly in the management.

* Enable debug mode

* Increase sleep duration in geolocation tests
2024-02-28 16:42:33 +03:00
pascal-fischer
b085419ab8 FIx order when validating account settings (#1632)
* moved extraSettings validation to the end

* moved extraSettings validation directly after permission check
2024-02-27 14:17:22 +01:00
Bethuel Mmbaga
d78b652ff7 Rename PrivateNetworkCheck to PeerNetworkRangeCheck (#1629)
* Rename PrivateNetworkCheck to PeerNetworkRangeCheck

* update description and example

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2024-02-27 11:59:48 +01:00
Viktor Liu
7251150c1c Combine update-available and connected/disconnected tray icon states (#1615)
This PR updates the system tray icons to reflect both connection status and availability of updates. Now, the tray will show distinct icons for the following states: connected, disconnected, update available while connected, and update available while disconnected. This change improves user experience by providing a clear visual status indicator.

- Add new icons for connected and disconnected states with update available.
- Implement logic to switch icons based on connection status and update availability.
- Remove old icon references for default and update states.
2024-02-26 23:28:33 +01:00
Bethuel Mmbaga
b65c2f69b0 Add support for downloading Geo databases to the management service (#1626)
Adds support for downloading Geo databases to the management service. If the Geo databases are not found, the service will automatically attempt to download them during startup.
2024-02-26 22:49:28 +01:00
Yury Gargay
d8ce08d898 Extend bypass middleware with support of wildcard paths (#1628)
---------

Co-authored-by: Viktor Liu <viktor@netbird.io>
2024-02-26 17:54:58 +01:00
Maycon Santos
e1c50248d9 Add support for device flow on getting started with zitadel (#1616) 2024-02-26 12:33:16 +01:00
Maycon Santos
ce2d14c08e Update download-geolite2.sh to use packages URLs (#1624)
makes use of our hosted download URLs for geolocation DBs
2024-02-26 10:29:54 +01:00
Misha Bragin
52fd9a575a Add quantum resistance status output (#1608) 2024-02-24 11:41:13 +00:00
Yury Gargay
9028c3c1f7 Check git status after go mod tidy on CI (#1614) 2024-02-23 12:00:43 +01:00
ph1ll
9357a587e9 Make SQLite default in configuration generation script (#1610) 2024-02-23 11:43:11 +01:00
Bethuel Mmbaga
a47c69c472 Add private network posture check (#1606)
* wip: Add PrivateNetworkCheck checks interface implementation

* use generic CheckAction constant

* Add private network check to posture checks

* Fix copy function target in posture checks

* Add network check functionality to posture package

* regenerate the openapi specs

* Update Posture Check actions in test file

* Remove unused function

* Refactor network address handling in PrivateNetworkCheck

* Refactor Prefixes to Ranges in private network checks

* Implement private network checks in posture checks handler tests

* Add test for check copy

* Add gorm serializer for network range
2024-02-22 19:22:43 +03:00
Yury Gargay
bbea4c3cc3 Use SQLite store as default when running tests when env is not set (#1612) 2024-02-22 16:51:56 +01:00
Viktor Liu
b7a6cbfaa5 Add account usage logic (#1567)
---------

Co-authored-by: Yury Gargay <yury.gargay@gmail.com>
2024-02-22 12:27:08 +01:00
114 changed files with 4173 additions and 1102 deletions

View File

@@ -2,7 +2,7 @@
name: Bug/Issue report name: Bug/Issue report
about: Create a report to help us improve about: Create a report to help us improve
title: '' title: ''
labels: ['triage'] labels: ['triage-needed']
assignees: '' assignees: ''
--- ---

View File

@@ -35,5 +35,8 @@ jobs:
- name: Install modules - name: Install modules
run: go mod tidy run: go mod tidy
- name: check git status
run: git --no-pager diff --exit-code
- name: Test - name: Test
run: NETBIRD_STORE_ENGINE=${{ matrix.store }} go test -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 5m -p 1 ./... run: NETBIRD_STORE_ENGINE=${{ matrix.store }} go test -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 5m -p 1 ./...

View File

@@ -41,6 +41,9 @@ jobs:
- name: Install modules - name: Install modules
run: go mod tidy run: go mod tidy
- name: check git status
run: git --no-pager diff --exit-code
- name: Test - name: Test
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} NETBIRD_STORE_ENGINE=${{ matrix.store }} go test -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 5m -p 1 ./... run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} NETBIRD_STORE_ENGINE=${{ matrix.store }} go test -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 5m -p 1 ./...
@@ -69,6 +72,9 @@ jobs:
- name: Install modules - name: Install modules
run: go mod tidy run: go mod tidy
- name: check git status
run: git --no-pager diff --exit-code
- name: Generate Iface Test bin - name: Generate Iface Test bin
run: CGO_ENABLED=0 go test -c -o iface-testing.bin ./iface/ run: CGO_ENABLED=0 go test -c -o iface-testing.bin ./iface/

View File

@@ -11,7 +11,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
andrloid_build: android_build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -41,7 +41,7 @@ jobs:
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20230531173138-3c911d8e3eda run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20230531173138-3c911d8e3eda
- name: gomobile init - name: gomobile init
run: gomobile init run: gomobile init
- name: build android nebtird lib - name: build android netbird 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 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: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
@@ -59,7 +59,7 @@ jobs:
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20230531173138-3c911d8e3eda run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20230531173138-3c911d8e3eda
- name: gomobile init - name: gomobile init
run: gomobile init run: gomobile init
- name: build iOS nebtird lib - name: build iOS netbird lib
run: PATH=$PATH:$(go env GOPATH) gomobile bind -target=ios -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=buildtest" -o $GITHUB_WORKSPACE/NetBirdSDK.xcframework $GITHUB_WORKSPACE/client/ios/NetBirdSDK run: PATH=$PATH:$(go env GOPATH) gomobile bind -target=ios -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=buildtest" -o $GITHUB_WORKSPACE/NetBirdSDK.xcframework $GITHUB_WORKSPACE/client/ios/NetBirdSDK
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0

View File

@@ -190,6 +190,9 @@ jobs:
- -
name: Install modules name: Install modules
run: go mod tidy run: go mod tidy
-
name: check git status
run: git --no-pager diff --exit-code
- -
name: Run GoReleaser name: Run GoReleaser
id: goreleaser id: goreleaser

View File

@@ -127,6 +127,9 @@ jobs:
- name: Install modules - name: Install modules
run: go mod tidy run: go mod tidy
- name: check git status
run: git --no-pager diff --exit-code
- name: Build management binary - name: Build management binary
working-directory: management working-directory: management
run: CGO_ENABLED=1 go build -o netbird-mgmt main.go run: CGO_ENABLED=1 go build -o netbird-mgmt main.go
@@ -159,6 +162,13 @@ jobs:
test $count -eq 4 test $count -eq 4
working-directory: infrastructure_files/artifacts working-directory: infrastructure_files/artifacts
- name: test geolocation databases
working-directory: infrastructure_files/artifacts
run: |
sleep 30
docker compose exec management ls -l /var/lib/netbird/ | grep -i GeoLite2-City.mmdb
docker compose exec management ls -l /var/lib/netbird/ | grep -i geonames.db
test-getting-started-script: test-getting-started-script:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -186,3 +196,16 @@ jobs:
run: test -f zitadel.env run: test -f zitadel.env
- name: test dashboard.env file gen - name: test dashboard.env file gen
run: test -f dashboard.env run: test -f dashboard.env
test-download-geolite2-script:
runs-on: ubuntu-latest
steps:
- name: Install jq
run: sudo apt-get update && sudo apt-get install -y unzip sqlite3
- name: Checkout code
uses: actions/checkout@v3
- name: test script
run: bash -x infrastructure_files/download-geolite2.sh
- name: test mmdb file exists
run: test -f GeoLite2-City.mmdb
- name: test geonames file exists
run: test -f geonames.db

View File

@@ -63,6 +63,14 @@ linters-settings:
enable: enable:
- nilness - nilness
revive:
rules:
- name: exported
severity: warning
disabled: false
arguments:
- "checkPrivateReceivers"
- "sayRepetitiveInsteadOfStutters"
tenv: tenv:
# The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures.
# Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked.
@@ -93,6 +101,7 @@ linters:
- nilerr # finds the code that returns nil even if it checks that the error is not nil - nilerr # finds the code that returns nil even if it checks that the error is not nil
- nilnil # checks that there is no simultaneous return of nil error and an invalid value - nilnil # checks that there is no simultaneous return of nil error and an invalid value
- predeclared # predeclared finds code that shadows one of Go's predeclared identifiers - predeclared # predeclared finds code that shadows one of Go's predeclared identifiers
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint.
- sqlclosecheck # checks that sql.Rows and sql.Stmt are closed - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed
- thelper # thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers. - thelper # thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers.
- wastedassign # wastedassign finds wasted assignment statements - wastedassign # wastedassign finds wasted assignment statements

View File

@@ -54,7 +54,7 @@ nfpms:
contents: contents:
- src: client/ui/netbird.desktop - src: client/ui/netbird.desktop
dst: /usr/share/applications/netbird.desktop dst: /usr/share/applications/netbird.desktop
- src: client/ui/netbird-systemtray-default.png - src: client/ui/netbird-systemtray-connected.png
dst: /usr/share/pixmaps/netbird.png dst: /usr/share/pixmaps/netbird.png
dependencies: dependencies:
- netbird - netbird
@@ -71,7 +71,7 @@ nfpms:
contents: contents:
- src: client/ui/netbird.desktop - src: client/ui/netbird.desktop
dst: /usr/share/applications/netbird.desktop dst: /usr/share/applications/netbird.desktop
- src: client/ui/netbird-systemtray-default.png - src: client/ui/netbird-systemtray-connected.png
dst: /usr/share/pixmaps/netbird.png dst: /usr/share/pixmaps/netbird.png
dependencies: dependencies:
- netbird - netbird

View File

@@ -1,6 +1,6 @@
<p align="center"> <p align="center">
<strong>:hatching_chick: New Release! Self-hosting in under 5 min.</strong> <strong>:hatching_chick: New Release! Device Posture Checks.</strong>
<a href="https://github.com/netbirdio/netbird#quickstart-with-self-hosted-netbird"> <a href="https://docs.netbird.io/how-to/manage-posture-checks">
Learn more Learn more
</a> </a>
</p> </p>
@@ -42,25 +42,22 @@
**Secure.** NetBird enables secure remote access by applying granular access policies, while allowing you to manage them intuitively from a single place. Works universally on any infrastructure. **Secure.** NetBird enables secure remote access by applying granular access policies, while allowing you to manage them intuitively from a single place. Works universally on any infrastructure.
### Secure peer-to-peer VPN with SSO and MFA in minutes ### Open-Source Network Security in a Single Platform
https://user-images.githubusercontent.com/700848/197345890-2e2cded5-7b7a-436f-a444-94e80dd24f46.mov ![download (2)](https://github.com/netbirdio/netbird/assets/700848/16210ac2-7265-44c1-8d4e-8fae85534dac)
### Key features ### Key features
| Connectivity | Management | Automation | Platforms | | Connectivity | Management | Security | Automation | Platforms |
|---------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|----------------------------------------------------------------------------|---------------------------------------| |------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
| <ul><li> - \[x] Kernel WireGuard </ul></li> | <ul><li> - \[x] [Admin Web UI](https://github.com/netbirdio/dashboard) </ul></li> | <ul><li> - \[x] [Public API](https://docs.netbird.io/api) </ul></li> | <ul><li> - \[x] Linux </ul></li> | | <ul><li> - \[x] Kernel WireGuard </ul></li> | <ul><li> - \[x] [Admin Web UI](https://github.com/netbirdio/dashboard) </ul></li> | <ul><li> - \[x] [SSO & MFA support](https://docs.netbird.io/how-to/installation#running-net-bird-with-sso-login) </ul></li> | <ul><li> - \[x] [Public API](https://docs.netbird.io/api) </ul></li> | <ul><li> - \[x] Linux </ul></li> |
| <ul><li> - \[x] Peer-to-peer connections </ul></li> | <ul><li> - \[x] Auto peer discovery and configuration </ul></li> | <ul><li> - \[x] [Setup keys for bulk network provisioning](https://docs.netbird.io/how-to/register-machines-using-setup-keys) </ul></li> | <ul><li> - \[x] Mac </ul></li> | | <ul><li> - \[x] Peer-to-peer connections </ul></li> | <ul><li> - \[x] Auto peer discovery and configuration </ul></li> | <ul><li> - \[x] [Access control - groups & rules](https://docs.netbird.io/how-to/manage-network-access) </ul></li> | <ul><li> - \[x] [Setup keys for bulk network provisioning](https://docs.netbird.io/how-to/register-machines-using-setup-keys) </ul></li> | <ul><li> - \[x] Mac </ul></li> |
| <ul><li> - \[x] Peer-to-peer encryption </ul></li> | <ul><li> - \[x] [IdP integrations](https://docs.netbird.io/selfhosted/identity-providers) </ul></li> | <ul><li> - \[x] [Self-hosting quickstart script](https://docs.netbird.io/selfhosted/selfhosted-quickstart) </ul></li> | <ul><li> - \[x] Windows </ul></li> | | <ul><li> - \[x] Connection relay fallback </ul></li> | <ul><li> - \[x] [IdP integrations](https://docs.netbird.io/selfhosted/identity-providers) </ul></li> | <ul><li> - \[x] [Activity logging](https://docs.netbird.io/how-to/monitor-system-and-network-activity) </ul></li> | <ul><li> - \[x] [Self-hosting quickstart script](https://docs.netbird.io/selfhosted/selfhosted-quickstart) </ul></li> | <ul><li> - \[x] Windows </ul></li> |
| <ul><li> - \[x] Connection relay fallback </ul></li> | <ul><li> - \[x] [SSO & MFA support](https://docs.netbird.io/how-to/installation#running-net-bird-with-sso-login) </ul></li> | <ul><li> - \[x] IdP groups sync with JWT </ul></li> | <ul><li> - \[x] Android </ul></li> | | <ul><li> - \[x] [Routes to external networks](https://docs.netbird.io/how-to/routing-traffic-to-private-networks) </ul></li> | <ul><li> - \[x] [Private DNS](https://docs.netbird.io/how-to/manage-dns-in-your-network) </ul></li> | <ul><li> - \[x] [Device posture checks](https://docs.netbird.io/how-to/manage-posture-checks) </ul></li> | <ul><li> - \[x] IdP groups sync with JWT </ul></li> | <ul><li> - \[x] Android </ul></li> |
| <ul><li> - \[x] [Routes to external networks](https://docs.netbird.io/how-to/routing-traffic-to-private-networks) </ul></li> | <ul><li> - \[x] [Access control - groups & rules](https://docs.netbird.io/how-to/manage-network-access) </ul></li> | | <ul><li> - \[x] iOS </ul></li> | | <ul><li> - \[x] NAT traversal with BPF </ul></li> | <ul><li> - \[x] [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network) </ul></li> | <ul><li> - \[x] Peer-to-peer encryption </ul></li> | | <ul><li> - \[x] iOS </ul></li> |
| <ul><li> - \[x] NAT traversal with BPF </ul></li> | <ul><li> - \[x] [Private DNS](https://docs.netbird.io/how-to/manage-dns-in-your-network) </ul></li> | | <ul><li> - \[x] Docker </ul></li> | | | | <ul><li> - \[x] [Quantum-resistance with Rosenpass](https://netbird.io/knowledge-hub/the-first-quantum-resistant-mesh-vpn) </ul></li> | | <ul><li> - \[x] OpenWRT </ul></li> |
| <ul><li> - \[x] Post-quantum-secure connection through [Rosenpass](https://rosenpass.eu) </ul></li> | <ul><li> - \[x] [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network) </ul></li> | | <ul><li> - \[x] OpenWRT </ul></li> | | | | <ui><li> - \[x] [Periodic re-authentication](https://docs.netbird.io/how-to/enforce-periodic-user-authentication)</ul></li> | | <ul><li> - \[x] [Serverless](https://docs.netbird.io/how-to/netbird-on-faas) </ul></li> |
| | <ul><li> - \[x] [Activity logging](https://docs.netbird.io/how-to/monitor-system-and-network-activity) </ul></li> | | | | | | | | <ul><li> - \[x] Docker </ul></li> |
| | <ul><li> - \[x] SSH access management </ul></li> | | |
### Quickstart with NetBird Cloud ### Quickstart with NetBird Cloud
- Download and install NetBird at [https://app.netbird.io/install](https://app.netbird.io/install) - Download and install NetBird at [https://app.netbird.io/install](https://app.netbird.io/install)
@@ -109,8 +106,8 @@ export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbird
See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details. See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details.
### Community projects ### Community projects
- [NetBird on OpenWRT](https://github.com/messense/openwrt-netbird)
- [NetBird installer script](https://github.com/physk/netbird-installer) - [NetBird installer script](https://github.com/physk/netbird-installer)
- [NetBird ansible collection by Dominion Solutions](https://galaxy.ansible.com/ui/repo/published/dominion_solutions/netbird/)
**Note**: The `main` branch may be in an *unstable or even broken state* during development. **Note**: The `main` branch may be in an *unstable or even broken state* during development.
For stable versions, see [releases](https://github.com/netbirdio/netbird/releases). For stable versions, see [releases](https://github.com/netbirdio/netbird/releases).

View File

@@ -79,6 +79,7 @@ func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsRead
return err return err
} }
c.recorder.UpdateManagementAddress(cfg.ManagementURL.String()) c.recorder.UpdateManagementAddress(cfg.ManagementURL.String())
c.recorder.UpdateRosenpass(cfg.RosenpassEnabled, cfg.RosenpassPermissive)
var ctx context.Context var ctx context.Context
//nolint //nolint
@@ -109,6 +110,7 @@ func (c *Client) RunWithoutLogin(dns *DNSList, dnsReadyListener DnsReadyListener
return err return err
} }
c.recorder.UpdateManagementAddress(cfg.ManagementURL.String()) c.recorder.UpdateManagementAddress(cfg.ManagementURL.String())
c.recorder.UpdateRosenpass(cfg.RosenpassEnabled, cfg.RosenpassPermissive)
var ctx context.Context var ctx context.Context
//nolint //nolint

View File

@@ -61,6 +61,7 @@ var (
serverSSHAllowed bool serverSSHAllowed bool
interfaceName string interfaceName string
wireguardPort uint16 wireguardPort uint16
serviceName string
autoConnectDisabled bool autoConnectDisabled bool
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "netbird", Use: "netbird",
@@ -100,9 +101,16 @@ func init() {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
defaultDaemonAddr = "tcp://127.0.0.1:41731" defaultDaemonAddr = "tcp://127.0.0.1:41731"
} }
defaultServiceName := "netbird"
if runtime.GOOS == "windows" {
defaultServiceName = "Netbird"
}
rootCmd.PersistentFlags().StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]") rootCmd.PersistentFlags().StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
rootCmd.PersistentFlags().StringVarP(&managementURL, "management-url", "m", "", fmt.Sprintf("Management Service URL [http|https]://[host]:[port] (default \"%s\")", internal.DefaultManagementURL)) rootCmd.PersistentFlags().StringVarP(&managementURL, "management-url", "m", "", fmt.Sprintf("Management Service URL [http|https]://[host]:[port] (default \"%s\")", internal.DefaultManagementURL))
rootCmd.PersistentFlags().StringVar(&adminURL, "admin-url", "", fmt.Sprintf("Admin Panel URL [http|https]://[host]:[port] (default \"%s\")", internal.DefaultAdminURL)) rootCmd.PersistentFlags().StringVar(&adminURL, "admin-url", "", fmt.Sprintf("Admin Panel URL [http|https]://[host]:[port] (default \"%s\")", internal.DefaultAdminURL))
rootCmd.PersistentFlags().StringVarP(&serviceName, "service", "s", defaultServiceName, "Netbird system service name")
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", defaultConfigPath, "Netbird config file location") rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", defaultConfigPath, "Netbird config file location")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "info", "sets Netbird log level") rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "info", "sets Netbird log level")
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout") rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout")

View File

@@ -2,8 +2,6 @@ package cmd
import ( import (
"context" "context"
"runtime"
"github.com/kardianos/service" "github.com/kardianos/service"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -24,12 +22,8 @@ func newProgram(ctx context.Context, cancel context.CancelFunc) *program {
} }
func newSVCConfig() *service.Config { func newSVCConfig() *service.Config {
name := "netbird"
if runtime.GOOS == "windows" {
name = "Netbird"
}
return &service.Config{ return &service.Config{
Name: name, Name: serviceName,
DisplayName: "Netbird", DisplayName: "Netbird",
Description: "A WireGuard-based mesh network that connects your devices into a single private network.", Description: "A WireGuard-based mesh network that connects your devices into a single private network.",
Option: make(service.KeyValue), Option: make(service.KeyValue),

View File

@@ -34,6 +34,8 @@ type peerStateDetailOutput struct {
LastWireguardHandshake time.Time `json:"lastWireguardHandshake" yaml:"lastWireguardHandshake"` LastWireguardHandshake time.Time `json:"lastWireguardHandshake" yaml:"lastWireguardHandshake"`
TransferReceived int64 `json:"transferReceived" yaml:"transferReceived"` TransferReceived int64 `json:"transferReceived" yaml:"transferReceived"`
TransferSent int64 `json:"transferSent" yaml:"transferSent"` TransferSent int64 `json:"transferSent" yaml:"transferSent"`
RosenpassEnabled bool `json:"quantumResistance" yaml:"quantumResistance"`
Routes []string `json:"routes" yaml:"routes"`
} }
type peersStateOutput struct { type peersStateOutput struct {
@@ -71,17 +73,28 @@ type iceCandidateType struct {
Remote string `json:"remote" yaml:"remote"` Remote string `json:"remote" yaml:"remote"`
} }
type nsServerGroupStateOutput struct {
Servers []string `json:"servers" yaml:"servers"`
Domains []string `json:"domains" yaml:"domains"`
Enabled bool `json:"enabled" yaml:"enabled"`
Error string `json:"error" yaml:"error"`
}
type statusOutputOverview struct { type statusOutputOverview struct {
Peers peersStateOutput `json:"peers" yaml:"peers"` Peers peersStateOutput `json:"peers" yaml:"peers"`
CliVersion string `json:"cliVersion" yaml:"cliVersion"` CliVersion string `json:"cliVersion" yaml:"cliVersion"`
DaemonVersion string `json:"daemonVersion" yaml:"daemonVersion"` DaemonVersion string `json:"daemonVersion" yaml:"daemonVersion"`
ManagementState managementStateOutput `json:"management" yaml:"management"` ManagementState managementStateOutput `json:"management" yaml:"management"`
SignalState signalStateOutput `json:"signal" yaml:"signal"` SignalState signalStateOutput `json:"signal" yaml:"signal"`
Relays relayStateOutput `json:"relays" yaml:"relays"` Relays relayStateOutput `json:"relays" yaml:"relays"`
IP string `json:"netbirdIp" yaml:"netbirdIp"` IP string `json:"netbirdIp" yaml:"netbirdIp"`
PubKey string `json:"publicKey" yaml:"publicKey"` PubKey string `json:"publicKey" yaml:"publicKey"`
KernelInterface bool `json:"usesKernelInterface" yaml:"usesKernelInterface"` KernelInterface bool `json:"usesKernelInterface" yaml:"usesKernelInterface"`
FQDN string `json:"fqdn" yaml:"fqdn"` FQDN string `json:"fqdn" yaml:"fqdn"`
RosenpassEnabled bool `json:"quantumResistance" yaml:"quantumResistance"`
RosenpassPermissive bool `json:"quantumResistancePermissive" yaml:"quantumResistancePermissive"`
Routes []string `json:"routes" yaml:"routes"`
NSServerGroups []nsServerGroupStateOutput `json:"dnsServers" yaml:"dnsServers"`
} }
var ( var (
@@ -165,7 +178,7 @@ func statusFunc(cmd *cobra.Command, args []string) error {
case yamlFlag: case yamlFlag:
statusOutputString, err = parseToYAML(outputInformationHolder) statusOutputString, err = parseToYAML(outputInformationHolder)
default: default:
statusOutputString = parseGeneralSummary(outputInformationHolder, false, false) statusOutputString = parseGeneralSummary(outputInformationHolder, false, false, false)
} }
if err != nil { if err != nil {
@@ -253,16 +266,20 @@ func convertToStatusOutputOverview(resp *proto.StatusResponse) statusOutputOverv
peersOverview := mapPeers(resp.GetFullStatus().GetPeers()) peersOverview := mapPeers(resp.GetFullStatus().GetPeers())
overview := statusOutputOverview{ overview := statusOutputOverview{
Peers: peersOverview, Peers: peersOverview,
CliVersion: version.NetbirdVersion(), CliVersion: version.NetbirdVersion(),
DaemonVersion: resp.GetDaemonVersion(), DaemonVersion: resp.GetDaemonVersion(),
ManagementState: managementOverview, ManagementState: managementOverview,
SignalState: signalOverview, SignalState: signalOverview,
Relays: relayOverview, Relays: relayOverview,
IP: pbFullStatus.GetLocalPeerState().GetIP(), IP: pbFullStatus.GetLocalPeerState().GetIP(),
PubKey: pbFullStatus.GetLocalPeerState().GetPubKey(), PubKey: pbFullStatus.GetLocalPeerState().GetPubKey(),
KernelInterface: pbFullStatus.GetLocalPeerState().GetKernelInterface(), KernelInterface: pbFullStatus.GetLocalPeerState().GetKernelInterface(),
FQDN: pbFullStatus.GetLocalPeerState().GetFqdn(), FQDN: pbFullStatus.GetLocalPeerState().GetFqdn(),
RosenpassEnabled: pbFullStatus.GetLocalPeerState().GetRosenpassEnabled(),
RosenpassPermissive: pbFullStatus.GetLocalPeerState().GetRosenpassPermissive(),
Routes: pbFullStatus.GetLocalPeerState().GetRoutes(),
NSServerGroups: mapNSGroups(pbFullStatus.GetDnsServers()),
} }
return overview return overview
@@ -294,6 +311,19 @@ func mapRelays(relays []*proto.RelayState) relayStateOutput {
} }
} }
func mapNSGroups(servers []*proto.NSGroupState) []nsServerGroupStateOutput {
mappedNSGroups := make([]nsServerGroupStateOutput, 0, len(servers))
for _, pbNsGroupServer := range servers {
mappedNSGroups = append(mappedNSGroups, nsServerGroupStateOutput{
Servers: pbNsGroupServer.GetServers(),
Domains: pbNsGroupServer.GetDomains(),
Enabled: pbNsGroupServer.GetEnabled(),
Error: pbNsGroupServer.GetError(),
})
}
return mappedNSGroups
}
func mapPeers(peers []*proto.PeerState) peersStateOutput { func mapPeers(peers []*proto.PeerState) peersStateOutput {
var peersStateDetail []peerStateDetailOutput var peersStateDetail []peerStateDetailOutput
localICE := "" localICE := ""
@@ -346,6 +376,8 @@ func mapPeers(peers []*proto.PeerState) peersStateOutput {
LastWireguardHandshake: lastHandshake, LastWireguardHandshake: lastHandshake,
TransferReceived: transferReceived, TransferReceived: transferReceived,
TransferSent: transferSent, TransferSent: transferSent,
RosenpassEnabled: pbPeerState.GetRosenpassEnabled(),
Routes: pbPeerState.GetRoutes(),
} }
peersStateDetail = append(peersStateDetail, peerState) peersStateDetail = append(peersStateDetail, peerState)
@@ -395,8 +427,7 @@ func parseToYAML(overview statusOutputOverview) (string, error) {
return string(yamlBytes), nil return string(yamlBytes), nil
} }
func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays bool) string { func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays bool, showNameServers bool) string {
var managementConnString string var managementConnString string
if overview.ManagementState.Connected { if overview.ManagementState.Connected {
managementConnString = "Connected" managementConnString = "Connected"
@@ -432,7 +463,7 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays
interfaceIP = "N/A" interfaceIP = "N/A"
} }
var relayAvailableString string var relaysString string
if showRelays { if showRelays {
for _, relay := range overview.Relays.Details { for _, relay := range overview.Relays.Details {
available := "Available" available := "Available"
@@ -441,12 +472,53 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays
available = "Unavailable" available = "Unavailable"
reason = fmt.Sprintf(", reason: %s", relay.Error) reason = fmt.Sprintf(", reason: %s", relay.Error)
} }
relayAvailableString += fmt.Sprintf("\n [%s] is %s%s", relay.URI, available, reason) relaysString += fmt.Sprintf("\n [%s] is %s%s", relay.URI, available, reason)
} }
} else { } else {
relaysString = fmt.Sprintf("%d/%d Available", overview.Relays.Available, overview.Relays.Total)
}
relayAvailableString = fmt.Sprintf("%d/%d Available", overview.Relays.Available, overview.Relays.Total) routes := "-"
if len(overview.Routes) > 0 {
sort.Strings(overview.Routes)
routes = strings.Join(overview.Routes, ", ")
}
var dnsServersString string
if showNameServers {
for _, nsServerGroup := range overview.NSServerGroups {
enabled := "Available"
if !nsServerGroup.Enabled {
enabled = "Unavailable"
}
errorString := ""
if nsServerGroup.Error != "" {
errorString = fmt.Sprintf(", reason: %s", nsServerGroup.Error)
errorString = strings.TrimSpace(errorString)
}
domainsString := strings.Join(nsServerGroup.Domains, ", ")
if domainsString == "" {
domainsString = "." // Show "." for the default zone
}
dnsServersString += fmt.Sprintf(
"\n [%s] for [%s] is %s%s",
strings.Join(nsServerGroup.Servers, ", "),
domainsString,
enabled,
errorString,
)
}
} else {
dnsServersString = fmt.Sprintf("%d/%d Available", countEnabled(overview.NSServerGroups), len(overview.NSServerGroups))
}
rosenpassEnabledStatus := "false"
if overview.RosenpassEnabled {
rosenpassEnabledStatus = "true"
if overview.RosenpassPermissive {
rosenpassEnabledStatus = "true (permissive)" //nolint:gosec
}
} }
peersCountString := fmt.Sprintf("%d/%d Connected", overview.Peers.Connected, overview.Peers.Total) peersCountString := fmt.Sprintf("%d/%d Connected", overview.Peers.Connected, overview.Peers.Total)
@@ -457,26 +529,32 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays
"Management: %s\n"+ "Management: %s\n"+
"Signal: %s\n"+ "Signal: %s\n"+
"Relays: %s\n"+ "Relays: %s\n"+
"Nameservers: %s\n"+
"FQDN: %s\n"+ "FQDN: %s\n"+
"NetBird IP: %s\n"+ "NetBird IP: %s\n"+
"Interface type: %s\n"+ "Interface type: %s\n"+
"Quantum resistance: %s\n"+
"Routes: %s\n"+
"Peers count: %s\n", "Peers count: %s\n",
overview.DaemonVersion, overview.DaemonVersion,
version.NetbirdVersion(), version.NetbirdVersion(),
managementConnString, managementConnString,
signalConnString, signalConnString,
relayAvailableString, relaysString,
dnsServersString,
overview.FQDN, overview.FQDN,
interfaceIP, interfaceIP,
interfaceTypeString, interfaceTypeString,
rosenpassEnabledStatus,
routes,
peersCountString, peersCountString,
) )
return summary return summary
} }
func parseToFullDetailSummary(overview statusOutputOverview) string { func parseToFullDetailSummary(overview statusOutputOverview) string {
parsedPeersString := parsePeers(overview.Peers) parsedPeersString := parsePeers(overview.Peers, overview.RosenpassEnabled, overview.RosenpassPermissive)
summary := parseGeneralSummary(overview, true, true) summary := parseGeneralSummary(overview, true, true, true)
return fmt.Sprintf( return fmt.Sprintf(
"Peers detail:"+ "Peers detail:"+
@@ -487,7 +565,7 @@ func parseToFullDetailSummary(overview statusOutputOverview) string {
) )
} }
func parsePeers(peers peersStateOutput) string { func parsePeers(peers peersStateOutput, rosenpassEnabled, rosenpassPermissive bool) string {
var ( var (
peersString = "" peersString = ""
) )
@@ -518,9 +596,32 @@ func parsePeers(peers peersStateOutput) string {
lastStatusUpdate = peerState.LastStatusUpdate.Format("2006-01-02 15:04:05") lastStatusUpdate = peerState.LastStatusUpdate.Format("2006-01-02 15:04:05")
} }
lastWireguardHandshake := "-" lastWireGuardHandshake := "-"
if !peerState.LastWireguardHandshake.IsZero() && peerState.LastWireguardHandshake != time.Unix(0, 0) { if !peerState.LastWireguardHandshake.IsZero() && peerState.LastWireguardHandshake != time.Unix(0, 0) {
lastWireguardHandshake = peerState.LastWireguardHandshake.Format("2006-01-02 15:04:05") lastWireGuardHandshake = peerState.LastWireguardHandshake.Format("2006-01-02 15:04:05")
}
rosenpassEnabledStatus := "false"
if rosenpassEnabled {
if peerState.RosenpassEnabled {
rosenpassEnabledStatus = "true"
} else {
if rosenpassPermissive {
rosenpassEnabledStatus = "false (remote didn't enable quantum resistance)"
} else {
rosenpassEnabledStatus = "false (connection won't work without a permissive mode)"
}
}
} else {
if peerState.RosenpassEnabled {
rosenpassEnabledStatus = "false (connection might not work without a remote permissive mode)"
}
}
routes := "-"
if len(peerState.Routes) > 0 {
sort.Strings(peerState.Routes)
routes = strings.Join(peerState.Routes, ", ")
} }
peerString := fmt.Sprintf( peerString := fmt.Sprintf(
@@ -534,8 +635,10 @@ func parsePeers(peers peersStateOutput) string {
" ICE candidate (Local/Remote): %s/%s\n"+ " ICE candidate (Local/Remote): %s/%s\n"+
" ICE candidate endpoints (Local/Remote): %s/%s\n"+ " ICE candidate endpoints (Local/Remote): %s/%s\n"+
" Last connection update: %s\n"+ " Last connection update: %s\n"+
" Last Wireguard handshake: %s\n"+ " Last WireGuard handshake: %s\n"+
" Transfer status (received/sent) %s/%s\n", " Transfer status (received/sent) %s/%s\n"+
" Quantum resistance: %s\n"+
" Routes: %s\n",
peerState.FQDN, peerState.FQDN,
peerState.IP, peerState.IP,
peerState.PubKey, peerState.PubKey,
@@ -547,9 +650,11 @@ func parsePeers(peers peersStateOutput) string {
localICEEndpoint, localICEEndpoint,
remoteICEEndpoint, remoteICEEndpoint,
lastStatusUpdate, lastStatusUpdate,
lastWireguardHandshake, lastWireGuardHandshake,
toIEC(peerState.TransferReceived), toIEC(peerState.TransferReceived),
toIEC(peerState.TransferSent), toIEC(peerState.TransferSent),
rosenpassEnabledStatus,
routes,
) )
peersString += peerString peersString += peerString
@@ -603,3 +708,13 @@ func toIEC(b int64) string {
return fmt.Sprintf("%.1f %ciB", return fmt.Sprintf("%.1f %ciB",
float64(b)/float64(div), "KMGTPE"[exp]) float64(b)/float64(div), "KMGTPE"[exp])
} }
func countEnabled(dnsServers []nsServerGroupStateOutput) int {
count := 0
for _, server := range dnsServers {
if server.Enabled {
count++
}
}
return count
}

View File

@@ -42,6 +42,9 @@ var resp = &proto.StatusResponse{
LastWireguardHandshake: timestamppb.New(time.Date(2001, time.Month(1), 1, 1, 1, 2, 0, time.UTC)), LastWireguardHandshake: timestamppb.New(time.Date(2001, time.Month(1), 1, 1, 1, 2, 0, time.UTC)),
BytesRx: 200, BytesRx: 200,
BytesTx: 100, BytesTx: 100,
Routes: []string{
"10.1.0.0/24",
},
}, },
{ {
IP: "192.168.178.102", IP: "192.168.178.102",
@@ -87,6 +90,31 @@ var resp = &proto.StatusResponse{
PubKey: "Some-Pub-Key", PubKey: "Some-Pub-Key",
KernelInterface: true, KernelInterface: true,
Fqdn: "some-localhost.awesome-domain.com", Fqdn: "some-localhost.awesome-domain.com",
Routes: []string{
"10.10.0.0/24",
},
},
DnsServers: []*proto.NSGroupState{
{
Servers: []string{
"8.8.8.8:53",
},
Domains: nil,
Enabled: true,
Error: "",
},
{
Servers: []string{
"1.1.1.1:53",
"2.2.2.2:53",
},
Domains: []string{
"example.com",
"example.net",
},
Enabled: false,
Error: "timeout",
},
}, },
}, },
DaemonVersion: "0.14.1", DaemonVersion: "0.14.1",
@@ -116,6 +144,9 @@ var overview = statusOutputOverview{
LastWireguardHandshake: time.Date(2001, 1, 1, 1, 1, 2, 0, time.UTC), LastWireguardHandshake: time.Date(2001, 1, 1, 1, 1, 2, 0, time.UTC),
TransferReceived: 200, TransferReceived: 200,
TransferSent: 100, TransferSent: 100,
Routes: []string{
"10.1.0.0/24",
},
}, },
{ {
IP: "192.168.178.102", IP: "192.168.178.102",
@@ -171,6 +202,31 @@ var overview = statusOutputOverview{
PubKey: "Some-Pub-Key", PubKey: "Some-Pub-Key",
KernelInterface: true, KernelInterface: true,
FQDN: "some-localhost.awesome-domain.com", FQDN: "some-localhost.awesome-domain.com",
NSServerGroups: []nsServerGroupStateOutput{
{
Servers: []string{
"8.8.8.8:53",
},
Domains: nil,
Enabled: true,
Error: "",
},
{
Servers: []string{
"1.1.1.1:53",
"2.2.2.2:53",
},
Domains: []string{
"example.com",
"example.net",
},
Enabled: false,
Error: "timeout",
},
},
Routes: []string{
"10.10.0.0/24",
},
} }
func TestConversionFromFullStatusToOutputOverview(t *testing.T) { func TestConversionFromFullStatusToOutputOverview(t *testing.T) {
@@ -231,7 +287,11 @@ func TestParsingToJSON(t *testing.T) {
}, },
"lastWireguardHandshake": "2001-01-01T01:01:02Z", "lastWireguardHandshake": "2001-01-01T01:01:02Z",
"transferReceived": 200, "transferReceived": 200,
"transferSent": 100 "transferSent": 100,
"quantumResistance": false,
"routes": [
"10.1.0.0/24"
]
}, },
{ {
"fqdn": "peer-2.awesome-domain.com", "fqdn": "peer-2.awesome-domain.com",
@@ -251,7 +311,9 @@ func TestParsingToJSON(t *testing.T) {
}, },
"lastWireguardHandshake": "2002-02-02T02:02:03Z", "lastWireguardHandshake": "2002-02-02T02:02:03Z",
"transferReceived": 2000, "transferReceived": 2000,
"transferSent": 1000 "transferSent": 1000,
"quantumResistance": false,
"routes": null
} }
] ]
}, },
@@ -286,7 +348,34 @@ func TestParsingToJSON(t *testing.T) {
"netbirdIp": "192.168.178.100/16", "netbirdIp": "192.168.178.100/16",
"publicKey": "Some-Pub-Key", "publicKey": "Some-Pub-Key",
"usesKernelInterface": true, "usesKernelInterface": true,
"fqdn": "some-localhost.awesome-domain.com" "fqdn": "some-localhost.awesome-domain.com",
"quantumResistance": false,
"quantumResistancePermissive": false,
"routes": [
"10.10.0.0/24"
],
"dnsServers": [
{
"servers": [
"8.8.8.8:53"
],
"domains": null,
"enabled": true,
"error": ""
},
{
"servers": [
"1.1.1.1:53",
"2.2.2.2:53"
],
"domains": [
"example.com",
"example.net"
],
"enabled": false,
"error": "timeout"
}
]
}` }`
// @formatter:on // @formatter:on
@@ -320,6 +409,9 @@ func TestParsingToYAML(t *testing.T) {
lastWireguardHandshake: 2001-01-01T01:01:02Z lastWireguardHandshake: 2001-01-01T01:01:02Z
transferReceived: 200 transferReceived: 200
transferSent: 100 transferSent: 100
quantumResistance: false
routes:
- 10.1.0.0/24
- fqdn: peer-2.awesome-domain.com - fqdn: peer-2.awesome-domain.com
netbirdIp: 192.168.178.102 netbirdIp: 192.168.178.102
publicKey: Pubkey2 publicKey: Pubkey2
@@ -336,6 +428,8 @@ func TestParsingToYAML(t *testing.T) {
lastWireguardHandshake: 2002-02-02T02:02:03Z lastWireguardHandshake: 2002-02-02T02:02:03Z
transferReceived: 2000 transferReceived: 2000
transferSent: 1000 transferSent: 1000
quantumResistance: false
routes: []
cliVersion: development cliVersion: development
daemonVersion: 0.14.1 daemonVersion: 0.14.1
management: management:
@@ -360,6 +454,24 @@ netbirdIp: 192.168.178.100/16
publicKey: Some-Pub-Key publicKey: Some-Pub-Key
usesKernelInterface: true usesKernelInterface: true
fqdn: some-localhost.awesome-domain.com fqdn: some-localhost.awesome-domain.com
quantumResistance: false
quantumResistancePermissive: false
routes:
- 10.10.0.0/24
dnsServers:
- servers:
- 8.8.8.8:53
domains: []
enabled: true
error: ""
- servers:
- 1.1.1.1:53
- 2.2.2.2:53
domains:
- example.com
- example.net
enabled: false
error: timeout
` `
assert.Equal(t, expectedYAML, yaml) assert.Equal(t, expectedYAML, yaml)
@@ -380,8 +492,10 @@ func TestParsingToDetail(t *testing.T) {
ICE candidate (Local/Remote): -/- ICE candidate (Local/Remote): -/-
ICE candidate endpoints (Local/Remote): -/- ICE candidate endpoints (Local/Remote): -/-
Last connection update: 2001-01-01 01:01:01 Last connection update: 2001-01-01 01:01:01
Last Wireguard handshake: 2001-01-01 01:01:02 Last WireGuard handshake: 2001-01-01 01:01:02
Transfer status (received/sent) 200 B/100 B Transfer status (received/sent) 200 B/100 B
Quantum resistance: false
Routes: 10.1.0.0/24
peer-2.awesome-domain.com: peer-2.awesome-domain.com:
NetBird IP: 192.168.178.102 NetBird IP: 192.168.178.102
@@ -393,8 +507,10 @@ func TestParsingToDetail(t *testing.T) {
ICE candidate (Local/Remote): relay/prflx ICE candidate (Local/Remote): relay/prflx
ICE candidate endpoints (Local/Remote): 10.0.0.1:10001/10.0.10.1:10002 ICE candidate endpoints (Local/Remote): 10.0.0.1:10001/10.0.10.1:10002
Last connection update: 2002-02-02 02:02:02 Last connection update: 2002-02-02 02:02:02
Last Wireguard handshake: 2002-02-02 02:02:03 Last WireGuard handshake: 2002-02-02 02:02:03
Transfer status (received/sent) 2.0 KiB/1000 B Transfer status (received/sent) 2.0 KiB/1000 B
Quantum resistance: false
Routes: -
Daemon version: 0.14.1 Daemon version: 0.14.1
CLI version: development CLI version: development
@@ -403,9 +519,14 @@ Signal: Connected to my-awesome-signal.com:443
Relays: Relays:
[stun:my-awesome-stun.com:3478] is Available [stun:my-awesome-stun.com:3478] is Available
[turns:my-awesome-turn.com:443?transport=tcp] is Unavailable, reason: context: deadline exceeded [turns:my-awesome-turn.com:443?transport=tcp] is Unavailable, reason: context: deadline exceeded
Nameservers:
[8.8.8.8:53] for [.] is Available
[1.1.1.1:53, 2.2.2.2:53] for [example.com, example.net] is Unavailable, reason: timeout
FQDN: some-localhost.awesome-domain.com FQDN: some-localhost.awesome-domain.com
NetBird IP: 192.168.178.100/16 NetBird IP: 192.168.178.100/16
Interface type: Kernel Interface type: Kernel
Quantum resistance: false
Routes: 10.10.0.0/24
Peers count: 2/2 Connected Peers count: 2/2 Connected
` `
@@ -413,7 +534,7 @@ Peers count: 2/2 Connected
} }
func TestParsingToShortVersion(t *testing.T) { func TestParsingToShortVersion(t *testing.T) {
shortVersion := parseGeneralSummary(overview, false, false) shortVersion := parseGeneralSummary(overview, false, false, false)
expectedString := expectedString :=
`Daemon version: 0.14.1 `Daemon version: 0.14.1
@@ -421,9 +542,12 @@ CLI version: development
Management: Connected Management: Connected
Signal: Connected Signal: Connected
Relays: 1/2 Available Relays: 1/2 Available
Nameservers: 1/2 Available
FQDN: some-localhost.awesome-domain.com FQDN: some-localhost.awesome-domain.com
NetBird IP: 192.168.178.100/16 NetBird IP: 192.168.178.100/16
Interface type: Kernel Interface type: Kernel
Quantum resistance: false
Routes: 10.10.0.0/24
Peers count: 2/2 Connected Peers count: 2/2 Connected
` `

View File

@@ -26,7 +26,7 @@ type HTTPClient interface {
} }
// AuthFlowInfo holds information for the OAuth 2.0 authorization flow // AuthFlowInfo holds information for the OAuth 2.0 authorization flow
type AuthFlowInfo struct { type AuthFlowInfo struct { //nolint:revive
DeviceCode string `json:"device_code"` DeviceCode string `json:"device_code"`
UserCode string `json:"user_code"` UserCode string `json:"user_code"`
VerificationURI string `json:"verification_uri"` VerificationURI string `json:"verification_uri"`

View File

@@ -8,6 +8,7 @@ import (
"net/netip" "net/netip"
"os" "os"
"strings" "strings"
"time"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@@ -23,10 +24,16 @@ const (
fileMaxNumberOfSearchDomains = 6 fileMaxNumberOfSearchDomains = 6
) )
const (
dnsFailoverTimeout = 4 * time.Second
dnsFailoverAttempts = 1
)
type fileConfigurator struct { type fileConfigurator struct {
repair *repair repair *repair
originalPerms os.FileMode originalPerms os.FileMode
nbNameserverIP string
} }
func newFileConfigurator() (hostManager, error) { func newFileConfigurator() (hostManager, error) {
@@ -64,7 +71,7 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error {
} }
nbSearchDomains := searchDomains(config) nbSearchDomains := searchDomains(config)
nbNameserverIP := config.ServerIP f.nbNameserverIP = config.ServerIP
resolvConf, err := parseBackupResolvConf() resolvConf, err := parseBackupResolvConf()
if err != nil { if err != nil {
@@ -73,11 +80,11 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error {
f.repair.stopWatchFileChanges() f.repair.stopWatchFileChanges()
err = f.updateConfig(nbSearchDomains, nbNameserverIP, resolvConf) err = f.updateConfig(nbSearchDomains, f.nbNameserverIP, resolvConf)
if err != nil { if err != nil {
return err return err
} }
f.repair.watchFileChanges(nbSearchDomains, nbNameserverIP) f.repair.watchFileChanges(nbSearchDomains, f.nbNameserverIP)
return nil return nil
} }
@@ -85,10 +92,11 @@ func (f *fileConfigurator) updateConfig(nbSearchDomains []string, nbNameserverIP
searchDomainList := mergeSearchDomains(nbSearchDomains, cfg.searchDomains) searchDomainList := mergeSearchDomains(nbSearchDomains, cfg.searchDomains)
nameServers := generateNsList(nbNameserverIP, cfg) nameServers := generateNsList(nbNameserverIP, cfg)
options := prepareOptionsWithTimeout(cfg.others, int(dnsFailoverTimeout.Seconds()), dnsFailoverAttempts)
buf := prepareResolvConfContent( buf := prepareResolvConfContent(
searchDomainList, searchDomainList,
nameServers, nameServers,
cfg.others) options)
log.Debugf("creating managed file %s", defaultResolvConfPath) log.Debugf("creating managed file %s", defaultResolvConfPath)
err := os.WriteFile(defaultResolvConfPath, buf.Bytes(), f.originalPerms) err := os.WriteFile(defaultResolvConfPath, buf.Bytes(), f.originalPerms)
@@ -131,7 +139,12 @@ func (f *fileConfigurator) backup() error {
} }
func (f *fileConfigurator) restore() error { func (f *fileConfigurator) restore() error {
err := copyFile(fileDefaultResolvConfBackupLocation, defaultResolvConfPath) err := removeFirstNbNameserver(fileDefaultResolvConfBackupLocation, f.nbNameserverIP)
if err != nil {
log.Errorf("Failed to remove netbird nameserver from %s on backup restore: %s", fileDefaultResolvConfBackupLocation, err)
}
err = copyFile(fileDefaultResolvConfBackupLocation, defaultResolvConfPath)
if err != nil { if err != nil {
return fmt.Errorf("restoring %s from %s: %w", defaultResolvConfPath, fileDefaultResolvConfBackupLocation, err) return fmt.Errorf("restoring %s from %s: %w", defaultResolvConfPath, fileDefaultResolvConfBackupLocation, err)
} }
@@ -157,7 +170,7 @@ func (f *fileConfigurator) restoreUncleanShutdownDNS(storedDNSAddress *netip.Add
currentDNSAddress, err := netip.ParseAddr(resolvConf.nameServers[0]) currentDNSAddress, err := netip.ParseAddr(resolvConf.nameServers[0])
// not a valid first nameserver -> restore // not a valid first nameserver -> restore
if err != nil { if err != nil {
log.Errorf("restoring unclean shutdown: parse dns address %s failed: %s", resolvConf.nameServers[1], err) log.Errorf("restoring unclean shutdown: parse dns address %s failed: %s", resolvConf.nameServers[0], err)
return restoreResolvConfFile() return restoreResolvConfFile()
} }

View File

@@ -5,6 +5,7 @@ package dns
import ( import (
"fmt" "fmt"
"os" "os"
"regexp"
"strings" "strings"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -14,6 +15,9 @@ const (
defaultResolvConfPath = "/etc/resolv.conf" defaultResolvConfPath = "/etc/resolv.conf"
) )
var timeoutRegex = regexp.MustCompile(`timeout:\d+`)
var attemptsRegex = regexp.MustCompile(`attempts:\d+`)
type resolvConf struct { type resolvConf struct {
nameServers []string nameServers []string
searchDomains []string searchDomains []string
@@ -103,3 +107,62 @@ func parseResolvConfFile(resolvConfFile string) (*resolvConf, error) {
} }
return rconf, nil return rconf, nil
} }
// prepareOptionsWithTimeout appends timeout to existing options if it doesn't exist,
// otherwise it adds a new option with timeout and attempts.
func prepareOptionsWithTimeout(input []string, timeout int, attempts int) []string {
configs := make([]string, len(input))
copy(configs, input)
for i, config := range configs {
if strings.HasPrefix(config, "options") {
config = strings.ReplaceAll(config, "rotate", "")
config = strings.Join(strings.Fields(config), " ")
if strings.Contains(config, "timeout:") {
config = timeoutRegex.ReplaceAllString(config, fmt.Sprintf("timeout:%d", timeout))
} else {
config = strings.Replace(config, "options ", fmt.Sprintf("options timeout:%d ", timeout), 1)
}
if strings.Contains(config, "attempts:") {
config = attemptsRegex.ReplaceAllString(config, fmt.Sprintf("attempts:%d", attempts))
} else {
config = strings.Replace(config, "options ", fmt.Sprintf("options attempts:%d ", attempts), 1)
}
configs[i] = config
return configs
}
}
return append(configs, fmt.Sprintf("options timeout:%d attempts:%d", timeout, attempts))
}
// removeFirstNbNameserver removes the given nameserver from the given file if it is in the first position
// and writes the file back to the original location
func removeFirstNbNameserver(filename, nameserverIP string) error {
resolvConf, err := parseResolvConfFile(filename)
if err != nil {
return fmt.Errorf("parse backup resolv.conf: %w", err)
}
content, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("read %s: %w", filename, err)
}
if len(resolvConf.nameServers) > 1 && resolvConf.nameServers[0] == nameserverIP {
newContent := strings.Replace(string(content), fmt.Sprintf("nameserver %s\n", nameserverIP), "", 1)
stat, err := os.Stat(filename)
if err != nil {
return fmt.Errorf("stat %s: %w", filename, err)
}
if err := os.WriteFile(filename, []byte(newContent), stat.Mode()); err != nil {
return fmt.Errorf("write %s: %w", filename, err)
}
}
return nil
}

View File

@@ -6,6 +6,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func Test_parseResolvConf(t *testing.T) { func Test_parseResolvConf(t *testing.T) {
@@ -172,3 +174,131 @@ nameserver 192.168.0.1
t.Errorf("unexpected resolv.conf content: %v", cfg) t.Errorf("unexpected resolv.conf content: %v", cfg)
} }
} }
func TestPrepareOptionsWithTimeout(t *testing.T) {
tests := []struct {
name string
others []string
timeout int
attempts int
expected []string
}{
{
name: "Append new options with timeout and attempts",
others: []string{"some config"},
timeout: 2,
attempts: 2,
expected: []string{"some config", "options timeout:2 attempts:2"},
},
{
name: "Modify existing options to exclude rotate and include timeout and attempts",
others: []string{"some config", "options rotate someother"},
timeout: 3,
attempts: 2,
expected: []string{"some config", "options attempts:2 timeout:3 someother"},
},
{
name: "Existing options with timeout and attempts are updated",
others: []string{"some config", "options timeout:4 attempts:3"},
timeout: 5,
attempts: 4,
expected: []string{"some config", "options timeout:5 attempts:4"},
},
{
name: "Modify existing options, add missing attempts before timeout",
others: []string{"some config", "options timeout:4"},
timeout: 4,
attempts: 3,
expected: []string{"some config", "options attempts:3 timeout:4"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := prepareOptionsWithTimeout(tc.others, tc.timeout, tc.attempts)
assert.Equal(t, tc.expected, result)
})
}
}
func TestRemoveFirstNbNameserver(t *testing.T) {
testCases := []struct {
name string
content string
ipToRemove string
expected string
}{
{
name: "Unrelated nameservers with comments and options",
content: `# This is a comment
options rotate
nameserver 1.1.1.1
# Another comment
nameserver 8.8.4.4
search example.com`,
ipToRemove: "9.9.9.9",
expected: `# This is a comment
options rotate
nameserver 1.1.1.1
# Another comment
nameserver 8.8.4.4
search example.com`,
},
{
name: "First nameserver matches",
content: `search example.com
nameserver 9.9.9.9
# oof, a comment
nameserver 8.8.4.4
options attempts:5`,
ipToRemove: "9.9.9.9",
expected: `search example.com
# oof, a comment
nameserver 8.8.4.4
options attempts:5`,
},
{
name: "Target IP not the first nameserver",
// nolint:dupword
content: `# Comment about the first nameserver
nameserver 8.8.4.4
# Comment before our target
nameserver 9.9.9.9
options timeout:2`,
ipToRemove: "9.9.9.9",
// nolint:dupword
expected: `# Comment about the first nameserver
nameserver 8.8.4.4
# Comment before our target
nameserver 9.9.9.9
options timeout:2`,
},
{
name: "Only nameserver matches",
content: `options debug
nameserver 9.9.9.9
search localdomain`,
ipToRemove: "9.9.9.9",
expected: `options debug
nameserver 9.9.9.9
search localdomain`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tempDir := t.TempDir()
tempFile := filepath.Join(tempDir, "resolv.conf")
err := os.WriteFile(tempFile, []byte(tc.content), 0644)
assert.NoError(t, err)
err = removeFirstNbNameserver(tempFile, tc.ipToRemove)
assert.NoError(t, err)
content, err := os.ReadFile(tempFile)
assert.NoError(t, err)
assert.Equal(t, tc.expected, string(content), "The resulting content should match the expected output.")
})
}
}

View File

@@ -65,7 +65,7 @@ func newHostManager(wgInterface string) (hostManager, error) {
return nil, err return nil, err
} }
log.Debugf("discovered mode is: %s", osManager) log.Infof("System DNS manager discovered: %s", osManager)
return newHostManagerFromType(wgInterface, osManager) return newHostManagerFromType(wgInterface, osManager)
} }

View File

@@ -53,10 +53,12 @@ func (r *resolvconf) applyDNSConfig(config HostDNSConfig) error {
searchDomainList := searchDomains(config) searchDomainList := searchDomains(config)
searchDomainList = mergeSearchDomains(searchDomainList, r.originalSearchDomains) searchDomainList = mergeSearchDomains(searchDomainList, r.originalSearchDomains)
options := prepareOptionsWithTimeout(r.othersConfigs, int(dnsFailoverTimeout.Seconds()), dnsFailoverAttempts)
buf := prepareResolvConfContent( buf := prepareResolvConfContent(
searchDomainList, searchDomainList,
append([]string{config.ServerIP}, r.originalNameServers...), append([]string{config.ServerIP}, r.originalNameServers...),
r.othersConfigs) options)
// create a backup for unclean shutdown detection before the resolv.conf is changed // create a backup for unclean shutdown detection before the resolv.conf is changed
if err := createUncleanShutdownIndicator(defaultResolvConfPath, resolvConfManager, config.ServerIP); err != nil { if err := createUncleanShutdownIndicator(defaultResolvConfPath, resolvConfManager, config.ServerIP); err != nil {

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"net/netip" "net/netip"
"strings"
"sync" "sync"
"github.com/miekg/dns" "github.com/miekg/dns"
@@ -11,6 +12,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/internal/listener" "github.com/netbirdio/netbird/client/internal/listener"
"github.com/netbirdio/netbird/client/internal/peer"
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
) )
@@ -59,6 +61,8 @@ type DefaultServer struct {
// make sense on mobile only // make sense on mobile only
searchDomainNotifier *notifier searchDomainNotifier *notifier
iosDnsManager IosDnsManager iosDnsManager IosDnsManager
statusRecorder *peer.Status
} }
type handlerWithStop interface { type handlerWithStop interface {
@@ -73,7 +77,12 @@ 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,
statusRecorder *peer.Status,
) (*DefaultServer, error) {
var addrPort *netip.AddrPort var addrPort *netip.AddrPort
if customAddress != "" { if customAddress != "" {
parsedAddrPort, err := netip.ParseAddrPort(customAddress) parsedAddrPort, err := netip.ParseAddrPort(customAddress)
@@ -90,13 +99,20 @@ 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, statusRecorder), 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, config nbdns.Config, listener listener.NetworkChangeListener) *DefaultServer { func NewDefaultServerPermanentUpstream(
ctx context.Context,
wgInterface WGIface,
hostsDnsList []string,
config nbdns.Config,
listener listener.NetworkChangeListener,
statusRecorder *peer.Status,
) *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), statusRecorder)
ds.permanent = true ds.permanent = true
ds.hostsDnsList = hostsDnsList ds.hostsDnsList = hostsDnsList
ds.addHostRootZone() ds.addHostRootZone()
@@ -108,13 +124,18 @@ func NewDefaultServerPermanentUpstream(ctx context.Context, wgInterface WGIface,
} }
// NewDefaultServerIos returns a new dns server. It optimized for ios // NewDefaultServerIos returns a new dns server. It optimized for ios
func NewDefaultServerIos(ctx context.Context, wgInterface WGIface, iosDnsManager IosDnsManager) *DefaultServer { func NewDefaultServerIos(
ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface)) ctx context.Context,
wgInterface WGIface,
iosDnsManager IosDnsManager,
statusRecorder *peer.Status,
) *DefaultServer {
ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface), statusRecorder)
ds.iosDnsManager = iosDnsManager ds.iosDnsManager = iosDnsManager
return ds return ds
} }
func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService service) *DefaultServer { func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService service, statusRecorder *peer.Status) *DefaultServer {
ctx, stop := context.WithCancel(ctx) ctx, stop := context.WithCancel(ctx)
defaultServer := &DefaultServer{ defaultServer := &DefaultServer{
ctx: ctx, ctx: ctx,
@@ -124,7 +145,8 @@ func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService servi
localResolver: &localResolver{ localResolver: &localResolver{
registeredMap: make(registrationMap), registeredMap: make(registrationMap),
}, },
wgInterface: wgInterface, wgInterface: wgInterface,
statusRecorder: statusRecorder,
} }
return defaultServer return defaultServer
@@ -299,6 +321,8 @@ func (s *DefaultServer) applyConfiguration(update nbdns.Config) error {
s.searchDomainNotifier.onNewSearchDomains(s.SearchDomains()) s.searchDomainNotifier.onNewSearchDomains(s.SearchDomains())
} }
s.updateNSGroupStates(update.NameServerGroups)
return nil return nil
} }
@@ -338,7 +362,13 @@ func (s *DefaultServer) buildUpstreamHandlerUpdate(nameServerGroups []*nbdns.Nam
continue continue
} }
handler, err := newUpstreamResolver(s.ctx, s.wgInterface.Name(), s.wgInterface.Address().IP, s.wgInterface.Address().Network) handler, err := newUpstreamResolver(
s.ctx,
s.wgInterface.Name(),
s.wgInterface.Address().IP,
s.wgInterface.Address().Network,
s.statusRecorder,
)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to create a new upstream resolver, error: %v", err) return nil, fmt.Errorf("unable to create a new upstream resolver, error: %v", err)
} }
@@ -460,14 +490,14 @@ func getNSHostPort(ns nbdns.NameServer) string {
func (s *DefaultServer) upstreamCallbacks( func (s *DefaultServer) upstreamCallbacks(
nsGroup *nbdns.NameServerGroup, nsGroup *nbdns.NameServerGroup,
handler dns.Handler, handler dns.Handler,
) (deactivate func(), reactivate func()) { ) (deactivate func(error), reactivate func()) {
var removeIndex map[string]int var removeIndex map[string]int
deactivate = func() { deactivate = func(err error) {
s.mux.Lock() s.mux.Lock()
defer s.mux.Unlock() defer s.mux.Unlock()
l := log.WithField("nameservers", nsGroup.NameServers) l := log.WithField("nameservers", nsGroup.NameServers)
l.Info("temporary deactivate nameservers group due timeout") l.Info("Temporarily deactivating nameservers group due to timeout")
removeIndex = make(map[string]int) removeIndex = make(map[string]int)
for _, domain := range nsGroup.Domains { for _, domain := range nsGroup.Domains {
@@ -486,8 +516,11 @@ func (s *DefaultServer) upstreamCallbacks(
} }
} }
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil { if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
l.WithError(err).Error("fail to apply nameserver deactivation on the host") l.Errorf("Failed to apply nameserver deactivation on the host: %v", err)
} }
s.updateNSState(nsGroup, err, false)
} }
reactivate = func() { reactivate = func() {
s.mux.Lock() s.mux.Lock()
@@ -510,12 +543,20 @@ func (s *DefaultServer) upstreamCallbacks(
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil { if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
l.WithError(err).Error("reactivate temporary disabled nameserver group, DNS update apply") l.WithError(err).Error("reactivate temporary disabled nameserver group, DNS update apply")
} }
s.updateNSState(nsGroup, nil, true)
} }
return return
} }
func (s *DefaultServer) addHostRootZone() { func (s *DefaultServer) addHostRootZone() {
handler, err := newUpstreamResolver(s.ctx, s.wgInterface.Name(), s.wgInterface.Address().IP, s.wgInterface.Address().Network) handler, err := newUpstreamResolver(
s.ctx,
s.wgInterface.Name(),
s.wgInterface.Address().IP,
s.wgInterface.Address().Network,
s.statusRecorder,
)
if err != nil { if err != nil {
log.Errorf("unable to create a new upstream resolver, error: %v", err) log.Errorf("unable to create a new upstream resolver, error: %v", err)
return return
@@ -535,7 +576,50 @@ func (s *DefaultServer) addHostRootZone() {
handler.upstreamServers[n] = fmt.Sprintf("%s:53", ipString) handler.upstreamServers[n] = fmt.Sprintf("%s:53", ipString)
} }
handler.deactivate = func() {} handler.deactivate = func(error) {}
handler.reactivate = func() {} handler.reactivate = func() {}
s.service.RegisterMux(nbdns.RootZone, handler) s.service.RegisterMux(nbdns.RootZone, handler)
} }
func (s *DefaultServer) updateNSGroupStates(groups []*nbdns.NameServerGroup) {
var states []peer.NSGroupState
for _, group := range groups {
var servers []string
for _, ns := range group.NameServers {
servers = append(servers, fmt.Sprintf("%s:%d", ns.IP, ns.Port))
}
state := peer.NSGroupState{
ID: generateGroupKey(group),
Servers: servers,
Domains: group.Domains,
// The probe will determine the state, default enabled
Enabled: true,
Error: nil,
}
states = append(states, state)
}
s.statusRecorder.UpdateDNSStates(states)
}
func (s *DefaultServer) updateNSState(nsGroup *nbdns.NameServerGroup, err error, enabled bool) {
states := s.statusRecorder.GetDNSStates()
id := generateGroupKey(nsGroup)
for i, state := range states {
if state.ID == id {
states[i].Enabled = enabled
states[i].Error = err
break
}
}
s.statusRecorder.UpdateDNSStates(states)
}
func generateGroupKey(nsGroup *nbdns.NameServerGroup) string {
var servers []string
for _, ns := range nsGroup.NameServers {
servers = append(servers, fmt.Sprintf("%s:%d", ns.IP, ns.Port))
}
return fmt.Sprintf("%s_%s_%s", nsGroup.ID, nsGroup.Name, strings.Join(servers, ","))
}

View File

@@ -15,6 +15,7 @@ import (
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"github.com/netbirdio/netbird/client/firewall/uspfilter" "github.com/netbirdio/netbird/client/firewall/uspfilter"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/stdnet" "github.com/netbirdio/netbird/client/internal/stdnet"
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/formatter" "github.com/netbirdio/netbird/formatter"
@@ -274,7 +275,7 @@ func TestUpdateDNSServer(t *testing.T) {
t.Log(err) t.Log(err)
} }
}() }()
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "") dnsServer, err := NewDefaultServer(context.Background(), wgIface, "", &peer.Status{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -375,7 +376,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) {
return return
} }
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "") dnsServer, err := NewDefaultServer(context.Background(), wgIface, "", &peer.Status{})
if err != nil { if err != nil {
t.Errorf("create DNS server: %v", err) t.Errorf("create DNS server: %v", err)
return return
@@ -470,7 +471,7 @@ func TestDNSServerStartStop(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
dnsServer, err := NewDefaultServer(context.Background(), &mocWGIface{}, testCase.addrPort) dnsServer, err := NewDefaultServer(context.Background(), &mocWGIface{}, testCase.addrPort, &peer.Status{})
if err != nil { if err != nil {
t.Fatalf("%v", err) t.Fatalf("%v", err)
} }
@@ -541,6 +542,7 @@ func TestDNSServerUpstreamDeactivateCallback(t *testing.T) {
{false, "domain2", false}, {false, "domain2", false},
}, },
}, },
statusRecorder: &peer.Status{},
} }
var domainsUpdate string var domainsUpdate string
@@ -563,7 +565,7 @@ func TestDNSServerUpstreamDeactivateCallback(t *testing.T) {
}, },
}, nil) }, nil)
deactivate() deactivate(nil)
expected := "domain0,domain2" expected := "domain0,domain2"
domains := []string{} domains := []string{}
for _, item := range server.currentConfig.Domains { for _, item := range server.currentConfig.Domains {
@@ -601,7 +603,7 @@ func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) {
var dnsList []string var dnsList []string
dnsConfig := nbdns.Config{} dnsConfig := nbdns.Config{}
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, dnsList, dnsConfig, nil) dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, dnsList, dnsConfig, nil, &peer.Status{})
err = dnsServer.Initialize() err = dnsServer.Initialize()
if err != nil { if err != nil {
t.Errorf("failed to initialize DNS server: %v", err) t.Errorf("failed to initialize DNS server: %v", err)
@@ -625,7 +627,7 @@ func TestDNSPermanent_updateUpstream(t *testing.T) {
} }
defer wgIFace.Close() defer wgIFace.Close()
dnsConfig := nbdns.Config{} dnsConfig := nbdns.Config{}
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil) dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil, &peer.Status{})
err = dnsServer.Initialize() err = dnsServer.Initialize()
if err != nil { if err != nil {
t.Errorf("failed to initialize DNS server: %v", err) t.Errorf("failed to initialize DNS server: %v", err)
@@ -717,7 +719,7 @@ func TestDNSPermanent_matchOnly(t *testing.T) {
} }
defer wgIFace.Close() defer wgIFace.Close()
dnsConfig := nbdns.Config{} dnsConfig := nbdns.Config{}
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil) dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil, &peer.Status{})
err = dnsServer.Initialize() err = dnsServer.Initialize()
if err != nil { if err != nil {
t.Errorf("failed to initialize DNS server: %v", err) t.Errorf("failed to initialize DNS server: %v", err)

View File

@@ -11,8 +11,11 @@ import (
"time" "time"
"github.com/cenkalti/backoff/v4" "github.com/cenkalti/backoff/v4"
"github.com/hashicorp/go-multierror"
"github.com/miekg/dns" "github.com/miekg/dns"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/internal/peer"
) )
const ( const (
@@ -45,12 +48,13 @@ type upstreamResolverBase struct {
reactivatePeriod time.Duration reactivatePeriod time.Duration
upstreamTimeout time.Duration upstreamTimeout time.Duration
deactivate func() deactivate func(error)
reactivate func() reactivate func()
statusRecorder *peer.Status
} }
func newUpstreamResolverBase(parentCTX context.Context) *upstreamResolverBase { func newUpstreamResolverBase(ctx context.Context, statusRecorder *peer.Status) *upstreamResolverBase {
ctx, cancel := context.WithCancel(parentCTX) ctx, cancel := context.WithCancel(ctx)
return &upstreamResolverBase{ return &upstreamResolverBase{
ctx: ctx, ctx: ctx,
@@ -58,6 +62,7 @@ func newUpstreamResolverBase(parentCTX context.Context) *upstreamResolverBase {
upstreamTimeout: upstreamTimeout, upstreamTimeout: upstreamTimeout,
reactivatePeriod: reactivatePeriod, reactivatePeriod: reactivatePeriod,
failsTillDeact: failsTillDeact, failsTillDeact: failsTillDeact,
statusRecorder: statusRecorder,
} }
} }
@@ -68,7 +73,10 @@ func (u *upstreamResolverBase) stop() {
// ServeDNS handles a DNS request // ServeDNS handles a DNS request
func (u *upstreamResolverBase) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { func (u *upstreamResolverBase) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
defer u.checkUpstreamFails() var err error
defer func() {
u.checkUpstreamFails(err)
}()
log.WithField("question", r.Question[0]).Trace("received an upstream question") log.WithField("question", r.Question[0]).Trace("received an upstream question")
@@ -81,7 +89,6 @@ func (u *upstreamResolverBase) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
for _, upstream := range u.upstreamServers { for _, upstream := range u.upstreamServers {
var rm *dns.Msg var rm *dns.Msg
var t time.Duration var t time.Duration
var err error
func() { func() {
ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout) ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout)
@@ -132,7 +139,7 @@ func (u *upstreamResolverBase) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
// If fails count is greater that failsTillDeact, upstream resolving // If fails count is greater that failsTillDeact, upstream resolving
// will be disabled for reactivatePeriod, after that time period fails counter // will be disabled for reactivatePeriod, after that time period fails counter
// will be reset and upstream will be reactivated. // will be reset and upstream will be reactivated.
func (u *upstreamResolverBase) checkUpstreamFails() { func (u *upstreamResolverBase) checkUpstreamFails(err error) {
u.mutex.Lock() u.mutex.Lock()
defer u.mutex.Unlock() defer u.mutex.Unlock()
@@ -146,7 +153,7 @@ func (u *upstreamResolverBase) checkUpstreamFails() {
default: default:
} }
u.disable() u.disable(err)
} }
// probeAvailability tests all upstream servers simultaneously and // probeAvailability tests all upstream servers simultaneously and
@@ -165,13 +172,16 @@ func (u *upstreamResolverBase) probeAvailability() {
var mu sync.Mutex var mu sync.Mutex
var wg sync.WaitGroup var wg sync.WaitGroup
var errors *multierror.Error
for _, upstream := range u.upstreamServers { for _, upstream := range u.upstreamServers {
upstream := upstream upstream := upstream
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := u.testNameserver(upstream); err != nil { err := u.testNameserver(upstream)
if err != nil {
errors = multierror.Append(errors, err)
log.Warnf("probing upstream nameserver %s: %s", upstream, err) log.Warnf("probing upstream nameserver %s: %s", upstream, err)
return return
} }
@@ -186,7 +196,7 @@ func (u *upstreamResolverBase) probeAvailability() {
// didn't find a working upstream server, let's disable and try later // didn't find a working upstream server, let's disable and try later
if !success { if !success {
u.disable() u.disable(errors.ErrorOrNil())
} }
} }
@@ -245,15 +255,15 @@ func isTimeout(err error) bool {
return false return false
} }
func (u *upstreamResolverBase) disable() { func (u *upstreamResolverBase) disable(err error) {
if u.disabled { if u.disabled {
return return
} }
// todo test the deactivation logic, it seems to affect the client // todo test the deactivation logic, it seems to affect the client
if runtime.GOOS != "ios" { if runtime.GOOS != "ios" {
log.Warnf("upstream resolving is Disabled for %v", reactivatePeriod) log.Warnf("Upstream resolving is Disabled for %v", reactivatePeriod)
u.deactivate() u.deactivate(err)
u.disabled = true u.disabled = true
go u.waitUntilResponse() go u.waitUntilResponse()
} }

View File

@@ -11,6 +11,8 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/netbirdio/netbird/client/internal/peer"
) )
type upstreamResolverIOS struct { type upstreamResolverIOS struct {
@@ -20,8 +22,14 @@ type upstreamResolverIOS struct {
iIndex int iIndex int
} }
func newUpstreamResolver(parentCTX context.Context, interfaceName string, ip net.IP, net *net.IPNet) (*upstreamResolverIOS, error) { func newUpstreamResolver(
upstreamResolverBase := newUpstreamResolverBase(parentCTX) ctx context.Context,
interfaceName string,
ip net.IP,
net *net.IPNet,
statusRecorder *peer.Status,
) (*upstreamResolverIOS, error) {
upstreamResolverBase := newUpstreamResolverBase(ctx, statusRecorder)
index, err := getInterfaceIndex(interfaceName) index, err := getInterfaceIndex(interfaceName)
if err != nil { if err != nil {

View File

@@ -8,14 +8,22 @@ import (
"time" "time"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/netbirdio/netbird/client/internal/peer"
) )
type upstreamResolverNonIOS struct { type upstreamResolverNonIOS struct {
*upstreamResolverBase *upstreamResolverBase
} }
func newUpstreamResolver(parentCTX context.Context, interfaceName string, ip net.IP, net *net.IPNet) (*upstreamResolverNonIOS, error) { func newUpstreamResolver(
upstreamResolverBase := newUpstreamResolverBase(parentCTX) ctx context.Context,
_ string,
_ net.IP,
_ *net.IPNet,
statusRecorder *peer.Status,
) (*upstreamResolverNonIOS, error) {
upstreamResolverBase := newUpstreamResolverBase(ctx, statusRecorder)
nonIOS := &upstreamResolverNonIOS{ nonIOS := &upstreamResolverNonIOS{
upstreamResolverBase: upstreamResolverBase, upstreamResolverBase: upstreamResolverBase,
} }

View File

@@ -58,7 +58,7 @@ func TestUpstreamResolver_ServeDNS(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
ctx, cancel := context.WithCancel(context.TODO()) ctx, cancel := context.WithCancel(context.TODO())
resolver, _ := newUpstreamResolver(ctx, "", net.IP{}, &net.IPNet{}) resolver, _ := newUpstreamResolver(ctx, "", net.IP{}, &net.IPNet{}, nil)
resolver.upstreamServers = testCase.InputServers resolver.upstreamServers = testCase.InputServers
resolver.upstreamTimeout = testCase.timeout resolver.upstreamTimeout = testCase.timeout
if testCase.cancelCTX { if testCase.cancelCTX {
@@ -131,7 +131,7 @@ func TestUpstreamResolver_DeactivationReactivation(t *testing.T) {
} }
failed := false failed := false
resolver.deactivate = func() { resolver.deactivate = func(error) {
failed = true failed = true
} }

View File

@@ -1188,14 +1188,21 @@ func (e *Engine) newDnsServer() ([]*route.Route, dns.Server, error) {
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
dnsServer := dns.NewDefaultServerPermanentUpstream(e.ctx, e.wgInterface, e.mobileDep.HostDNSAddresses, *dnsConfig, e.mobileDep.NetworkChangeListener) dnsServer := dns.NewDefaultServerPermanentUpstream(
e.ctx,
e.wgInterface,
e.mobileDep.HostDNSAddresses,
*dnsConfig,
e.mobileDep.NetworkChangeListener,
e.statusRecorder,
)
go e.mobileDep.DnsReadyListener.OnReady() go e.mobileDep.DnsReadyListener.OnReady()
return routes, dnsServer, nil return routes, dnsServer, nil
case "ios": case "ios":
dnsServer := dns.NewDefaultServerIos(e.ctx, e.wgInterface, e.mobileDep.DnsManager) dnsServer := dns.NewDefaultServerIos(e.ctx, e.wgInterface, e.mobileDep.DnsManager, e.statusRecorder)
return nil, dnsServer, nil return nil, dnsServer, nil
default: default:
dnsServer, err := dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress) dnsServer, err := dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress, e.statusRecorder)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -1286,7 +1293,7 @@ func (e *Engine) receiveProbeEvents() {
log.Debugf("failed to get wg stats for peer %s: %s", key, err) log.Debugf("failed to get wg stats for peer %s: %s", key, err)
} }
// wgStats could be zero value, in which case we just reset the stats // wgStats could be zero value, in which case we just reset the stats
if err := e.statusRecorder.UpdateWireguardPeerState(key, wgStats); err != nil { if err := e.statusRecorder.UpdateWireGuardPeerState(key, wgStats); err != nil {
log.Debugf("failed to update wg stats for peer %s: %s", key, err) log.Debugf("failed to update wg stats for peer %s: %s", key, err)
} }
} }

View File

@@ -38,7 +38,7 @@ func IsLoginRequired(ctx context.Context, privateKey string, mgmURL *url.URL, ss
return false, err return false, err
} }
_, err = doMgmLogin(ctx, mgmClient, pubSSHKey) _, err = doMgmLogin(ctx, mgmClient, pubSSHKey, &Config{})
if isLoginNeeded(err) { if isLoginNeeded(err) {
return true, nil return true, nil
} }
@@ -67,7 +67,7 @@ func Login(ctx context.Context, config *Config, setupKey string, jwtToken string
return err return err
} }
serverKey, err := doMgmLogin(ctx, mgmClient, pubSSHKey) serverKey, err := doMgmLogin(ctx, mgmClient, pubSSHKey, config)
if isRegistrationNeeded(err) { if isRegistrationNeeded(err) {
log.Debugf("peer registration required") log.Debugf("peer registration required")
_, err = registerPeer(ctx, *serverKey, mgmClient, setupKey, jwtToken, pubSSHKey) _, err = registerPeer(ctx, *serverKey, mgmClient, setupKey, jwtToken, pubSSHKey)
@@ -99,14 +99,14 @@ func getMgmClient(ctx context.Context, privateKey string, mgmURL *url.URL) (*mgm
return mgmClient, err return mgmClient, err
} }
func doMgmLogin(ctx context.Context, mgmClient *mgm.GrpcClient, pubSSHKey []byte) (*wgtypes.Key, error) { func doMgmLogin(ctx context.Context, mgmClient *mgm.GrpcClient, pubSSHKey []byte, config *Config) (*wgtypes.Key, error) {
serverKey, err := mgmClient.GetServerPublicKey() serverKey, err := mgmClient.GetServerPublicKey()
if err != nil { if err != nil {
log.Errorf("failed while getting Management Service public key: %v", err) log.Errorf("failed while getting Management Service public key: %v", err)
return nil, err return nil, err
} }
sysInfo := system.GetInfo(ctx) sysInfo := system.GetInfo(ctx, *config)
_, err = mgmClient.Login(*serverKey, sysInfo, pubSSHKey) _, err = mgmClient.Login(*serverKey, sysInfo, pubSSHKey)
return serverKey, err return serverKey, err
} }
@@ -120,7 +120,7 @@ func registerPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.
} }
log.Debugf("sending peer registration request to Management Service") log.Debugf("sending peer registration request to Management Service")
info := system.GetInfo(ctx) info := system.GetInfo(ctx, Config{})
loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), jwtToken, info, pubSSHKey) loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), jwtToken, info, pubSSHKey)
if err != nil { if err != nil {
log.Errorf("failed registering peer %v,%s", err, validSetupKey.String()) log.Errorf("failed registering peer %v,%s", err, validSetupKey.String())

View File

@@ -407,6 +407,10 @@ func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int, rem
} }
conn.status = StatusConnected conn.status = StatusConnected
rosenpassEnabled := false
if remoteRosenpassPubKey != nil {
rosenpassEnabled = true
}
peerState := State{ peerState := State{
PubKey: conn.config.Key, PubKey: conn.config.Key,
@@ -417,6 +421,7 @@ func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int, rem
LocalIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Local.Address(), pair.Local.Port()), LocalIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Local.Address(), pair.Local.Port()),
RemoteIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Remote.Address(), pair.Local.Port()), RemoteIceCandidateEndpoint: fmt.Sprintf("%s:%d", pair.Remote.Address(), pair.Local.Port()),
Direct: !isRelayCandidate(pair.Local), Direct: !isRelayCandidate(pair.Local),
RosenpassEnabled: rosenpassEnabled,
} }
if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay { if pair.Local.Type() == ice.CandidateTypeRelay || pair.Remote.Type() == ice.CandidateTypeRelay {
peerState.Relayed = true peerState.Relayed = true
@@ -505,7 +510,7 @@ func (conn *Conn) cleanup() error {
// todo rethink status updates // todo rethink status updates
log.Debugf("error while updating peer's %s state, err: %v", conn.config.Key, err) log.Debugf("error while updating peer's %s state, err: %v", conn.config.Key, err)
} }
if err := conn.statusRecorder.UpdateWireguardPeerState(conn.config.Key, iface.WGStats{}); err != nil { if err := conn.statusRecorder.UpdateWireGuardPeerState(conn.config.Key, iface.WGStats{}); err != nil {
log.Debugf("failed to reset wireguard stats for peer %s: %s", conn.config.Key, err) log.Debugf("failed to reset wireguard stats for peer %s: %s", conn.config.Key, err)
} }

View File

@@ -5,6 +5,9 @@ import (
"sync" "sync"
"time" "time"
"google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status"
"github.com/netbirdio/netbird/client/internal/relay" "github.com/netbirdio/netbird/client/internal/relay"
"github.com/netbirdio/netbird/iface" "github.com/netbirdio/netbird/iface"
) )
@@ -25,6 +28,8 @@ type State struct {
LastWireguardHandshake time.Time LastWireguardHandshake time.Time
BytesTx int64 BytesTx int64
BytesRx int64 BytesRx int64
RosenpassEnabled bool
Routes map[string]struct{}
} }
// LocalPeerState contains the latest state of the local peer // LocalPeerState contains the latest state of the local peer
@@ -33,6 +38,7 @@ type LocalPeerState struct {
PubKey string PubKey string
KernelInterface bool KernelInterface bool
FQDN string FQDN string
Routes map[string]struct{}
} }
// SignalState contains the latest state of a signal connection // SignalState contains the latest state of a signal connection
@@ -49,30 +55,51 @@ type ManagementState struct {
Error error Error error
} }
// RosenpassState contains the latest state of the Rosenpass configuration
type RosenpassState struct {
Enabled bool
Permissive bool
}
// NSGroupState represents the status of a DNS server group, including associated domains,
// whether it's enabled, and the last error message encountered during probing.
type NSGroupState struct {
ID string
Servers []string
Domains []string
Enabled bool
Error error
}
// FullStatus contains the full state held by the Status instance // FullStatus contains the full state held by the Status instance
type FullStatus struct { type FullStatus struct {
Peers []State Peers []State
ManagementState ManagementState ManagementState ManagementState
SignalState SignalState SignalState SignalState
LocalPeerState LocalPeerState LocalPeerState LocalPeerState
RosenpassState RosenpassState
Relays []relay.ProbeResult Relays []relay.ProbeResult
NSGroupStates []NSGroupState
} }
// Status holds a state of peers, signal, management connections and relays // Status holds a state of peers, signal, management connections and relays
type Status struct { type Status struct {
mux sync.Mutex mux sync.Mutex
peers map[string]State peers map[string]State
changeNotify map[string]chan struct{} changeNotify map[string]chan struct{}
signalState bool signalState bool
signalError error signalError error
managementState bool managementState bool
managementError error managementError error
relayStates []relay.ProbeResult relayStates []relay.ProbeResult
localPeer LocalPeerState localPeer LocalPeerState
offlinePeers []State offlinePeers []State
mgmAddress string mgmAddress string
signalAddress string signalAddress string
notifier *notifier notifier *notifier
rosenpassEnabled bool
rosenpassPermissive bool
nsGroupStates []NSGroupState
// To reduce the number of notification invocation this bool will be true when need to call the notification // To reduce the number of notification invocation this bool will be true when need to call the notification
// Some Peer actions mostly used by in a batch when the network map has been synchronized. In these type of events // Some Peer actions mostly used by in a batch when the network map has been synchronized. In these type of events
@@ -161,6 +188,10 @@ func (d *Status) UpdatePeerState(receivedState State) error {
peerState.IP = receivedState.IP peerState.IP = receivedState.IP
} }
if receivedState.Routes != nil {
peerState.Routes = receivedState.Routes
}
skipNotification := shouldSkipNotify(receivedState, peerState) skipNotification := shouldSkipNotify(receivedState, peerState)
if receivedState.ConnStatus != peerState.ConnStatus { if receivedState.ConnStatus != peerState.ConnStatus {
@@ -172,6 +203,7 @@ func (d *Status) UpdatePeerState(receivedState State) error {
peerState.RemoteIceCandidateType = receivedState.RemoteIceCandidateType peerState.RemoteIceCandidateType = receivedState.RemoteIceCandidateType
peerState.LocalIceCandidateEndpoint = receivedState.LocalIceCandidateEndpoint peerState.LocalIceCandidateEndpoint = receivedState.LocalIceCandidateEndpoint
peerState.RemoteIceCandidateEndpoint = receivedState.RemoteIceCandidateEndpoint peerState.RemoteIceCandidateEndpoint = receivedState.RemoteIceCandidateEndpoint
peerState.RosenpassEnabled = receivedState.RosenpassEnabled
} }
d.peers[receivedState.PubKey] = peerState d.peers[receivedState.PubKey] = peerState
@@ -190,8 +222,8 @@ func (d *Status) UpdatePeerState(receivedState State) error {
return nil return nil
} }
// UpdateWireguardPeerState updates the wireguard bits of the peer state // UpdateWireGuardPeerState updates the WireGuard bits of the peer state
func (d *Status) UpdateWireguardPeerState(pubKey string, wgStats iface.WGStats) error { func (d *Status) UpdateWireGuardPeerState(pubKey string, wgStats iface.WGStats) error {
d.mux.Lock() d.mux.Lock()
defer d.mux.Unlock() defer d.mux.Unlock()
@@ -264,6 +296,13 @@ func (d *Status) GetPeerStateChangeNotifier(peer string) <-chan struct{} {
return ch return ch
} }
// GetLocalPeerState returns the local peer state
func (d *Status) GetLocalPeerState() LocalPeerState {
d.mux.Lock()
defer d.mux.Unlock()
return d.localPeer
}
// UpdateLocalPeerState updates local peer status // UpdateLocalPeerState updates local peer status
func (d *Status) UpdateLocalPeerState(localPeerState LocalPeerState) { func (d *Status) UpdateLocalPeerState(localPeerState LocalPeerState) {
d.mux.Lock() d.mux.Lock()
@@ -316,6 +355,14 @@ func (d *Status) UpdateManagementAddress(mgmAddress string) {
d.mgmAddress = mgmAddress d.mgmAddress = mgmAddress
} }
// UpdateRosenpass update the Rosenpass configuration
func (d *Status) UpdateRosenpass(rosenpassEnabled, rosenpassPermissive bool) {
d.mux.Lock()
defer d.mux.Unlock()
d.rosenpassPermissive = rosenpassPermissive
d.rosenpassEnabled = rosenpassEnabled
}
// MarkSignalDisconnected sets SignalState to disconnected // MarkSignalDisconnected sets SignalState to disconnected
func (d *Status) MarkSignalDisconnected(err error) { func (d *Status) MarkSignalDisconnected(err error) {
d.mux.Lock() d.mux.Lock()
@@ -342,6 +389,19 @@ func (d *Status) UpdateRelayStates(relayResults []relay.ProbeResult) {
d.relayStates = relayResults d.relayStates = relayResults
} }
func (d *Status) UpdateDNSStates(dnsStates []NSGroupState) {
d.mux.Lock()
defer d.mux.Unlock()
d.nsGroupStates = dnsStates
}
func (d *Status) GetRosenpassState() RosenpassState {
return RosenpassState{
d.rosenpassEnabled,
d.rosenpassPermissive,
}
}
func (d *Status) GetManagementState() ManagementState { func (d *Status) GetManagementState() ManagementState {
return ManagementState{ return ManagementState{
d.mgmAddress, d.mgmAddress,
@@ -350,6 +410,24 @@ func (d *Status) GetManagementState() ManagementState {
} }
} }
// IsLoginRequired determines if a peer's login has expired.
func (d *Status) IsLoginRequired() bool {
d.mux.Lock()
defer d.mux.Unlock()
// if peer is connected to the management then login is not expired
if d.managementState {
return false
}
s, ok := gstatus.FromError(d.managementError)
if ok && (s.Code() == codes.InvalidArgument || s.Code() == codes.PermissionDenied) {
return true
}
return false
}
func (d *Status) GetSignalState() SignalState { func (d *Status) GetSignalState() SignalState {
return SignalState{ return SignalState{
d.signalAddress, d.signalAddress,
@@ -362,6 +440,10 @@ func (d *Status) GetRelayStates() []relay.ProbeResult {
return d.relayStates return d.relayStates
} }
func (d *Status) GetDNSStates() []NSGroupState {
return d.nsGroupStates
}
// GetFullStatus gets full status // GetFullStatus gets full status
func (d *Status) GetFullStatus() FullStatus { func (d *Status) GetFullStatus() FullStatus {
d.mux.Lock() d.mux.Lock()
@@ -372,6 +454,8 @@ func (d *Status) GetFullStatus() FullStatus {
SignalState: d.GetSignalState(), SignalState: d.GetSignalState(),
LocalPeerState: d.localPeer, LocalPeerState: d.localPeer,
Relays: d.GetRelayStates(), Relays: d.GetRelayStates(),
RosenpassState: d.GetRosenpassState(),
NSGroupStates: d.GetDNSStates(),
} }
for _, status := range d.peers { for _, status := range d.peers {

View File

@@ -160,6 +160,12 @@ func (c *clientNetwork) removeRouteFromWireguardPeer(peerKey string) error {
if err != nil { if err != nil {
return err return err
} }
delete(state.Routes, c.network.String())
if err := c.statusRecorder.UpdatePeerState(state); err != nil {
log.Warnf("Failed to update peer state: %v", err)
}
if state.ConnStatus != peer.StatusConnected { if state.ConnStatus != peer.StatusConnected {
return nil return nil
} }
@@ -225,6 +231,20 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error {
} }
c.chosenRoute = c.routes[chosen] c.chosenRoute = c.routes[chosen]
state, err := c.statusRecorder.GetPeer(c.chosenRoute.Peer)
if err != nil {
log.Errorf("Failed to get peer state: %v", err)
} else {
if state.Routes == nil {
state.Routes = map[string]struct{}{}
}
state.Routes[c.network.String()] = struct{}{}
if err := c.statusRecorder.UpdatePeerState(state); err != nil {
log.Warnf("Failed to update peer state: %v", err)
}
}
err = c.wgInterface.AddAllowedIP(c.chosenRoute.Peer, c.network.String()) err = c.wgInterface.AddAllowedIP(c.chosenRoute.Peer, c.network.String())
if err != nil { if err != nil {
log.Errorf("couldn't add allowed IP %s added for peer %s, err: %v", log.Errorf("couldn't add allowed IP %s added for peer %s, err: %v",

View File

@@ -58,7 +58,7 @@ func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface,
func (m *DefaultManager) EnableServerRouter(firewall firewall.Manager) error { func (m *DefaultManager) EnableServerRouter(firewall firewall.Manager) error {
var err error var err error
m.serverRouter, err = newServerRouter(m.ctx, m.wgInterface, firewall) m.serverRouter, err = newServerRouter(m.ctx, m.wgInterface, firewall, m.statusRecorder)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -7,9 +7,10 @@ import (
"fmt" "fmt"
firewall "github.com/netbirdio/netbird/client/firewall/manager" firewall "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/iface" "github.com/netbirdio/netbird/iface"
) )
func newServerRouter(context.Context, *iface.WGIface, firewall.Manager) (serverRouter, error) { func newServerRouter(context.Context, *iface.WGIface, firewall.Manager, *peer.Status) (serverRouter, error) {
return nil, fmt.Errorf("server route not supported on this os") return nil, fmt.Errorf("server route not supported on this os")
} }

View File

@@ -10,24 +10,27 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
firewall "github.com/netbirdio/netbird/client/firewall/manager" firewall "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/iface" "github.com/netbirdio/netbird/iface"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
type defaultServerRouter struct { type defaultServerRouter struct {
mux sync.Mutex mux sync.Mutex
ctx context.Context ctx context.Context
routes map[string]*route.Route routes map[string]*route.Route
firewall firewall.Manager firewall firewall.Manager
wgInterface *iface.WGIface wgInterface *iface.WGIface
statusRecorder *peer.Status
} }
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface, firewall firewall.Manager) (serverRouter, error) { func newServerRouter(ctx context.Context, wgInterface *iface.WGIface, firewall firewall.Manager, statusRecorder *peer.Status) (serverRouter, error) {
return &defaultServerRouter{ return &defaultServerRouter{
ctx: ctx, ctx: ctx,
routes: make(map[string]*route.Route), routes: make(map[string]*route.Route),
firewall: firewall, firewall: firewall,
wgInterface: wgInterface, wgInterface: wgInterface,
statusRecorder: statusRecorder,
}, nil }, nil
} }
@@ -88,6 +91,11 @@ func (m *defaultServerRouter) removeFromServerNetwork(route *route.Route) error
return err return err
} }
delete(m.routes, route.ID) delete(m.routes, route.ID)
state := m.statusRecorder.GetLocalPeerState()
delete(state.Routes, route.Network.String())
m.statusRecorder.UpdateLocalPeerState(state)
return nil return nil
} }
} }
@@ -105,6 +113,14 @@ func (m *defaultServerRouter) addToServerNetwork(route *route.Route) error {
return err return err
} }
m.routes[route.ID] = route m.routes[route.ID] = route
state := m.statusRecorder.GetLocalPeerState()
if state.Routes == nil {
state.Routes = map[string]struct{}{}
}
state.Routes[route.Network.String()] = struct{}{}
m.statusRecorder.UpdateLocalPeerState(state)
return nil return nil
} }
} }
@@ -117,6 +133,10 @@ func (m *defaultServerRouter) cleanUp() {
if err != nil { if err != nil {
log.Warnf("failed to remove clean up route: %s", r.ID) log.Warnf("failed to remove clean up route: %s", r.ID)
} }
state := m.statusRecorder.GetLocalPeerState()
state.Routes = nil
m.statusRecorder.UpdateLocalPeerState(state)
} }
} }

View File

@@ -0,0 +1,82 @@
package internal
import (
"context"
"os/exec"
"strings"
"sync"
"time"
"github.com/netbirdio/netbird/client/internal/peer"
)
type SessionWatcher struct {
ctx context.Context
mutex sync.Mutex
peerStatusRecorder *peer.Status
watchTicker *time.Ticker
sendNotification bool
onExpireListener func()
}
// NewSessionWatcher creates a new instance of SessionWatcher.
func NewSessionWatcher(ctx context.Context, peerStatusRecorder *peer.Status) *SessionWatcher {
s := &SessionWatcher{
ctx: ctx,
peerStatusRecorder: peerStatusRecorder,
watchTicker: time.NewTicker(2 * time.Second),
}
go s.startWatcher()
return s
}
// SetOnExpireListener sets the callback func to be called when the session expires.
func (s *SessionWatcher) SetOnExpireListener(onExpire func()) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.onExpireListener = onExpire
}
// startWatcher continuously checks if the session requires login and
// calls the onExpireListener if login is required.
func (s *SessionWatcher) startWatcher() {
for {
select {
case <-s.ctx.Done():
s.watchTicker.Stop()
return
case <-s.watchTicker.C:
managementState := s.peerStatusRecorder.GetManagementState()
if managementState.Connected {
s.sendNotification = true
}
isLoginRequired := s.peerStatusRecorder.IsLoginRequired()
if isLoginRequired && s.sendNotification && s.onExpireListener != nil {
s.mutex.Lock()
s.onExpireListener()
s.sendNotification = false
s.mutex.Unlock()
}
}
}
}
// CheckUIApp checks whether UI application is running.
func CheckUIApp() bool {
cmd := exec.Command("ps", "-ef")
output, err := cmd.Output()
if err != nil {
return false
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "netbird-ui") && !strings.Contains(line, "grep") {
return true
}
}
return false
}

View File

@@ -82,6 +82,7 @@ func (c *Client) Run(fd int32, interfaceName string) error {
return err return err
} }
c.recorder.UpdateManagementAddress(cfg.ManagementURL.String()) c.recorder.UpdateManagementAddress(cfg.ManagementURL.String())
c.recorder.UpdateRosenpass(cfg.RosenpassEnabled, cfg.RosenpassPermissive)
var ctx context.Context var ctx context.Context
//nolint //nolint

View File

@@ -1,16 +1,16 @@
// 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 v4.24.3 // protoc v3.12.4
// source: daemon.proto // source: daemon.proto
package proto package proto
import ( import (
_ "github.com/golang/protobuf/protoc-gen-go/descriptor"
timestamp "github.com/golang/protobuf/ptypes/timestamp"
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
_ "google.golang.org/protobuf/types/descriptorpb"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect" reflect "reflect"
sync "sync" sync "sync"
) )
@@ -757,20 +757,22 @@ type PeerState struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
IP string `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"` IP string `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"`
PubKey string `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"` PubKey string `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
ConnStatus string `protobuf:"bytes,3,opt,name=connStatus,proto3" json:"connStatus,omitempty"` ConnStatus string `protobuf:"bytes,3,opt,name=connStatus,proto3" json:"connStatus,omitempty"`
ConnStatusUpdate *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=connStatusUpdate,proto3" json:"connStatusUpdate,omitempty"` ConnStatusUpdate *timestamp.Timestamp `protobuf:"bytes,4,opt,name=connStatusUpdate,proto3" json:"connStatusUpdate,omitempty"`
Relayed bool `protobuf:"varint,5,opt,name=relayed,proto3" json:"relayed,omitempty"` Relayed bool `protobuf:"varint,5,opt,name=relayed,proto3" json:"relayed,omitempty"`
Direct bool `protobuf:"varint,6,opt,name=direct,proto3" json:"direct,omitempty"` Direct bool `protobuf:"varint,6,opt,name=direct,proto3" json:"direct,omitempty"`
LocalIceCandidateType string `protobuf:"bytes,7,opt,name=localIceCandidateType,proto3" json:"localIceCandidateType,omitempty"` LocalIceCandidateType string `protobuf:"bytes,7,opt,name=localIceCandidateType,proto3" json:"localIceCandidateType,omitempty"`
RemoteIceCandidateType string `protobuf:"bytes,8,opt,name=remoteIceCandidateType,proto3" json:"remoteIceCandidateType,omitempty"` RemoteIceCandidateType string `protobuf:"bytes,8,opt,name=remoteIceCandidateType,proto3" json:"remoteIceCandidateType,omitempty"`
Fqdn string `protobuf:"bytes,9,opt,name=fqdn,proto3" json:"fqdn,omitempty"` Fqdn string `protobuf:"bytes,9,opt,name=fqdn,proto3" json:"fqdn,omitempty"`
LocalIceCandidateEndpoint string `protobuf:"bytes,10,opt,name=localIceCandidateEndpoint,proto3" json:"localIceCandidateEndpoint,omitempty"` LocalIceCandidateEndpoint string `protobuf:"bytes,10,opt,name=localIceCandidateEndpoint,proto3" json:"localIceCandidateEndpoint,omitempty"`
RemoteIceCandidateEndpoint string `protobuf:"bytes,11,opt,name=remoteIceCandidateEndpoint,proto3" json:"remoteIceCandidateEndpoint,omitempty"` RemoteIceCandidateEndpoint string `protobuf:"bytes,11,opt,name=remoteIceCandidateEndpoint,proto3" json:"remoteIceCandidateEndpoint,omitempty"`
LastWireguardHandshake *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=lastWireguardHandshake,proto3" json:"lastWireguardHandshake,omitempty"` LastWireguardHandshake *timestamp.Timestamp `protobuf:"bytes,12,opt,name=lastWireguardHandshake,proto3" json:"lastWireguardHandshake,omitempty"`
BytesRx int64 `protobuf:"varint,13,opt,name=bytesRx,proto3" json:"bytesRx,omitempty"` BytesRx int64 `protobuf:"varint,13,opt,name=bytesRx,proto3" json:"bytesRx,omitempty"`
BytesTx int64 `protobuf:"varint,14,opt,name=bytesTx,proto3" json:"bytesTx,omitempty"` BytesTx int64 `protobuf:"varint,14,opt,name=bytesTx,proto3" json:"bytesTx,omitempty"`
RosenpassEnabled bool `protobuf:"varint,15,opt,name=rosenpassEnabled,proto3" json:"rosenpassEnabled,omitempty"`
Routes []string `protobuf:"bytes,16,rep,name=routes,proto3" json:"routes,omitempty"`
} }
func (x *PeerState) Reset() { func (x *PeerState) Reset() {
@@ -826,7 +828,7 @@ func (x *PeerState) GetConnStatus() string {
return "" return ""
} }
func (x *PeerState) GetConnStatusUpdate() *timestamppb.Timestamp { func (x *PeerState) GetConnStatusUpdate() *timestamp.Timestamp {
if x != nil { if x != nil {
return x.ConnStatusUpdate return x.ConnStatusUpdate
} }
@@ -882,7 +884,7 @@ func (x *PeerState) GetRemoteIceCandidateEndpoint() string {
return "" return ""
} }
func (x *PeerState) GetLastWireguardHandshake() *timestamppb.Timestamp { func (x *PeerState) GetLastWireguardHandshake() *timestamp.Timestamp {
if x != nil { if x != nil {
return x.LastWireguardHandshake return x.LastWireguardHandshake
} }
@@ -903,16 +905,33 @@ func (x *PeerState) GetBytesTx() int64 {
return 0 return 0
} }
func (x *PeerState) GetRosenpassEnabled() bool {
if x != nil {
return x.RosenpassEnabled
}
return false
}
func (x *PeerState) GetRoutes() []string {
if x != nil {
return x.Routes
}
return nil
}
// LocalPeerState contains the latest state of the local peer // LocalPeerState contains the latest state of the local peer
type LocalPeerState struct { type LocalPeerState struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
IP string `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"` IP string `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"`
PubKey string `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"` PubKey string `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
KernelInterface bool `protobuf:"varint,3,opt,name=kernelInterface,proto3" json:"kernelInterface,omitempty"` KernelInterface bool `protobuf:"varint,3,opt,name=kernelInterface,proto3" json:"kernelInterface,omitempty"`
Fqdn string `protobuf:"bytes,4,opt,name=fqdn,proto3" json:"fqdn,omitempty"` Fqdn string `protobuf:"bytes,4,opt,name=fqdn,proto3" json:"fqdn,omitempty"`
RosenpassEnabled bool `protobuf:"varint,5,opt,name=rosenpassEnabled,proto3" json:"rosenpassEnabled,omitempty"`
RosenpassPermissive bool `protobuf:"varint,6,opt,name=rosenpassPermissive,proto3" json:"rosenpassPermissive,omitempty"`
Routes []string `protobuf:"bytes,7,rep,name=routes,proto3" json:"routes,omitempty"`
} }
func (x *LocalPeerState) Reset() { func (x *LocalPeerState) Reset() {
@@ -975,6 +994,27 @@ func (x *LocalPeerState) GetFqdn() string {
return "" return ""
} }
func (x *LocalPeerState) GetRosenpassEnabled() bool {
if x != nil {
return x.RosenpassEnabled
}
return false
}
func (x *LocalPeerState) GetRosenpassPermissive() bool {
if x != nil {
return x.RosenpassPermissive
}
return false
}
func (x *LocalPeerState) GetRoutes() []string {
if x != nil {
return x.Routes
}
return nil
}
// SignalState contains the latest state of a signal connection // SignalState contains the latest state of a signal connection
type SignalState struct { type SignalState struct {
state protoimpl.MessageState state protoimpl.MessageState
@@ -1167,6 +1207,77 @@ func (x *RelayState) GetError() string {
return "" return ""
} }
type NSGroupState struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Servers []string `protobuf:"bytes,1,rep,name=servers,proto3" json:"servers,omitempty"`
Domains []string `protobuf:"bytes,2,rep,name=domains,proto3" json:"domains,omitempty"`
Enabled bool `protobuf:"varint,3,opt,name=enabled,proto3" json:"enabled,omitempty"`
Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"`
}
func (x *NSGroupState) Reset() {
*x = NSGroupState{}
if protoimpl.UnsafeEnabled {
mi := &file_daemon_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *NSGroupState) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NSGroupState) ProtoMessage() {}
func (x *NSGroupState) ProtoReflect() protoreflect.Message {
mi := &file_daemon_proto_msgTypes[17]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NSGroupState.ProtoReflect.Descriptor instead.
func (*NSGroupState) Descriptor() ([]byte, []int) {
return file_daemon_proto_rawDescGZIP(), []int{17}
}
func (x *NSGroupState) GetServers() []string {
if x != nil {
return x.Servers
}
return nil
}
func (x *NSGroupState) GetDomains() []string {
if x != nil {
return x.Domains
}
return nil
}
func (x *NSGroupState) GetEnabled() bool {
if x != nil {
return x.Enabled
}
return false
}
func (x *NSGroupState) GetError() string {
if x != nil {
return x.Error
}
return ""
}
// FullStatus contains the full state held by the Status instance // FullStatus contains the full state held by the Status instance
type FullStatus struct { type FullStatus struct {
state protoimpl.MessageState state protoimpl.MessageState
@@ -1178,12 +1289,13 @@ type FullStatus struct {
LocalPeerState *LocalPeerState `protobuf:"bytes,3,opt,name=localPeerState,proto3" json:"localPeerState,omitempty"` LocalPeerState *LocalPeerState `protobuf:"bytes,3,opt,name=localPeerState,proto3" json:"localPeerState,omitempty"`
Peers []*PeerState `protobuf:"bytes,4,rep,name=peers,proto3" json:"peers,omitempty"` Peers []*PeerState `protobuf:"bytes,4,rep,name=peers,proto3" json:"peers,omitempty"`
Relays []*RelayState `protobuf:"bytes,5,rep,name=relays,proto3" json:"relays,omitempty"` Relays []*RelayState `protobuf:"bytes,5,rep,name=relays,proto3" json:"relays,omitempty"`
DnsServers []*NSGroupState `protobuf:"bytes,6,rep,name=dns_servers,json=dnsServers,proto3" json:"dns_servers,omitempty"`
} }
func (x *FullStatus) Reset() { func (x *FullStatus) Reset() {
*x = FullStatus{} *x = FullStatus{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_daemon_proto_msgTypes[17] mi := &file_daemon_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@@ -1196,7 +1308,7 @@ func (x *FullStatus) String() string {
func (*FullStatus) ProtoMessage() {} func (*FullStatus) ProtoMessage() {}
func (x *FullStatus) ProtoReflect() protoreflect.Message { func (x *FullStatus) ProtoReflect() protoreflect.Message {
mi := &file_daemon_proto_msgTypes[17] mi := &file_daemon_proto_msgTypes[18]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@@ -1209,7 +1321,7 @@ func (x *FullStatus) ProtoReflect() protoreflect.Message {
// Deprecated: Use FullStatus.ProtoReflect.Descriptor instead. // Deprecated: Use FullStatus.ProtoReflect.Descriptor instead.
func (*FullStatus) Descriptor() ([]byte, []int) { func (*FullStatus) Descriptor() ([]byte, []int) {
return file_daemon_proto_rawDescGZIP(), []int{17} return file_daemon_proto_rawDescGZIP(), []int{18}
} }
func (x *FullStatus) GetManagementState() *ManagementState { func (x *FullStatus) GetManagementState() *ManagementState {
@@ -1247,6 +1359,13 @@ func (x *FullStatus) GetRelays() []*RelayState {
return nil return nil
} }
func (x *FullStatus) GetDnsServers() []*NSGroupState {
if x != nil {
return x.DnsServers
}
return nil
}
var File_daemon_proto protoreflect.FileDescriptor var File_daemon_proto protoreflect.FileDescriptor
var file_daemon_proto_rawDesc = []byte{ var file_daemon_proto_rawDesc = []byte{
@@ -1356,7 +1475,7 @@ var file_daemon_proto_rawDesc = []byte{
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65,
0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
0x22, 0xd5, 0x04, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x22, 0x99, 0x05, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e,
0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16,
0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74,
@@ -1393,73 +1512,96 @@ var file_daemon_proto_rawDesc = []byte{
0x68, 0x61, 0x6b, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x78, 0x18, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x78, 0x18,
0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x78, 0x12, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x78, 0x12, 0x18,
0x0a, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x54, 0x78, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x54, 0x78, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52,
0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x54, 0x78, 0x22, 0x76, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x54, 0x78, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x6f, 0x73, 0x65,
0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x28, 0x08, 0x52, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61,
0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x10,
0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0xec, 0x01, 0x0a,
0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12,
0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x22, 0x53, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65,
0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63,
0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61,
0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x57, 0x0a, 0x0f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65,
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13,
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73,
0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x52, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20,
0x0a, 0x0a, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0x53, 0x0a, 0x0b, 0x53,
0x55, 0x52, 0x49, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x49, 0x12, 0x1c, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52,
0x0a, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09,
0x08, 0x52, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72,
0x6f, 0x72, 0x22, 0x9b, 0x02, 0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x22, 0x57, 0x0a, 0x0f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74,
0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
0x61, 0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
0x74, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01,
0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x52, 0x0a, 0x0a, 0x52, 0x65, 0x6c,
0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x49, 0x18, 0x01,
0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x49, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x76, 0x61,
0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x76,
0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x72, 0x0a,
0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x0c, 0x4e, 0x53, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a,
0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07,
0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69,
0x65, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x18, 0x05, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01,
0x6c, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65,
0x32, 0xf7, 0x02, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x72, 0x22, 0xd2, 0x02, 0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74,
0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61,
0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74,
0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f,
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01,
0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61,
0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d,
0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65,
0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x18, 0x05, 0x20,
0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x6c,
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x12,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x35, 0x0a, 0x0b, 0x64, 0x6e, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x06,
0x66, 0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4e, 0x53,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x64, 0x6e, 0x73, 0x53,
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x32, 0xf7, 0x02, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53,
0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e,
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f,
0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a,
0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e,
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12,
0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f,
0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09,
0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d,
0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
} }
var ( var (
@@ -1474,54 +1616,56 @@ func file_daemon_proto_rawDescGZIP() []byte {
return file_daemon_proto_rawDescData return file_daemon_proto_rawDescData
} }
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
var file_daemon_proto_goTypes = []interface{}{ var file_daemon_proto_goTypes = []interface{}{
(*LoginRequest)(nil), // 0: daemon.LoginRequest (*LoginRequest)(nil), // 0: daemon.LoginRequest
(*LoginResponse)(nil), // 1: daemon.LoginResponse (*LoginResponse)(nil), // 1: daemon.LoginResponse
(*WaitSSOLoginRequest)(nil), // 2: daemon.WaitSSOLoginRequest (*WaitSSOLoginRequest)(nil), // 2: daemon.WaitSSOLoginRequest
(*WaitSSOLoginResponse)(nil), // 3: daemon.WaitSSOLoginResponse (*WaitSSOLoginResponse)(nil), // 3: daemon.WaitSSOLoginResponse
(*UpRequest)(nil), // 4: daemon.UpRequest (*UpRequest)(nil), // 4: daemon.UpRequest
(*UpResponse)(nil), // 5: daemon.UpResponse (*UpResponse)(nil), // 5: daemon.UpResponse
(*StatusRequest)(nil), // 6: daemon.StatusRequest (*StatusRequest)(nil), // 6: daemon.StatusRequest
(*StatusResponse)(nil), // 7: daemon.StatusResponse (*StatusResponse)(nil), // 7: daemon.StatusResponse
(*DownRequest)(nil), // 8: daemon.DownRequest (*DownRequest)(nil), // 8: daemon.DownRequest
(*DownResponse)(nil), // 9: daemon.DownResponse (*DownResponse)(nil), // 9: daemon.DownResponse
(*GetConfigRequest)(nil), // 10: daemon.GetConfigRequest (*GetConfigRequest)(nil), // 10: daemon.GetConfigRequest
(*GetConfigResponse)(nil), // 11: daemon.GetConfigResponse (*GetConfigResponse)(nil), // 11: daemon.GetConfigResponse
(*PeerState)(nil), // 12: daemon.PeerState (*PeerState)(nil), // 12: daemon.PeerState
(*LocalPeerState)(nil), // 13: daemon.LocalPeerState (*LocalPeerState)(nil), // 13: daemon.LocalPeerState
(*SignalState)(nil), // 14: daemon.SignalState (*SignalState)(nil), // 14: daemon.SignalState
(*ManagementState)(nil), // 15: daemon.ManagementState (*ManagementState)(nil), // 15: daemon.ManagementState
(*RelayState)(nil), // 16: daemon.RelayState (*RelayState)(nil), // 16: daemon.RelayState
(*FullStatus)(nil), // 17: daemon.FullStatus (*NSGroupState)(nil), // 17: daemon.NSGroupState
(*timestamppb.Timestamp)(nil), // 18: google.protobuf.Timestamp (*FullStatus)(nil), // 18: daemon.FullStatus
(*timestamp.Timestamp)(nil), // 19: google.protobuf.Timestamp
} }
var file_daemon_proto_depIdxs = []int32{ var file_daemon_proto_depIdxs = []int32{
17, // 0: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus 18, // 0: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus
18, // 1: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp 19, // 1: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp
18, // 2: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp 19, // 2: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp
15, // 3: daemon.FullStatus.managementState:type_name -> daemon.ManagementState 15, // 3: daemon.FullStatus.managementState:type_name -> daemon.ManagementState
14, // 4: daemon.FullStatus.signalState:type_name -> daemon.SignalState 14, // 4: daemon.FullStatus.signalState:type_name -> daemon.SignalState
13, // 5: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState 13, // 5: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState
12, // 6: daemon.FullStatus.peers:type_name -> daemon.PeerState 12, // 6: daemon.FullStatus.peers:type_name -> daemon.PeerState
16, // 7: daemon.FullStatus.relays:type_name -> daemon.RelayState 16, // 7: daemon.FullStatus.relays:type_name -> daemon.RelayState
0, // 8: daemon.DaemonService.Login:input_type -> daemon.LoginRequest 17, // 8: daemon.FullStatus.dns_servers:type_name -> daemon.NSGroupState
2, // 9: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest 0, // 9: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
4, // 10: daemon.DaemonService.Up:input_type -> daemon.UpRequest 2, // 10: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
6, // 11: daemon.DaemonService.Status:input_type -> daemon.StatusRequest 4, // 11: daemon.DaemonService.Up:input_type -> daemon.UpRequest
8, // 12: daemon.DaemonService.Down:input_type -> daemon.DownRequest 6, // 12: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
10, // 13: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest 8, // 13: daemon.DaemonService.Down:input_type -> daemon.DownRequest
1, // 14: daemon.DaemonService.Login:output_type -> daemon.LoginResponse 10, // 14: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
3, // 15: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse 1, // 15: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
5, // 16: daemon.DaemonService.Up:output_type -> daemon.UpResponse 3, // 16: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
7, // 17: daemon.DaemonService.Status:output_type -> daemon.StatusResponse 5, // 17: daemon.DaemonService.Up:output_type -> daemon.UpResponse
9, // 18: daemon.DaemonService.Down:output_type -> daemon.DownResponse 7, // 18: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
11, // 19: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse 9, // 19: daemon.DaemonService.Down:output_type -> daemon.DownResponse
14, // [14:20] is the sub-list for method output_type 11, // 20: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
8, // [8:14] is the sub-list for method input_type 15, // [15:21] is the sub-list for method output_type
8, // [8:8] is the sub-list for extension type_name 9, // [9:15] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension extendee 9, // [9:9] is the sub-list for extension type_name
0, // [0:8] is the sub-list for field type_name 9, // [9:9] is the sub-list for extension extendee
0, // [0:9] is the sub-list for field type_name
} }
func init() { file_daemon_proto_init() } func init() { file_daemon_proto_init() }
@@ -1735,6 +1879,18 @@ func file_daemon_proto_init() {
} }
} }
file_daemon_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { file_daemon_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NSGroupState); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_daemon_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FullStatus); i { switch v := v.(*FullStatus); i {
case 0: case 0:
return &v.state return &v.state
@@ -1754,7 +1910,7 @@ func file_daemon_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_daemon_proto_rawDesc, RawDescriptor: file_daemon_proto_rawDesc,
NumEnums: 0, NumEnums: 0,
NumMessages: 18, NumMessages: 19,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@@ -34,7 +34,7 @@ message LoginRequest {
// This is the old PreSharedKey field which will be deprecated in favor of optionalPreSharedKey field that is defined as optional // This is the old PreSharedKey field which will be deprecated in favor of optionalPreSharedKey field that is defined as optional
// to allow clearing of preshared key while being able to persist in the config file. // to allow clearing of preshared key while being able to persist in the config file.
string preSharedKey = 2 [deprecated=true]; string preSharedKey = 2 [deprecated = true];
// managementUrl to authenticate. // managementUrl to authenticate.
string managementUrl = 3; string managementUrl = 3;
@@ -140,6 +140,8 @@ message PeerState {
google.protobuf.Timestamp lastWireguardHandshake = 12; google.protobuf.Timestamp lastWireguardHandshake = 12;
int64 bytesRx = 13; int64 bytesRx = 13;
int64 bytesTx = 14; int64 bytesTx = 14;
bool rosenpassEnabled = 15;
repeated string routes = 16;
} }
// LocalPeerState contains the latest state of the local peer // LocalPeerState contains the latest state of the local peer
@@ -148,6 +150,9 @@ message LocalPeerState {
string pubKey = 2; string pubKey = 2;
bool kernelInterface = 3; bool kernelInterface = 3;
string fqdn = 4; string fqdn = 4;
bool rosenpassEnabled = 5;
bool rosenpassPermissive = 6;
repeated string routes = 7;
} }
// SignalState contains the latest state of a signal connection // SignalState contains the latest state of a signal connection
@@ -171,6 +176,13 @@ message RelayState {
string error = 3; string error = 3;
} }
message NSGroupState {
repeated string servers = 1;
repeated string domains = 2;
bool enabled = 3;
string error = 4;
}
// FullStatus contains the full state held by the Status instance // FullStatus contains the full state held by the Status instance
message FullStatus { message FullStatus {
ManagementState managementState = 1; ManagementState managementState = 1;
@@ -178,4 +190,5 @@ message FullStatus {
LocalPeerState localPeerState = 3; LocalPeerState localPeerState = 3;
repeated PeerState peers = 4; repeated PeerState peers = 4;
repeated RelayState relays = 5; repeated RelayState relays = 5;
repeated NSGroupState dns_servers = 6;
} }

View File

@@ -3,9 +3,16 @@ package server
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"os/exec"
"runtime"
"strconv"
"sync" "sync"
"time" "time"
"github.com/cenkalti/backoff/v4"
"golang.org/x/exp/maps"
"github.com/netbirdio/netbird/client/internal/auth" "github.com/netbirdio/netbird/client/internal/auth"
"github.com/netbirdio/netbird/client/system" "github.com/netbirdio/netbird/client/system"
@@ -21,7 +28,17 @@ import (
"github.com/netbirdio/netbird/version" "github.com/netbirdio/netbird/version"
) )
const probeThreshold = time.Second * 5 const (
probeThreshold = time.Second * 5
retryInitialIntervalVar = "NB_CONN_RETRY_INTERVAL_TIME"
maxRetryIntervalVar = "NB_CONN_MAX_RETRY_INTERVAL_TIME"
maxRetryTimeVar = "NB_CONN_MAX_RETRY_TIME_TIME"
retryMultiplierVar = "NB_CONN_RETRY_MULTIPLIER"
defaultInitialRetryTime = 14 * 24 * time.Hour
defaultMaxRetryInterval = 60 * time.Minute
defaultMaxRetryTime = 14 * 24 * time.Hour
defaultRetryMultiplier = 1.7
)
// Server for service control. // Server for service control.
type Server struct { type Server struct {
@@ -39,6 +56,7 @@ type Server struct {
proto.UnimplementedDaemonServiceServer proto.UnimplementedDaemonServiceServer
statusRecorder *peer.Status statusRecorder *peer.Status
sessionWatcher *internal.SessionWatcher
mgmProbe *internal.Probe mgmProbe *internal.Probe
signalProbe *internal.Probe signalProbe *internal.Probe
@@ -112,21 +130,120 @@ func (s *Server) Start() error {
if s.statusRecorder == nil { if s.statusRecorder == nil {
s.statusRecorder = peer.NewRecorder(config.ManagementURL.String()) s.statusRecorder = peer.NewRecorder(config.ManagementURL.String())
} else { }
s.statusRecorder.UpdateManagementAddress(config.ManagementURL.String()) s.statusRecorder.UpdateManagementAddress(config.ManagementURL.String())
s.statusRecorder.UpdateRosenpass(config.RosenpassEnabled, config.RosenpassPermissive)
if s.sessionWatcher == nil {
s.sessionWatcher = internal.NewSessionWatcher(s.rootCtx, s.statusRecorder)
s.sessionWatcher.SetOnExpireListener(s.onSessionExpire)
} }
if !config.DisableAutoConnect { if !config.DisableAutoConnect {
go func() { go s.connectWithRetryRuns(ctx, config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe)
if err := internal.RunClientWithProbes(ctx, config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe); err != nil {
log.Errorf("init connections: %v", err)
}
}()
} }
return nil return nil
} }
// connectWithRetryRuns runs the client connection with a backoff strategy where we retry the operation as additional
// mechanism to keep the client connected even when the connection is lost.
// we cancel retry if the client receive a stop or down command, or if disable auto connect is configured.
func (s *Server) connectWithRetryRuns(ctx context.Context, config *internal.Config, statusRecorder *peer.Status,
mgmProbe *internal.Probe, signalProbe *internal.Probe, relayProbe *internal.Probe, wgProbe *internal.Probe) {
backOff := getConnectWithBackoff(ctx)
retryStarted := false
go func() {
t := time.NewTicker(24 * time.Hour)
for {
select {
case <-ctx.Done():
t.Stop()
return
case <-t.C:
if retryStarted {
mgmtState := statusRecorder.GetManagementState()
signalState := statusRecorder.GetSignalState()
if mgmtState.Connected && signalState.Connected {
log.Tracef("resetting status")
retryStarted = false
} else {
log.Tracef("not resetting status: mgmt: %v, signal: %v", mgmtState.Connected, signalState.Connected)
}
}
}
}
}()
runOperation := func() error {
log.Tracef("running client connection")
err := internal.RunClientWithProbes(ctx, config, statusRecorder, mgmProbe, signalProbe, relayProbe, wgProbe)
if err != nil {
log.Debugf("run client connection exited with error: %v. Will retry in the background", err)
}
if config.DisableAutoConnect {
return backoff.Permanent(err)
}
if !retryStarted {
retryStarted = true
backOff.Reset()
}
log.Tracef("client connection exited")
return fmt.Errorf("client connection exited")
}
err := backoff.Retry(runOperation, backOff)
if s, ok := gstatus.FromError(err); ok && s.Code() != codes.Canceled {
log.Errorf("received an error when trying to connect: %v", err)
} else {
log.Tracef("retry canceled")
}
}
// getConnectWithBackoff returns a backoff with exponential backoff strategy for connection retries
func getConnectWithBackoff(ctx context.Context) backoff.BackOff {
initialInterval := parseEnvDuration(retryInitialIntervalVar, defaultInitialRetryTime)
maxInterval := parseEnvDuration(maxRetryIntervalVar, defaultMaxRetryInterval)
maxElapsedTime := parseEnvDuration(maxRetryTimeVar, defaultMaxRetryTime)
multiplier := defaultRetryMultiplier
if envValue := os.Getenv(retryMultiplierVar); envValue != "" {
// parse the multiplier from the environment variable string value to float64
value, err := strconv.ParseFloat(envValue, 64)
if err != nil {
log.Warnf("unable to parse environment variable %s: %s. using default: %f", retryMultiplierVar, envValue, multiplier)
} else {
multiplier = value
}
}
return backoff.WithContext(&backoff.ExponentialBackOff{
InitialInterval: initialInterval,
RandomizationFactor: 1,
Multiplier: multiplier,
MaxInterval: maxInterval,
MaxElapsedTime: maxElapsedTime, // 14 days
Stop: backoff.Stop,
Clock: backoff.SystemClock,
}, ctx)
}
// parseEnvDuration parses the environment variable and returns the duration
func parseEnvDuration(envVar string, defaultDuration time.Duration) time.Duration {
if envValue := os.Getenv(envVar); envValue != "" {
if duration, err := time.ParseDuration(envValue); err == nil {
return duration
}
log.Warnf("unable to parse environment variable %s: %s. using default: %s", envVar, envValue, defaultDuration)
}
return defaultDuration
}
// loginAttempt attempts to login using the provided information. it returns a status in case something fails // loginAttempt attempts to login using the provided information. it returns a status in case something fails
func (s *Server) loginAttempt(ctx context.Context, setupKey, jwtToken string) (internal.StatusType, error) { func (s *Server) loginAttempt(ctx context.Context, setupKey, jwtToken string) (internal.StatusType, error) {
var status internal.StatusType var status internal.StatusType
@@ -433,16 +550,11 @@ func (s *Server) Up(callerCtx context.Context, _ *proto.UpRequest) (*proto.UpRes
if s.statusRecorder == nil { if s.statusRecorder == nil {
s.statusRecorder = peer.NewRecorder(s.config.ManagementURL.String()) s.statusRecorder = peer.NewRecorder(s.config.ManagementURL.String())
} else {
s.statusRecorder.UpdateManagementAddress(s.config.ManagementURL.String())
} }
s.statusRecorder.UpdateManagementAddress(s.config.ManagementURL.String())
s.statusRecorder.UpdateRosenpass(s.config.RosenpassEnabled, s.config.RosenpassPermissive)
go func() { go s.connectWithRetryRuns(ctx, s.config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe)
if err := internal.RunClientWithProbes(ctx, s.config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe); err != nil {
log.Errorf("run client connection: %v", err)
return
}
}()
return &proto.UpResponse{}, nil return &proto.UpResponse{}, nil
} }
@@ -479,9 +591,9 @@ func (s *Server) Status(
if s.statusRecorder == nil { if s.statusRecorder == nil {
s.statusRecorder = peer.NewRecorder(s.config.ManagementURL.String()) s.statusRecorder = peer.NewRecorder(s.config.ManagementURL.String())
} else {
s.statusRecorder.UpdateManagementAddress(s.config.ManagementURL.String())
} }
s.statusRecorder.UpdateManagementAddress(s.config.ManagementURL.String())
s.statusRecorder.UpdateRosenpass(s.config.RosenpassEnabled, s.config.RosenpassPermissive)
if msg.GetFullPeerStatus { if msg.GetFullPeerStatus {
s.runProbes() s.runProbes()
@@ -542,13 +654,23 @@ func (s *Server) GetConfig(_ context.Context, _ *proto.GetConfigRequest) (*proto
}, nil }, nil
} }
func (s *Server) onSessionExpire() {
if runtime.GOOS != "windows" {
isUIActive := internal.CheckUIApp()
if !isUIActive {
if err := sendTerminalNotification(); err != nil {
log.Errorf("send session expire terminal notification: %v", err)
}
}
}
}
func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus { func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
pbFullStatus := proto.FullStatus{ pbFullStatus := proto.FullStatus{
ManagementState: &proto.ManagementState{}, ManagementState: &proto.ManagementState{},
SignalState: &proto.SignalState{}, SignalState: &proto.SignalState{},
LocalPeerState: &proto.LocalPeerState{}, LocalPeerState: &proto.LocalPeerState{},
Peers: []*proto.PeerState{}, Peers: []*proto.PeerState{},
Relays: []*proto.RelayState{},
} }
pbFullStatus.ManagementState.URL = fullStatus.ManagementState.URL pbFullStatus.ManagementState.URL = fullStatus.ManagementState.URL
@@ -567,6 +689,9 @@ func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
pbFullStatus.LocalPeerState.PubKey = fullStatus.LocalPeerState.PubKey pbFullStatus.LocalPeerState.PubKey = fullStatus.LocalPeerState.PubKey
pbFullStatus.LocalPeerState.KernelInterface = fullStatus.LocalPeerState.KernelInterface pbFullStatus.LocalPeerState.KernelInterface = fullStatus.LocalPeerState.KernelInterface
pbFullStatus.LocalPeerState.Fqdn = fullStatus.LocalPeerState.FQDN pbFullStatus.LocalPeerState.Fqdn = fullStatus.LocalPeerState.FQDN
pbFullStatus.LocalPeerState.RosenpassPermissive = fullStatus.RosenpassState.Permissive
pbFullStatus.LocalPeerState.RosenpassEnabled = fullStatus.RosenpassState.Enabled
pbFullStatus.LocalPeerState.Routes = maps.Keys(fullStatus.LocalPeerState.Routes)
for _, peerState := range fullStatus.Peers { for _, peerState := range fullStatus.Peers {
pbPeerState := &proto.PeerState{ pbPeerState := &proto.PeerState{
@@ -584,6 +709,8 @@ func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
LastWireguardHandshake: timestamppb.New(peerState.LastWireguardHandshake), LastWireguardHandshake: timestamppb.New(peerState.LastWireguardHandshake),
BytesRx: peerState.BytesRx, BytesRx: peerState.BytesRx,
BytesTx: peerState.BytesTx, BytesTx: peerState.BytesTx,
RosenpassEnabled: peerState.RosenpassEnabled,
Routes: maps.Keys(peerState.Routes),
} }
pbFullStatus.Peers = append(pbFullStatus.Peers, pbPeerState) pbFullStatus.Peers = append(pbFullStatus.Peers, pbPeerState)
} }
@@ -599,5 +726,47 @@ func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
pbFullStatus.Relays = append(pbFullStatus.Relays, pbRelayState) pbFullStatus.Relays = append(pbFullStatus.Relays, pbRelayState)
} }
for _, dnsState := range fullStatus.NSGroupStates {
var err string
if dnsState.Error != nil {
err = dnsState.Error.Error()
}
pbDnsState := &proto.NSGroupState{
Servers: dnsState.Servers,
Domains: dnsState.Domains,
Enabled: dnsState.Enabled,
Error: err,
}
pbFullStatus.DnsServers = append(pbFullStatus.DnsServers, pbDnsState)
}
return &pbFullStatus return &pbFullStatus
} }
// sendTerminalNotification sends a terminal notification message
// to inform the user that the NetBird connection session has expired.
func sendTerminalNotification() error {
message := "NetBird connection session expired\n\nPlease re-authenticate to connect to the network."
echoCmd := exec.Command("echo", message)
wallCmd := exec.Command("sudo", "wall")
echoCmdStdout, err := echoCmd.StdoutPipe()
if err != nil {
return err
}
wallCmd.Stdin = echoCmdStdout
if err := echoCmd.Start(); err != nil {
return err
}
if err := wallCmd.Start(); err != nil {
return err
}
if err := echoCmd.Wait(); err != nil {
return err
}
return wallCmd.Wait()
}

View File

@@ -0,0 +1,157 @@
package server
import (
"context"
"net"
"testing"
"time"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/internal/peer"
mgmtProto "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/signal/proto"
signalServer "github.com/netbirdio/netbird/signal/server"
)
var (
kaep = keepalive.EnforcementPolicy{
MinTime: 15 * time.Second,
PermitWithoutStream: true,
}
kasp = keepalive.ServerParameters{
MaxConnectionIdle: 15 * time.Second,
MaxConnectionAgeGrace: 5 * time.Second,
Time: 5 * time.Second,
Timeout: 2 * time.Second,
}
)
// TestConnectWithRetryRuns checks that the connectWithRetry function runs and runs the retries according to the times specified via environment variables
// we will use a management server started via to simulate the server and capture the number of retries
func TestConnectWithRetryRuns(t *testing.T) {
// start the signal server
_, signalAddr, err := startSignal()
if err != nil {
t.Fatalf("failed to start signal server: %v", err)
}
counter := 0
// start the management server
_, mgmtAddr, err := startManagement(t, signalAddr, &counter)
if err != nil {
t.Fatalf("failed to start management server: %v", err)
}
ctx := internal.CtxInitState(context.Background())
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(30*time.Second))
defer cancel()
// create new server
s := New(ctx, t.TempDir()+"/config.json", "debug")
s.latestConfigInput.ManagementURL = "http://" + mgmtAddr
config, err := internal.UpdateOrCreateConfig(s.latestConfigInput)
if err != nil {
t.Fatalf("failed to create config: %v", err)
}
s.config = config
s.statusRecorder = peer.NewRecorder(config.ManagementURL.String())
t.Setenv(retryInitialIntervalVar, "1s")
t.Setenv(maxRetryIntervalVar, "2s")
t.Setenv(maxRetryTimeVar, "5s")
t.Setenv(retryMultiplierVar, "1")
s.connectWithRetryRuns(ctx, config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe)
if counter < 3 {
t.Fatalf("expected counter > 2, got %d", counter)
}
}
type mockServer struct {
mgmtProto.ManagementServiceServer
counter *int
}
func (m *mockServer) Login(ctx context.Context, req *mgmtProto.EncryptedMessage) (*mgmtProto.EncryptedMessage, error) {
*m.counter++
return m.ManagementServiceServer.Login(ctx, req)
}
func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Server, string, error) {
t.Helper()
dataDir := t.TempDir()
config := &server.Config{
Stuns: []*server.Host{},
TURNConfig: &server.TURNConfig{},
Signal: &server.Host{
Proto: "http",
URI: signalAddr,
},
Datadir: dataDir,
HttpConfig: nil,
}
lis, err := net.Listen("tcp", "localhost:0")
if err != nil {
return nil, "", err
}
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
store, err := server.NewStoreFromJson(config.Datadir, nil)
if err != nil {
return nil, "", err
}
peersUpdateManager := server.NewPeersUpdateManager(nil)
eventStore := &activity.InMemoryEventStore{}
if err != nil {
return nil, "", err
}
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false)
if err != nil {
return nil, "", err
}
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, nil, nil)
if err != nil {
return nil, "", err
}
mock := &mockServer{
ManagementServiceServer: mgmtServer,
counter: counter,
}
mgmtProto.RegisterManagementServiceServer(s, mock)
go func() {
if err = s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}()
return s, lis.Addr().String(), nil
}
func startSignal() (*grpc.Server, string, error) {
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
lis, err := net.Listen("tcp", "localhost:0")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
proto.RegisterSignalExchangeServer(s, signalServer.NewServer())
go func() {
if err = s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}()
return s, lis.Addr().String(), nil
}

View File

@@ -30,6 +30,12 @@ type Environment struct {
Platform string Platform string
} }
type Config struct {
RosenpassEnabled bool
RosenpassPermissive bool
ServerSSHAllowed bool
}
// 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 {
@@ -48,6 +54,14 @@ type Info struct {
SystemProductName string SystemProductName string
SystemManufacturer string SystemManufacturer string
Environment Environment Environment Environment
Config Config
}
// GetInfo retrieves and parses the system information
func GetInfo(ctx context.Context, config Config) *Info {
info := getInfo(ctx)
info.Config = config
return info
} }
// extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context // extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context

View File

@@ -15,8 +15,7 @@ import (
"github.com/netbirdio/netbird/version" "github.com/netbirdio/netbird/version"
) )
// GetInfo retrieves and parses the system information func getInfo(ctx context.Context) *Info {
func GetInfo(ctx context.Context) *Info {
kernel := "android" kernel := "android"
osInfo := uname() osInfo := uname()
if len(osInfo) == 2 { if len(osInfo) == 2 {
@@ -28,7 +27,16 @@ func GetInfo(ctx context.Context) *Info {
kernelVersion = osInfo[2] kernelVersion = osInfo[2]
} }
gio := &Info{Kernel: kernel, Platform: "unknown", OS: "android", OSVersion: osVersion(), GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), KernelVersion: kernelVersion} gio := &Info{
Kernel: kernel,
Platform: "unknown",
OS: "android",
OSVersion: osVersion(),
GoOS: runtime.GOOS,
CPUs: runtime.NumCPU(),
KernelVersion: kernelVersion,
}
gio.Hostname = extractDeviceName(ctx, "android") gio.Hostname = extractDeviceName(ctx, "android")
gio.WiretrusteeVersion = version.NetbirdVersion() gio.WiretrusteeVersion = version.NetbirdVersion()
gio.UIVersion = extractUserAgent(ctx) gio.UIVersion = extractUserAgent(ctx)

View File

@@ -20,8 +20,7 @@ import (
"github.com/netbirdio/netbird/version" "github.com/netbirdio/netbird/version"
) )
// GetInfo retrieves and parses the system information func getInfo(ctx context.Context) *Info {
func GetInfo(ctx context.Context) *Info {
utsname := unix.Utsname{} utsname := unix.Utsname{}
err := unix.Uname(&utsname) err := unix.Uname(&utsname)
if err != nil { if err != nil {

View File

@@ -15,8 +15,7 @@ import (
"github.com/netbirdio/netbird/version" "github.com/netbirdio/netbird/version"
) )
// GetInfo retrieves and parses the system information func getInfo(ctx context.Context) *Info {
func GetInfo(ctx context.Context) *Info {
out := _getInfo() out := _getInfo()
for strings.Contains(out, "broken pipe") { for strings.Contains(out, "broken pipe") {
out = _getInfo() out = _getInfo()
@@ -31,7 +30,15 @@ func GetInfo(ctx context.Context) *Info {
Platform: detect_platform.Detect(ctx), Platform: detect_platform.Detect(ctx),
} }
gio := &Info{Kernel: osInfo[0], Platform: runtime.GOARCH, OS: osInfo[2], GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), KernelVersion: osInfo[1], Environment: env} gio := &Info{
Kernel: osInfo[0],
Platform: runtime.GOARCH,
OS: osInfo[2],
GoOS: runtime.GOOS,
CPUs: runtime.NumCPU(),
KernelVersion: osInfo[1],
Environment: env,
}
systemHostname, _ := os.Hostname() systemHostname, _ := os.Hostname()
gio.Hostname = extractDeviceName(ctx, systemHostname) gio.Hostname = extractDeviceName(ctx, systemHostname)

View File

@@ -10,14 +10,21 @@ import (
"github.com/netbirdio/netbird/version" "github.com/netbirdio/netbird/version"
) )
// GetInfo retrieves and parses the system information func getInfo(ctx context.Context) *Info {
func GetInfo(ctx context.Context) *Info {
// Convert fixed-size byte arrays to Go strings // Convert fixed-size byte arrays to Go strings
sysName := extractOsName(ctx, "sysName") sysName := extractOsName(ctx, "sysName")
swVersion := extractOsVersion(ctx, "swVersion") swVersion := extractOsVersion(ctx, "swVersion")
gio := &Info{Kernel: sysName, OSVersion: swVersion, Platform: "unknown", OS: sysName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), KernelVersion: swVersion} gio := &Info{
Kernel: sysName,
OSVersion: swVersion,
Platform: "unknown",
OS: sysName,
GoOS: runtime.GOOS,
CPUs: runtime.NumCPU(),
KernelVersion: swVersion,
}
gio.Hostname = extractDeviceName(ctx, "hostname") gio.Hostname = extractDeviceName(ctx, "hostname")
gio.WiretrusteeVersion = version.NetbirdVersion() gio.WiretrusteeVersion = version.NetbirdVersion()
gio.UIVersion = extractUserAgent(ctx) gio.UIVersion = extractUserAgent(ctx)

View File

@@ -20,8 +20,7 @@ import (
"github.com/netbirdio/netbird/version" "github.com/netbirdio/netbird/version"
) )
// GetInfo retrieves and parses the system information func getInfo(ctx context.Context) *Info {
func GetInfo(ctx context.Context) *Info {
info := _getInfo() info := _getInfo()
for strings.Contains(info, "broken pipe") { for strings.Contains(info, "broken pipe") {
info = _getInfo() info = _getInfo()

View File

@@ -8,7 +8,6 @@ import (
"strings" "strings"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/yusufpapurcu/wmi"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
"github.com/netbirdio/netbird/client/system/detect_cloud" "github.com/netbirdio/netbird/client/system/detect_cloud"
@@ -32,8 +31,7 @@ type Win32_BIOS struct {
SerialNumber string SerialNumber string
} }
// GetInfo retrieves and parses the system information func getInfo(ctx context.Context) *Info {
func GetInfo(ctx context.Context) *Info {
osName, osVersion := getOSNameAndVersion() osName, osVersion := getOSNameAndVersion()
buildVersion := getBuildVersion() buildVersion := getBuildVersion()
@@ -165,6 +163,10 @@ func sysProductName() (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
// `ComputerSystemProduct` could be empty on some virtualized systems
if len(dst) < 1 {
return "unknown", nil
}
return dst[0].Name, nil return dst[0].Name, nil
} }

View File

@@ -61,7 +61,7 @@ func main() {
flag.Parse() flag.Parse()
a := app.New() a := app.NewWithID("NetBird")
a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnectedPNG)) a.SetIcon(fyne.NewStaticResource("netbird", iconDisconnectedPNG))
client := newServiceClient(daemonAddr, a, showSettings) client := newServiceClient(daemonAddr, a, showSettings)
@@ -82,17 +82,23 @@ var iconConnectedICO []byte
//go:embed netbird-systemtray-connected.png //go:embed netbird-systemtray-connected.png
var iconConnectedPNG []byte var iconConnectedPNG []byte
//go:embed netbird-systemtray-default.ico //go:embed netbird-systemtray-disconnected.ico
var iconDisconnectedICO []byte var iconDisconnectedICO []byte
//go:embed netbird-systemtray-default.png //go:embed netbird-systemtray-disconnected.png
var iconDisconnectedPNG []byte var iconDisconnectedPNG []byte
//go:embed netbird-systemtray-update.ico //go:embed netbird-systemtray-update-disconnected.ico
var iconUpdateICO []byte var iconUpdateDisconnectedICO []byte
//go:embed netbird-systemtray-update.png //go:embed netbird-systemtray-update-disconnected.png
var iconUpdatePNG []byte var iconUpdateDisconnectedPNG []byte
//go:embed netbird-systemtray-update-connected.ico
var iconUpdateConnectedICO []byte
//go:embed netbird-systemtray-update-connected.png
var iconUpdateConnectedPNG []byte
//go:embed netbird-systemtray-update-cloud.ico //go:embed netbird-systemtray-update-cloud.ico
var iconUpdateCloudICO []byte var iconUpdateCloudICO []byte
@@ -105,10 +111,11 @@ type serviceClient struct {
addr string addr string
conn proto.DaemonServiceClient conn proto.DaemonServiceClient
icConnected []byte icConnected []byte
icDisconnected []byte icDisconnected []byte
icUpdate []byte icUpdateConnected []byte
icUpdateCloud []byte icUpdateDisconnected []byte
icUpdateCloud []byte
// systray menu items // systray menu items
mStatus *systray.MenuItem mStatus *systray.MenuItem
@@ -123,9 +130,10 @@ type serviceClient struct {
mQuit *systray.MenuItem mQuit *systray.MenuItem
// application with main windows. // application with main windows.
app fyne.App app fyne.App
wSettings fyne.Window wSettings fyne.Window
showSettings bool showSettings bool
sendNotification bool
// input elements for settings form // input elements for settings form
iMngURL *widget.Entry iMngURL *widget.Entry
@@ -139,6 +147,7 @@ type serviceClient struct {
preSharedKey string preSharedKey string
adminURL string adminURL string
connected bool
update *version.Update update *version.Update
daemonVersion string daemonVersion string
updateIndicationLock sync.Mutex updateIndicationLock sync.Mutex
@@ -150,9 +159,10 @@ type serviceClient struct {
// This constructor also builds the UI elements for the settings window. // This constructor also builds the UI elements for the settings window.
func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient { func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient {
s := &serviceClient{ s := &serviceClient{
ctx: context.Background(), ctx: context.Background(),
addr: addr, addr: addr,
app: a, app: a,
sendNotification: false,
showSettings: showSettings, showSettings: showSettings,
update: version.NewUpdate(), update: version.NewUpdate(),
@@ -161,13 +171,15 @@ func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
s.icConnected = iconConnectedICO s.icConnected = iconConnectedICO
s.icDisconnected = iconDisconnectedICO s.icDisconnected = iconDisconnectedICO
s.icUpdate = iconUpdateICO s.icUpdateConnected = iconUpdateConnectedICO
s.icUpdateDisconnected = iconUpdateDisconnectedICO
s.icUpdateCloud = iconUpdateCloudICO s.icUpdateCloud = iconUpdateCloudICO
} else { } else {
s.icConnected = iconConnectedPNG s.icConnected = iconConnectedPNG
s.icDisconnected = iconDisconnectedPNG s.icDisconnected = iconDisconnectedPNG
s.icUpdate = iconUpdatePNG s.icUpdateConnected = iconUpdateConnectedPNG
s.icUpdateDisconnected = iconUpdateDisconnectedPNG
s.icUpdateCloud = iconUpdateCloudPNG s.icUpdateCloud = iconUpdateCloudPNG
} }
@@ -367,9 +379,18 @@ func (s *serviceClient) updateStatus() error {
s.updateIndicationLock.Lock() s.updateIndicationLock.Lock()
defer s.updateIndicationLock.Unlock() defer s.updateIndicationLock.Unlock()
// notify the user when the session has expired
if status.Status == string(internal.StatusNeedsLogin) {
s.onSessionExpire()
}
var systrayIconState bool var systrayIconState bool
if status.Status == string(internal.StatusConnected) && !s.mUp.Disabled() { if status.Status == string(internal.StatusConnected) && !s.mUp.Disabled() {
if !s.isUpdateIconActive { s.connected = true
s.sendNotification = true
if s.isUpdateIconActive {
systray.SetIcon(s.icUpdateConnected)
} else {
systray.SetIcon(s.icConnected) systray.SetIcon(s.icConnected)
} }
systray.SetTooltip("NetBird (Connected)") systray.SetTooltip("NetBird (Connected)")
@@ -378,7 +399,10 @@ func (s *serviceClient) updateStatus() error {
s.mDown.Enable() s.mDown.Enable()
systrayIconState = true systrayIconState = true
} else if status.Status != string(internal.StatusConnected) && s.mUp.Disabled() { } else if status.Status != string(internal.StatusConnected) && s.mUp.Disabled() {
if !s.isUpdateIconActive { s.connected = false
if s.isUpdateIconActive {
systray.SetIcon(s.icUpdateDisconnected)
} else {
systray.SetIcon(s.icDisconnected) systray.SetIcon(s.icDisconnected)
} }
systray.SetTooltip("NetBird (Disconnected)") systray.SetTooltip("NetBird (Disconnected)")
@@ -605,10 +629,30 @@ func (s *serviceClient) onUpdateAvailable() {
defer s.updateIndicationLock.Unlock() defer s.updateIndicationLock.Unlock()
s.mUpdate.Show() s.mUpdate.Show()
s.mAbout.SetIcon(s.icUpdateCloud)
s.isUpdateIconActive = true s.isUpdateIconActive = true
systray.SetIcon(s.icUpdate)
if s.connected {
systray.SetIcon(s.icUpdateConnected)
} else {
systray.SetIcon(s.icUpdateDisconnected)
}
}
// onSessionExpire sends a notification to the user when the session expires.
func (s *serviceClient) onSessionExpire() {
if s.sendNotification {
title := "Connection session expired"
if runtime.GOOS == "darwin" {
title = "NetBird connection session expired"
}
s.app.SendNotification(
fyne.NewNotification(
title,
"Please re-authenticate to connect to the network",
),
)
s.sendNotification = false
}
} }
func openURL(url string) error { func openURL(url string) error {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

6
go.mod
View File

@@ -48,6 +48,7 @@ require (
github.com/google/gopacket v1.1.19 github.com/google/gopacket v1.1.19
github.com/google/nftables v0.0.0-20220808154552-2eca00135732 github.com/google/nftables v0.0.0-20220808154552-2eca00135732
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357
github.com/hashicorp/go-multierror v1.1.0
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/go-version v1.6.0
github.com/libp2p/go-netroute v0.2.0 github.com/libp2p/go-netroute v0.2.0
@@ -57,8 +58,8 @@ require (
github.com/miekg/dns v1.1.43 github.com/miekg/dns v1.1.43
github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/nadoo/ipset v0.5.0 github.com/nadoo/ipset v0.5.0
github.com/netbirdio/management-integrations/additions v0.0.0-20240118163419-8a7c87accb22 github.com/netbirdio/management-integrations/additions v0.0.0-20240212121739-8ea8c89a4552
github.com/netbirdio/management-integrations/integrations v0.0.0-20240118163419-8a7c87accb22 github.com/netbirdio/management-integrations/integrations v0.0.0-20240212121739-8ea8c89a4552
github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/okta/okta-sdk-golang/v2 v2.18.0
github.com/oschwald/maxminddb-golang v1.12.0 github.com/oschwald/maxminddb-golang v1.12.0
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
@@ -123,6 +124,7 @@ require (
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.10.0 // indirect github.com/googleapis/gax-go/v2 v2.10.0 // indirect
github.com/gopacket/gopacket v1.1.1 // indirect github.com/gopacket/gopacket v1.1.1 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect

12
go.sum
View File

@@ -289,6 +289,10 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357 h1:Fkzd8ktnpOR9h47SXHe2AYPwelXLH2GjGsjlAloiWfo= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357 h1:Fkzd8ktnpOR9h47SXHe2AYPwelXLH2GjGsjlAloiWfo=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng= github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
@@ -376,10 +380,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ=
github.com/netbirdio/management-integrations/additions v0.0.0-20240118163419-8a7c87accb22 h1:XTiNnVB6OEwung8WIiGJNzOTLVefuSzAA/cu+6Sst8A= github.com/netbirdio/management-integrations/additions v0.0.0-20240212121739-8ea8c89a4552 h1:yzcQKizAK9YufCHMMCIsr467Dw/OU/4xyHbWizGb1E4=
github.com/netbirdio/management-integrations/additions v0.0.0-20240118163419-8a7c87accb22/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= github.com/netbirdio/management-integrations/additions v0.0.0-20240212121739-8ea8c89a4552/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA=
github.com/netbirdio/management-integrations/integrations v0.0.0-20240118163419-8a7c87accb22 h1:FNc4p8RS/gFm5jlmvUFWC4/5YxPDWejYyqEBVziFZwo= github.com/netbirdio/management-integrations/integrations v0.0.0-20240212121739-8ea8c89a4552 h1:OFlzVZtkXCoJsfDKrMigFpuad8ZXTm8epq6x27K0irA=
github.com/netbirdio/management-integrations/integrations v0.0.0-20240118163419-8a7c87accb22/go.mod h1:B0nMS3es77gOvPYhc0K91fAzTkQLi/jRq5TffUN3klM= github.com/netbirdio/management-integrations/integrations v0.0.0-20240212121739-8ea8c89a4552/go.mod h1:B0nMS3es77gOvPYhc0K91fAzTkQLi/jRq5TffUN3klM=
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g=
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM= github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM=

View File

@@ -8,7 +8,7 @@ import (
"golang.zx2c4.com/wireguard/tun/netstack" "golang.zx2c4.com/wireguard/tun/netstack"
) )
type NetStackTun struct { type NetStackTun struct { //nolint:revive
address string address string
mtu int mtu int
listenAddress string listenAddress string

View File

@@ -62,7 +62,7 @@ NETBIRD_DASH_AUTH_USE_AUDIENCE=${NETBIRD_DASH_AUTH_USE_AUDIENCE:-true}
NETBIRD_DASH_AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE NETBIRD_DASH_AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
# Store config # Store config
NETBIRD_STORE_CONFIG_ENGINE=${NETBIRD_STORE_CONFIG_ENGINE:-"jsonfile"} NETBIRD_STORE_CONFIG_ENGINE=${NETBIRD_STORE_CONFIG_ENGINE:-"sqlite"}
# Image tags # Image tags
NETBIRD_DASHBOARD_TAG=${NETBIRD_DASHBOARD_TAG:-"latest"} NETBIRD_DASHBOARD_TAG=${NETBIRD_DASHBOARD_TAG:-"latest"}

View File

@@ -1,20 +1,5 @@
#!/bin/bash #!/bin/bash
# set $MM_ACCOUNT_ID and $MM_LICENSE_KEY when calling this script
# see https://dev.maxmind.com/geoip/updating-databases#directly-downloading-databases
# Check if MM_ACCOUNT_ID is set
if [ -z "$MM_ACCOUNT_ID" ]; then
echo "MM_ACCOUNT_ID is not set. Please set the environment variable."
exit 1
fi
# Check if MM_LICENSE_KEY is set
if [ -z "$MM_LICENSE_KEY" ]; then
echo "MM_LICENSE_KEY is not set. Please set the environment variable."
exit 1
fi
# to install sha256sum on mac: brew install coreutils # to install sha256sum on mac: brew install coreutils
if ! command -v sha256sum &> /dev/null if ! command -v sha256sum &> /dev/null
then then
@@ -28,15 +13,20 @@ then
exit 1 exit 1
fi fi
download_geolite_mmdb() { if ! command -v unzip &> /dev/null
DATABASE_URL="https://download.maxmind.com/geoip/databases/GeoLite2-City/download?suffix=tar.gz" then
SIGNATURE_URL="https://download.maxmind.com/geoip/databases/GeoLite2-City/download?suffix=tar.gz.sha256" echo "unzip is not installed or not in PATH, please install with your package manager. e.g. sudo apt install unzip" > /dev/stderr
exit 1
fi
download_geolite_mmdb() {
DATABASE_URL="https://pkgs.netbird.io/geolocation-dbs/GeoLite2-City/download?suffix=tar.gz"
SIGNATURE_URL="https://pkgs.netbird.io/geolocation-dbs/GeoLite2-City/download?suffix=tar.gz.sha256"
# Download the database and signature files # Download the database and signature files
echo "Downloading mmdb database file..."
DATABASE_FILE=$(curl -s -u "$MM_ACCOUNT_ID":"$MM_LICENSE_KEY" -L -O -J "$DATABASE_URL" -w "%{filename_effective}")
echo "Downloading mmdb signature file..." echo "Downloading mmdb signature file..."
SIGNATURE_FILE=$(curl -s -u "$MM_ACCOUNT_ID":"$MM_LICENSE_KEY" -L -O -J "$SIGNATURE_URL" -w "%{filename_effective}") SIGNATURE_FILE=$(curl -s -L -O -J "$SIGNATURE_URL" -w "%{filename_effective}")
echo "Downloading mmdb database file..."
DATABASE_FILE=$(curl -s -L -O -J "$DATABASE_URL" -w "%{filename_effective}")
# Verify the signature # Verify the signature
echo "Verifying signature..." echo "Verifying signature..."
@@ -53,33 +43,31 @@ download_geolite_mmdb() {
mkdir -p "$EXTRACTION_DIR" mkdir -p "$EXTRACTION_DIR"
tar -xzvf "$DATABASE_FILE" > /dev/null 2>&1 tar -xzvf "$DATABASE_FILE" > /dev/null 2>&1
# Create a SHA256 signature file
MMDB_FILE="GeoLite2-City.mmdb" MMDB_FILE="GeoLite2-City.mmdb"
cd "$EXTRACTION_DIR" cp "$EXTRACTION_DIR"/"$MMDB_FILE" $MMDB_FILE
sha256sum "$MMDB_FILE" > "$MMDB_FILE.sha256"
echo "SHA256 signature created for $MMDB_FILE."
cd - > /dev/null 2>&1
# Remove downloaded files # Remove downloaded files
rm -r "$EXTRACTION_DIR"
rm "$DATABASE_FILE" "$SIGNATURE_FILE" rm "$DATABASE_FILE" "$SIGNATURE_FILE"
# Done. Print next steps # Done. Print next steps
echo ""
echo "Process completed successfully." echo "Process completed successfully."
echo "Now you can place $EXTRACTION_DIR/$MMDB_FILE to 'datadir' of management service." echo "Now you can place $MMDB_FILE to 'datadir' of management service."
echo -e "Example:\n\tdocker compose cp $EXTRACTION_DIR/$MMDB_FILE management:/var/lib/netbird/" echo -e "Example:\n\tdocker compose cp $MMDB_FILE management:/var/lib/netbird/"
} }
download_geolite_csv_and_create_sqlite_db() { download_geolite_csv_and_create_sqlite_db() {
DATABASE_URL="https://download.maxmind.com/geoip/databases/GeoLite2-City-CSV/download?suffix=zip" DATABASE_URL="https://pkgs.netbird.io/geolocation-dbs/GeoLite2-City-CSV/download?suffix=zip"
SIGNATURE_URL="https://download.maxmind.com/geoip/databases/GeoLite2-City-CSV/download?suffix=zip.sha256" SIGNATURE_URL="https://pkgs.netbird.io/geolocation-dbs/GeoLite2-City-CSV/download?suffix=zip.sha256"
# Download the database file # Download the database file
echo "Downloading csv database file..."
DATABASE_FILE=$(curl -s -u "$MM_ACCOUNT_ID":"$MM_LICENSE_KEY" -L -O -J "$DATABASE_URL" -w "%{filename_effective}")
echo "Downloading csv signature file..." echo "Downloading csv signature file..."
SIGNATURE_FILE=$(curl -s -u "$MM_ACCOUNT_ID":"$MM_LICENSE_KEY" -L -O -J "$SIGNATURE_URL" -w "%{filename_effective}") SIGNATURE_FILE=$(curl -s -L -O -J "$SIGNATURE_URL" -w "%{filename_effective}")
echo "Downloading csv database file..."
DATABASE_FILE=$(curl -s -L -O -J "$DATABASE_URL" -w "%{filename_effective}")
# Verify the signature # Verify the signature
echo "Verifying signature..." echo "Verifying signature..."
@@ -107,12 +95,15 @@ EOF
# Remove downloaded and extracted files # Remove downloaded and extracted files
rm -r -r "$EXTRACTION_DIR" rm -r -r "$EXTRACTION_DIR"
rm "$DATABASE_FILE" "$SIGNATURE_FILE" rm "$DATABASE_FILE" "$SIGNATURE_FILE"
echo ""
echo "SQLite database '$DB_NAME' created successfully." echo "SQLite database '$DB_NAME' created successfully."
echo "Now you can place $DB_NAME to 'datadir' of management service." echo "Now you can place $DB_NAME to 'datadir' of management service."
echo -e "Example:\n\tdocker compose cp $DB_NAME management:/var/lib/netbird/" echo -e "Example:\n\tdocker compose cp $DB_NAME management:/var/lib/netbird/"
} }
download_geolite_mmdb download_geolite_mmdb
echo "" echo -e "\n\n"
download_geolite_csv_and_create_sqlite_db download_geolite_csv_and_create_sqlite_db
echo -e "\n\n"
echo "After copying the database files to the management service. You can restart the management service with:"
echo -e "Example:\n\tdocker compose restart management"

View File

@@ -137,6 +137,13 @@ create_new_application() {
BASE_REDIRECT_URL2=$5 BASE_REDIRECT_URL2=$5
LOGOUT_URL=$6 LOGOUT_URL=$6
ZITADEL_DEV_MODE=$7 ZITADEL_DEV_MODE=$7
DEVICE_CODE=$8
if [[ $DEVICE_CODE == "true" ]]; then
GRANT_TYPES='["OIDC_GRANT_TYPE_AUTHORIZATION_CODE","OIDC_GRANT_TYPE_DEVICE_CODE","OIDC_GRANT_TYPE_REFRESH_TOKEN"]'
else
GRANT_TYPES='["OIDC_GRANT_TYPE_AUTHORIZATION_CODE","OIDC_GRANT_TYPE_REFRESH_TOKEN"]'
fi
RESPONSE=$( RESPONSE=$(
curl -sS -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \ curl -sS -X POST "$INSTANCE_URL/management/v1/projects/$PROJECT_ID/apps/oidc" \
@@ -154,10 +161,7 @@ create_new_application() {
"RESPONSETypes": [ "RESPONSETypes": [
"OIDC_RESPONSE_TYPE_CODE" "OIDC_RESPONSE_TYPE_CODE"
], ],
"grantTypes": [ "grantTypes": '"$GRANT_TYPES"',
"OIDC_GRANT_TYPE_AUTHORIZATION_CODE",
"OIDC_GRANT_TYPE_REFRESH_TOKEN"
],
"appType": "OIDC_APP_TYPE_USER_AGENT", "appType": "OIDC_APP_TYPE_USER_AGENT",
"authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE", "authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE",
"version": "OIDC_VERSION_1_0", "version": "OIDC_VERSION_1_0",
@@ -340,10 +344,10 @@ init_zitadel() {
# create zitadel spa applications # create zitadel spa applications
echo "Creating new Zitadel SPA Dashboard application" echo "Creating new Zitadel SPA Dashboard application"
DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "$BASE_REDIRECT_URL/nb-auth" "$BASE_REDIRECT_URL/nb-silent-auth" "$BASE_REDIRECT_URL/" "$ZITADEL_DEV_MODE") DASHBOARD_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Dashboard" "$BASE_REDIRECT_URL/nb-auth" "$BASE_REDIRECT_URL/nb-silent-auth" "$BASE_REDIRECT_URL/" "$ZITADEL_DEV_MODE" "false")
echo "Creating new Zitadel SPA Cli application" echo "Creating new Zitadel SPA Cli application"
CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "http://localhost:53000/" "true") CLI_APPLICATION_CLIENT_ID=$(create_new_application "$INSTANCE_URL" "$PAT" "Cli" "http://localhost:53000/" "http://localhost:54000/" "http://localhost:53000/" "true" "true")
MACHINE_USER_ID=$(create_service_user "$INSTANCE_URL" "$PAT") MACHINE_USER_ID=$(create_service_user "$INSTANCE_URL" "$PAT")
@@ -561,6 +565,8 @@ renderCaddyfile() {
reverse_proxy /.well-known/openid-configuration h2c://zitadel:8080 reverse_proxy /.well-known/openid-configuration h2c://zitadel:8080
reverse_proxy /openapi/* h2c://zitadel:8080 reverse_proxy /openapi/* h2c://zitadel:8080
reverse_proxy /debug/* h2c://zitadel:8080 reverse_proxy /debug/* h2c://zitadel:8080
reverse_proxy /device/* h2c://zitadel:8080
reverse_proxy /device h2c://zitadel:8080
# Dashboard # Dashboard
reverse_proxy /* dashboard:80 reverse_proxy /* dashboard:80
} }
@@ -629,6 +635,14 @@ renderManagementJson() {
"ManagementEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/management/v1" "ManagementEndpoint": "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/management/v1"
} }
}, },
"DeviceAuthorizationFlow": {
"Provider": "hosted",
"ProviderConfig": {
"Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI",
"ClientID": "$NETBIRD_AUTH_CLIENT_ID_CLI",
"Scope": "openid"
}
},
"PKCEAuthorizationFlow": { "PKCEAuthorizationFlow": {
"ProviderConfig": { "ProviderConfig": {
"Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI", "Audience": "$NETBIRD_AUTH_CLIENT_ID_CLI",

View File

@@ -26,6 +26,13 @@
"Username": "", "Username": "",
"Password": null "Password": null
}, },
"ReverseProxy": {
"TrustedHTTPProxies": [],
"TrustedHTTPProxiesCount": 0,
"TrustedPeers": [
"0.0.0.0/0"
]
},
"Datadir": "", "Datadir": "",
"DataStoreEncryptionKey": "$NETBIRD_DATASTORE_ENC_KEY", "DataStoreEncryptionKey": "$NETBIRD_DATASTORE_ENC_KEY",
"StoreConfig": { "StoreConfig": {

View File

@@ -46,6 +46,7 @@ server {
proxy_set_header X-Scheme $scheme; proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Host $host;
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Proxy dashboard # Proxy dashboard
location / { location / {

View File

@@ -163,7 +163,7 @@ func TestClient_LoginUnregistered_ShouldThrow_401(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
sysInfo := system.GetInfo(context.TODO()) sysInfo := &system.Info{Hostname: "test"}
_, err = client.Login(*key, sysInfo, nil) _, err = client.Login(*key, sysInfo, nil)
if err == nil { if err == nil {
t.Error("expecting err on unregistered login, got nil") t.Error("expecting err on unregistered login, got nil")
@@ -191,7 +191,7 @@ func TestClient_LoginRegistered(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
info := system.GetInfo(context.TODO()) info := &system.Info{Hostname: "test"}
resp, err := client.Register(*key, ValidKey, "", info, nil) resp, err := client.Register(*key, ValidKey, "", info, nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@@ -221,7 +221,7 @@ func TestClient_Sync(t *testing.T) {
t.Error(err) t.Error(err)
} }
info := system.GetInfo(context.TODO()) info := &system.Info{Hostname: "test"}
_, err = client.Register(*serverKey, ValidKey, "", info, nil) _, err = client.Register(*serverKey, ValidKey, "", info, nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@@ -237,7 +237,6 @@ func TestClient_Sync(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
info = system.GetInfo(context.TODO())
_, err = remoteClient.Register(*serverKey, ValidKey, "", info, nil) _, err = remoteClient.Register(*serverKey, ValidKey, "", info, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -335,7 +334,7 @@ func Test_SystemMetaDataFromClient(t *testing.T) {
}, nil }, nil
} }
info := system.GetInfo(context.TODO()) info := &system.Info{Hostname: "test"}
_, err = testClient.Register(*key, ValidKey, "", info, nil) _, err = testClient.Register(*key, ValidKey, "", info, nil)
if err != nil { if err != nil {
t.Errorf("error while trying to register client: %v", err) t.Errorf("error while trying to register client: %v", err)
@@ -363,10 +362,11 @@ func Test_SystemMetaDataFromClient(t *testing.T) {
WiretrusteeVersion: info.WiretrusteeVersion, WiretrusteeVersion: info.WiretrusteeVersion,
KernelVersion: info.KernelVersion, KernelVersion: info.KernelVersion,
NetworkAddresses: protoNetAddr, NetworkAddresses: protoNetAddr,
SysSerialNumber: info.SystemSerialNumber, SysSerialNumber: info.SystemSerialNumber,
SysProductName: info.SystemProductName, SysProductName: info.SystemProductName,
SysManufacturer: info.SystemManufacturer, SysManufacturer: info.SystemManufacturer,
Environment: &mgmtProto.Environment{Cloud: info.Environment.Cloud, Platform: info.Environment.Platform},
} }
assert.Equal(t, ValidKey, actualValidKey) assert.Equal(t, ValidKey, actualValidKey)
@@ -407,7 +407,9 @@ func isEqual(a, b *mgmtProto.PeerSystemMeta) bool {
a.GetUiVersion() == b.GetUiVersion() && a.GetUiVersion() == b.GetUiVersion() &&
a.GetSysSerialNumber() == b.GetSysSerialNumber() && a.GetSysSerialNumber() == b.GetSysSerialNumber() &&
a.GetSysProductName() == b.GetSysProductName() && a.GetSysProductName() == b.GetSysProductName() &&
a.GetSysManufacturer() == b.GetSysManufacturer() a.GetSysManufacturer() == b.GetSysManufacturer() &&
a.GetEnvironment().Cloud == b.GetEnvironment().Cloud &&
a.GetEnvironment().Platform == b.GetEnvironment().Platform
} }
func Test_GetDeviceAuthorizationFlow(t *testing.T) { func Test_GetDeviceAuthorizationFlow(t *testing.T) {

View File

@@ -26,6 +26,8 @@ import (
"github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/proto"
) )
const ConnectTimeout = 10 * time.Second
// ConnStateNotifier is a wrapper interface of the status recorders // ConnStateNotifier is a wrapper interface of the status recorders
type ConnStateNotifier interface { type ConnStateNotifier interface {
MarkManagementDisconnected(error) MarkManagementDisconnected(error)
@@ -49,7 +51,7 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE
transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})) transportOption = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
} }
mgmCtx, cancel := context.WithTimeout(ctx, 5*time.Second) mgmCtx, cancel := context.WithTimeout(ctx, ConnectTimeout)
defer cancel() defer cancel()
conn, err := grpc.DialContext( conn, err := grpc.DialContext(
mgmCtx, mgmCtx,
@@ -318,7 +320,7 @@ func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*pro
log.Errorf("failed to encrypt message: %s", err) log.Errorf("failed to encrypt message: %s", err)
return nil, err return nil, err
} }
mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second) mgmCtx, cancel := context.WithTimeout(c.ctx, ConnectTimeout)
defer cancel() defer cancel()
resp, err := c.realClient.Login(mgmCtx, &proto.EncryptedMessage{ resp, err := c.realClient.Login(mgmCtx, &proto.EncryptedMessage{
WgPubKey: c.key.PublicKey().String(), WgPubKey: c.key.PublicKey().String(),
@@ -474,5 +476,14 @@ func infoToMetaData(info *system.Info) *proto.PeerSystemMeta {
SysSerialNumber: info.SystemSerialNumber, SysSerialNumber: info.SystemSerialNumber,
SysManufacturer: info.SystemManufacturer, SysManufacturer: info.SystemManufacturer,
SysProductName: info.SystemProductName, SysProductName: info.SystemProductName,
Environment: &proto.Environment{
Cloud: info.Environment.Cloud,
Platform: info.Environment.Platform,
},
Config: &proto.Config{
RosenpassEnabled: info.Config.RosenpassEnabled,
RosenpassPermissive: info.Config.RosenpassPermissive,
ServerSSHAllowed: info.Config.ServerSSHAllowed,
},
} }
} }

View File

@@ -43,6 +43,7 @@ import (
"github.com/netbirdio/netbird/management/server/metrics" "github.com/netbirdio/netbird/management/server/metrics"
"github.com/netbirdio/netbird/management/server/telemetry" "github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/util" "github.com/netbirdio/netbird/util"
"github.com/netbirdio/netbird/version"
) )
// ManagementLegacyPort is the port that was used before by the Management gRPC server. // ManagementLegacyPort is the port that was used before by the Management gRPC server.
@@ -166,7 +167,7 @@ var (
geo, err := geolocation.NewGeolocation(config.Datadir) geo, err := geolocation.NewGeolocation(config.Datadir)
if err != nil { if err != nil {
log.Warnf("could not initialize geo location service, we proceed without geo support") log.Warnf("could not initialize geo location service: %v, we proceed without geo support", err)
} else { } else {
log.Infof("geo location service has been initialized from %s", config.Datadir) log.Infof("geo location service has been initialized from %s", config.Datadir)
} }
@@ -242,7 +243,10 @@ var (
UserIDClaim: config.HttpConfig.AuthUserIDClaim, UserIDClaim: config.HttpConfig.AuthUserIDClaim,
KeysLocation: config.HttpConfig.AuthKeysLocation, KeysLocation: config.HttpConfig.AuthKeysLocation,
} }
httpAPIHandler, err := httpapi.APIHandler(accountManager, geo, *jwtValidator, appMetrics, httpAPIAuthCfg)
ctx, cancel := context.WithCancel(cmd.Context())
defer cancel()
httpAPIHandler, err := httpapi.APIHandler(ctx, accountManager, geo, *jwtValidator, appMetrics, httpAPIAuthCfg)
if err != nil { if err != nil {
return fmt.Errorf("failed creating HTTP API handler: %v", err) return fmt.Errorf("failed creating HTTP API handler: %v", err)
} }
@@ -264,8 +268,6 @@ var (
} }
if !disableMetrics { if !disableMetrics {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
idpManager := "disabled" idpManager := "disabled"
if config.IdpManagerConfig != nil && config.IdpManagerConfig.ManagerType != "" { if config.IdpManagerConfig != nil && config.IdpManagerConfig.ManagerType != "" {
idpManager = config.IdpManagerConfig.ManagerType idpManager = config.IdpManagerConfig.ManagerType
@@ -314,6 +316,7 @@ var (
} }
} }
log.Infof("management server version %s", version.NetbirdVersion())
log.Infof("running HTTP server and gRPC server on the same port: %s", listener.Addr().String()) log.Infof("running HTTP server and gRPC server on the same port: %s", listener.Addr().String())
serveGRPCWithHTTP(listener, rootHandler, tlsEnabled) serveGRPCWithHTTP(listener, rootHandler, tlsEnabled)

File diff suppressed because it is too large Load Diff

View File

@@ -92,6 +92,21 @@ message PeerKeys {
bytes wgPubKey = 2; bytes wgPubKey = 2;
} }
// Environment is part of the PeerSystemMeta and describes the environment the agent is running in.
message Environment {
// cloud is the cloud provider the agent is running in if applicable.
string cloud = 1;
// platform is the platform the agent is running on if applicable.
string platform = 2;
}
// Config is a message with local configuration settings of the peer
message Config {
bool rosenpassEnabled = 1;
bool rosenpassPermissive = 2;
bool serverSSHAllowed = 3;
}
// PeerSystemMeta is machine meta data like OS and version. // PeerSystemMeta is machine meta data like OS and version.
message PeerSystemMeta { message PeerSystemMeta {
string hostname = 1; string hostname = 1;
@@ -108,6 +123,8 @@ message PeerSystemMeta {
string sysSerialNumber = 12; string sysSerialNumber = 12;
string sysProductName = 13; string sysProductName = 13;
string sysManufacturer = 14; string sysManufacturer = 14;
Environment environment = 15;
Config config = 16;
} }
message LoginResponse { message LoginResponse {

View File

@@ -110,7 +110,7 @@ type AccountManager interface {
DeleteNameServerGroup(accountID, nsGroupID, userID string) error DeleteNameServerGroup(accountID, nsGroupID, userID string) error
ListNameServerGroups(accountID string, userID string) ([]*nbdns.NameServerGroup, error) ListNameServerGroups(accountID string, userID string) ([]*nbdns.NameServerGroup, error)
GetDNSDomain() string GetDNSDomain() string
StoreEvent(initiatorID, targetID, accountID string, activityID activity.Activity, meta map[string]any) StoreEvent(initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any)
GetEvents(accountID, userID string) ([]*activity.Event, error) GetEvents(accountID, userID string) ([]*activity.Event, error)
GetDNSSettings(accountID string, userID string) (*DNSSettings, error) GetDNSSettings(accountID string, userID string) (*DNSSettings, error)
SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error
@@ -125,6 +125,7 @@ type AccountManager interface {
SavePostureChecks(accountID, userID string, postureChecks *posture.Checks) error SavePostureChecks(accountID, userID string, postureChecks *posture.Checks) error
DeletePostureChecks(accountID, postureChecksID, userID string) error DeletePostureChecks(accountID, postureChecksID, userID string) error
ListPostureChecks(accountID, userID string) ([]*posture.Checks, error) ListPostureChecks(accountID, userID string) ([]*posture.Checks, error)
GetIdpManager() idp.Manager
} }
type DefaultAccountManager struct { type DefaultAccountManager struct {
@@ -204,6 +205,7 @@ type Account struct {
// User.Id it was created by // User.Id it was created by
CreatedBy string CreatedBy string
CreatedAt time.Time
Domain string `gorm:"index"` Domain string `gorm:"index"`
DomainCategory string DomainCategory string
IsDomainPrimaryAccount bool IsDomainPrimaryAccount bool
@@ -453,6 +455,11 @@ func (a *Account) GetNextPeerExpiration() (time.Duration, bool) {
} }
_, duration := peer.LoginExpired(a.Settings.PeerLoginExpiration) _, duration := peer.LoginExpired(a.Settings.PeerLoginExpiration)
if nextExpiry == nil || duration < *nextExpiry { if nextExpiry == nil || duration < *nextExpiry {
// if expiration is below 1s return 1s duration
// this avoids issues with ticker that can't be set to < 0
if duration < time.Second {
return time.Second, true
}
nextExpiry = &duration nextExpiry = &duration
} }
} }
@@ -674,6 +681,7 @@ func (a *Account) Copy() *Account {
return &Account{ return &Account{
Id: a.Id, Id: a.Id,
CreatedBy: a.CreatedBy, CreatedBy: a.CreatedBy,
CreatedAt: a.CreatedAt,
Domain: a.Domain, Domain: a.Domain,
DomainCategory: a.DomainCategory, DomainCategory: a.DomainCategory,
IsDomainPrimaryAccount: a.IsDomainPrimaryAccount, IsDomainPrimaryAccount: a.IsDomainPrimaryAccount,
@@ -891,6 +899,10 @@ func (am *DefaultAccountManager) GetExternalCacheManager() ExternalCacheManager
return am.externalCacheManager return am.externalCacheManager
} }
func (am *DefaultAccountManager) GetIdpManager() idp.Manager {
return am.idpManager
}
// UpdateAccountSettings updates Account settings. // UpdateAccountSettings updates Account settings.
// Only users with role UserRoleAdmin can update the account. // Only users with role UserRoleAdmin can update the account.
// User that performs the update has to belong to the account. // User that performs the update has to belong to the account.
@@ -908,12 +920,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string,
unlock := am.Store.AcquireAccountLock(accountID) unlock := am.Store.AcquireAccountLock(accountID)
defer unlock() defer unlock()
account, err := am.Store.GetAccountByUser(userID) account, err := am.Store.GetAccount(accountID)
if err != nil {
return nil, err
}
err = additions.ValidateExtraSettings(newSettings.Extra, account.Settings.Extra, account.Peers, userID, accountID, am.eventStore)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -927,6 +934,11 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string,
return nil, status.Errorf(status.PermissionDenied, "user is not allowed to update account") return nil, status.Errorf(status.PermissionDenied, "user is not allowed to update account")
} }
err = additions.ValidateExtraSettings(newSettings.Extra, account.Settings.Extra, account.Peers, userID, accountID, am.eventStore)
if err != nil {
return nil, err
}
oldSettings := account.Settings oldSettings := account.Settings
if oldSettings.PeerLoginExpirationEnabled != newSettings.PeerLoginExpirationEnabled { if oldSettings.PeerLoginExpirationEnabled != newSettings.PeerLoginExpirationEnabled {
event := activity.AccountPeerLoginExpirationEnabled event := activity.AccountPeerLoginExpirationEnabled
@@ -1107,6 +1119,7 @@ func (am *DefaultAccountManager) DeleteAccount(accountID, userID string) error {
// GetAccountByUserOrAccountID looks for an account by user or accountID, if no account is provided and // GetAccountByUserOrAccountID looks for an account by user or accountID, if no account is provided and
// userID doesn't have an account associated with it, one account is created // userID doesn't have an account associated with it, one account is created
// domain is used to create a new account if no account is found
func (am *DefaultAccountManager) GetAccountByUserOrAccountID(userID, accountID, domain string) (*Account, error) { func (am *DefaultAccountManager) GetAccountByUserOrAccountID(userID, accountID, domain string) (*Account, error) {
if accountID != "" { if accountID != "" {
return am.Store.GetAccount(accountID) return am.Store.GetAccount(accountID)
@@ -1791,7 +1804,7 @@ func (am *DefaultAccountManager) CheckUserAccessByJWTGroups(claims jwtclaims.Aut
return nil return nil
} }
// addAllGroup to account object if it doesn't exists // addAllGroup to account object if it doesn't exist
func addAllGroup(account *Account) error { func addAllGroup(account *Account) error {
if len(account.Groups) == 0 { if len(account.Groups) == 0 {
allGroup := &Group{ allGroup := &Group{
@@ -1849,6 +1862,7 @@ func newAccountWithId(accountID, userID, domain string) *Account {
acc := &Account{ acc := &Account{
Id: accountID, Id: accountID,
CreatedAt: time.Now().UTC(),
SetupKeys: setupKeys, SetupKeys: setupKeys,
Network: network, Network: network,
Peers: peers, Peers: peers,

View File

@@ -94,6 +94,10 @@ func verifyNewAccountHasDefaultFields(t *testing.T, account *Account, createdBy
t.Errorf("expecting newly created account to be created by user %s, got %s", createdBy, account.CreatedBy) t.Errorf("expecting newly created account to be created by user %s, got %s", createdBy, account.CreatedBy)
} }
if account.CreatedAt.IsZero() {
t.Errorf("expecting newly created account to have a non-zero creation time")
}
if account.Domain != domain { if account.Domain != domain {
t.Errorf("expecting newly created account to have domain %s, got %s", domain, account.Domain) t.Errorf("expecting newly created account to have domain %s, got %s", domain, account.Domain)
} }
@@ -1473,6 +1477,7 @@ func TestAccount_Copy(t *testing.T) {
account := &Account{ account := &Account{
Id: "account1", Id: "account1",
CreatedBy: "tester", CreatedBy: "tester",
CreatedAt: time.Now().UTC(),
Domain: "test.com", Domain: "test.com",
DomainCategory: "public", DomainCategory: "public",
IsDomainPrimaryAccount: true, IsDomainPrimaryAccount: true,

View File

@@ -1,12 +1,14 @@
package activity package activity
import "maps"
// Activity that triggered an Event // Activity that triggered an Event
type Activity int type Activity int
// Code is an activity string representation // Code is an activity string representation
type Code struct { type Code struct {
message string Message string
code string Code string
} }
const ( const (
@@ -207,7 +209,7 @@ var activityMap = map[Activity]Code{
// StringCode returns a string code of the activity // StringCode returns a string code of the activity
func (a Activity) StringCode() string { func (a Activity) StringCode() string {
if code, ok := activityMap[a]; ok { if code, ok := activityMap[a]; ok {
return code.code return code.Code
} }
return "UNKNOWN_ACTIVITY" return "UNKNOWN_ACTIVITY"
} }
@@ -215,7 +217,12 @@ func (a Activity) StringCode() string {
// Message returns a string representation of an activity // Message returns a string representation of an activity
func (a Activity) Message() string { func (a Activity) Message() string {
if code, ok := activityMap[a]; ok { if code, ok := activityMap[a]; ok {
return code.message return code.Message
} }
return "UNKNOWN_ACTIVITY" return "UNKNOWN_ACTIVITY"
} }
// RegisterActivityMap adds new codes to the activity map
func RegisterActivityMap(codes map[Activity]Code) {
maps.Copy(activityMap, codes)
}

View File

@@ -8,12 +8,18 @@ const (
SystemInitiator = "sys" SystemInitiator = "sys"
) )
// ActivityDescriber is an interface that describes an activity
type ActivityDescriber interface { //nolint:revive
StringCode() string
Message() string
}
// Event represents a network/system activity event. // Event represents a network/system activity event.
type Event struct { type Event struct {
// Timestamp of the event // Timestamp of the event
Timestamp time.Time Timestamp time.Time
// Activity that was performed during the event // Activity that was performed during the event
Activity Activity Activity ActivityDescriber
// ID of the event (can be empty, meaning that it wasn't yet generated) // ID of the event (can be empty, meaning that it wasn't yet generated)
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)

View File

@@ -54,8 +54,7 @@ func (am *DefaultAccountManager) GetEvents(accountID, userID string) ([]*activit
return filtered, nil return filtered, nil
} }
func (am *DefaultAccountManager) StoreEvent(initiatorID, targetID, accountID string, activityID activity.Activity, func (am *DefaultAccountManager) StoreEvent(initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
meta map[string]any) {
go func() { go func() {
_, err := am.eventStore.Save(&activity.Event{ _, err := am.eventStore.Save(&activity.Event{

View File

@@ -0,0 +1,210 @@
package geolocation
import (
"encoding/csv"
"fmt"
"io"
"net/url"
"os"
"path"
"strconv"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
const (
geoLiteCityTarGZURL = "https://pkgs.netbird.io/geolocation-dbs/GeoLite2-City/download?suffix=tar.gz"
geoLiteCityZipURL = "https://pkgs.netbird.io/geolocation-dbs/GeoLite2-City-CSV/download?suffix=zip"
geoLiteCitySha256TarURL = "https://pkgs.netbird.io/geolocation-dbs/GeoLite2-City/download?suffix=tar.gz.sha256"
geoLiteCitySha256ZipURL = "https://pkgs.netbird.io/geolocation-dbs/GeoLite2-City-CSV/download?suffix=zip.sha256"
)
// loadGeolocationDatabases loads the MaxMind databases.
func loadGeolocationDatabases(dataDir string) error {
files := []string{MMDBFileName, GeoSqliteDBFile}
for _, file := range files {
exists, _ := fileExists(path.Join(dataDir, file))
if exists {
continue
}
switch file {
case MMDBFileName:
extractFunc := func(src string, dst string) error {
if err := decompressTarGzFile(src, dst); err != nil {
return err
}
return copyFile(path.Join(dst, MMDBFileName), path.Join(dataDir, MMDBFileName))
}
if err := loadDatabase(
geoLiteCitySha256TarURL,
geoLiteCityTarGZURL,
extractFunc,
); err != nil {
return err
}
case GeoSqliteDBFile:
extractFunc := func(src string, dst string) error {
if err := decompressZipFile(src, dst); err != nil {
return err
}
extractedCsvFile := path.Join(dst, "GeoLite2-City-Locations-en.csv")
return importCsvToSqlite(dataDir, extractedCsvFile)
}
if err := loadDatabase(
geoLiteCitySha256ZipURL,
geoLiteCityZipURL,
extractFunc,
); err != nil {
return err
}
}
}
return nil
}
// loadDatabase downloads a file from the specified URL and verifies its checksum.
// It then calls the extract function to perform additional processing on the extracted files.
func loadDatabase(checksumURL string, fileURL string, extractFunc func(src string, dst string) error) error {
temp, err := os.MkdirTemp(os.TempDir(), "geolite")
if err != nil {
return err
}
defer os.RemoveAll(temp)
checksumFile := path.Join(temp, getDatabaseFileName(checksumURL))
err = downloadFile(checksumURL, checksumFile)
if err != nil {
return err
}
sha256sum, err := loadChecksumFromFile(checksumFile)
if err != nil {
return err
}
dbFile := path.Join(temp, getDatabaseFileName(fileURL))
err = downloadFile(fileURL, dbFile)
if err != nil {
return err
}
if err := verifyChecksum(dbFile, sha256sum); err != nil {
return err
}
return extractFunc(dbFile, temp)
}
// importCsvToSqlite imports a CSV file into a SQLite database.
func importCsvToSqlite(dataDir string, csvFile string) error {
geonames, err := loadGeonamesCsv(csvFile)
if err != nil {
return err
}
db, err := gorm.Open(sqlite.Open(path.Join(dataDir, GeoSqliteDBFile)), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
CreateBatchSize: 1000,
PrepareStmt: true,
})
if err != nil {
return err
}
defer func() {
sql, err := db.DB()
if err != nil {
return
}
sql.Close()
}()
if err := db.AutoMigrate(&GeoNames{}); err != nil {
return err
}
return db.Create(geonames).Error
}
func loadGeonamesCsv(filepath string) ([]GeoNames, error) {
f, err := os.Open(filepath)
if err != nil {
return nil, err
}
defer f.Close()
reader := csv.NewReader(f)
records, err := reader.ReadAll()
if err != nil {
return nil, err
}
var geoNames []GeoNames
for index, record := range records {
if index == 0 {
continue
}
geoNameID, err := strconv.Atoi(record[0])
if err != nil {
return nil, err
}
geoName := GeoNames{
GeoNameID: geoNameID,
LocaleCode: record[1],
ContinentCode: record[2],
ContinentName: record[3],
CountryIsoCode: record[4],
CountryName: record[5],
Subdivision1IsoCode: record[6],
Subdivision1Name: record[7],
Subdivision2IsoCode: record[8],
Subdivision2Name: record[9],
CityName: record[10],
MetroCode: record[11],
TimeZone: record[12],
IsInEuropeanUnion: record[13],
}
geoNames = append(geoNames, geoName)
}
return geoNames, nil
}
// getDatabaseFileName extracts the file name from a given URL string.
func getDatabaseFileName(urlStr string) string {
u, err := url.Parse(urlStr)
if err != nil {
panic(err)
}
ext := u.Query().Get("suffix")
fileName := fmt.Sprintf("%s.%s", path.Base(u.Path), ext)
return fileName
}
// copyFile performs a file copy operation from the source file to the destination.
func copyFile(src string, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return err
}
return nil
}

View File

@@ -2,9 +2,7 @@ package geolocation
import ( import (
"bytes" "bytes"
"crypto/sha256"
"fmt" "fmt"
"io"
"net" "net"
"os" "os"
"path" "path"
@@ -54,20 +52,23 @@ type Country struct {
CountryName string CountryName string
} }
func NewGeolocation(datadir string) (*Geolocation, error) { func NewGeolocation(dataDir string) (*Geolocation, error) {
mmdbPath := path.Join(datadir, MMDBFileName) if err := loadGeolocationDatabases(dataDir); err != nil {
return nil, fmt.Errorf("failed to load MaxMind databases: %v", err)
}
mmdbPath := path.Join(dataDir, MMDBFileName)
db, err := openDB(mmdbPath) db, err := openDB(mmdbPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sha256sum, err := getSha256sum(mmdbPath) sha256sum, err := calculateFileSHA256(mmdbPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
locationDB, err := NewSqliteStore(datadir) locationDB, err := NewSqliteStore(dataDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -104,21 +105,6 @@ func openDB(mmdbPath string) (*maxminddb.Reader, error) {
return db, nil return db, nil
} }
func getSha256sum(mmdbPath string) ([]byte, error) {
f, err := os.Open(mmdbPath)
if err != nil {
return nil, err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
func (gl *Geolocation) Lookup(ip net.IP) (*Record, error) { func (gl *Geolocation) Lookup(ip net.IP) (*Record, error) {
gl.mux.RLock() gl.mux.RLock()
defer gl.mux.RUnlock() defer gl.mux.RUnlock()
@@ -189,7 +175,7 @@ func (gl *Geolocation) reloader() {
log.Errorf("geonames db reload failed: %s", err) log.Errorf("geonames db reload failed: %s", err)
} }
newSha256sum1, err := getSha256sum(gl.mmdbPath) newSha256sum1, err := calculateFileSHA256(gl.mmdbPath)
if err != nil { if err != nil {
log.Errorf("failed to calculate sha256 sum for '%s': %s", gl.mmdbPath, err) log.Errorf("failed to calculate sha256 sum for '%s': %s", gl.mmdbPath, err)
continue continue
@@ -198,7 +184,7 @@ func (gl *Geolocation) reloader() {
// we check sum twice just to avoid possible case when we reload during update of the file // we check sum twice just to avoid possible case when we reload during update of the file
// considering the frequency of file update (few times a week) checking sum twice should be enough // considering the frequency of file update (few times a week) checking sum twice should be enough
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
newSha256sum2, err := getSha256sum(gl.mmdbPath) newSha256sum2, err := calculateFileSHA256(gl.mmdbPath)
if err != nil { if err != nil {
log.Errorf("failed to calculate sha256 sum for '%s': %s", gl.mmdbPath, err) log.Errorf("failed to calculate sha256 sum for '%s': %s", gl.mmdbPath, err)
continue continue

View File

@@ -20,6 +20,27 @@ const (
GeoSqliteDBFile = "geonames.db" GeoSqliteDBFile = "geonames.db"
) )
type GeoNames struct {
GeoNameID int `gorm:"column:geoname_id"`
LocaleCode string `gorm:"column:locale_code"`
ContinentCode string `gorm:"column:continent_code"`
ContinentName string `gorm:"column:continent_name"`
CountryIsoCode string `gorm:"column:country_iso_code"`
CountryName string `gorm:"column:country_name"`
Subdivision1IsoCode string `gorm:"column:subdivision_1_iso_code"`
Subdivision1Name string `gorm:"column:subdivision_1_name"`
Subdivision2IsoCode string `gorm:"column:subdivision_2_iso_code"`
Subdivision2Name string `gorm:"column:subdivision_2_name"`
CityName string `gorm:"column:city_name"`
MetroCode string `gorm:"column:metro_code"`
TimeZone string `gorm:"column:time_zone"`
IsInEuropeanUnion string `gorm:"column:is_in_european_union"`
}
func (*GeoNames) TableName() string {
return "geonames"
}
// SqliteStore represents a location storage backed by a Sqlite DB. // SqliteStore represents a location storage backed by a Sqlite DB.
type SqliteStore struct { type SqliteStore struct {
db *gorm.DB db *gorm.DB
@@ -37,7 +58,7 @@ func NewSqliteStore(dataDir string) (*SqliteStore, error) {
return nil, err return nil, err
} }
sha256sum, err := getSha256sum(file) sha256sum, err := calculateFileSHA256(file)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -60,7 +81,7 @@ func (s *SqliteStore) GetAllCountries() ([]Country, error) {
} }
var countries []Country var countries []Country
result := s.db.Table("geonames"). result := s.db.Model(&GeoNames{}).
Select("country_iso_code", "country_name"). Select("country_iso_code", "country_name").
Group("country_name"). Group("country_name").
Scan(&countries) Scan(&countries)
@@ -81,7 +102,7 @@ func (s *SqliteStore) GetCitiesByCountry(countryISOCode string) ([]City, error)
} }
var cities []City var cities []City
result := s.db.Table("geonames"). result := s.db.Model(&GeoNames{}).
Select("geoname_id", "city_name"). Select("geoname_id", "city_name").
Where("country_iso_code = ?", countryISOCode). Where("country_iso_code = ?", countryISOCode).
Group("city_name"). Group("city_name").
@@ -98,7 +119,7 @@ func (s *SqliteStore) reload() error {
s.mux.Lock() s.mux.Lock()
defer s.mux.Unlock() defer s.mux.Unlock()
newSha256sum1, err := getSha256sum(s.filePath) newSha256sum1, err := calculateFileSHA256(s.filePath)
if err != nil { if err != nil {
log.Errorf("failed to calculate sha256 sum for '%s': %s", s.filePath, err) log.Errorf("failed to calculate sha256 sum for '%s': %s", s.filePath, err)
} }
@@ -107,7 +128,7 @@ func (s *SqliteStore) reload() error {
// we check sum twice just to avoid possible case when we reload during update of the file // we check sum twice just to avoid possible case when we reload during update of the file
// considering the frequency of file update (few times a week) checking sum twice should be enough // considering the frequency of file update (few times a week) checking sum twice should be enough
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
newSha256sum2, err := getSha256sum(s.filePath) newSha256sum2, err := calculateFileSHA256(s.filePath)
if err != nil { if err != nil {
return fmt.Errorf("failed to calculate sha256 sum for '%s': %s", s.filePath, err) return fmt.Errorf("failed to calculate sha256 sum for '%s': %s", s.filePath, err)
} }

View File

@@ -0,0 +1,176 @@
package geolocation
import (
"archive/tar"
"archive/zip"
"bufio"
"bytes"
"compress/gzip"
"crypto/sha256"
"errors"
"fmt"
"io"
"net/http"
"os"
"path"
"strings"
)
// decompressTarGzFile decompresses a .tar.gz file.
func decompressTarGzFile(filepath, destDir string) error {
file, err := os.Open(filepath)
if err != nil {
return err
}
defer file.Close()
gzipReader, err := gzip.NewReader(file)
if err != nil {
return err
}
defer gzipReader.Close()
tarReader := tar.NewReader(gzipReader)
for {
header, err := tarReader.Next()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
}
if header.Typeflag == tar.TypeReg {
outFile, err := os.Create(path.Join(destDir, path.Base(header.Name)))
if err != nil {
return err
}
_, err = io.Copy(outFile, tarReader) // #nosec G110
outFile.Close()
if err != nil {
return err
}
}
}
return nil
}
// decompressZipFile decompresses a .zip file.
func decompressZipFile(filepath, destDir string) error {
r, err := zip.OpenReader(filepath)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
if f.FileInfo().IsDir() {
continue
}
outFile, err := os.Create(path.Join(destDir, path.Base(f.Name)))
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
outFile.Close()
return err
}
_, err = io.Copy(outFile, rc) // #nosec G110
outFile.Close()
rc.Close()
if err != nil {
return err
}
}
return nil
}
// calculateFileSHA256 calculates the SHA256 checksum of a file.
func calculateFileSHA256(filepath string) ([]byte, error) {
file, err := os.Open(filepath)
if err != nil {
return nil, err
}
defer file.Close()
h := sha256.New()
if _, err := io.Copy(h, file); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
// loadChecksumFromFile loads the first checksum from a file.
func loadChecksumFromFile(filepath string) (string, error) {
file, err := os.Open(filepath)
if err != nil {
return "", err
}
defer file.Close()
scanner := bufio.NewScanner(file)
if scanner.Scan() {
parts := strings.Fields(scanner.Text())
if len(parts) > 0 {
return parts[0], nil
}
}
if err := scanner.Err(); err != nil {
return "", err
}
return "", nil
}
// verifyChecksum compares the calculated SHA256 checksum of a file against the expected checksum.
func verifyChecksum(filepath, expectedChecksum string) error {
calculatedChecksum, err := calculateFileSHA256(filepath)
fileCheckSum := fmt.Sprintf("%x", calculatedChecksum)
if err != nil {
return err
}
if fileCheckSum != expectedChecksum {
return fmt.Errorf("checksum mismatch: expected %s, got %s", expectedChecksum, fileCheckSum)
}
return nil
}
// downloadFile downloads a file from a URL and saves it to a local file path.
func downloadFile(url, filepath string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected error occurred while downloading the file: %s", string(bodyBytes))
}
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, bytes.NewBuffer(bodyBytes))
return err
}

View File

@@ -288,6 +288,13 @@ func extractPeerMeta(loginReq *proto.LoginRequest) nbpeer.PeerSystemMeta {
SystemSerialNumber: loginReq.GetMeta().GetSysSerialNumber(), SystemSerialNumber: loginReq.GetMeta().GetSysSerialNumber(),
SystemProductName: loginReq.GetMeta().GetSysProductName(), SystemProductName: loginReq.GetMeta().GetSysProductName(),
SystemManufacturer: loginReq.GetMeta().GetSysManufacturer(), SystemManufacturer: loginReq.GetMeta().GetSysManufacturer(),
Environment: nbpeer.Environment{
Cloud: loginReq.GetMeta().GetEnvironment().GetCloud(),
Platform: loginReq.GetMeta().GetEnvironment().GetPlatform(),
},
RosenpassEnabled: loginReq.GetMeta().GetRosenpassEnabled(),
RosenpassPermissive: loginReq.GetMeta().GetRosenpassPermissive(),
ServerSSHAllowed: loginReq.GetMeta().GetServerSSHAllowed(),
} }
} }

View File

@@ -121,7 +121,7 @@ components:
description: Last time this user performed a login to the dashboard description: Last time this user performed a login to the dashboard
type: string type: string
format: date-time format: date-time
example: 2023-05-05T09:00:35.477782Z example: "2023-05-05T09:00:35.477782Z"
auto_groups: auto_groups:
description: Group IDs to auto-assign to peers registered by this user description: Group IDs to auto-assign to peers registered by this user
type: array type: array
@@ -259,7 +259,7 @@ components:
description: Last time peer connected to Netbird's management service description: Last time peer connected to Netbird's management service
type: string type: string
format: date-time format: date-time
example: 2023-05-05T10:05:26.420578Z example: "2023-05-05T10:05:26.420578Z"
os: os:
description: Peer's operating system and version description: Peer's operating system and version
type: string type: string
@@ -313,7 +313,7 @@ components:
description: Last time this peer performed log in (authentication). E.g., user authenticated. description: Last time this peer performed log in (authentication). E.g., user authenticated.
type: string type: string
format: date-time format: date-time
example: 2023-05-05T09:00:35.477782Z example: "2023-05-05T09:00:35.477782Z"
approval_required: approval_required:
description: (Cloud only) Indicates whether peer needs approval description: (Cloud only) Indicates whether peer needs approval
type: boolean type: boolean
@@ -405,7 +405,7 @@ components:
description: Setup Key expiration date description: Setup Key expiration date
type: string type: string
format: date-time format: date-time
example: 2023-06-01T14:47:22.291057Z example: "2023-06-01T14:47:22.291057Z"
type: type:
description: Setup key type, one-off for single time usage and reusable description: Setup key type, one-off for single time usage and reusable
type: string type: string
@@ -426,7 +426,7 @@ components:
description: Setup key last usage date description: Setup key last usage date
type: string type: string
format: date-time format: date-time
example: 2023-05-05T09:00:35.477782Z example: "2023-05-05T09:00:35.477782Z"
state: state:
description: Setup key status, "valid", "overused","expired" or "revoked" description: Setup key status, "valid", "overused","expired" or "revoked"
type: string type: string
@@ -441,7 +441,7 @@ components:
description: Setup key last update date description: Setup key last update date
type: string type: string
format: date-time format: date-time
example: 2023-05-05T09:00:35.477782Z example: "2023-05-05T09:00:35.477782Z"
usage_limit: usage_limit:
description: A number of times this key can be used. The value of 0 indicates the unlimited usage. description: A number of times this key can be used. The value of 0 indicates the unlimited usage.
type: integer type: integer
@@ -522,7 +522,7 @@ components:
description: Date the token expires description: Date the token expires
type: string type: string
format: date-time format: date-time
example: 2023-05-05T14:38:28.977616Z example: "2023-05-05T14:38:28.977616Z"
created_by: created_by:
description: User ID of the user who created the token description: User ID of the user who created the token
type: string type: string
@@ -531,12 +531,12 @@ components:
description: Date the token was created description: Date the token was created
type: string type: string
format: date-time format: date-time
example: 2023-05-02T14:48:20.465209Z example: "2023-05-02T14:48:20.465209Z"
last_used: last_used:
description: Date the token was last used description: Date the token was last used
type: string type: string
format: date-time format: date-time
example: 2023-05-04T12:45:25.9723616Z example: "2023-05-04T12:45:25.9723616Z"
required: required:
- id - id
- name - name
@@ -862,6 +862,8 @@ components:
$ref: '#/components/schemas/OSVersionCheck' $ref: '#/components/schemas/OSVersionCheck'
geo_location_check: geo_location_check:
$ref: '#/components/schemas/GeoLocationCheck' $ref: '#/components/schemas/GeoLocationCheck'
peer_network_range_check:
$ref: '#/components/schemas/PeerNetworkRangeCheck'
NBVersionCheck: NBVersionCheck:
description: Posture check for the version of NetBird description: Posture check for the version of NetBird
type: object type: object
@@ -932,6 +934,24 @@ components:
required: required:
- locations - locations
- action - action
PeerNetworkRangeCheck:
description: Posture check for allow or deny access based on peer local network addresses
type: object
properties:
ranges:
description: List of peer network ranges in CIDR notation
type: array
items:
type: string
example: ["192.168.1.0/24", "10.0.0.0/8", "2001:db8:1234:1a00::/56"]
action:
description: Action to take upon policy match
type: string
enum: [ "allow", "deny" ]
example: "allow"
required:
- ranges
- action
Location: Location:
description: Describe geographical location information description: Describe geographical location information
type: object type: object
@@ -959,7 +979,7 @@ components:
type: string type: string
example: "Germany" example: "Germany"
country_code: country_code:
$ref: '#/components/schemas/CountryCode' $ref: '#/components/schemas/CountryCode'
required: required:
- country_name - country_name
- country_code - country_code
@@ -1177,7 +1197,7 @@ components:
description: The date and time when the event occurred description: The date and time when the event occurred
type: string type: string
format: date-time format: date-time
example: 2023-05-05T10:04:37.473542Z example: "2023-05-05T10:04:37.473542Z"
activity: activity:
description: The activity that occurred during the event description: The activity that occurred during the event
type: string type: string

View File

@@ -74,6 +74,12 @@ const (
NameserverNsTypeUdp NameserverNsType = "udp" NameserverNsTypeUdp NameserverNsType = "udp"
) )
// Defines values for PeerNetworkRangeCheckAction.
const (
PeerNetworkRangeCheckActionAllow PeerNetworkRangeCheckAction = "allow"
PeerNetworkRangeCheckActionDeny PeerNetworkRangeCheckAction = "deny"
)
// Defines values for PolicyRuleAction. // Defines values for PolicyRuleAction.
const ( const (
PolicyRuleActionAccept PolicyRuleAction = "accept" PolicyRuleActionAccept PolicyRuleAction = "accept"
@@ -186,10 +192,15 @@ type AccountSettings struct {
type Checks struct { type Checks struct {
// GeoLocationCheck Posture check for geo location // GeoLocationCheck Posture check for geo location
GeoLocationCheck *GeoLocationCheck `json:"geo_location_check,omitempty"` GeoLocationCheck *GeoLocationCheck `json:"geo_location_check,omitempty"`
NbVersionCheck *NBVersionCheck `json:"nb_version_check,omitempty"`
// NbVersionCheck Posture check for the version of operating system
NbVersionCheck *NBVersionCheck `json:"nb_version_check,omitempty"`
// OsVersionCheck Posture check for the version of operating system // OsVersionCheck Posture check for the version of operating system
OsVersionCheck *OSVersionCheck `json:"os_version_check,omitempty"` OsVersionCheck *OSVersionCheck `json:"os_version_check,omitempty"`
// PeerNetworkRangeCheck Posture check for allow or deny access based on peer local network addresses
PeerNetworkRangeCheck *PeerNetworkRangeCheck `json:"peer_network_range_check,omitempty"`
} }
// City Describe city geographical location information // City Describe city geographical location information
@@ -324,13 +335,13 @@ type MinKernelVersionCheck struct {
MinKernelVersion string `json:"min_kernel_version"` MinKernelVersion string `json:"min_kernel_version"`
} }
// MinVersionCheck defines model for MinVersionCheck. // MinVersionCheck Posture check for the version of operating system
type MinVersionCheck struct { type MinVersionCheck struct {
// MinVersion Minimum acceptable version // MinVersion Minimum acceptable version
MinVersion string `json:"min_version"` MinVersion string `json:"min_version"`
} }
// NBVersionCheck defines model for NBVersionCheck. // NBVersionCheck Posture check for the version of operating system
type NBVersionCheck = MinVersionCheck type NBVersionCheck = MinVersionCheck
// Nameserver defines model for Nameserver. // Nameserver defines model for Nameserver.
@@ -407,9 +418,14 @@ type NameserverGroupRequest struct {
// OSVersionCheck Posture check for the version of operating system // OSVersionCheck Posture check for the version of operating system
type OSVersionCheck struct { type OSVersionCheck struct {
// Android Posture check for the version of operating system
Android *MinVersionCheck `json:"android,omitempty"` Android *MinVersionCheck `json:"android,omitempty"`
Darwin *MinVersionCheck `json:"darwin,omitempty"`
Ios *MinVersionCheck `json:"ios,omitempty"` // Darwin Posture check for the version of operating system
Darwin *MinVersionCheck `json:"darwin,omitempty"`
// Ios Posture check for the version of operating system
Ios *MinVersionCheck `json:"ios,omitempty"`
// Linux Posture check with the kernel version // Linux Posture check with the kernel version
Linux *MinKernelVersionCheck `json:"linux,omitempty"` Linux *MinKernelVersionCheck `json:"linux,omitempty"`
@@ -427,22 +443,22 @@ type Peer struct {
ApprovalRequired *bool `json:"approval_required,omitempty"` ApprovalRequired *bool `json:"approval_required,omitempty"`
// CityName Commonly used English name of the city // CityName Commonly used English name of the city
CityName *CityName `json:"city_name,omitempty"` CityName CityName `json:"city_name"`
// Connected Peer to Management connection status // Connected Peer to Management connection status
Connected bool `json:"connected"` Connected bool `json:"connected"`
// ConnectionIp Peer's public connection IP address // ConnectionIp Peer's public connection IP address
ConnectionIp *string `json:"connection_ip,omitempty"` ConnectionIp string `json:"connection_ip"`
// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country // CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country
CountryCode *CountryCode `json:"country_code,omitempty"` CountryCode CountryCode `json:"country_code"`
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud // DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
DnsLabel string `json:"dns_label"` DnsLabel string `json:"dns_label"`
// GeonameId Unique identifier from the GeoNames database for a specific geographical location. // GeonameId Unique identifier from the GeoNames database for a specific geographical location.
GeonameId *int `json:"geoname_id,omitempty"` GeonameId int `json:"geoname_id"`
// Groups Groups that the peer belongs to // Groups Groups that the peer belongs to
Groups []GroupMinimum `json:"groups"` Groups []GroupMinimum `json:"groups"`
@@ -457,7 +473,7 @@ type Peer struct {
Ip string `json:"ip"` Ip string `json:"ip"`
// KernelVersion Peer's operating system kernel version // KernelVersion Peer's operating system kernel version
KernelVersion *string `json:"kernel_version,omitempty"` KernelVersion string `json:"kernel_version"`
// LastLogin Last time this peer performed log in (authentication). E.g., user authenticated. // LastLogin Last time this peer performed log in (authentication). E.g., user authenticated.
LastLogin time.Time `json:"last_login"` LastLogin time.Time `json:"last_login"`
@@ -481,10 +497,10 @@ type Peer struct {
SshEnabled bool `json:"ssh_enabled"` SshEnabled bool `json:"ssh_enabled"`
// UiVersion Peer's desktop UI version // UiVersion Peer's desktop UI version
UiVersion *string `json:"ui_version,omitempty"` UiVersion string `json:"ui_version"`
// UserId User ID of the user that enrolled this peer // UserId User ID of the user that enrolled this peer
UserId *string `json:"user_id,omitempty"` UserId string `json:"user_id"`
// Version Peer's daemon or cli version // Version Peer's daemon or cli version
Version string `json:"version"` Version string `json:"version"`
@@ -496,22 +512,22 @@ type PeerBase struct {
ApprovalRequired *bool `json:"approval_required,omitempty"` ApprovalRequired *bool `json:"approval_required,omitempty"`
// CityName Commonly used English name of the city // CityName Commonly used English name of the city
CityName *CityName `json:"city_name,omitempty"` CityName CityName `json:"city_name"`
// Connected Peer to Management connection status // Connected Peer to Management connection status
Connected bool `json:"connected"` Connected bool `json:"connected"`
// ConnectionIp Peer's public connection IP address // ConnectionIp Peer's public connection IP address
ConnectionIp *string `json:"connection_ip,omitempty"` ConnectionIp string `json:"connection_ip"`
// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country // CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country
CountryCode *CountryCode `json:"country_code,omitempty"` CountryCode CountryCode `json:"country_code"`
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud // DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
DnsLabel string `json:"dns_label"` DnsLabel string `json:"dns_label"`
// GeonameId Unique identifier from the GeoNames database for a specific geographical location. // GeonameId Unique identifier from the GeoNames database for a specific geographical location.
GeonameId *int `json:"geoname_id,omitempty"` GeonameId int `json:"geoname_id"`
// Groups Groups that the peer belongs to // Groups Groups that the peer belongs to
Groups []GroupMinimum `json:"groups"` Groups []GroupMinimum `json:"groups"`
@@ -526,7 +542,7 @@ type PeerBase struct {
Ip string `json:"ip"` Ip string `json:"ip"`
// KernelVersion Peer's operating system kernel version // KernelVersion Peer's operating system kernel version
KernelVersion *string `json:"kernel_version,omitempty"` KernelVersion string `json:"kernel_version"`
// LastLogin Last time this peer performed log in (authentication). E.g., user authenticated. // LastLogin Last time this peer performed log in (authentication). E.g., user authenticated.
LastLogin time.Time `json:"last_login"` LastLogin time.Time `json:"last_login"`
@@ -550,10 +566,10 @@ type PeerBase struct {
SshEnabled bool `json:"ssh_enabled"` SshEnabled bool `json:"ssh_enabled"`
// UiVersion Peer's desktop UI version // UiVersion Peer's desktop UI version
UiVersion *string `json:"ui_version,omitempty"` UiVersion string `json:"ui_version"`
// UserId User ID of the user that enrolled this peer // UserId User ID of the user that enrolled this peer
UserId *string `json:"user_id,omitempty"` UserId string `json:"user_id"`
// Version Peer's daemon or cli version // Version Peer's daemon or cli version
Version string `json:"version"` Version string `json:"version"`
@@ -568,22 +584,22 @@ type PeerBatch struct {
ApprovalRequired *bool `json:"approval_required,omitempty"` ApprovalRequired *bool `json:"approval_required,omitempty"`
// CityName Commonly used English name of the city // CityName Commonly used English name of the city
CityName *CityName `json:"city_name,omitempty"` CityName CityName `json:"city_name"`
// Connected Peer to Management connection status // Connected Peer to Management connection status
Connected bool `json:"connected"` Connected bool `json:"connected"`
// ConnectionIp Peer's public connection IP address // ConnectionIp Peer's public connection IP address
ConnectionIp *string `json:"connection_ip,omitempty"` ConnectionIp string `json:"connection_ip"`
// CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country // CountryCode 2-letter ISO 3166-1 alpha-2 code that represents the country
CountryCode *CountryCode `json:"country_code,omitempty"` CountryCode CountryCode `json:"country_code"`
// DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud // DnsLabel Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud
DnsLabel string `json:"dns_label"` DnsLabel string `json:"dns_label"`
// GeonameId Unique identifier from the GeoNames database for a specific geographical location. // GeonameId Unique identifier from the GeoNames database for a specific geographical location.
GeonameId *int `json:"geoname_id,omitempty"` GeonameId int `json:"geoname_id"`
// Groups Groups that the peer belongs to // Groups Groups that the peer belongs to
Groups []GroupMinimum `json:"groups"` Groups []GroupMinimum `json:"groups"`
@@ -598,7 +614,7 @@ type PeerBatch struct {
Ip string `json:"ip"` Ip string `json:"ip"`
// KernelVersion Peer's operating system kernel version // KernelVersion Peer's operating system kernel version
KernelVersion *string `json:"kernel_version,omitempty"` KernelVersion string `json:"kernel_version"`
// LastLogin Last time this peer performed log in (authentication). E.g., user authenticated. // LastLogin Last time this peer performed log in (authentication). E.g., user authenticated.
LastLogin time.Time `json:"last_login"` LastLogin time.Time `json:"last_login"`
@@ -622,10 +638,10 @@ type PeerBatch struct {
SshEnabled bool `json:"ssh_enabled"` SshEnabled bool `json:"ssh_enabled"`
// UiVersion Peer's desktop UI version // UiVersion Peer's desktop UI version
UiVersion *string `json:"ui_version,omitempty"` UiVersion string `json:"ui_version"`
// UserId User ID of the user that enrolled this peer // UserId User ID of the user that enrolled this peer
UserId *string `json:"user_id,omitempty"` UserId string `json:"user_id"`
// Version Peer's daemon or cli version // Version Peer's daemon or cli version
Version string `json:"version"` Version string `json:"version"`
@@ -640,6 +656,18 @@ type PeerMinimum struct {
Name string `json:"name"` Name string `json:"name"`
} }
// PeerNetworkRangeCheck Posture check for allow or deny access based on peer local network addresses
type PeerNetworkRangeCheck struct {
// Action Action to take upon policy match
Action PeerNetworkRangeCheckAction `json:"action"`
// Ranges List of peer network ranges in CIDR notation
Ranges []string `json:"ranges"`
}
// PeerNetworkRangeCheckAction Action to take upon policy match
type PeerNetworkRangeCheckAction string
// PeerRequest defines model for PeerRequest. // PeerRequest defines model for PeerRequest.
type PeerRequest struct { type PeerRequest struct {
// ApprovalRequired (Cloud only) Indicates whether peer needs approval // ApprovalRequired (Cloud only) Indicates whether peer needs approval

View File

@@ -1,6 +1,8 @@
package http package http
import ( import (
"context"
"fmt"
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@@ -15,6 +17,8 @@ import (
"github.com/netbirdio/netbird/management/server/telemetry" "github.com/netbirdio/netbird/management/server/telemetry"
) )
const apiPrefix = "/api"
// AuthCfg contains parameters for authentication middleware // AuthCfg contains parameters for authentication middleware
type AuthCfg struct { type AuthCfg struct {
Issuer string Issuer string
@@ -35,7 +39,7 @@ type emptyObject struct {
} }
// APIHandler creates the Management service HTTP API handler registering all the available endpoints. // APIHandler creates the Management service HTTP API handler registering all the available endpoints.
func APIHandler(accountManager s.AccountManager, LocationManager *geolocation.Geolocation, jwtValidator jwtclaims.JWTValidator, appMetrics telemetry.AppMetrics, authCfg AuthCfg) (http.Handler, error) { func APIHandler(ctx context.Context, accountManager s.AccountManager, LocationManager *geolocation.Geolocation, jwtValidator jwtclaims.JWTValidator, appMetrics telemetry.AppMetrics, authCfg AuthCfg) (http.Handler, error) {
claimsExtractor := jwtclaims.NewClaimsExtractor( claimsExtractor := jwtclaims.NewClaimsExtractor(
jwtclaims.WithAudience(authCfg.Audience), jwtclaims.WithAudience(authCfg.Audience),
jwtclaims.WithUserIDClaim(authCfg.UserIDClaim), jwtclaims.WithUserIDClaim(authCfg.UserIDClaim),
@@ -61,7 +65,8 @@ func APIHandler(accountManager s.AccountManager, LocationManager *geolocation.Ge
rootRouter := mux.NewRouter() rootRouter := mux.NewRouter()
metricsMiddleware := appMetrics.HTTPMiddleware() metricsMiddleware := appMetrics.HTTPMiddleware()
router := rootRouter.PathPrefix("/api").Subrouter() prefix := apiPrefix
router := rootRouter.PathPrefix(prefix).Subrouter()
router.Use(metricsMiddleware.Handler, corsMiddleware.Handler, authMiddleware.Handler, acMiddleware.Handler) router.Use(metricsMiddleware.Handler, corsMiddleware.Handler, authMiddleware.Handler, acMiddleware.Handler)
api := apiHandler{ api := apiHandler{
@@ -71,7 +76,10 @@ func APIHandler(accountManager s.AccountManager, LocationManager *geolocation.Ge
AuthCfg: authCfg, AuthCfg: authCfg,
} }
integrations.RegisterHandlers(api.Router, accountManager, claimsExtractor) if _, err := integrations.RegisterHandlers(ctx, prefix, api.Router, accountManager, claimsExtractor); err != nil {
return nil, fmt.Errorf("register integrations endpoints: %w", err)
}
api.addAccountsEndpoint() api.addAccountsEndpoint()
api.addPeersEndpoint() api.addPeersEndpoint()
api.addUsersEndpoint() api.addUsersEndpoint()

View File

@@ -7,6 +7,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/http/middleware/bypass"
"github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
@@ -36,9 +37,13 @@ func NewAccessControl(audience, userIDClaim string, getUser GetUser) *AccessCont
var tokenPathRegexp = regexp.MustCompile(`^.*/api/users/.*/tokens.*$`) var tokenPathRegexp = regexp.MustCompile(`^.*/api/users/.*/tokens.*$`)
// Handler method of the middleware which forbids all modify requests for non admin users // Handler method of the middleware which forbids all modify requests for non admin users
// It also adds
func (a *AccessControl) Handler(h http.Handler) http.Handler { func (a *AccessControl) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if bypass.ShouldBypass(r.URL.Path, h, w, r) {
return
}
claims := a.claimsExtract.FromRequestContext(r) claims := a.claimsExtract.FromRequestContext(r)
user, err := a.getUser(claims) user, err := a.getUser(claims)

View File

@@ -12,6 +12,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/http/middleware/bypass"
"github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
@@ -66,6 +67,11 @@ func NewAuthMiddleware(getAccountFromPAT GetAccountFromPATFunc, validateAndParse
// Handler method of the middleware which authenticates a user either by JWT claims or by PAT // Handler method of the middleware which authenticates a user either by JWT claims or by PAT
func (m *AuthMiddleware) Handler(h http.Handler) http.Handler { func (m *AuthMiddleware) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if bypass.ShouldBypass(r.URL.Path, h, w, r) {
return
}
auth := strings.Split(r.Header.Get("Authorization"), " ") auth := strings.Split(r.Header.Get("Authorization"), " ")
authType := strings.ToLower(auth[0]) authType := strings.ToLower(auth[0])

View File

@@ -10,6 +10,7 @@ import (
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/http/middleware/bypass"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
) )
@@ -88,39 +89,68 @@ func mockCheckUserAccessByJWTGroups(claims jwtclaims.AuthorizationClaims) error
func TestAuthMiddleware_Handler(t *testing.T) { func TestAuthMiddleware_Handler(t *testing.T) {
tt := []struct { tt := []struct {
name string name string
path string
authHeader string authHeader string
expectedStatusCode int expectedStatusCode int
shouldBypassAuth bool
}{ }{
{ {
name: "Valid PAT Token", name: "Valid PAT Token",
path: "/test",
authHeader: "Token " + PAT, authHeader: "Token " + PAT,
expectedStatusCode: 200, expectedStatusCode: 200,
}, },
{ {
name: "Invalid PAT Token", name: "Invalid PAT Token",
path: "/test",
authHeader: "Token " + wrongToken, authHeader: "Token " + wrongToken,
expectedStatusCode: 401, expectedStatusCode: 401,
}, },
{ {
name: "Fallback to PAT Token", name: "Fallback to PAT Token",
path: "/test",
authHeader: "Bearer " + PAT, authHeader: "Bearer " + PAT,
expectedStatusCode: 200, expectedStatusCode: 200,
}, },
{ {
name: "Valid JWT Token", name: "Valid JWT Token",
path: "/test",
authHeader: "Bearer " + JWT, authHeader: "Bearer " + JWT,
expectedStatusCode: 200, expectedStatusCode: 200,
}, },
{ {
name: "Invalid JWT Token", name: "Invalid JWT Token",
path: "/test",
authHeader: "Bearer " + wrongToken, authHeader: "Bearer " + wrongToken,
expectedStatusCode: 401, expectedStatusCode: 401,
}, },
{ {
name: "Basic Auth", name: "Basic Auth",
path: "/test",
authHeader: "Basic " + PAT, authHeader: "Basic " + PAT,
expectedStatusCode: 401, expectedStatusCode: 401,
}, },
{
name: "Webhook Path Bypass",
path: "/webhook",
authHeader: "",
expectedStatusCode: 200,
shouldBypassAuth: true,
},
{
name: "Webhook Path Bypass with Subpath",
path: "/webhook/test",
authHeader: "",
expectedStatusCode: 200,
shouldBypassAuth: true,
},
{
name: "Different Webhook Path",
path: "/webhooktest",
authHeader: "",
expectedStatusCode: 401,
shouldBypassAuth: false,
},
} }
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -146,7 +176,14 @@ func TestAuthMiddleware_Handler(t *testing.T) {
for _, tc := range tt { for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "http://testing", nil) if tc.shouldBypassAuth {
err := bypass.AddBypassPath(tc.path)
if err != nil {
t.Fatalf("failed to add bypass path: %v", err)
}
}
req := httptest.NewRequest("GET", "http://testing"+tc.path, nil)
req.Header.Set("Authorization", tc.authHeader) req.Header.Set("Authorization", tc.authHeader)
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
@@ -159,5 +196,4 @@ func TestAuthMiddleware_Handler(t *testing.T) {
} }
}) })
} }
} }

View File

@@ -0,0 +1,74 @@
package bypass
import (
"fmt"
"net/http"
"path"
"sync"
log "github.com/sirupsen/logrus"
)
var byPassMutex sync.RWMutex
// bypassPaths is a set of paths that should bypass middleware.
var bypassPaths = make(map[string]struct{})
// AddBypassPath adds an exact path to the list of paths that bypass middleware.
// Paths can include wildcards, such as /api/*. Paths are matched using path.Match.
// Returns an error if the path has invalid pattern.
func AddBypassPath(path string) error {
byPassMutex.Lock()
defer byPassMutex.Unlock()
if err := validatePath(path); err != nil {
return fmt.Errorf("validate: %w", err)
}
bypassPaths[path] = struct{}{}
return nil
}
// RemovePath removes a path from the list of paths that bypass middleware.
func RemovePath(path string) {
byPassMutex.Lock()
defer byPassMutex.Unlock()
delete(bypassPaths, path)
}
// GetList returns a list of all bypass paths.
func GetList() []string {
byPassMutex.RLock()
defer byPassMutex.RUnlock()
list := make([]string, 0, len(bypassPaths))
for k := range bypassPaths {
list = append(list, k)
}
return list
}
// ShouldBypass checks if the request path is one of the auth bypass paths and returns true if the middleware should be bypassed.
// This can be used to bypass authz/authn middlewares for certain paths, such as webhooks that implement their own authentication.
func ShouldBypass(requestPath string, h http.Handler, w http.ResponseWriter, r *http.Request) bool {
byPassMutex.RLock()
defer byPassMutex.RUnlock()
for bypassPath := range bypassPaths {
matched, err := path.Match(bypassPath, requestPath)
if err != nil {
log.Errorf("Error matching path %s with %s from %s: %v", bypassPath, requestPath, GetList(), err)
continue
}
if matched {
h.ServeHTTP(w, r)
return true
}
}
return false
}
func validatePath(p string) error {
_, err := path.Match(p, "")
return err
}

View File

@@ -0,0 +1,131 @@
package bypass_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/management/server/http/middleware/bypass"
)
func TestGetList(t *testing.T) {
bypassPaths := []string{"/path1", "/path2", "/path3"}
for _, path := range bypassPaths {
err := bypass.AddBypassPath(path)
require.NoError(t, err, "Adding bypass path should not fail")
}
list := bypass.GetList()
assert.ElementsMatch(t, bypassPaths, list, "Bypass path list did not match expected paths")
}
func TestAuthBypass(t *testing.T) {
dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
tests := []struct {
name string
pathToAdd string
pathToRemove string
testPath string
expectBypass bool
expectHTTPCode int
}{
{
name: "Path added to bypass",
pathToAdd: "/bypass",
testPath: "/bypass",
expectBypass: true,
expectHTTPCode: http.StatusOK,
},
{
name: "Wildcard path added to bypass",
pathToAdd: "/bypass/*",
testPath: "/bypass/extra",
expectBypass: true,
expectHTTPCode: http.StatusOK,
},
{
name: "Path not added to bypass",
testPath: "/no-bypass",
expectBypass: false,
expectHTTPCode: http.StatusOK,
},
{
name: "Path removed from bypass",
pathToAdd: "/remove-bypass",
pathToRemove: "/remove-bypass",
testPath: "/remove-bypass",
expectBypass: false,
expectHTTPCode: http.StatusOK,
},
{
name: "Exact path matches bypass",
pathToAdd: "/webhook",
testPath: "/webhook",
expectBypass: true,
expectHTTPCode: http.StatusOK,
},
{
name: "Subpath does not match bypass",
pathToAdd: "/webhook",
testPath: "/webhook/extra",
expectBypass: false,
expectHTTPCode: http.StatusOK,
},
{
name: "Wildcard subpath does not match bypass",
pathToAdd: "/webhook/*",
testPath: "/webhook/extra/path",
expectBypass: false,
expectHTTPCode: http.StatusOK,
},
{
name: "Similar path does not match bypass",
pathToAdd: "/webhook",
testPath: "/webhooking",
expectBypass: false,
expectHTTPCode: http.StatusOK,
},
{
name: "Prefix path does not match bypass",
pathToAdd: "/webhook",
testPath: "/web",
expectBypass: false,
expectHTTPCode: http.StatusOK,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if tc.pathToAdd != "" {
err := bypass.AddBypassPath(tc.pathToAdd)
require.NoError(t, err, "Adding bypass path should not fail")
defer bypass.RemovePath(tc.pathToAdd)
}
if tc.pathToRemove != "" {
bypass.RemovePath(tc.pathToRemove)
}
request, err := http.NewRequest("GET", tc.testPath, nil)
require.NoError(t, err, "Creating request should not fail")
recorder := httptest.NewRecorder()
bypassed := bypass.ShouldBypass(tc.testPath, dummyHandler, recorder, request)
assert.Equal(t, tc.expectBypass, bypassed, "Bypass check did not match expectation")
if tc.expectBypass {
assert.Equal(t, tc.expectHTTPCode, recorder.Code, "HTTP status code did not match expectation for bypassed path")
}
})
}
}

View File

@@ -3,7 +3,6 @@ package http
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net"
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@@ -231,44 +230,36 @@ func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMin
return groupsInfo return groupsInfo
} }
func connectionIPoString(ip net.IP) *string {
publicIP := ""
if ip != nil {
publicIP = ip.String()
}
return &publicIP
}
func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer { func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer {
osVersion := peer.Meta.OSVersion osVersion := peer.Meta.OSVersion
if osVersion == "" { if osVersion == "" {
osVersion = peer.Meta.Core osVersion = peer.Meta.Core
} }
geonameID := int(peer.Location.GeoNameID)
return &api.Peer{ return &api.Peer{
Id: peer.ID, Id: peer.ID,
Name: peer.Name, Name: peer.Name,
Ip: peer.IP.String(), Ip: peer.IP.String(),
ConnectionIp: connectionIPoString(peer.Location.ConnectionIP), ConnectionIp: peer.Location.ConnectionIP.String(),
Connected: peer.Status.Connected, Connected: peer.Status.Connected,
LastSeen: peer.Status.LastSeen, LastSeen: peer.Status.LastSeen,
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion), Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
KernelVersion: &peer.Meta.KernelVersion, KernelVersion: peer.Meta.KernelVersion,
GeonameId: &geonameID, GeonameId: int(peer.Location.GeoNameID),
Version: peer.Meta.WtVersion, Version: peer.Meta.WtVersion,
Groups: groupsInfo, Groups: groupsInfo,
SshEnabled: peer.SSHEnabled, SshEnabled: peer.SSHEnabled,
Hostname: peer.Meta.Hostname, Hostname: peer.Meta.Hostname,
UserId: &peer.UserID, UserId: peer.UserID,
UiVersion: &peer.Meta.UIVersion, UiVersion: peer.Meta.UIVersion,
DnsLabel: fqdn(peer, dnsDomain), DnsLabel: fqdn(peer, dnsDomain),
LoginExpirationEnabled: peer.LoginExpirationEnabled, LoginExpirationEnabled: peer.LoginExpirationEnabled,
LastLogin: peer.LastLogin, LastLogin: peer.LastLogin,
LoginExpired: peer.Status.LoginExpired, LoginExpired: peer.Status.LoginExpired,
AccessiblePeers: accessiblePeer, AccessiblePeers: accessiblePeer,
ApprovalRequired: &peer.Status.RequiresApproval, ApprovalRequired: &peer.Status.RequiresApproval,
CountryCode: &peer.Location.CountryCode, CountryCode: peer.Location.CountryCode,
CityName: &peer.Location.CityName, CityName: peer.Location.CityName,
} }
} }
@@ -277,31 +268,31 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn
if osVersion == "" { if osVersion == "" {
osVersion = peer.Meta.Core osVersion = peer.Meta.Core
} }
geonameID := int(peer.Location.GeoNameID)
return &api.PeerBatch{ return &api.PeerBatch{
Id: peer.ID, Id: peer.ID,
Name: peer.Name, Name: peer.Name,
Ip: peer.IP.String(), Ip: peer.IP.String(),
ConnectionIp: connectionIPoString(peer.Location.ConnectionIP), ConnectionIp: peer.Location.ConnectionIP.String(),
Connected: peer.Status.Connected, Connected: peer.Status.Connected,
LastSeen: peer.Status.LastSeen, LastSeen: peer.Status.LastSeen,
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion), Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
KernelVersion: &peer.Meta.KernelVersion, KernelVersion: peer.Meta.KernelVersion,
GeonameId: &geonameID, GeonameId: int(peer.Location.GeoNameID),
Version: peer.Meta.WtVersion, Version: peer.Meta.WtVersion,
Groups: groupsInfo, Groups: groupsInfo,
SshEnabled: peer.SSHEnabled, SshEnabled: peer.SSHEnabled,
Hostname: peer.Meta.Hostname, Hostname: peer.Meta.Hostname,
UserId: &peer.UserID, UserId: peer.UserID,
UiVersion: &peer.Meta.UIVersion, UiVersion: peer.Meta.UIVersion,
DnsLabel: fqdn(peer, dnsDomain), DnsLabel: fqdn(peer, dnsDomain),
LoginExpirationEnabled: peer.LoginExpirationEnabled, LoginExpirationEnabled: peer.LoginExpirationEnabled,
LastLogin: peer.LastLogin, LastLogin: peer.LastLogin,
LoginExpired: peer.Status.LoginExpired, LoginExpired: peer.Status.LoginExpired,
AccessiblePeersCount: accessiblePeersCount, AccessiblePeersCount: accessiblePeersCount,
ApprovalRequired: &peer.Status.RequiresApproval, ApprovalRequired: &peer.Status.RequiresApproval,
CountryCode: &peer.Location.CountryCode, CountryCode: peer.Location.CountryCode,
CityName: &peer.Location.CityName, CityName: peer.Location.CityName,
} }
} }

View File

@@ -3,6 +3,7 @@ package http
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"net/netip"
"regexp" "regexp"
"slices" "slices"
@@ -212,6 +213,14 @@ func (p *PostureChecksHandler) savePostureChecks(
postureChecks.Checks.GeoLocationCheck = toPostureGeoLocationCheck(geoLocationCheck) postureChecks.Checks.GeoLocationCheck = toPostureGeoLocationCheck(geoLocationCheck)
} }
if peerNetworkRangeCheck := req.Checks.PeerNetworkRangeCheck; peerNetworkRangeCheck != nil {
postureChecks.Checks.PeerNetworkRangeCheck, err = toPeerNetworkRangeCheck(peerNetworkRangeCheck)
if err != nil {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid network prefix"), w)
return
}
}
if err := p.accountManager.SavePostureChecks(account.Id, user.Id, &postureChecks); err != nil { if err := p.accountManager.SavePostureChecks(account.Id, user.Id, &postureChecks); err != nil {
util.WriteError(err, w) util.WriteError(err, w)
return return
@@ -226,7 +235,7 @@ func validatePostureChecksUpdate(req api.PostureCheckUpdate) error {
} }
if req.Checks == nil || (req.Checks.NbVersionCheck == nil && req.Checks.OsVersionCheck == nil && if req.Checks == nil || (req.Checks.NbVersionCheck == nil && req.Checks.OsVersionCheck == nil &&
req.Checks.GeoLocationCheck == nil) { req.Checks.GeoLocationCheck == nil && req.Checks.PeerNetworkRangeCheck == nil) {
return status.Errorf(status.InvalidArgument, "posture checks shouldn't be empty") return status.Errorf(status.InvalidArgument, "posture checks shouldn't be empty")
} }
@@ -267,7 +276,20 @@ func validatePostureChecksUpdate(req api.PostureCheckUpdate) error {
return status.Errorf(status.InvalidArgument, "country code must be 2 letters (ISO 3166-1 alpha-2 format)") return status.Errorf(status.InvalidArgument, "country code must be 2 letters (ISO 3166-1 alpha-2 format)")
} }
} }
}
if peerNetworkRangeCheck := req.Checks.PeerNetworkRangeCheck; peerNetworkRangeCheck != nil {
if peerNetworkRangeCheck.Action == "" {
return status.Errorf(status.InvalidArgument, "action for peer network range check shouldn't be empty")
}
allowedActions := []api.PeerNetworkRangeCheckAction{api.PeerNetworkRangeCheckActionAllow, api.PeerNetworkRangeCheckActionDeny}
if !slices.Contains(allowedActions, peerNetworkRangeCheck.Action) {
return status.Errorf(status.InvalidArgument, "action for peer network range check is not valid value")
}
if len(peerNetworkRangeCheck.Ranges) == 0 {
return status.Errorf(status.InvalidArgument, "network ranges for peer network range check shouldn't be empty")
}
} }
return nil return nil
@@ -296,6 +318,10 @@ func toPostureChecksResponse(postureChecks *posture.Checks) *api.PostureCheck {
checks.GeoLocationCheck = toGeoLocationCheckResponse(postureChecks.Checks.GeoLocationCheck) checks.GeoLocationCheck = toGeoLocationCheckResponse(postureChecks.Checks.GeoLocationCheck)
} }
if postureChecks.Checks.PeerNetworkRangeCheck != nil {
checks.PeerNetworkRangeCheck = toPeerNetworkRangeCheckResponse(postureChecks.Checks.PeerNetworkRangeCheck)
}
return &api.PostureCheck{ return &api.PostureCheck{
Id: postureChecks.ID, Id: postureChecks.ID,
Name: postureChecks.Name, Name: postureChecks.Name,
@@ -342,3 +368,31 @@ func toPostureGeoLocationCheck(apiGeoLocationCheck *api.GeoLocationCheck) *postu
Locations: locations, Locations: locations,
} }
} }
func toPeerNetworkRangeCheckResponse(check *posture.PeerNetworkRangeCheck) *api.PeerNetworkRangeCheck {
netPrefixes := make([]string, 0, len(check.Ranges))
for _, netPrefix := range check.Ranges {
netPrefixes = append(netPrefixes, netPrefix.String())
}
return &api.PeerNetworkRangeCheck{
Ranges: netPrefixes,
Action: api.PeerNetworkRangeCheckAction(check.Action),
}
}
func toPeerNetworkRangeCheck(check *api.PeerNetworkRangeCheck) (*posture.PeerNetworkRangeCheck, error) {
prefixes := make([]netip.Prefix, 0)
for _, prefix := range check.Ranges {
parsedPrefix, err := netip.ParsePrefix(prefix)
if err != nil {
return nil, err
}
prefixes = append(prefixes, parsedPrefix)
}
return &posture.PeerNetworkRangeCheck{
Ranges: prefixes,
Action: string(check.Action),
}, nil
}

View File

@@ -6,6 +6,7 @@ import (
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/netip"
"strings" "strings"
"testing" "testing"
@@ -122,7 +123,19 @@ func TestGetPostureCheck(t *testing.T) {
CityName: "Berlin", CityName: "Berlin",
}, },
}, },
Action: posture.GeoLocationActionAllow, Action: posture.CheckActionAllow,
},
},
}
privateNetworkCheck := &posture.Checks{
ID: "privateNetworkPostureCheck",
Name: "privateNetwork",
Checks: posture.ChecksDefinition{
PeerNetworkRangeCheck: &posture.PeerNetworkRangeCheck{
Ranges: []netip.Prefix{
netip.MustParsePrefix("192.168.0.0/24"),
},
Action: posture.CheckActionAllow,
}, },
}, },
} }
@@ -156,6 +169,13 @@ func TestGetPostureCheck(t *testing.T) {
checkName: geoPostureCheck.Name, checkName: geoPostureCheck.Name,
expectedStatus: http.StatusOK, expectedStatus: http.StatusOK,
}, },
{
name: "GetPostureCheck PrivateNetwork OK",
expectedBody: true,
id: privateNetworkCheck.ID,
checkName: privateNetworkCheck.Name,
expectedStatus: http.StatusOK,
},
{ {
name: "GetPostureCheck Not Found", name: "GetPostureCheck Not Found",
id: "not-exists", id: "not-exists",
@@ -163,7 +183,7 @@ func TestGetPostureCheck(t *testing.T) {
}, },
} }
p := initPostureChecksTestData(postureCheck, osPostureCheck, geoPostureCheck) p := initPostureChecksTestData(postureCheck, osPostureCheck, geoPostureCheck, privateNetworkCheck)
for _, tc := range tt { for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
@@ -354,6 +374,39 @@ func TestPostureCheckUpdate(t *testing.T) {
}, },
}, },
}, },
{
name: "Create Posture Checks Peer Network Range",
requestType: http.MethodPost,
requestPath: "/api/posture-checks",
requestBody: bytes.NewBuffer(
[]byte(`{
"name": "default",
"description": "default",
"checks": {
"peer_network_range_check": {
"action": "allow",
"ranges": [
"10.0.0.0/8"
]
}
}
}`)),
expectedStatus: http.StatusOK,
expectedBody: true,
expectedPostureCheck: &api.PostureCheck{
Id: "postureCheck",
Name: "default",
Description: str("default"),
Checks: api.Checks{
PeerNetworkRangeCheck: &api.PeerNetworkRangeCheck{
Ranges: []string{
"10.0.0.0/8",
},
Action: api.PeerNetworkRangeCheckActionAllow,
},
},
},
},
{ {
name: "Create Posture Checks Geo Location with No geolocation DB", name: "Create Posture Checks Geo Location with No geolocation DB",
requestType: http.MethodPost, requestType: http.MethodPost,
@@ -661,6 +714,38 @@ func TestPostureCheckUpdate(t *testing.T) {
expectedStatus: http.StatusBadRequest, expectedStatus: http.StatusBadRequest,
expectedBody: false, expectedBody: false,
}, },
{
name: "Update Posture Checks Peer Network Range",
requestType: http.MethodPut,
requestPath: "/api/posture-checks/peerNetworkRangePostureCheck",
requestBody: bytes.NewBuffer(
[]byte(`{
"name": "default",
"checks": {
"peer_network_range_check": {
"action": "deny",
"ranges": [
"192.168.1.0/24"
]
}
}
}`)),
expectedStatus: http.StatusOK,
expectedBody: true,
expectedPostureCheck: &api.PostureCheck{
Id: "postureCheck",
Name: "default",
Description: str(""),
Checks: api.Checks{
PeerNetworkRangeCheck: &api.PeerNetworkRangeCheck{
Ranges: []string{
"192.168.1.0/24",
},
Action: api.PeerNetworkRangeCheckActionDeny,
},
},
},
},
} }
p := initPostureChecksTestData(&posture.Checks{ p := initPostureChecksTestData(&posture.Checks{
@@ -694,7 +779,19 @@ func TestPostureCheckUpdate(t *testing.T) {
CityName: "Berlin", CityName: "Berlin",
}, },
}, },
Action: posture.GeoLocationActionDeny, Action: posture.CheckActionDeny,
},
},
},
&posture.Checks{
ID: "peerNetworkRangePostureCheck",
Name: "peerNetworkRange",
Checks: posture.ChecksDefinition{
PeerNetworkRangeCheck: &posture.PeerNetworkRangeCheck{
Ranges: []netip.Prefix{
netip.MustParsePrefix("192.168.0.0/24"),
},
Action: posture.CheckActionAllow,
}, },
}, },
}, },
@@ -793,4 +890,51 @@ func TestPostureCheck_validatePostureChecksUpdate(t *testing.T) {
} }
err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{OsVersionCheck: &osVersionCheck}}) err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{OsVersionCheck: &osVersionCheck}})
assert.NoError(t, err) assert.NoError(t, err)
// valid peer network range check
peerNetworkRangeCheck := api.PeerNetworkRangeCheck{
Action: api.PeerNetworkRangeCheckActionAllow,
Ranges: []string{
"192.168.1.0/24", "10.0.0.0/8",
},
}
err = validatePostureChecksUpdate(
api.PostureCheckUpdate{
Name: "Default",
Checks: &api.Checks{
PeerNetworkRangeCheck: &peerNetworkRangeCheck,
},
},
)
assert.NoError(t, err)
// invalid peer network range check
peerNetworkRangeCheck = api.PeerNetworkRangeCheck{
Action: api.PeerNetworkRangeCheckActionDeny,
Ranges: []string{},
}
err = validatePostureChecksUpdate(
api.PostureCheckUpdate{
Name: "Default",
Checks: &api.Checks{
PeerNetworkRangeCheck: &peerNetworkRangeCheck,
},
},
)
assert.Error(t, err)
// invalid peer network range check
peerNetworkRangeCheck = api.PeerNetworkRangeCheck{
Action: "unknownAction",
Ranges: []string{},
}
err = validatePostureChecksUpdate(
api.PostureCheckUpdate{
Name: "Default",
Checks: &api.Checks{
PeerNetworkRangeCheck: &peerNetworkRangeCheck,
},
},
)
assert.Error(t, err)
} }

View File

@@ -114,6 +114,22 @@ type auth0Profile struct {
LastLogin string `json:"last_login"` LastLogin string `json:"last_login"`
} }
// Connections represents a single Auth0 connection
// https://auth0.com/docs/api/management/v2/connections/get-connections
type Connection struct {
Id string `json:"id"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
IsDomainConnection bool `json:"is_domain_connection"`
Realms []string `json:"realms"`
Metadata map[string]string `json:"metadata"`
Options ConnectionOptions `json:"options"`
}
type ConnectionOptions struct {
DomainAliases []string `json:"domain_aliases"`
}
// NewAuth0Manager creates a new instance of the Auth0Manager // NewAuth0Manager creates a new instance of the Auth0Manager
func NewAuth0Manager(config Auth0ClientConfig, appMetrics telemetry.AppMetrics) (*Auth0Manager, error) { func NewAuth0Manager(config Auth0ClientConfig, appMetrics telemetry.AppMetrics) (*Auth0Manager, error) {
httpTransport := http.DefaultTransport.(*http.Transport).Clone() httpTransport := http.DefaultTransport.(*http.Transport).Clone()
@@ -581,13 +597,13 @@ func (am *Auth0Manager) GetAllAccounts() (map[string][]*UserData, error) {
body, err := io.ReadAll(jobResp.Body) body, err := io.ReadAll(jobResp.Body)
if err != nil { if err != nil {
log.Debugf("Coudln't read export job response; %v", err) log.Debugf("Couldn't read export job response; %v", err)
return nil, err return nil, err
} }
err = am.helper.Unmarshal(body, &exportJobResp) err = am.helper.Unmarshal(body, &exportJobResp)
if err != nil { if err != nil {
log.Debugf("Coudln't unmarshal export job response; %v", err) log.Debugf("Couldn't unmarshal export job response; %v", err)
return nil, err return nil, err
} }
@@ -635,7 +651,7 @@ func (am *Auth0Manager) GetUserByEmail(email string) ([]*UserData, error) {
err = am.helper.Unmarshal(body, &userResp) err = am.helper.Unmarshal(body, &userResp)
if err != nil { if err != nil {
log.Debugf("Coudln't unmarshal export job response; %v", err) log.Debugf("Couldn't unmarshal export job response; %v", err)
return nil, err return nil, err
} }
@@ -684,13 +700,13 @@ func (am *Auth0Manager) CreateUser(email, name, accountID, invitedByEmail string
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
log.Debugf("Coudln't read export job response; %v", err) log.Debugf("Couldn't read export job response; %v", err)
return nil, err return nil, err
} }
err = am.helper.Unmarshal(body, &createResp) err = am.helper.Unmarshal(body, &createResp)
if err != nil { if err != nil {
log.Debugf("Coudln't unmarshal export job response; %v", err) log.Debugf("Couldn't unmarshal export job response; %v", err)
return nil, err return nil, err
} }
@@ -777,6 +793,56 @@ func (am *Auth0Manager) DeleteUser(userID string) error {
return nil return nil
} }
// GetAllConnections returns detailed list of all connections filtered by given params.
// Note this method is not part of the IDP Manager interface as this is Auth0 specific.
func (am *Auth0Manager) GetAllConnections(strategy []string) ([]Connection, error) {
var connections []Connection
q := make(url.Values)
q.Set("strategy", strings.Join(strategy, ","))
req, err := am.createRequest(http.MethodGet, "/api/v2/connections?"+q.Encode(), nil)
if err != nil {
return connections, err
}
resp, err := am.httpClient.Do(req)
if err != nil {
log.Debugf("execute get connections request: %v", err)
if am.appMetrics != nil {
am.appMetrics.IDPMetrics().CountRequestError()
}
return connections, err
}
defer func() {
err = resp.Body.Close()
if err != nil {
log.Errorf("close get connections request body: %v", err)
}
}()
if resp.StatusCode != 200 {
if am.appMetrics != nil {
am.appMetrics.IDPMetrics().CountRequestStatusError()
}
return connections, fmt.Errorf("unable to get connections, statusCode %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Debugf("Couldn't read get connections response; %v", err)
return connections, err
}
err = am.helper.Unmarshal(body, &connections)
if err != nil {
log.Debugf("Couldn't unmarshal get connection response; %v", err)
return connections, err
}
return connections, err
}
// 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) {

View File

@@ -10,6 +10,7 @@ import (
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"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"
"github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
nbpeer "github.com/netbirdio/netbird/management/server/peer" nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/posture" "github.com/netbirdio/netbird/management/server/posture"
@@ -75,7 +76,7 @@ type MockAccountManager struct {
CheckUserAccessByJWTGroupsFunc func(claims jwtclaims.AuthorizationClaims) error CheckUserAccessByJWTGroupsFunc func(claims jwtclaims.AuthorizationClaims) error
DeleteAccountFunc func(accountID, userID string) error DeleteAccountFunc func(accountID, userID string) error
GetDNSDomainFunc func() string GetDNSDomainFunc func() string
StoreEventFunc func(initiatorID, targetID, accountID string, activityID activity.Activity, meta map[string]any) StoreEventFunc func(initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any)
GetEventsFunc func(accountID, userID string) ([]*activity.Event, error) GetEventsFunc func(accountID, userID string) ([]*activity.Event, error)
GetDNSSettingsFunc func(accountID, userID string) (*server.DNSSettings, error) GetDNSSettingsFunc func(accountID, userID string) (*server.DNSSettings, error)
SaveDNSSettingsFunc func(accountID, userID string, dnsSettingsToSave *server.DNSSettings) error SaveDNSSettingsFunc func(accountID, userID string, dnsSettingsToSave *server.DNSSettings) error
@@ -91,6 +92,7 @@ type MockAccountManager struct {
SavePostureChecksFunc func(accountID, userID string, postureChecks *posture.Checks) error SavePostureChecksFunc func(accountID, userID string, postureChecks *posture.Checks) error
DeletePostureChecksFunc func(accountID, postureChecksID, userID string) error DeletePostureChecksFunc func(accountID, postureChecksID, userID string) error
ListPostureChecksFunc func(accountID, userID string) ([]*posture.Checks, error) ListPostureChecksFunc func(accountID, userID string) ([]*posture.Checks, error)
GetIdpManagerFunc func() idp.Manager
} }
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface // GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
@@ -646,7 +648,7 @@ func (am *MockAccountManager) GetAllConnectedPeers() (map[string]struct{}, error
return nil, status.Errorf(codes.Unimplemented, "method GetAllConnectedPeers is not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetAllConnectedPeers is not implemented")
} }
// HasconnectedChannel mocks HasConnectedChannel of the AccountManager interface // HasConnectedChannel mocks HasConnectedChannel of the AccountManager interface
func (am *MockAccountManager) HasConnectedChannel(peerID string) bool { func (am *MockAccountManager) HasConnectedChannel(peerID string) bool {
if am.HasConnectedChannelFunc != nil { if am.HasConnectedChannelFunc != nil {
return am.HasConnectedChannelFunc(peerID) return am.HasConnectedChannelFunc(peerID)
@@ -655,7 +657,7 @@ func (am *MockAccountManager) HasConnectedChannel(peerID string) bool {
} }
// StoreEvent mocks StoreEvent of the AccountManager interface // StoreEvent mocks StoreEvent of the AccountManager interface
func (am *MockAccountManager) StoreEvent(initiatorID, targetID, accountID string, activityID activity.Activity, meta map[string]any) { func (am *MockAccountManager) StoreEvent(initiatorID, targetID, accountID string, activityID activity.ActivityDescriber, meta map[string]any) {
if am.StoreEventFunc != nil { if am.StoreEventFunc != nil {
am.StoreEventFunc(initiatorID, targetID, accountID, activityID, meta) am.StoreEventFunc(initiatorID, targetID, accountID, activityID, meta)
} }
@@ -702,3 +704,11 @@ func (am *MockAccountManager) ListPostureChecks(accountID, userID string) ([]*po
} }
return nil, status.Errorf(codes.Unimplemented, "method ListPostureChecks is not implemented") return nil, status.Errorf(codes.Unimplemented, "method ListPostureChecks is not implemented")
} }
// GetIdpManager mocks GetIdpManager of the AccountManager interface
func (am *MockAccountManager) GetIdpManager() idp.Manager {
if am.GetIdpManagerFunc != nil {
return am.GetIdpManagerFunc()
}
return nil
}

View File

@@ -410,6 +410,8 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
return nil, nil, err return nil, nil, err
} }
registrationTime := time.Now().UTC()
newPeer := &nbpeer.Peer{ newPeer := &nbpeer.Peer{
ID: xid.New().String(), ID: xid.New().String(),
Key: peer.Key, Key: peer.Key,
@@ -419,10 +421,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
Name: peer.Meta.Hostname, Name: peer.Meta.Hostname,
DNSLabel: newLabel, DNSLabel: newLabel,
UserID: userID, UserID: userID,
Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, Status: &nbpeer.PeerStatus{Connected: false, LastSeen: registrationTime},
SSHEnabled: false, SSHEnabled: false,
SSHKey: peer.SSHKey, SSHKey: peer.SSHKey,
LastLogin: time.Now().UTC(), LastLogin: registrationTime,
CreatedAt: registrationTime,
LoginExpirationEnabled: addedByUser, LoginExpirationEnabled: addedByUser,
Ephemeral: ephemeral, Ephemeral: ephemeral,
} }

View File

@@ -40,13 +40,15 @@ type Peer struct {
LoginExpirationEnabled bool LoginExpirationEnabled bool
// LastLogin the time when peer performed last login operation // LastLogin the time when peer performed last login operation
LastLogin time.Time LastLogin time.Time
// CreatedAt records the time the peer was created
CreatedAt time.Time
// Indicate ephemeral peer attribute // Indicate ephemeral peer attribute
Ephemeral bool Ephemeral bool
// Geo location based on connection IP // Geo location based on connection IP
Location Location `gorm:"embedded;embeddedPrefix:location_"` Location Location `gorm:"embedded;embeddedPrefix:location_"`
} }
type PeerStatus struct { type PeerStatus struct { //nolint:revive
// LastSeen is the last time peer was connected to the management service // LastSeen is the last time peer was connected to the management service
LastSeen time.Time LastSeen time.Time
// Connected indicates whether peer is connected to the management service or not // Connected indicates whether peer is connected to the management service or not
@@ -71,22 +73,32 @@ type NetworkAddress struct {
Mac string Mac string
} }
// Environment is a system environment information
type Environment struct {
Cloud string
Platform string
}
// PeerSystemMeta is a metadata of a Peer machine system // PeerSystemMeta is a metadata of a Peer machine system
type PeerSystemMeta struct { type PeerSystemMeta struct { //nolint:revive
Hostname string Hostname string
GoOS string GoOS string
Kernel string Kernel string
Core string Core string
Platform string Platform string
OS string OS string
OSVersion string OSVersion string
WtVersion string WtVersion string
UIVersion string UIVersion string
KernelVersion string KernelVersion string
NetworkAddresses []NetworkAddress `gorm:"serializer:json"` NetworkAddresses []NetworkAddress `gorm:"serializer:json"`
SystemSerialNumber string SystemSerialNumber string
SystemProductName string SystemProductName string
SystemManufacturer string SystemManufacturer string
Environment Environment `gorm:"serializer:json"`
RosenpassEnabled bool
RosenpassPermissive bool
ServerSSHAllowed bool
} }
func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool { func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
@@ -119,7 +131,12 @@ func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
p.UIVersion == other.UIVersion && p.UIVersion == other.UIVersion &&
p.SystemSerialNumber == other.SystemSerialNumber && p.SystemSerialNumber == other.SystemSerialNumber &&
p.SystemProductName == other.SystemProductName && p.SystemProductName == other.SystemProductName &&
p.SystemManufacturer == other.SystemManufacturer p.SystemManufacturer == other.SystemManufacturer &&
p.Environment.Cloud == other.Environment.Cloud &&
p.Environment.Platform == other.Environment.Platform &&
p.RosenpassEnabled == other.RosenpassEnabled &&
p.RosenpassPermissive == other.RosenpassPermissive &&
p.ServerSSHAllowed == other.ServerSSHAllowed
} }
// AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user. // AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user.
@@ -148,6 +165,7 @@ func (p *Peer) Copy() *Peer {
SSHEnabled: p.SSHEnabled, SSHEnabled: p.SSHEnabled,
LoginExpirationEnabled: p.LoginExpirationEnabled, LoginExpirationEnabled: p.LoginExpirationEnabled,
LastLogin: p.LastLogin, LastLogin: p.LastLogin,
CreatedAt: p.CreatedAt,
Ephemeral: p.Ephemeral, Ephemeral: p.Ephemeral,
Location: p.Location, Location: p.Location,
} }
@@ -204,7 +222,7 @@ func (p *Peer) FQDN(dnsDomain string) string {
// EventMeta returns activity event meta related to the peer // EventMeta returns activity event meta related to the peer
func (p *Peer) EventMeta(dnsDomain string) map[string]any { func (p *Peer) EventMeta(dnsDomain string) map[string]any {
return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP} return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP, "created_at": p.CreatedAt}
} }
// Copy PeerStatus // Copy PeerStatus

View File

@@ -2,6 +2,7 @@ package posture
import ( import (
"fmt" "fmt"
"net/netip"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
@@ -9,9 +10,13 @@ import (
) )
const ( const (
NBVersionCheckName = "NBVersionCheck" NBVersionCheckName = "NBVersionCheck"
OSVersionCheckName = "OSVersionCheck" OSVersionCheckName = "OSVersionCheck"
GeoLocationCheckName = "GeoLocationCheck" GeoLocationCheckName = "GeoLocationCheck"
PeerNetworkRangeCheckName = "PeerNetworkRangeCheck"
CheckActionAllow string = "allow"
CheckActionDeny string = "deny"
) )
// Check represents an interface for performing a check on a peer. // Check represents an interface for performing a check on a peer.
@@ -39,9 +44,10 @@ type Checks struct {
// ChecksDefinition contains definition of actual check // ChecksDefinition contains definition of actual check
type ChecksDefinition struct { type ChecksDefinition struct {
NBVersionCheck *NBVersionCheck `json:",omitempty"` NBVersionCheck *NBVersionCheck `json:",omitempty"`
OSVersionCheck *OSVersionCheck `json:",omitempty"` OSVersionCheck *OSVersionCheck `json:",omitempty"`
GeoLocationCheck *GeoLocationCheck `json:",omitempty"` GeoLocationCheck *GeoLocationCheck `json:",omitempty"`
PeerNetworkRangeCheck *PeerNetworkRangeCheck `json:",omitempty"`
} }
// Copy returns a copy of a checks definition. // Copy returns a copy of a checks definition.
@@ -54,7 +60,7 @@ func (cd ChecksDefinition) Copy() ChecksDefinition {
} }
if cd.OSVersionCheck != nil { if cd.OSVersionCheck != nil {
cdCopy.OSVersionCheck = &OSVersionCheck{} cdCopy.OSVersionCheck = &OSVersionCheck{}
osCheck := cdCopy.OSVersionCheck osCheck := cd.OSVersionCheck
if osCheck.Android != nil { if osCheck.Android != nil {
cdCopy.OSVersionCheck.Android = &MinVersionCheck{MinVersion: osCheck.Android.MinVersion} cdCopy.OSVersionCheck.Android = &MinVersionCheck{MinVersion: osCheck.Android.MinVersion}
} }
@@ -79,6 +85,14 @@ func (cd ChecksDefinition) Copy() ChecksDefinition {
} }
copy(cdCopy.GeoLocationCheck.Locations, geoCheck.Locations) copy(cdCopy.GeoLocationCheck.Locations, geoCheck.Locations)
} }
if cd.PeerNetworkRangeCheck != nil {
peerNetRangeCheck := cd.PeerNetworkRangeCheck
cdCopy.PeerNetworkRangeCheck = &PeerNetworkRangeCheck{
Action: peerNetRangeCheck.Action,
Ranges: make([]netip.Prefix, len(peerNetRangeCheck.Ranges)),
}
copy(cdCopy.PeerNetworkRangeCheck.Ranges, peerNetRangeCheck.Ranges)
}
return cdCopy return cdCopy
} }
@@ -116,6 +130,9 @@ func (pc *Checks) GetChecks() []Check {
if pc.Checks.GeoLocationCheck != nil { if pc.Checks.GeoLocationCheck != nil {
checks = append(checks, pc.Checks.GeoLocationCheck) checks = append(checks, pc.Checks.GeoLocationCheck)
} }
if pc.Checks.PeerNetworkRangeCheck != nil {
checks = append(checks, pc.Checks.PeerNetworkRangeCheck)
}
return checks return checks
} }

View File

@@ -2,6 +2,7 @@ package posture
import ( import (
"encoding/json" "encoding/json"
"net/netip"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -216,3 +217,62 @@ func TestChecks_Validate(t *testing.T) {
}) })
} }
} }
func TestChecks_Copy(t *testing.T) {
check := &Checks{
ID: "1",
Name: "default",
Description: "description",
AccountID: "accountID",
Checks: ChecksDefinition{
NBVersionCheck: &NBVersionCheck{
MinVersion: "0.25.0",
},
OSVersionCheck: &OSVersionCheck{
Android: &MinVersionCheck{
MinVersion: "13",
},
Darwin: &MinVersionCheck{
MinVersion: "14.2.0",
},
Ios: &MinVersionCheck{
MinVersion: "17.3.0",
},
Linux: &MinKernelVersionCheck{
MinKernelVersion: "6.5.11-linuxkit",
},
Windows: &MinKernelVersionCheck{
MinKernelVersion: "10.0.14393",
},
},
GeoLocationCheck: &GeoLocationCheck{
Locations: []Location{
{
CountryCode: "DE",
CityName: "Berlin",
},
},
Action: CheckActionAllow,
},
PeerNetworkRangeCheck: &PeerNetworkRangeCheck{
Ranges: []netip.Prefix{
netip.MustParsePrefix("192.168.0.0/24"),
netip.MustParsePrefix("10.0.0.0/8"),
},
Action: CheckActionDeny,
},
},
}
checkCopy := check.Copy()
assert.Equal(t, check.ID, checkCopy.ID)
assert.Equal(t, check.Name, checkCopy.Name)
assert.Equal(t, check.Description, checkCopy.Description)
assert.Equal(t, check.AccountID, checkCopy.AccountID)
assert.Equal(t, check.Checks.Copy(), checkCopy.Checks.Copy())
assert.ElementsMatch(t, check.GetChecks(), checkCopy.GetChecks())
// Updating the original check should not take effect on copy
check.Name = "name"
assert.NotSame(t, check, checkCopy)
}

Some files were not shown because too many files have changed in this diff Show More