mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-01 12:19:56 +00:00
Compare commits
96 Commits
v0.11.2
...
feature/ev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
213ab7d43e | ||
|
|
b1cc1b880e | ||
|
|
7cf9ab71f2 | ||
|
|
6143aeef35 | ||
|
|
afacac174c | ||
|
|
fea4336bca | ||
|
|
0e5cfcf9ce | ||
|
|
37b22de660 | ||
|
|
0600df1217 | ||
|
|
9e95c82a98 | ||
|
|
22934ac640 | ||
|
|
ad2ac7a383 | ||
|
|
86f731a76b | ||
|
|
7e0237d2cb | ||
|
|
6bc2fb2d74 | ||
|
|
81ad6db431 | ||
|
|
59eac3cb63 | ||
|
|
fa9007bc04 | ||
|
|
fbd03e8ec2 | ||
|
|
1a0e2c6d30 | ||
|
|
4f9e047e7e | ||
|
|
e750a1a40e | ||
|
|
4105a1a24b | ||
|
|
bfe84fd6e8 | ||
|
|
91b61efe60 | ||
|
|
747192c9f8 | ||
|
|
dbda9087f5 | ||
|
|
599c823750 | ||
|
|
5cb2dc676b | ||
|
|
cc12aff3a4 | ||
|
|
647f726737 | ||
|
|
1174141abc | ||
|
|
26f721179b | ||
|
|
a74b4c4b42 | ||
|
|
6733e9d51e | ||
|
|
1cf041fe07 | ||
|
|
94d5d499ac | ||
|
|
1a7f441652 | ||
|
|
908eb4d051 | ||
|
|
e12b74760c | ||
|
|
31fda2b062 | ||
|
|
7a3c6b576c | ||
|
|
b7f4a6f098 | ||
|
|
cafd10b3a3 | ||
|
|
50caacff69 | ||
|
|
359b2a13a7 | ||
|
|
a32bc441d2 | ||
|
|
ef171ebdd4 | ||
|
|
f485b654b4 | ||
|
|
d18966276a | ||
|
|
ca291a0fd6 | ||
|
|
6b32e2dc07 | ||
|
|
c0a62b6ddc | ||
|
|
7dfef091bb | ||
|
|
3d6b0e3638 | ||
|
|
c50d07b83f | ||
|
|
4440ad1271 | ||
|
|
f684baa665 | ||
|
|
cf27bd8951 | ||
|
|
739afea4b4 | ||
|
|
2dec9989c6 | ||
|
|
5d544ae55e | ||
|
|
94803417cf | ||
|
|
1835dd3e3c | ||
|
|
cfed36ff8d | ||
|
|
a212686193 | ||
|
|
93fcfeae91 | ||
|
|
6f610dca89 | ||
|
|
eec24fc730 | ||
|
|
1204bbd54a | ||
|
|
0be46c083d | ||
|
|
0fbfec4ce4 | ||
|
|
e741775d4d | ||
|
|
04ea300043 | ||
|
|
e75c25b10d | ||
|
|
e81f4af3e8 | ||
|
|
c09c3a70ab | ||
|
|
0e3bbf0e55 | ||
|
|
0f4d132b93 | ||
|
|
ef97e0840c | ||
|
|
d98a8e0645 | ||
|
|
1c1e639445 | ||
|
|
65afc9a884 | ||
|
|
d43f0200a6 | ||
|
|
16e4d109ff | ||
|
|
a387e3cfc2 | ||
|
|
0f68edbefb | ||
|
|
d1b7c23b19 | ||
|
|
d2d5d4b4b9 | ||
|
|
d029136d3d | ||
|
|
a6d2f673ad | ||
|
|
0cf0dc048b | ||
|
|
5ade879e31 | ||
|
|
a814715ef8 | ||
|
|
4a30b66503 | ||
|
|
ae500b63a7 |
30
.github/ISSUE_TEMPLATE/bug-issue-report.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE/bug-issue-report.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: Bug/Issue report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the problem**
|
||||
A clear and concise description of what the problem is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**NetBird status -d output:**
|
||||
If applicable, add the output of the `netbird status -d` command
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
10
.github/pull_request_template.md
vendored
Normal file
10
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
## Describe your changes
|
||||
|
||||
## Issue ticket number and link
|
||||
|
||||
### Checklist
|
||||
- [ ] Is it a bug fix
|
||||
- [ ] Is a typo/documentation fix
|
||||
- [ ] Is a feature enhancement
|
||||
- [ ] Created tests that fail without the change (if possible)
|
||||
- [ ] Extended the README / documentation, if necessary
|
||||
2
.github/workflows/golang-test-darwin.yml
vendored
2
.github/workflows/golang-test-darwin.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
|
||||
10
.github/workflows/golang-test-linux.yml
vendored
10
.github/workflows/golang-test-linux.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
|
||||
|
||||
- name: Cache Go modules
|
||||
@@ -31,13 +31,13 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev
|
||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib
|
||||
|
||||
- name: Install modules
|
||||
run: go mod tidy
|
||||
|
||||
- name: Test
|
||||
run: GOARCH=${{ matrix.arch }} go test -exec 'sudo --preserve-env=CI' -timeout 5m -p 1 ./...
|
||||
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} go test -exec 'sudo --preserve-env=CI' -timeout 5m -p 1 ./...
|
||||
|
||||
test_client_on_docker:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
|
||||
|
||||
- name: Cache Go modules
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev
|
||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev
|
||||
|
||||
- name: Install modules
|
||||
run: go mod tidy
|
||||
|
||||
2
.github/workflows/golang-test-windows.yml
vendored
2
.github/workflows/golang-test-windows.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
|
||||
9
.github/workflows/golangci-lint.yml
vendored
9
.github/workflows/golangci-lint.yml
vendored
@@ -9,13 +9,10 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
- name: Install dependencies
|
||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev
|
||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
# SA1019: "io/ioutil" has been deprecated since Go 1.16
|
||||
args: --timeout=6m -e SA1019
|
||||
|
||||
|
||||
args: --timeout=6m
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
-
|
||||
name: Cache Go modules
|
||||
uses: actions/cache@v1
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
run: git --no-pager diff --exit-code
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-mingw-w64-x86-64
|
||||
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-mingw-w64-x86-64
|
||||
- name: Install rsrc
|
||||
run: go install github.com/akavel/rsrc@v0.10.2
|
||||
- name: Generate windows rsrc
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: 1.19
|
||||
-
|
||||
name: Cache Go modules
|
||||
uses: actions/cache@v1
|
||||
|
||||
12
.github/workflows/test-docker-compose-linux.yml
vendored
12
.github/workflows/test-docker-compose-linux.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.x
|
||||
go-version: 1.19.x
|
||||
|
||||
- name: Cache Go modules
|
||||
uses: actions/cache@v2
|
||||
@@ -39,7 +39,8 @@ jobs:
|
||||
working-directory: infrastructure_files
|
||||
run: bash -x configure.sh
|
||||
env:
|
||||
CI_NETBIRD_AUTH_CLIENT_ID: ${{ secrets.CI_NETBIRD_AUTH_CLIENT_ID }}
|
||||
CI_NETBIRD_DOMAIN: localhost
|
||||
CI_NETBIRD_AUTH_CLIENT_ID: testing.client.id
|
||||
CI_NETBIRD_AUTH_AUDIENCE: testing.ci
|
||||
CI_NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT: https://example.eu.auth0.com/.well-known/openid-configuration
|
||||
CI_NETBIRD_USE_AUTH0: true
|
||||
@@ -47,7 +48,8 @@ jobs:
|
||||
- name: check values
|
||||
working-directory: infrastructure_files
|
||||
env:
|
||||
CI_NETBIRD_AUTH_CLIENT_ID: ${{ secrets.CI_NETBIRD_AUTH_CLIENT_ID }}
|
||||
CI_NETBIRD_DOMAIN: localhost
|
||||
CI_NETBIRD_AUTH_CLIENT_ID: testing.client.id
|
||||
CI_NETBIRD_AUTH_AUDIENCE: testing.ci
|
||||
CI_NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT: https://example.eu.auth0.com/.well-known/openid-configuration
|
||||
CI_NETBIRD_USE_AUTH0: true
|
||||
@@ -63,7 +65,7 @@ jobs:
|
||||
grep AUTH_AUDIENCE docker-compose.yml | grep $CI_NETBIRD_AUTH_AUDIENCE
|
||||
grep AUTH_SUPPORTED_SCOPES docker-compose.yml | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES"
|
||||
grep USE_AUTH0 docker-compose.yml | grep $CI_NETBIRD_USE_AUTH0
|
||||
grep NETBIRD_MGMT_API_ENDPOINT docker-compose.yml | grep "http://localhost:33073"
|
||||
grep NETBIRD_MGMT_API_ENDPOINT docker-compose.yml | grep "$CI_NETBIRD_DOMAIN:33073"
|
||||
grep AUTH_REDIRECT_URI docker-compose.yml | grep $CI_NETBIRD_AUTH_REDIRECT_URI
|
||||
grep AUTH_SILENT_REDIRECT_URI docker-compose.yml | egrep 'AUTH_SILENT_REDIRECT_URI=$'
|
||||
|
||||
@@ -72,6 +74,8 @@ jobs:
|
||||
run: |
|
||||
docker-compose up -d
|
||||
sleep 5
|
||||
docker-compose ps
|
||||
docker-compose logs --tail=20
|
||||
|
||||
- name: test running containers
|
||||
run: |
|
||||
|
||||
@@ -74,11 +74,6 @@ nfpms:
|
||||
formats:
|
||||
- deb
|
||||
|
||||
replaces:
|
||||
- wiretrustee
|
||||
conflicts:
|
||||
- wiretrustee
|
||||
|
||||
scripts:
|
||||
postinstall: "release_files/post_install.sh"
|
||||
preremove: "release_files/pre_remove.sh"
|
||||
@@ -93,12 +88,6 @@ nfpms:
|
||||
formats:
|
||||
- rpm
|
||||
|
||||
replaces:
|
||||
- wiretrustee
|
||||
|
||||
conflicts:
|
||||
- wiretrustee
|
||||
|
||||
scripts:
|
||||
postinstall: "release_files/post_install.sh"
|
||||
preremove: "release_files/pre_remove.sh"
|
||||
@@ -348,8 +337,6 @@ brews:
|
||||
license: "BSD3"
|
||||
test: |
|
||||
system "#{bin}/{{ .ProjectName }} version"
|
||||
conflicts:
|
||||
- wiretrustee
|
||||
|
||||
uploads:
|
||||
- name: debian
|
||||
|
||||
@@ -60,7 +60,7 @@ representative at an online or offline event.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
dev@wiretrustee.com.
|
||||
community@netbird.io.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
|
||||
218
CONTRIBUTING.md
Normal file
218
CONTRIBUTING.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# Contributing to NetBird
|
||||
|
||||
Thanks for your interest in contributing to NetBird.
|
||||
|
||||
There are many ways that you can contribute:
|
||||
- Reporting issues
|
||||
- Updating documentation
|
||||
- Sharing use cases in slack or Reddit
|
||||
- Bug fix or feature enhancement
|
||||
|
||||
If you haven't already, join our slack workspace [here](https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A), we would love to discuss topics that need community contribution and enhancements to existing features.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Contributing to NetBird](#contributing-to-netbird)
|
||||
- [Contents](#contents)
|
||||
- [Code of conduct](#code-of-conduct)
|
||||
- [Directory structure](#directory-structure)
|
||||
- [Development setup](#development-setup)
|
||||
- [Requirements](#requirements)
|
||||
- [Local NetBird setup](#local-netbird-setup)
|
||||
- [Build and start](#build-and-start)
|
||||
- [Test suite](#test-suite)
|
||||
- [Checklist before submitting a PR](#checklist-before-submitting-a-pr)
|
||||
- [Other project repositories](#other-project-repositories)
|
||||
- [Checklist before submitting a new node](#checklist-before-submitting-a-new-node)
|
||||
- [Contributor License Agreement](#contributor-license-agreement)
|
||||
|
||||
## Code of conduct
|
||||
|
||||
This project and everyone participating in it are governed by the Code of
|
||||
Conduct which can be found in the file [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
|
||||
By participating, you are expected to uphold this code. Please report
|
||||
unacceptable behavior to community@netbird.io.
|
||||
|
||||
## Directory structure
|
||||
|
||||
The NetBird project monorepo is organized to maintain most of its individual dependencies code within their directories, except for a few auxiliary or shared packages.
|
||||
|
||||
The most important directories are:
|
||||
|
||||
- [/.github](/.github) - Github actions workflow files and issue templates
|
||||
- [/client](/client) - NetBird agent code
|
||||
- [/client/cmd](/client/cmd) - NetBird agent cli code
|
||||
- [/client/internal](/client/internal) - NetBird agent business logic code
|
||||
- [/client/proto](/client/proto) - NetBird agent daemon GRPC proto files
|
||||
- [/client/server](/client/server) - NetBird agent daemon code for background execution
|
||||
- [/client/ui](/client/ui) - NetBird agent UI code
|
||||
- [/encryption](/encryption) - Contain main encryption code for agent communication
|
||||
- [/iface](/iface) - Wireguard® interface code
|
||||
- [/infrastructure_files](/infrastructure_files) - Getting started files containing docker and template scripts
|
||||
- [/management](/management) - Management service code
|
||||
- [/management/client](/management/client) - Management service client code which is imported by the agent code
|
||||
- [/management/proto](/management/proto) - Management service GRPC proto files
|
||||
- [/management/server](/management/server) - Management service server code
|
||||
- [/management/server/http](/management/server/http) - Management service REST API code
|
||||
- [/management/server/idp](/management/server/idp) - Management service IDP management code
|
||||
- [/release_files](/release_files) - Files that goes into release packages
|
||||
- [/signal](/signal) - Signal service code
|
||||
- [/signal/client](/signal/client) - Signal service client code which is imported by the agent code
|
||||
- [/signal/peer](/signal/peer) - Signal service peer message logic
|
||||
- [/signal/proto](/signal/proto) - Signal service GRPC proto files
|
||||
- [/signal/server](/signal/server) - Signal service server code
|
||||
|
||||
|
||||
## Development setup
|
||||
|
||||
If you want to contribute to bug fixes or improve existing features, you have to ensure that all needed
|
||||
dependencies are installed. Here is a short guide on how that can be done.
|
||||
|
||||
### Requirements
|
||||
|
||||
#### Go 1.19
|
||||
|
||||
Follow the installation guide from https://go.dev/
|
||||
|
||||
#### UI client - Fyne toolkit
|
||||
|
||||
We use the fyne toolkit in our UI client. You can follow its requirement guide to have all its dependencies installed: https://developer.fyne.io/started/#prerequisites
|
||||
|
||||
#### gRPC
|
||||
You can follow the instructions from the quickstarter guide https://grpc.io/docs/languages/go/quickstart/#prerequisites and then run the `generate.sh` files located in each `proto` directory to generate changes.
|
||||
> **IMPORTANT**: We are very open to contributions that can improve the client daemon protocol. For Signal and Management protocols, please reach out on slack or via github issues with your proposals.
|
||||
|
||||
#### Docker
|
||||
|
||||
Follow the installation guide from https://docs.docker.com/get-docker/
|
||||
|
||||
#### Goreleaser and golangci-lint
|
||||
|
||||
We utilize two tools in our Github actions workflows:
|
||||
- Goreleaser: Used for release packaging. You can follow the installation steps [here](https://goreleaser.com/install/); keep in mind to match the version defined in [release.yml](/.github/workflows/release.yml)
|
||||
- golangci-lint: Used for linting checks. You can follow the installation steps [here](https://golangci-lint.run/usage/install/); keep in mind to match the version defined in [golangci-lint.yml](/.github/workflows/golangci-lint.yml)
|
||||
|
||||
They can be executed from the repository root before every push or PR:
|
||||
|
||||
**Goreleaser**
|
||||
```shell
|
||||
goreleaser --snapshot --rm-dist
|
||||
```
|
||||
**golangci-lint**
|
||||
```shell
|
||||
golangci-lint run
|
||||
```
|
||||
|
||||
### Local NetBird setup
|
||||
|
||||
> **IMPORTANT**: All the steps below have to get executed at least once to get the development setup up and running!
|
||||
|
||||
Now that everything NetBird requires to run is installed, the actual NetBird code can be
|
||||
checked out and set up:
|
||||
|
||||
1. [Fork](https://guides.github.com/activities/forking/#fork) the NetBird repository
|
||||
|
||||
2. Clone your forked repository
|
||||
|
||||
```
|
||||
git clone https://github.com/<your_github_username>/netbird.git
|
||||
```
|
||||
|
||||
3. Go into the repository folder
|
||||
|
||||
```
|
||||
cd netbird
|
||||
```
|
||||
|
||||
4. Add the original NetBird repository as `upstream` to your forked repository
|
||||
|
||||
```
|
||||
git remote add upstream https://github.com/netbirdio/netbird.git
|
||||
```
|
||||
|
||||
5. Install all Go dependencies:
|
||||
|
||||
```
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
### Build and start
|
||||
#### Client
|
||||
|
||||
> Windows clients have a Wireguard driver requirement. We provide a bash script that can be executed in WLS 2 with docker support [wireguard_nt.sh](/client/wireguard_nt.sh).
|
||||
|
||||
To start NetBird, execute:
|
||||
```
|
||||
cd client
|
||||
# bash wireguard_nt.sh # if windows
|
||||
go build .
|
||||
```
|
||||
|
||||
To start NetBird the client in the foreground:
|
||||
|
||||
```
|
||||
sudo ./client up --log-level debug --log-file console
|
||||
```
|
||||
> On Windows use a powershell with administrator privileges
|
||||
#### Signal service
|
||||
|
||||
To start NetBird's signal, execute:
|
||||
|
||||
```
|
||||
cd signal
|
||||
go build .
|
||||
```
|
||||
|
||||
To start NetBird the signal service:
|
||||
|
||||
```
|
||||
./signal run --log-level debug --log-file console
|
||||
```
|
||||
|
||||
#### Management service
|
||||
> You may need to generate a configuration file for management. Follow steps 2 to 5 from our [self-hosting guide](https://netbird.io/docs/getting-started/self-hosting).
|
||||
|
||||
To start NetBird's management, execute:
|
||||
|
||||
```
|
||||
cd management
|
||||
go build .
|
||||
```
|
||||
|
||||
To start NetBird the management service:
|
||||
|
||||
```
|
||||
./management management --log-level debug --log-file console --config ./management.json
|
||||
```
|
||||
|
||||
### Test suite
|
||||
|
||||
The tests can be started via:
|
||||
|
||||
```
|
||||
cd netbird
|
||||
go test -exec sudo ./...
|
||||
```
|
||||
> On Windows use a powershell with administrator privileges
|
||||
|
||||
## Checklist before submitting a PR
|
||||
As a critical network service and open-source project, we must enforce a few things before submitting the pull-requests:
|
||||
- Keep functions as simple as possible, with a single purpose
|
||||
- Use private functions and constants where possible
|
||||
- Comment on any new public functions
|
||||
- Add unit tests for any new public function
|
||||
|
||||
> When pushing fixes to the PR comments, please push as separate commits; we will squash the PR before merging, so there is no need to squash it before pushing it, and we are more than okay with 10-100 commits in a single PR. This helps review the fixes to the requested changes.
|
||||
|
||||
## Other project repositories
|
||||
|
||||
NetBird project is composed of 3 main repositories:
|
||||
- NetBird: This repository, which contains the code for the agents and control plane services.
|
||||
- Dashboard: https://github.com/netbirdio/dashboard, contains the Administration UI for the management service
|
||||
- Documentations: https://github.com/netbirdio/docs, contains the documentation from https://netbird.io/docs
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
That we do not have any potential problems later it is sadly necessary to sign a [Contributor License Agreement](CONTRIBUTOR_LICENSE_AGREEMENT.md). That can be done literally with the push of a button.
|
||||
|
||||
A bot will automatically comment on the pull request once it got opened asking for the agreement to be signed. Before it did not get signed it is sadly not possible to merge it in.
|
||||
148
CONTRIBUTOR_LICENSE_AGREEMENT.md
Normal file
148
CONTRIBUTOR_LICENSE_AGREEMENT.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# Contributor License Agreement
|
||||
|
||||
We are incredibly thankful for the contributions we receive from the community.
|
||||
We require our external contributors to sign a Contributor License Agreement ("CLA") in
|
||||
order to ensure that our projects remain licensed under Free and Open Source licenses such
|
||||
as BSD-3 while allowing Wiretrustee to build a sustainable business.
|
||||
|
||||
Wiretrustee is committed to having a true Open Source Software ("OSS") license for
|
||||
our software. A CLA enables Wiretrustee to safely commercialize our products
|
||||
while keeping a standard OSS license with all the rights that license grants to users: the
|
||||
ability to use the project in their own projects or businesses, to republish modified
|
||||
source, or to completely fork the project.
|
||||
|
||||
This page gives a human-friendly summary of our CLA, details on why we require a CLA, how
|
||||
contributors can sign our CLA, and more. You may view the full legal CLA document (below).
|
||||
|
||||
# Human-friendly summary
|
||||
|
||||
This is a human-readable summary of (and not a substitute for) the full agreement (below).
|
||||
This highlights only some of key terms of the CLA. It has no legal value and you should
|
||||
carefully review all the terms of the actual CLA before agreeing.
|
||||
|
||||
<li>Grant of copyright license. You give Wiretrustee permission to use your copyrighted work
|
||||
in commercial products.
|
||||
</li>
|
||||
|
||||
<li>Grant of patent license. If your contributed work uses a patent, you give Wiretrustee a
|
||||
license to use that patent including within commercial products. You also agree that you
|
||||
have permission to grant this license.
|
||||
</li>
|
||||
|
||||
<li>No Warranty or Support Obligations.
|
||||
By making a contribution, you are not obligating yourself to provide support for the
|
||||
contribution, and you are not taking on any warranty obligations or providing any
|
||||
assurances about how it will perform.
|
||||
</li>
|
||||
|
||||
The CLA does not change the terms of the standard open source license used by our software
|
||||
such as BSD-3 or MIT.
|
||||
You are still free to use our projects within your own projects or businesses, republish
|
||||
modified source, and more.
|
||||
Please reference the appropriate license for the project you're contributing to to learn
|
||||
more.
|
||||
|
||||
# Why require a CLA?
|
||||
|
||||
Agreeing to a CLA explicitly states that you are entitled to provide a contribution, that you cannot withdraw permission
|
||||
to use your contribution at a later date, and that Wiretrustee has permission to use your contribution in our commercial
|
||||
products.
|
||||
|
||||
This removes any ambiguities or uncertainties caused by not having a CLA and allows users and customers to confidently
|
||||
adopt our projects. At the same time, the CLA ensures that all contributions to our open source projects are licensed
|
||||
under the project's respective open source license, such as BSD-3.
|
||||
|
||||
Requiring a CLA is a common and well-accepted practice in open source. Major open source projects require CLAs such as
|
||||
Apache Software Foundation projects, Facebook projects (such as React), Google projects (including Go), Python, Django,
|
||||
and more. Each of these projects remains licensed under permissive OSS licenses such as MIT, Apache, BSD, and more.
|
||||
|
||||
# Signing the CLA
|
||||
|
||||
Open a pull request ("PR") to any of our open source projects to sign the CLA. A bot will comment on the PR asking you
|
||||
to sign the CLA if you haven't already.
|
||||
|
||||
Follow the steps given by the bot to sign the CLA. This will require you to log in with GitHub (we only request public
|
||||
information from your account) and to fill in a few additional details such as your name and email address. We will only
|
||||
use this information for CLA tracking; none of your submitted information will be used for marketing purposes.
|
||||
|
||||
You only have to sign the CLA once. Once you've signed the CLA, future contributions to any Wiretrustee project will not
|
||||
require you to sign again.
|
||||
|
||||
# Legal Terms and Agreement
|
||||
|
||||
In order to clarify the intellectual property license granted with Contributions from any person or entity, Wiretrustee
|
||||
UG (haftungsbeschränkt) ("Wiretrustee") must have a Contributor License Agreement ("CLA") on file that has been signed
|
||||
by each Contributor, indicating agreement to the license terms below. This license does not change your rights to use
|
||||
your own Contributions for any other purpose.
|
||||
|
||||
You accept and agree to the following terms and conditions for Your present and future Contributions submitted to
|
||||
Wiretrustee. Except for the license granted herein to Wiretrustee and recipients of software distributed by Wiretrustee,
|
||||
You reserve all right, title, and interest in and to Your Contributions.
|
||||
|
||||
1. Definitions.
|
||||
|
||||
```
|
||||
"You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner
|
||||
that is making this Agreement with Wiretrustee. For legal entities, the entity making a Contribution and all other
|
||||
entities that control, are controlled by, or are under common control with that entity are considered
|
||||
to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect,
|
||||
to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty
|
||||
percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
```
|
||||
```
|
||||
"Contribution" shall mean any original work of authorship, including any modifications or additions to
|
||||
an existing work, that is or previously has been intentionally submitted by You to Wiretrustee for inclusion in,
|
||||
or documentation of, any of the products owned or managed by Wiretrustee (the "Work").
|
||||
For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication
|
||||
sent to Wiretrustee or its representatives, including but not limited to communication on electronic mailing lists,
|
||||
source code control systems, and issue tracking systems that are managed by, or on behalf of,
|
||||
Wiretrustee for the purpose of discussing and improving the Work, but excluding communication that is conspicuously
|
||||
marked or otherwise designated in writing by You as "Not a Contribution."
|
||||
```
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to Wiretrustee
|
||||
and to recipients of software distributed by Wiretrustee a perpetual, worldwide, non-exclusive, no-charge,
|
||||
royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly
|
||||
perform, sublicense, and distribute Your Contributions and such derivative works.
|
||||
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to Wiretrustee and
|
||||
to recipients of software distributed by Wiretrustee a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import,
|
||||
and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are
|
||||
necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which
|
||||
such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (
|
||||
including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have
|
||||
contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity
|
||||
under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
|
||||
4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to
|
||||
intellectual property that you create that includes your Contributions, you represent that you have received
|
||||
permission to make Contributions on behalf of that employer, that you will have received permission from your current
|
||||
and future employers for all future Contributions, that your applicable employer has waived such rights for all of
|
||||
your current and future Contributions to Wiretrustee, or that your employer has executed a separate Corporate CLA
|
||||
with Wiretrustee.
|
||||
|
||||
|
||||
5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of
|
||||
others). You represent that Your Contribution submissions include complete details of any third-party license or
|
||||
other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware
|
||||
and which are associated with any part of Your Contributions.
|
||||
|
||||
|
||||
6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support.
|
||||
You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in
|
||||
writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT,
|
||||
MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
|
||||
7. Should You wish to submit work that is not Your original creation, You may submit it to Wiretrustee separately from
|
||||
any Contribution, identifying the complete details of its source and of any license or other restriction (including,
|
||||
but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and
|
||||
conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
|
||||
|
||||
|
||||
8. You agree to notify Wiretrustee of any facts or circumstances of which you become aware that would make these
|
||||
representations inaccurate in any respect.
|
||||
12
SECURITY.md
Normal file
12
SECURITY.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Security Policy
|
||||
|
||||
NetBird's goal is to provide a secure network. If you find a vulnerability or bug, please report it by opening an issue [here](https://github.com/netbirdio/netbird/issues/new?assignees=&labels=&template=bug-issue-report.md&title=) or by contacting us by email.
|
||||
|
||||
There has yet to be an official bug bounty program for the NetBird project.
|
||||
|
||||
## Supported Versions
|
||||
- We currently support only the latest version
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report security issues to `security@netbird.io`
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
@@ -236,7 +235,7 @@ func copySymLink(source, dest string) error {
|
||||
|
||||
func cpDir(src string, dst string) error {
|
||||
var err error
|
||||
var fds []os.FileInfo
|
||||
var fds []os.DirEntry
|
||||
var srcinfo os.FileInfo
|
||||
|
||||
if srcinfo, err = os.Stat(src); err != nil {
|
||||
@@ -247,7 +246,7 @@ func cpDir(src string, dst string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if fds, err = ioutil.ReadDir(src); err != nil {
|
||||
if fds, err = os.ReadDir(src); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fd := range fds {
|
||||
|
||||
@@ -3,6 +3,11 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal"
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
@@ -11,10 +16,6 @@ import (
|
||||
"github.com/netbirdio/netbird/util"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc/status"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -197,7 +198,7 @@ func parseFullStatus(fullStatus nbStatus.FullStatus, printDetail bool, daemonSta
|
||||
"CLI version: %s\n"+
|
||||
"%s"+ // daemon status
|
||||
"Management: %s%s\n"+
|
||||
"Signal: %s%s\n"+
|
||||
"Signal: %s%s\n"+
|
||||
"Domain: %s\n"+
|
||||
"NetBird IP: %s\n"+
|
||||
"Interface type: %s\n"+
|
||||
|
||||
@@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -68,7 +69,12 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
|
||||
}
|
||||
|
||||
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "")
|
||||
eventStore := &activity.NoopEventStore{}
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -177,6 +177,9 @@ func RunClient(ctx context.Context, config *Config, statusRecorder *nbStatus.Sta
|
||||
err = backoff.Retry(operation, backOff)
|
||||
if err != nil {
|
||||
log.Debugf("exiting client retry loop due to unrecoverable error: %s", err)
|
||||
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.PermissionDenied) {
|
||||
state.Set(StatusNeedsLogin)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -44,7 +44,7 @@ func (f *fileConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
||||
return fmt.Errorf("unable to configure DNS for this peer using file manager without a Primary nameserver group. Restoring the original file return err: %s", err)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unable to configure DNS for this peer using file manager without a Primary nameserver group")
|
||||
return fmt.Errorf("unable to configure DNS for this peer using file manager without a nameserver group with all domains configured")
|
||||
}
|
||||
managerType, err := getOSDNSManagerType()
|
||||
if err != nil {
|
||||
@@ -92,7 +92,7 @@ func (f *fileConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
log.Infof("created a NetBird managed %s file with your DNS settings", defaultResolvConfPath)
|
||||
log.Infof("created a NetBird managed %s file with your DNS settings. Added %d search domains. Search list: %s", defaultResolvConfPath, appendedDomains, searchDomains)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ func newHostManager(wgInterface *iface.WGIface) (hostManager, error) {
|
||||
return newNetworkManagerDbusConfigurator(wgInterface)
|
||||
case systemdManager:
|
||||
return newSystemdDbusConfigurator(wgInterface)
|
||||
case resolvConfManager:
|
||||
return newResolvConfConfigurator(wgInterface)
|
||||
default:
|
||||
return newFileConfigurator()
|
||||
}
|
||||
@@ -68,6 +70,16 @@ func getOSDNSManagerType() (osManagerType, error) {
|
||||
return systemdManager, nil
|
||||
}
|
||||
if strings.Contains(text, "resolvconf") {
|
||||
if isDbusListenerRunning(systemdResolvedDest, systemdDbusObjectNode) {
|
||||
var value string
|
||||
err = getSystemdDbusProperty(systemdDbusResolvConfModeProperty, &value)
|
||||
if err == nil {
|
||||
if value == systemdDbusResolvConfModeForeign {
|
||||
return systemdManager, nil
|
||||
}
|
||||
}
|
||||
log.Errorf("got an error while checking systemd resolv conf mode, error: %s", err)
|
||||
}
|
||||
return resolvConfManager, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,10 @@ func (n *networkManagerDbusConfigurator) applyDNSConfig(config hostDNSConfig) er
|
||||
|
||||
connSettings.cleanDeprecatedSettings()
|
||||
|
||||
dnsIP := netip.MustParseAddr(config.serverIP)
|
||||
dnsIP, err := netip.ParseAddr(config.serverIP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse ip address, error: %s", err)
|
||||
}
|
||||
convDNSIP := binary.LittleEndian.Uint32(dnsIP.AsSlice())
|
||||
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSKey] = dbus.MakeVariant([]uint32{convDNSIP})
|
||||
var (
|
||||
|
||||
84
client/internal/dns/resolvconf_linux.go
Normal file
84
client/internal/dns/resolvconf_linux.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const resolvconfCommand = "resolvconf"
|
||||
|
||||
type resolvconf struct {
|
||||
ifaceName string
|
||||
}
|
||||
|
||||
func newResolvConfConfigurator(wgInterface *iface.WGIface) (hostManager, error) {
|
||||
return &resolvconf{
|
||||
ifaceName: wgInterface.GetName(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *resolvconf) applyDNSConfig(config hostDNSConfig) error {
|
||||
var err error
|
||||
if !config.routeAll {
|
||||
err = r.restoreHostDNS()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return fmt.Errorf("unable to configure DNS for this peer using resolvconf manager without a nameserver group with all domains configured")
|
||||
}
|
||||
|
||||
var searchDomains string
|
||||
appendedDomains := 0
|
||||
for _, dConf := range config.domains {
|
||||
if dConf.matchOnly {
|
||||
continue
|
||||
}
|
||||
|
||||
if appendedDomains >= fileMaxNumberOfSearchDomains {
|
||||
// lets log all skipped domains
|
||||
log.Infof("already appended %d domains to search list. Skipping append of %s domain", fileMaxNumberOfSearchDomains, dConf.domain)
|
||||
continue
|
||||
}
|
||||
|
||||
if fileSearchLineBeginCharCount+len(searchDomains) > fileMaxLineCharsLimit {
|
||||
// lets log all skipped domains
|
||||
log.Infof("search list line is larger than %d characters. Skipping append of %s domain", fileMaxLineCharsLimit, dConf.domain)
|
||||
continue
|
||||
}
|
||||
|
||||
searchDomains += " " + dConf.domain
|
||||
appendedDomains++
|
||||
}
|
||||
|
||||
content := fmt.Sprintf(fileGeneratedResolvConfContentFormat, fileDefaultResolvConfBackupLocation, config.serverIP, searchDomains)
|
||||
|
||||
err = r.applyConfig(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("added %d search domains. Search list: %s", appendedDomains, searchDomains)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resolvconf) restoreHostDNS() error {
|
||||
cmd := exec.Command(resolvconfCommand, "-f", "-d", r.ifaceName)
|
||||
_, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("got an error while removing resolvconf configuration for %s interface, error: %s", r.ifaceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resolvconf) applyConfig(content string) error {
|
||||
cmd := exec.Command(resolvconfCommand, "-x", "-a", r.ifaceName)
|
||||
cmd.Stdin = strings.NewReader(content)
|
||||
_, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("got an error while appying resolvconf configuration for %s interface, error: %s", r.ifaceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -15,9 +16,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
port = 53
|
||||
customPort = 5053
|
||||
defaultIP = "127.0.0.1"
|
||||
defaultPort = 53
|
||||
customPort = 5053
|
||||
defaultIP = "127.0.0.1"
|
||||
customIP = "127.0.0.153"
|
||||
)
|
||||
|
||||
// Server is a dns server interface
|
||||
@@ -29,19 +31,20 @@ type Server interface {
|
||||
|
||||
// DefaultServer dns server object
|
||||
type DefaultServer struct {
|
||||
ctx context.Context
|
||||
stop context.CancelFunc
|
||||
mux sync.Mutex
|
||||
server *dns.Server
|
||||
dnsMux *dns.ServeMux
|
||||
dnsMuxMap registrationMap
|
||||
localResolver *localResolver
|
||||
wgInterface *iface.WGIface
|
||||
hostManager hostManager
|
||||
updateSerial uint64
|
||||
listenerIsRunning bool
|
||||
runtimePort int
|
||||
runtimeIP string
|
||||
ctx context.Context
|
||||
stop context.CancelFunc
|
||||
mux sync.Mutex
|
||||
server *dns.Server
|
||||
dnsMux *dns.ServeMux
|
||||
dnsMuxMap registrationMap
|
||||
localResolver *localResolver
|
||||
wgInterface *iface.WGIface
|
||||
hostManager hostManager
|
||||
updateSerial uint64
|
||||
listenerIsRunning bool
|
||||
runtimePort int
|
||||
runtimeIP string
|
||||
previousConfigHash uint64
|
||||
}
|
||||
|
||||
type registrationMap map[string]struct{}
|
||||
@@ -54,13 +57,8 @@ type muxUpdate struct {
|
||||
// NewDefaultServer returns a new dns server
|
||||
func NewDefaultServer(ctx context.Context, wgInterface *iface.WGIface) (*DefaultServer, error) {
|
||||
mux := dns.NewServeMux()
|
||||
listenIP := defaultIP
|
||||
if runtime.GOOS != "darwin" && wgInterface != nil {
|
||||
listenIP = wgInterface.GetAddress().IP.String()
|
||||
}
|
||||
|
||||
dnsServer := &dns.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", listenIP, port),
|
||||
Net: "udp",
|
||||
Handler: mux,
|
||||
UDPSize: 65535,
|
||||
@@ -78,8 +76,7 @@ func NewDefaultServer(ctx context.Context, wgInterface *iface.WGIface) (*Default
|
||||
registeredMap: make(registrationMap),
|
||||
},
|
||||
wgInterface: wgInterface,
|
||||
runtimePort: port,
|
||||
runtimeIP: listenIP,
|
||||
runtimePort: defaultPort,
|
||||
}
|
||||
|
||||
hostmanager, err := newHostManager(wgInterface)
|
||||
@@ -92,19 +89,15 @@ func NewDefaultServer(ctx context.Context, wgInterface *iface.WGIface) (*Default
|
||||
|
||||
// Start runs the listener in a go routine
|
||||
func (s *DefaultServer) Start() {
|
||||
s.runtimePort = port
|
||||
udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort(s.server.Addr))
|
||||
probeListener, err := net.ListenUDP("udp", udpAddr)
|
||||
|
||||
ip, port, err := s.getFirstListenerAvailable()
|
||||
if err != nil {
|
||||
log.Warnf("using a custom port for dns server")
|
||||
s.runtimePort = customPort
|
||||
s.server.Addr = fmt.Sprintf("%s:%d", s.runtimeIP, customPort)
|
||||
} else {
|
||||
err = probeListener.Close()
|
||||
if err != nil {
|
||||
log.Errorf("got an error closing the probe listener, error: %s", err)
|
||||
}
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
s.runtimeIP = ip
|
||||
s.runtimePort = port
|
||||
s.server.Addr = fmt.Sprintf("%s:%d", s.runtimeIP, s.runtimePort)
|
||||
|
||||
log.Debugf("starting dns on %s", s.server.Addr)
|
||||
|
||||
@@ -119,6 +112,30 @@ func (s *DefaultServer) Start() {
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *DefaultServer) getFirstListenerAvailable() (string, int, error) {
|
||||
ips := []string{defaultIP, customIP}
|
||||
if runtime.GOOS != "darwin" && s.wgInterface != nil {
|
||||
ips = append([]string{s.wgInterface.GetAddress().IP.String()}, ips...)
|
||||
}
|
||||
ports := []int{defaultPort, customPort}
|
||||
for _, port := range ports {
|
||||
for _, ip := range ips {
|
||||
addrString := fmt.Sprintf("%s:%d", ip, port)
|
||||
udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort(addrString))
|
||||
probeListener, err := net.ListenUDP("udp", udpAddr)
|
||||
if err == nil {
|
||||
err = probeListener.Close()
|
||||
if err != nil {
|
||||
log.Errorf("got an error closing the probe listener, error: %s", err)
|
||||
}
|
||||
return ip, port, nil
|
||||
}
|
||||
log.Warnf("binding dns on %s is not available, error: %s", addrString, err)
|
||||
}
|
||||
}
|
||||
return "", 0, fmt.Errorf("unable to find an unused ip and port combination. IPs tested: %v and ports %v", ips, ports)
|
||||
}
|
||||
|
||||
func (s *DefaultServer) setListenerStatus(running bool) {
|
||||
s.listenerIsRunning = running
|
||||
}
|
||||
@@ -169,6 +186,20 @@ func (s *DefaultServer) UpdateDNSServer(serial uint64, update nbdns.Config) erro
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
hash, err := hashstructure.Hash(update, hashstructure.FormatV2, &hashstructure.HashOptions{
|
||||
ZeroNil: true,
|
||||
IgnoreZeroValue: true,
|
||||
SlicesAsSets: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("unable to hash the dns configuration update, got error: %s", err)
|
||||
}
|
||||
|
||||
if s.previousConfigHash == hash {
|
||||
log.Debugf("not applying the dns configuration update as there is nothing new")
|
||||
s.updateSerial = serial
|
||||
return nil
|
||||
}
|
||||
// is the service should be disabled, we stop the listener
|
||||
// and proceed with a regular update to clean up the handlers and records
|
||||
if !update.ServiceEnable {
|
||||
@@ -200,6 +231,7 @@ func (s *DefaultServer) UpdateDNSServer(serial uint64, update nbdns.Config) erro
|
||||
}
|
||||
|
||||
s.updateSerial = serial
|
||||
s.previousConfigHash = hash
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/miekg/dns"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
@@ -185,13 +186,44 @@ func TestUpdateDNSServer(t *testing.T) {
|
||||
expectedUpstreamMap: make(registrationMap),
|
||||
expectedLocalMap: make(registrationMap),
|
||||
},
|
||||
{
|
||||
name: "Disabled Service Should clean map",
|
||||
initLocalMap: registrationMap{"netbird.cloud": struct{}{}},
|
||||
initUpstreamMap: registrationMap{zoneRecords[0].Name: struct{}{}},
|
||||
initSerial: 0,
|
||||
inputSerial: 1,
|
||||
inputUpdate: nbdns.Config{ServiceEnable: false},
|
||||
expectedUpstreamMap: make(registrationMap),
|
||||
expectedLocalMap: make(registrationMap),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
for n, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
dnsServer := getDefaultServerWithNoHostManager("127.0.0.1")
|
||||
|
||||
dnsServer.hostManager = newNoopHostMocker()
|
||||
wgIface, err := iface.NewWGIFace(fmt.Sprintf("utun230%d", n), fmt.Sprintf("100.66.100.%d/32", n+1), iface.DefaultMTU)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = wgIface.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = wgIface.Close()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
dnsServer, err := NewDefaultServer(context.Background(), wgIface)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = dnsServer.hostManager.restoreHostDNS()
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
|
||||
dnsServer.dnsMuxMap = testCase.initUpstreamMap
|
||||
dnsServer.localResolver.registeredMap = testCase.initLocalMap
|
||||
@@ -199,7 +231,7 @@ func TestUpdateDNSServer(t *testing.T) {
|
||||
// pretend we are running
|
||||
dnsServer.listenerIsRunning = true
|
||||
|
||||
err := dnsServer.UpdateDNSServer(testCase.inputSerial, testCase.inputUpdate)
|
||||
err = dnsServer.UpdateDNSServer(testCase.inputSerial, testCase.inputUpdate)
|
||||
if err != nil {
|
||||
if testCase.shouldFail {
|
||||
return
|
||||
@@ -241,9 +273,12 @@ func TestDNSServerStartStop(t *testing.T) {
|
||||
}
|
||||
|
||||
dnsServer.hostManager = newNoopHostMocker()
|
||||
|
||||
dnsServer.Start()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if !dnsServer.listenerIsRunning {
|
||||
t.Fatal("dns server listener is not running")
|
||||
}
|
||||
defer dnsServer.Stop()
|
||||
err := dnsServer.localResolver.registerRecord(zoneRecords[0])
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -257,7 +292,7 @@ func TestDNSServerStartStop(t *testing.T) {
|
||||
d := net.Dialer{
|
||||
Timeout: time.Second * 5,
|
||||
}
|
||||
addr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||
addr := fmt.Sprintf("%s:%d", dnsServer.runtimeIP, dnsServer.runtimePort)
|
||||
conn, err := d.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
@@ -297,7 +332,7 @@ func getDefaultServerWithNoHostManager(ip string) *DefaultServer {
|
||||
}
|
||||
|
||||
dnsServer := &dns.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", ip, port),
|
||||
Addr: fmt.Sprintf("%s:%d", ip, defaultPort),
|
||||
Net: "udp",
|
||||
Handler: mux,
|
||||
UDPSize: 65535,
|
||||
@@ -314,7 +349,7 @@ func getDefaultServerWithNoHostManager(ip string) *DefaultServer {
|
||||
localResolver: &localResolver{
|
||||
registeredMap: make(registrationMap),
|
||||
},
|
||||
runtimePort: port,
|
||||
runtimePort: defaultPort,
|
||||
runtimeIP: listenIP,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,13 @@ const (
|
||||
systemdDbusObjectNode = "/org/freedesktop/resolve1"
|
||||
systemdDbusGetLinkMethod = systemdDbusManagerInterface + ".GetLink"
|
||||
systemdDbusFlushCachesMethod = systemdDbusManagerInterface + ".FlushCaches"
|
||||
systemdDbusResolvConfModeProperty = systemdDbusManagerInterface + ".ResolvConfMode"
|
||||
systemdDbusLinkInterface = "org.freedesktop.resolve1.Link"
|
||||
systemdDbusRevertMethodSuffix = systemdDbusLinkInterface + ".Revert"
|
||||
systemdDbusSetDNSMethodSuffix = systemdDbusLinkInterface + ".SetDNS"
|
||||
systemdDbusSetDefaultRouteMethodSuffix = systemdDbusLinkInterface + ".SetDefaultRoute"
|
||||
systemdDbusSetDomainsMethodSuffix = systemdDbusLinkInterface + ".SetDomains"
|
||||
systemdDbusResolvConfModeForeign = "foreign"
|
||||
)
|
||||
|
||||
type systemdDbusConfigurator struct {
|
||||
@@ -73,12 +75,16 @@ func newSystemdDbusConfigurator(wgInterface *iface.WGIface) (hostManager, error)
|
||||
}
|
||||
|
||||
func (s *systemdDbusConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
||||
parsedIP := netip.MustParseAddr(config.serverIP).As4()
|
||||
parsedIP, err := netip.ParseAddr(config.serverIP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse ip address, error: %s", err)
|
||||
}
|
||||
ipAs4 := parsedIP.As4()
|
||||
defaultLinkInput := systemdDbusDNSInput{
|
||||
Family: unix.AF_INET,
|
||||
Address: parsedIP[:],
|
||||
Address: ipAs4[:],
|
||||
}
|
||||
err := s.callLinkMethod(systemdDbusSetDNSMethodSuffix, []systemdDbusDNSInput{defaultLinkInput})
|
||||
err = s.callLinkMethod(systemdDbusSetDNSMethodSuffix, []systemdDbusDNSInput{defaultLinkInput})
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting the interface DNS server %s:%d failed with error: %s", config.serverIP, config.serverPort, err)
|
||||
}
|
||||
@@ -183,3 +189,18 @@ func (s *systemdDbusConfigurator) callLinkMethod(method string, value any) error
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSystemdDbusProperty(property string, store any) error {
|
||||
obj, closeConn, err := getDbusObject(systemdResolvedDest, systemdDbusObjectNode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("got error while attempting to retrieve the systemd dns manager object, error: %s", err)
|
||||
}
|
||||
defer closeConn()
|
||||
|
||||
v, e := obj.GetProperty(property)
|
||||
if e != nil {
|
||||
return fmt.Errorf("got an error getting property %s: %v", property, e)
|
||||
}
|
||||
|
||||
return v.Store(store)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
@@ -71,6 +72,10 @@ func TestEngine_SSH(t *testing.T) {
|
||||
WgPort: 33100,
|
||||
}, nbstatus.NewRecorder())
|
||||
|
||||
engine.dnsServer = &dns.MockServer{
|
||||
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
|
||||
}
|
||||
|
||||
var sshKeysAdded []string
|
||||
var sshPeersRemoved []string
|
||||
|
||||
@@ -385,6 +390,10 @@ func TestEngine_Sync(t *testing.T) {
|
||||
WgPort: 33100,
|
||||
}, nbstatus.NewRecorder())
|
||||
|
||||
engine.dnsServer = &dns.MockServer{
|
||||
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := engine.Stop()
|
||||
if err != nil {
|
||||
@@ -945,7 +954,12 @@ func startManagement(port int, dataDir string) (*grpc.Server, error) {
|
||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
||||
}
|
||||
peersUpdateManager := server.NewPeersUpdateManager()
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "")
|
||||
eventStore := &activity.NoopEventStore{}
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@ package routemanager
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/status"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type routerPeerStatus struct {
|
||||
@@ -52,10 +53,6 @@ func newClientNetworkWatcher(ctx context.Context, wgInterface *iface.WGIface, st
|
||||
return client
|
||||
}
|
||||
|
||||
func getClientNetworkID(input *route.Route) string {
|
||||
return input.NetID + "-" + input.Network.String()
|
||||
}
|
||||
|
||||
func (c *clientNetwork) getRouterPeerStatuses() map[string]routerPeerStatus {
|
||||
routePeerStatuses := make(map[string]routerPeerStatus)
|
||||
for _, r := range c.routes {
|
||||
|
||||
@@ -18,7 +18,6 @@ func (unimplementedFirewall) RemoveRoutingRules(pair routerPair) error {
|
||||
}
|
||||
|
||||
func (unimplementedFirewall) CleanRoutingRules() {
|
||||
return
|
||||
}
|
||||
|
||||
// NewFirewall returns an unimplemented Firewall manager
|
||||
|
||||
@@ -3,13 +3,14 @@ package routemanager
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/netbirdio/netbird/client/status"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Manager is a route manager interface
|
||||
@@ -147,16 +148,24 @@ func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Ro
|
||||
|
||||
newClientRoutesIDMap := make(map[string][]*route.Route)
|
||||
newServerRoutesMap := make(map[string]*route.Route)
|
||||
ownNetworkIDs := make(map[string]bool)
|
||||
|
||||
for _, newRoute := range newRoutes {
|
||||
// only linux is supported for now
|
||||
networkID := route.GetHAUniqueID(newRoute)
|
||||
if newRoute.Peer == m.pubKey {
|
||||
ownNetworkIDs[networkID] = true
|
||||
// only linux is supported for now
|
||||
if runtime.GOOS != "linux" {
|
||||
log.Warnf("received a route to manage, but agent doesn't support router mode on %s OS", runtime.GOOS)
|
||||
continue
|
||||
}
|
||||
newServerRoutesMap[newRoute.ID] = newRoute
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
for _, newRoute := range newRoutes {
|
||||
networkID := route.GetHAUniqueID(newRoute)
|
||||
if !ownNetworkIDs[networkID] {
|
||||
// if prefix is too small, lets assume is a possible default route which is not yet supported
|
||||
// we skip this route management
|
||||
if newRoute.Network.Bits() < 7 {
|
||||
@@ -164,8 +173,7 @@ func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Ro
|
||||
system.NetbirdVersion(), newRoute.Network)
|
||||
continue
|
||||
}
|
||||
clientNetworkID := getClientNetworkID(newRoute)
|
||||
newClientRoutesIDMap[clientNetworkID] = append(newClientRoutesIDMap[clientNetworkID], newRoute)
|
||||
newClientRoutesIDMap[networkID] = append(newClientRoutesIDMap[networkID], newRoute)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@ package routemanager
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/netbirdio/netbird/client/status"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// send 5 routes, one for server and 4 for clients, one normal and 2 HA and one small
|
||||
@@ -336,6 +337,55 @@ func TestManagerUpdateRoutes(t *testing.T) {
|
||||
serverRoutesExpected: 0,
|
||||
clientNetworkWatchersExpected: 0,
|
||||
},
|
||||
{
|
||||
name: "HA server should not register routes from the same HA group",
|
||||
inputRoutes: []*route.Route{
|
||||
{
|
||||
ID: "l1",
|
||||
NetID: "routeA",
|
||||
Peer: localPeerKey,
|
||||
Network: netip.MustParsePrefix("100.64.251.250/30"),
|
||||
NetworkType: route.IPv4Network,
|
||||
Metric: 9999,
|
||||
Masquerade: false,
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ID: "l2",
|
||||
NetID: "routeA",
|
||||
Peer: localPeerKey,
|
||||
Network: netip.MustParsePrefix("8.8.9.8/32"),
|
||||
NetworkType: route.IPv4Network,
|
||||
Metric: 9999,
|
||||
Masquerade: false,
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ID: "r1",
|
||||
NetID: "routeA",
|
||||
Peer: remotePeerKey1,
|
||||
Network: netip.MustParsePrefix("100.64.251.250/30"),
|
||||
NetworkType: route.IPv4Network,
|
||||
Metric: 9999,
|
||||
Masquerade: false,
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ID: "r2",
|
||||
NetID: "routeC",
|
||||
Peer: remotePeerKey1,
|
||||
Network: netip.MustParsePrefix("8.8.9.9/32"),
|
||||
NetworkType: route.IPv4Network,
|
||||
Metric: 9999,
|
||||
Masquerade: false,
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
inputSerial: 1,
|
||||
shouldCheckServerRoutes: runtime.GOOS == "linux",
|
||||
serverRoutesExpected: 2,
|
||||
clientNetworkWatchersExpected: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for n, testCase := range testCases {
|
||||
|
||||
@@ -2,9 +2,9 @@ package routemanager
|
||||
|
||||
import (
|
||||
"github.com/vishvananda/netlink"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
)
|
||||
|
||||
const ipv4ForwardingPath = "/proc/sys/net/ipv4/ip_forward"
|
||||
@@ -59,12 +59,12 @@ func removeFromRouteTable(prefix netip.Prefix) error {
|
||||
}
|
||||
|
||||
func enableIPForwarding() error {
|
||||
err := ioutil.WriteFile(ipv4ForwardingPath, []byte("1"), 0644)
|
||||
err := os.WriteFile(ipv4ForwardingPath, []byte("1"), 0644)
|
||||
return err
|
||||
}
|
||||
|
||||
func isNetForwardHistoryEnabled() bool {
|
||||
out, err := ioutil.ReadFile(ipv4ForwardingPath)
|
||||
out, err := os.ReadFile(ipv4ForwardingPath)
|
||||
if err != nil {
|
||||
// todo
|
||||
panic(err)
|
||||
|
||||
@@ -335,7 +335,7 @@ func (s *Server) WaitSSOLogin(callerCtx context.Context, msg *proto.WaitSSOLogin
|
||||
}
|
||||
|
||||
// Up starts engine work in the daemon.
|
||||
func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpResponse, error) {
|
||||
func (s *Server) Up(callerCtx context.Context, _ *proto.UpRequest) (*proto.UpResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
@@ -375,7 +375,7 @@ func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpR
|
||||
|
||||
go func() {
|
||||
if err := internal.RunClient(ctx, s.config, s.statusRecorder); err != nil {
|
||||
log.Errorf("run client connection: %v", state.Wrap(err))
|
||||
log.Errorf("run client connection: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
@@ -384,7 +384,7 @@ func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpR
|
||||
}
|
||||
|
||||
// Down engine work in the daemon.
|
||||
func (s *Server) Down(ctx context.Context, msg *proto.DownRequest) (*proto.DownResponse, error) {
|
||||
func (s *Server) Down(_ context.Context, _ *proto.DownRequest) (*proto.DownResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
@@ -392,6 +392,8 @@ func (s *Server) Down(ctx context.Context, msg *proto.DownRequest) (*proto.DownR
|
||||
return nil, fmt.Errorf("service is not up")
|
||||
}
|
||||
s.actCancel()
|
||||
state := internal.CtxGetState(s.rootCtx)
|
||||
state.Set(internal.StatusIdle)
|
||||
|
||||
return &proto.DownResponse{}, nil
|
||||
}
|
||||
@@ -425,7 +427,7 @@ func (s *Server) Status(
|
||||
}
|
||||
|
||||
// GetConfig of the daemon.
|
||||
func (s *Server) GetConfig(ctx context.Context, msg *proto.GetConfigRequest) (*proto.GetConfigResponse, error) {
|
||||
func (s *Server) GetConfig(_ context.Context, _ *proto.GetConfigRequest) (*proto.GetConfigResponse, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
|
||||
4
go.mod
4
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/netbirdio/netbird
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v4 v4.1.3
|
||||
@@ -40,7 +40,9 @@ require (
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/libp2p/go-netroute v0.2.0
|
||||
github.com/magiconair/properties v1.8.5
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/miekg/dns v1.1.41
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/rs/xid v1.3.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -432,6 +432,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
@@ -465,6 +467,8 @@ github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCL
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -295,7 +295,7 @@ func loadModule(name, path string) error {
|
||||
// first try finit_module(2), then init_module(2)
|
||||
err = unix.FinitModule(int(f.Fd()), "", 0)
|
||||
if errors.Is(err, unix.ENOSYS) {
|
||||
buf, err := ioutil.ReadAll(f)
|
||||
buf, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sys/unix"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -133,7 +132,7 @@ func resetGlobals() {
|
||||
|
||||
func createFiles(t *testing.T) (string, []module) {
|
||||
writeFile := func(path, text string) {
|
||||
if err := ioutil.WriteFile(path, []byte(text), 0644); err != nil {
|
||||
if err := os.WriteFile(path, []byte(text), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ NETBIRD_MGMT_API_CERT_FILE="/etc/letsencrypt/live/$NETBIRD_DOMAIN/fullchain.pem"
|
||||
NETBIRD_MGMT_API_CERT_KEY_FILE="/etc/letsencrypt/live/$NETBIRD_DOMAIN/privkey.pem"
|
||||
# By default Management single account mode is enabled and domain set to $NETBIRD_DOMAIN, you may want to set this to your user's email domain
|
||||
NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN=$NETBIRD_DOMAIN
|
||||
|
||||
NETBIRD_MGMT_DNS_DOMAIN=${NETBIRD_MGMT_DNS_DOMAIN:-netbird.selfhosted}
|
||||
# Turn credentials
|
||||
|
||||
# User
|
||||
@@ -60,3 +60,5 @@ export MGMT_VOLUMESUFFIX
|
||||
export SIGNAL_VOLUMESUFFIX
|
||||
export LETSENCRYPT_VOLUMESUFFIX
|
||||
export NETBIRD_DISABLE_ANONYMOUS_METRICS
|
||||
export NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN
|
||||
export NETBIRD_MGMT_DNS_DOMAIN
|
||||
@@ -49,6 +49,7 @@ fi
|
||||
# local development or tests
|
||||
if [[ $NETBIRD_DOMAIN == "localhost" || $NETBIRD_DOMAIN == "127.0.0.1" ]]
|
||||
then
|
||||
export NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN="netbird.selfhosted"
|
||||
export NETBIRD_MGMT_API_ENDPOINT=http://$NETBIRD_DOMAIN:$NETBIRD_MGMT_API_PORT
|
||||
unset NETBIRD_MGMT_API_CERT_FILE
|
||||
unset NETBIRD_MGMT_API_CERT_KEY_FILE
|
||||
|
||||
@@ -2,7 +2,7 @@ version: "3"
|
||||
services:
|
||||
#UI dashboard
|
||||
dashboard:
|
||||
image: wiretrustee/dashboard:main
|
||||
image: wiretrustee/dashboard:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 80:80
|
||||
@@ -45,10 +45,9 @@ services:
|
||||
- ./management.json:/etc/netbird/management.json
|
||||
ports:
|
||||
- $NETBIRD_MGMT_API_PORT:443 #API port
|
||||
# # port and command for Let's Encrypt validation without dashboard container
|
||||
# - 443:443
|
||||
# # command for Let's Encrypt validation without dashboard container
|
||||
# command: ["--letsencrypt-domain", "$NETBIRD_DOMAIN", "--log-file", "console"]
|
||||
command: ["--port", "443", "--log-file", "console", "--disable-anonymous-metrics=$NETBIRD_DISABLE_ANONYMOUS_METRICS", "--single-account-mode-domain=$NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN"]
|
||||
command: ["--port", "443", "--log-file", "console", "--disable-anonymous-metrics=$NETBIRD_DISABLE_ANONYMOUS_METRICS", "--single-account-mode-domain=$NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN", "--dns-domain=$NETBIRD_MGMT_DNS_DOMAIN"]
|
||||
# Coturn
|
||||
coturn:
|
||||
image: coturn/coturn
|
||||
@@ -59,6 +58,9 @@ services:
|
||||
# - ./privkey.pem:/etc/coturn/private/privkey.pem:ro
|
||||
# - ./cert.pem:/etc/coturn/certs/cert.pem:ro
|
||||
network_mode: host
|
||||
command:
|
||||
- -c /etc/turnserver.conf
|
||||
|
||||
volumes:
|
||||
$MGMT_VOLUMENAME:
|
||||
$SIGNAL_VOLUMENAME:
|
||||
|
||||
@@ -19,4 +19,6 @@ NETBIRD_LETSENCRYPT_EMAIL=""
|
||||
# NETBIRD_AUTH_SILENT_REDIRECT_URI="/add-peers"
|
||||
|
||||
# Disable anonymous metrics collection, see more information at https://netbird.io/docs/FAQ/metrics-collection
|
||||
NETBIRD_DISABLE_ANONYMOUS_METRICS=false
|
||||
NETBIRD_DISABLE_ANONYMOUS_METRICS=false
|
||||
# DNS DOMAIN configures the domain name used for peer resolution. By default it is netbird.selfhosted
|
||||
NETBIRD_MGMT_DNS_DOMAIN=netbird.selfhosted
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## example file, you can copy this file to setup.env and update its values
|
||||
##
|
||||
# Dashboard domain. e.g. app.mydomain.com
|
||||
NETBIRD_DOMAIN="localhost"
|
||||
NETBIRD_DOMAIN=$CI_NETBIRD_DOMAIN
|
||||
# e.g. https://dev-24vkclam.us.auth0.com/ or https://YOUR-KEYCLOAK-HOST:8080/realms/netbird
|
||||
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT="https://example.eu.auth0.com/.well-known/openid-configuration"
|
||||
# e.g. netbird-client
|
||||
|
||||
@@ -2,6 +2,7 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
activity "github.com/netbirdio/netbird/management/server/activity/sqlite"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
@@ -55,7 +56,12 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
}
|
||||
|
||||
peersUpdateManager := mgmt.NewPeersUpdateManager()
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "")
|
||||
eventStore, err := activity.NewSQLiteStore(t.TempDir())
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
accountManager, err := mgmt.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/netbirdio/netbird/management/server/activity/sqlite"
|
||||
httpapi "github.com/netbirdio/netbird/management/server/http"
|
||||
"github.com/netbirdio/netbird/management/server/metrics"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
@@ -142,7 +143,12 @@ var (
|
||||
if disableSingleAccMode {
|
||||
mgmtSingleAccModeDomain = ""
|
||||
}
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain, dnsDomain)
|
||||
eventStore, err := sqlite.NewSQLiteStore(config.Datadir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain,
|
||||
dnsDomain, eventStore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build default manager: %v", err)
|
||||
}
|
||||
|
||||
@@ -22,9 +22,10 @@ var (
|
||||
disableSingleAccMode bool
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "netbird-mgmt",
|
||||
Short: "",
|
||||
Long: "",
|
||||
Use: "netbird-mgmt",
|
||||
Short: "",
|
||||
Long: "",
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
// Execution control channel for stopCh signal
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/eko/gocache/v3/cache"
|
||||
cacheStore "github.com/eko/gocache/v3/store"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"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/status"
|
||||
@@ -38,15 +39,10 @@ func cacheEntryExpiration() time.Duration {
|
||||
|
||||
type AccountManager interface {
|
||||
GetOrCreateAccountByUser(userId, domain string) (*Account, error)
|
||||
CreateSetupKey(
|
||||
accountId string,
|
||||
keyName string,
|
||||
keyType SetupKeyType,
|
||||
expiresIn time.Duration,
|
||||
autoGroups []string,
|
||||
) (*SetupKey, error)
|
||||
SaveSetupKey(accountID string, key *SetupKey) (*SetupKey, error)
|
||||
CreateUser(accountID string, key *UserInfo) (*UserInfo, error)
|
||||
CreateSetupKey(accountID string, keyName string, keyType SetupKeyType, expiresIn time.Duration,
|
||||
autoGroups []string, usageLimit int, userID string) (*SetupKey, error)
|
||||
SaveSetupKey(accountID string, key *SetupKey, userID string) (*SetupKey, error)
|
||||
CreateUser(accountID, userID string, key *UserInfo) (*UserInfo, error)
|
||||
ListSetupKeys(accountID, userID string) ([]*SetupKey, error)
|
||||
SaveUser(accountID string, key *User) (*UserInfo, error)
|
||||
GetSetupKey(accountID, userID, keyID string) (*SetupKey, error)
|
||||
@@ -57,7 +53,7 @@ type AccountManager interface {
|
||||
GetPeer(peerKey string) (*Peer, error)
|
||||
GetPeers(accountID, userID string) ([]*Peer, error)
|
||||
MarkPeerConnected(peerKey string, connected bool) error
|
||||
DeletePeer(accountId string, peerKey string) (*Peer, error)
|
||||
DeletePeer(accountID, peerKey, userID string) (*Peer, error)
|
||||
GetPeerByIP(accountId string, peerIP string) (*Peer, error)
|
||||
UpdatePeer(accountID string, peer *Peer) (*Peer, error)
|
||||
GetNetworkMap(peerKey string) (*NetworkMap, error)
|
||||
@@ -67,7 +63,7 @@ type AccountManager interface {
|
||||
UpdatePeerSSHKey(peerKey string, sshKey string) error
|
||||
GetUsersFromAccount(accountID, userID string) ([]*UserInfo, error)
|
||||
GetGroup(accountId, groupID string) (*Group, error)
|
||||
SaveGroup(accountId string, group *Group) error
|
||||
SaveGroup(accountID, userID string, group *Group) error
|
||||
UpdateGroup(accountID string, groupID string, operations []GroupUpdateOperation) (*Group, error)
|
||||
DeleteGroup(accountId, groupID string) error
|
||||
ListGroups(accountId string) ([]*Group, error)
|
||||
@@ -75,12 +71,12 @@ type AccountManager interface {
|
||||
GroupDeletePeer(accountId, groupID, peerKey string) error
|
||||
GroupListPeers(accountId, groupID string) ([]*Peer, error)
|
||||
GetRule(accountID, ruleID, userID string) (*Rule, error)
|
||||
SaveRule(accountID string, rule *Rule) error
|
||||
SaveRule(accountID, userID string, rule *Rule) error
|
||||
UpdateRule(accountID string, ruleID string, operations []RuleUpdateOperation) (*Rule, error)
|
||||
DeleteRule(accountId, ruleID string) error
|
||||
DeleteRule(accountID, ruleID, userID string) error
|
||||
ListRules(accountID, userID string) ([]*Rule, error)
|
||||
GetRoute(accountID, routeID, userID string) (*route.Route, error)
|
||||
CreateRoute(accountID string, prefix, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error)
|
||||
CreateRoute(accountID string, prefix, peer, description, netID string, masquerade bool, metric int, groups []string, enabled bool) (*route.Route, error)
|
||||
SaveRoute(accountID string, route *route.Route) error
|
||||
UpdateRoute(accountID string, routeID string, operations []RouteUpdateOperation) (*route.Route, error)
|
||||
DeleteRoute(accountID, routeID string) error
|
||||
@@ -92,6 +88,7 @@ type AccountManager interface {
|
||||
DeleteNameServerGroup(accountID, nsGroupID string) error
|
||||
ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error)
|
||||
GetDNSDomain() string
|
||||
GetEvents(accountID, userID string) ([]*activity.Event, error)
|
||||
}
|
||||
|
||||
type DefaultAccountManager struct {
|
||||
@@ -104,6 +101,7 @@ type DefaultAccountManager struct {
|
||||
idpManager idp.Manager
|
||||
cacheManager cache.CacheInterface[[]*idp.UserData]
|
||||
ctx context.Context
|
||||
eventStore activity.Store
|
||||
|
||||
// singleAccountMode indicates whether the instance has a single account.
|
||||
// If true, then every new user will end up under the same account.
|
||||
@@ -142,36 +140,68 @@ type UserInfo struct {
|
||||
Status string `json:"-"`
|
||||
}
|
||||
|
||||
// GetPeersRoutes returns all active routes of provided peers
|
||||
func (a *Account) GetPeersRoutes(givenPeers []*Peer) []*route.Route {
|
||||
//TODO Peer.ID migration: we will need to replace search by Peer.ID here
|
||||
routes := make([]*route.Route, 0)
|
||||
for _, peer := range givenPeers {
|
||||
peerRoutes := a.GetPeerRoutes(peer.Key)
|
||||
activeRoutes := make([]*route.Route, 0)
|
||||
for _, pr := range peerRoutes {
|
||||
if pr.Enabled {
|
||||
activeRoutes = append(activeRoutes, pr)
|
||||
}
|
||||
}
|
||||
if len(activeRoutes) > 0 {
|
||||
routes = append(routes, activeRoutes...)
|
||||
}
|
||||
// getRoutesToSync returns the enabled routes for the peer ID and the routes
|
||||
// from the ACL peers that have distribution groups associated with the peer ID
|
||||
func (a *Account) getRoutesToSync(peerID string, aclPeers []*Peer) []*route.Route {
|
||||
routes, peerDisabledRoutes := a.getEnabledAndDisabledRoutesByPeer(peerID)
|
||||
peerRoutesMembership := make(lookupMap)
|
||||
for _, r := range append(routes, peerDisabledRoutes...) {
|
||||
peerRoutesMembership[route.GetHAUniqueID(r)] = struct{}{}
|
||||
}
|
||||
|
||||
groupListMap := a.getPeerGroups(peerID)
|
||||
for _, peer := range aclPeers {
|
||||
activeRoutes, _ := a.getEnabledAndDisabledRoutesByPeer(peer.Key)
|
||||
groupFilteredRoutes := a.filterRoutesByGroups(activeRoutes, groupListMap)
|
||||
filteredRoutes := a.filterRoutesFromPeersOfSameHAGroup(groupFilteredRoutes, peerRoutesMembership)
|
||||
routes = append(routes, filteredRoutes...)
|
||||
}
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
// GetPeerRoutes returns a list of routes of a given peer
|
||||
func (a *Account) GetPeerRoutes(peerPubKey string) []*route.Route {
|
||||
//TODO Peer.ID migration: we will need to replace search by Peer.ID here
|
||||
var routes []*route.Route
|
||||
for _, r := range a.Routes {
|
||||
if r.Peer == peerPubKey {
|
||||
routes = append(routes, r)
|
||||
continue
|
||||
// filterRoutesByHAMembership filters and returns a list of routes that don't share the same HA route membership
|
||||
func (a *Account) filterRoutesFromPeersOfSameHAGroup(routes []*route.Route, peerMemberships lookupMap) []*route.Route {
|
||||
var filteredRoutes []*route.Route
|
||||
for _, r := range routes {
|
||||
_, found := peerMemberships[route.GetHAUniqueID(r)]
|
||||
if !found {
|
||||
filteredRoutes = append(filteredRoutes, r)
|
||||
}
|
||||
}
|
||||
return routes
|
||||
return filteredRoutes
|
||||
}
|
||||
|
||||
// filterRoutesByGroups returns a list with routes that have distribution groups in the group's map
|
||||
func (a *Account) filterRoutesByGroups(routes []*route.Route, groupListMap lookupMap) []*route.Route {
|
||||
var filteredRoutes []*route.Route
|
||||
for _, r := range routes {
|
||||
for _, groupID := range r.Groups {
|
||||
_, found := groupListMap[groupID]
|
||||
if found {
|
||||
filteredRoutes = append(filteredRoutes, r)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return filteredRoutes
|
||||
}
|
||||
|
||||
// getEnabledAndDisabledRoutesByPeer returns the enabled and disabled lists of routes that belong to a peer
|
||||
func (a *Account) getEnabledAndDisabledRoutesByPeer(peerPubKey string) ([]*route.Route, []*route.Route) {
|
||||
//TODO Peer.ID migration: we will need to replace search by Peer.ID here
|
||||
var enabledRoutes []*route.Route
|
||||
var disabledRoutes []*route.Route
|
||||
for _, r := range a.Routes {
|
||||
if r.Peer == peerPubKey {
|
||||
if r.Enabled {
|
||||
enabledRoutes = append(enabledRoutes, r)
|
||||
continue
|
||||
}
|
||||
disabledRoutes = append(disabledRoutes, r)
|
||||
}
|
||||
}
|
||||
return enabledRoutes, disabledRoutes
|
||||
}
|
||||
|
||||
// GetRoutesByPrefix return list of routes by account and route prefix
|
||||
@@ -304,6 +334,19 @@ func (a *Account) getUserGroups(userID string) ([]string, error) {
|
||||
return user.AutoGroups, nil
|
||||
}
|
||||
|
||||
func (a *Account) getPeerGroups(peerID string) lookupMap {
|
||||
groupList := make(lookupMap)
|
||||
for groupID, group := range a.Groups {
|
||||
for _, id := range group.Peers {
|
||||
if id == peerID {
|
||||
groupList[groupID] = struct{}{}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return groupList
|
||||
}
|
||||
|
||||
func (a *Account) getSetupKeyGroups(setupKey string) ([]string, error) {
|
||||
key, err := a.FindSetupKey(setupKey)
|
||||
if err != nil {
|
||||
@@ -395,7 +438,7 @@ func (a *Account) GetGroupAll() (*Group, error) {
|
||||
|
||||
// BuildManager creates a new DefaultAccountManager with a provided Store
|
||||
func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManager idp.Manager,
|
||||
singleAccountModeDomain string, dnsDomain string) (*DefaultAccountManager, error) {
|
||||
singleAccountModeDomain string, dnsDomain string, eventStore activity.Store) (*DefaultAccountManager, error) {
|
||||
am := &DefaultAccountManager{
|
||||
Store: store,
|
||||
peersUpdateManager: peersUpdateManager,
|
||||
@@ -404,11 +447,15 @@ func BuildManager(store Store, peersUpdateManager *PeersUpdateManager, idpManage
|
||||
cacheMux: sync.Mutex{},
|
||||
cacheLoading: map[string]chan struct{}{},
|
||||
dnsDomain: dnsDomain,
|
||||
eventStore: eventStore,
|
||||
}
|
||||
allAccounts := store.GetAllAccounts()
|
||||
// enable single account mode only if configured by user and number of existing accounts is not grater than 1
|
||||
am.singleAccountMode = singleAccountModeDomain != "" && len(allAccounts) <= 1
|
||||
if am.singleAccountMode {
|
||||
if !isDomainValid(singleAccountModeDomain) {
|
||||
return nil, status.Errorf(status.InvalidArgument, "invalid domain \"%s\" provided for single accound mode. Please review your input for --single-account-mode-domain", singleAccountModeDomain)
|
||||
}
|
||||
am.singleAccountModeDomain = singleAccountModeDomain
|
||||
log.Infof("single account mode enabled, accounts number %d", len(allAccounts))
|
||||
} else {
|
||||
@@ -467,7 +514,18 @@ func (am *DefaultAccountManager) newAccount(userID, domain string) (*Account, er
|
||||
log.Warnf("an account with ID already exists, retrying...")
|
||||
continue
|
||||
} else if statusErr.Type() == status.NotFound {
|
||||
return newAccountWithId(accountId, userID, domain), nil
|
||||
newAccount := newAccountWithId(accountId, userID, domain)
|
||||
_, err = am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.AccountCreated,
|
||||
AccountID: newAccount.Id,
|
||||
TargetID: newAccount.Id,
|
||||
InitiatorID: userID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newAccount, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
@@ -754,6 +812,19 @@ func (am *DefaultAccountManager) handleNewUserAccount(domainAcc *Account, claims
|
||||
return nil, err
|
||||
}
|
||||
|
||||
event := &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.UserJoined,
|
||||
AccountID: account.Id,
|
||||
TargetID: claims.UserId,
|
||||
InitiatorID: claims.UserId,
|
||||
}
|
||||
|
||||
_, err = am.eventStore.Save(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
@@ -785,6 +856,17 @@ func (am *DefaultAccountManager) redeemInvite(account *Account, userID string) e
|
||||
return
|
||||
}
|
||||
log.Debugf("user %s of account %s redeemed invite", user.ID, account.Id)
|
||||
_, err = am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.UserJoined,
|
||||
AccountID: account.Id,
|
||||
TargetID: userID,
|
||||
InitiatorID: userID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Warnf("failed saving activity event %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -945,7 +1027,8 @@ func newAccountWithId(accountId, userId, domain string) *Account {
|
||||
|
||||
setupKeys := make(map[string]*SetupKey)
|
||||
defaultKey := GenerateDefaultSetupKey()
|
||||
oneOffKey := GenerateSetupKey("One-off key", SetupKeyOneOff, DefaultSetupKeyDuration, []string{})
|
||||
oneOffKey := GenerateSetupKey("One-off key", SetupKeyOneOff, DefaultSetupKeyDuration, []string{},
|
||||
SetupKeyUnlimitedUsage)
|
||||
setupKeys[defaultKey.Key] = defaultKey
|
||||
setupKeys[oneOffKey.Key] = oneOffKey
|
||||
network := NewNetwork()
|
||||
|
||||
@@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"fmt"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
activity "github.com/netbirdio/netbird/management/server/activity/sqlite"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
"net"
|
||||
"reflect"
|
||||
@@ -632,7 +633,9 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
account, err := createAccount(manager, "test_account", "account_creator", "")
|
||||
userID := "account_creator"
|
||||
|
||||
account, err := createAccount(manager, "test_account", userID, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -714,7 +717,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := manager.SaveGroup(account.Id, &group); err != nil {
|
||||
if err := manager.SaveGroup(account.Id, userID, &group); err != nil {
|
||||
t.Errorf("save group: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -739,7 +742,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
defaultRule = r
|
||||
}
|
||||
|
||||
if err := manager.DeleteRule(account.Id, defaultRule.ID); err != nil {
|
||||
if err := manager.DeleteRule(account.Id, defaultRule.ID, userID); err != nil {
|
||||
t.Errorf("delete default rule: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -759,7 +762,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := manager.SaveRule(account.Id, &rule); err != nil {
|
||||
if err := manager.SaveRule(account.Id, userID, &rule); err != nil {
|
||||
t.Errorf("delete default rule: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -779,7 +782,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := manager.DeletePeer(account.Id, peer3.Key); err != nil {
|
||||
if _, err := manager.DeletePeer(account.Id, peer3.Key, userID); err != nil {
|
||||
t.Errorf("delete peer: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -814,8 +817,8 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
account, err := createAccount(manager, "test_account", "account_creator", "")
|
||||
userID := "account_creator"
|
||||
account, err := createAccount(manager, "test_account", userID, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -843,7 +846,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = manager.DeletePeer(account.Id, peerKey)
|
||||
_, err = manager.DeletePeer(account.Id, peerKey, userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -1075,15 +1078,20 @@ func TestFileStore_GetRoutesByPrefix(t *testing.T) {
|
||||
assert.Contains(t, routeIDs, "route-2")
|
||||
}
|
||||
|
||||
func TestAccount_GetPeersRoutes(t *testing.T) {
|
||||
func TestAccount_GetRoutesToSync(t *testing.T) {
|
||||
_, prefix, err := route.ParseNetwork("192.168.64.0/24")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, prefix2, err := route.ParseNetwork("192.168.0.0/24")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
account := &Account{
|
||||
Peers: map[string]*Peer{
|
||||
"peer-1": {Key: "peer-1"}, "peer-2": {Key: "peer-2"}, "peer-3": {Key: "peer-1"},
|
||||
},
|
||||
Groups: map[string]*Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}},
|
||||
Routes: map[string]*route.Route{
|
||||
"route-1": {
|
||||
ID: "route-1",
|
||||
@@ -1095,9 +1103,22 @@ func TestAccount_GetPeersRoutes(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 999,
|
||||
Enabled: true,
|
||||
Groups: []string{"group1"},
|
||||
},
|
||||
"route-2": {
|
||||
ID: "route-2",
|
||||
Network: prefix2,
|
||||
NetID: "network-2",
|
||||
Description: "network-2",
|
||||
Peer: "peer-2",
|
||||
NetworkType: 0,
|
||||
Masquerade: false,
|
||||
Metric: 999,
|
||||
Enabled: true,
|
||||
Groups: []string{"group1"},
|
||||
},
|
||||
"route-3": {
|
||||
ID: "route-3",
|
||||
Network: prefix,
|
||||
NetID: "network-1",
|
||||
Description: "network-1",
|
||||
@@ -1106,20 +1127,24 @@ func TestAccount_GetPeersRoutes(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 999,
|
||||
Enabled: true,
|
||||
Groups: []string{"group1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
routes := account.GetPeersRoutes([]*Peer{{Key: "peer-1"}, {Key: "peer-2"}, {Key: "non-existing-peer"}})
|
||||
routes := account.getRoutesToSync("peer-2", []*Peer{{Key: "peer-1"}, {Key: "peer-3"}})
|
||||
|
||||
assert.Len(t, routes, 2)
|
||||
routeIDs := make(map[string]struct{}, 2)
|
||||
for _, r := range routes {
|
||||
routeIDs[r.ID] = struct{}{}
|
||||
}
|
||||
assert.Contains(t, routeIDs, "route-1")
|
||||
assert.Contains(t, routeIDs, "route-2")
|
||||
assert.Contains(t, routeIDs, "route-3")
|
||||
|
||||
emptyRoutes := account.getRoutesToSync("peer-3", []*Peer{{Key: "peer-1"}, {Key: "peer-2"}})
|
||||
|
||||
assert.Len(t, emptyRoutes, 0)
|
||||
}
|
||||
|
||||
func TestAccount_Copy(t *testing.T) {
|
||||
@@ -1206,7 +1231,11 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "")
|
||||
eventStore, err := activity.NewSQLiteStore(t.TempDir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore)
|
||||
}
|
||||
|
||||
func createStore(t *testing.T) (Store, error) {
|
||||
|
||||
223
management/server/activity/event.go
Normal file
223
management/server/activity/event.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package activity
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
// PeerAddedByUser indicates that a user added a new peer to the system
|
||||
PeerAddedByUser Activity = iota
|
||||
// PeerAddedWithSetupKey indicates that a new peer joined the system using a setup key
|
||||
PeerAddedWithSetupKey
|
||||
// UserJoined indicates that a new user joined the account
|
||||
UserJoined
|
||||
// UserInvited indicates that a new user was invited to join the account
|
||||
UserInvited
|
||||
// AccountCreated indicates that a new account has been created
|
||||
AccountCreated
|
||||
// PeerRemovedByUser indicates that a user removed a peer from the system
|
||||
PeerRemovedByUser
|
||||
// RuleAdded indicates that a user added a new rule
|
||||
RuleAdded
|
||||
// RuleUpdated indicates that a user updated a rule
|
||||
RuleUpdated
|
||||
// RuleRemoved indicates that a user removed a rule
|
||||
RuleRemoved
|
||||
// SetupKeyCreated indicates that a user created a new setup key
|
||||
SetupKeyCreated
|
||||
// SetupKeyUpdated indicates that a user updated a setup key
|
||||
SetupKeyUpdated
|
||||
// SetupKeyRevoked indicates that a user revoked a setup key
|
||||
SetupKeyRevoked
|
||||
// SetupKeyOverused indicates that setup key usage exhausted
|
||||
SetupKeyOverused
|
||||
// GroupCreated indicates that a user created a group
|
||||
GroupCreated
|
||||
// GroupUpdated indicates that a user updated a group
|
||||
GroupUpdated
|
||||
// PeerGroupsUpdated indicates that a user updated groups of a peer
|
||||
PeerGroupsUpdated
|
||||
)
|
||||
|
||||
const (
|
||||
// PeerAddedByUserMessage is a human-readable text message of the PeerAddedByUser activity
|
||||
PeerAddedByUserMessage string = "Peer added"
|
||||
// PeerAddedWithSetupKeyMessage is a human-readable text message of the PeerAddedWithSetupKey activity
|
||||
PeerAddedWithSetupKeyMessage = PeerAddedByUserMessage
|
||||
//UserJoinedMessage is a human-readable text message of the UserJoined activity
|
||||
UserJoinedMessage string = "User joined"
|
||||
//UserInvitedMessage is a human-readable text message of the UserInvited activity
|
||||
UserInvitedMessage string = "User invited"
|
||||
//AccountCreatedMessage is a human-readable text message of the AccountCreated activity
|
||||
AccountCreatedMessage string = "Account created"
|
||||
// PeerRemovedByUserMessage is a human-readable text message of the PeerRemovedByUser activity
|
||||
PeerRemovedByUserMessage string = "Peer deleted"
|
||||
// RuleAddedMessage is a human-readable text message of the RuleAdded activity
|
||||
RuleAddedMessage string = "Rule added"
|
||||
// RuleRemovedMessage is a human-readable text message of the RuleRemoved activity
|
||||
RuleRemovedMessage string = "Rule deleted"
|
||||
// RuleUpdatedMessage is a human-readable text message of the RuleRemoved activity
|
||||
RuleUpdatedMessage string = "Rule updated"
|
||||
// SetupKeyCreatedMessage is a human-readable text message of the SetupKeyCreated activity
|
||||
SetupKeyCreatedMessage string = "Setup key created"
|
||||
// SetupKeyUpdatedMessage is a human-readable text message of the SetupKeyUpdated activity
|
||||
SetupKeyUpdatedMessage string = "Setup key updated"
|
||||
// SetupKeyRevokedMessage is a human-readable text message of the SetupKeyRevoked activity
|
||||
SetupKeyRevokedMessage string = "Setup key revoked"
|
||||
// SetupKeyOverusedMessage is a human-readable text message of the SetupKeyOverused activity
|
||||
SetupKeyOverusedMessage string = "Setup key overused"
|
||||
// GroupCreatedMessage is a human-readable text message of the GroupCreated activity
|
||||
GroupCreatedMessage string = "Group created"
|
||||
// GroupUpdatedMessage is a human-readable text message of the GroupUpdated activity
|
||||
GroupUpdatedMessage string = "Group updated"
|
||||
// PeerGroupsUpdatedMessage is a human-readable text message of the PeerGroupsUpdated activity
|
||||
PeerGroupsUpdatedMessage string = "Peer groups updated"
|
||||
)
|
||||
|
||||
// Activity that triggered an Event
|
||||
type Activity int
|
||||
|
||||
// Message returns a string representation of an activity
|
||||
func (a Activity) Message() string {
|
||||
switch a {
|
||||
case PeerAddedByUser:
|
||||
return PeerAddedByUserMessage
|
||||
case PeerRemovedByUser:
|
||||
return PeerRemovedByUserMessage
|
||||
case PeerAddedWithSetupKey:
|
||||
return PeerAddedWithSetupKeyMessage
|
||||
case UserJoined:
|
||||
return UserJoinedMessage
|
||||
case UserInvited:
|
||||
return UserInvitedMessage
|
||||
case AccountCreated:
|
||||
return AccountCreatedMessage
|
||||
case RuleAdded:
|
||||
return RuleAddedMessage
|
||||
case RuleRemoved:
|
||||
return RuleRemovedMessage
|
||||
case RuleUpdated:
|
||||
return RuleUpdatedMessage
|
||||
case SetupKeyCreated:
|
||||
return SetupKeyCreatedMessage
|
||||
case SetupKeyUpdated:
|
||||
return SetupKeyUpdatedMessage
|
||||
case SetupKeyRevoked:
|
||||
return SetupKeyRevokedMessage
|
||||
case SetupKeyOverused:
|
||||
return SetupKeyOverusedMessage
|
||||
case GroupCreated:
|
||||
return GroupCreatedMessage
|
||||
case GroupUpdated:
|
||||
return GroupUpdatedMessage
|
||||
case PeerGroupsUpdated:
|
||||
return PeerGroupsUpdatedMessage
|
||||
default:
|
||||
return "UNKNOWN_ACTIVITY"
|
||||
}
|
||||
}
|
||||
|
||||
// StringCode returns a string code of the activity
|
||||
func (a Activity) StringCode() string {
|
||||
switch a {
|
||||
case PeerAddedByUser:
|
||||
return "user.peer.add"
|
||||
case PeerRemovedByUser:
|
||||
return "user.peer.delete"
|
||||
case PeerAddedWithSetupKey:
|
||||
return "setupkey.peer.add"
|
||||
case UserJoined:
|
||||
return "user.join"
|
||||
case UserInvited:
|
||||
return "user.invite"
|
||||
case AccountCreated:
|
||||
return "account.create"
|
||||
case RuleAdded:
|
||||
return "rule.add"
|
||||
case RuleRemoved:
|
||||
return "rule.delete"
|
||||
case RuleUpdated:
|
||||
return "rule.update"
|
||||
case SetupKeyCreated:
|
||||
return "setupkey.add"
|
||||
case SetupKeyRevoked:
|
||||
return "setupkey.revoke"
|
||||
case SetupKeyOverused:
|
||||
return "setupkey.overuse"
|
||||
case SetupKeyUpdated:
|
||||
return "setupkey.update"
|
||||
case GroupCreated:
|
||||
return "group.add"
|
||||
case GroupUpdated:
|
||||
return "group.update"
|
||||
case PeerGroupsUpdated:
|
||||
return "peer.groups.update"
|
||||
default:
|
||||
return "UNKNOWN_ACTIVITY"
|
||||
}
|
||||
}
|
||||
|
||||
// Store provides an interface to store or stream events.
|
||||
type Store interface {
|
||||
// Save an event in the store
|
||||
Save(event *Event) (*Event, error)
|
||||
// Get returns "limit" number of events from the "offset" index ordered descending or ascending by a timestamp
|
||||
Get(accountID string, offset, limit int, descending bool) ([]*Event, error)
|
||||
// Close the sink flushing events if necessary
|
||||
Close() error
|
||||
}
|
||||
|
||||
// NoopEventStore implements the Store interface without doing any operations
|
||||
type NoopEventStore struct {
|
||||
}
|
||||
|
||||
// Save sets the Event.ID to 1
|
||||
func (store *NoopEventStore) Save(event *Event) (*Event, error) {
|
||||
event.ID = 1
|
||||
return event, nil
|
||||
}
|
||||
|
||||
// Get returns an empty list of events
|
||||
func (store *NoopEventStore) Get(accountID string, offset, limit int, descending bool) ([]*Event, error) {
|
||||
return []*Event{}, nil
|
||||
}
|
||||
|
||||
// Close doesn't close anything
|
||||
func (store *NoopEventStore) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Event represents a network/system activity event.
|
||||
type Event struct {
|
||||
// Timestamp of the event
|
||||
Timestamp time.Time
|
||||
// Activity that was performed during the event
|
||||
Activity Activity
|
||||
// ID of the event (can be empty, meaning that it wasn't yet generated)
|
||||
ID uint64
|
||||
// InitiatorID is the ID of an object that initiated the event (e.g., a user)
|
||||
InitiatorID string
|
||||
// TargetID is the ID of an object that was effected by the event (e.g., a peer)
|
||||
TargetID string
|
||||
// AccountID is the ID of an account where the event happened
|
||||
AccountID string
|
||||
// Meta of the event, e.g. deleted peer information like name, IP, etc
|
||||
Meta map[string]any
|
||||
}
|
||||
|
||||
// Copy the event
|
||||
func (e *Event) Copy() *Event {
|
||||
|
||||
meta := make(map[string]any, len(e.Meta))
|
||||
for key, value := range e.Meta {
|
||||
meta[key] = value
|
||||
}
|
||||
|
||||
return &Event{
|
||||
Timestamp: e.Timestamp,
|
||||
Activity: e.Activity,
|
||||
ID: e.ID,
|
||||
InitiatorID: e.InitiatorID,
|
||||
TargetID: e.TargetID,
|
||||
AccountID: e.AccountID,
|
||||
Meta: meta,
|
||||
}
|
||||
}
|
||||
1
management/server/activity/mock.go
Normal file
1
management/server/activity/mock.go
Normal file
@@ -0,0 +1 @@
|
||||
package activity
|
||||
145
management/server/activity/sqlite/sqlite.go
Normal file
145
management/server/activity/sqlite/sqlite.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
|
||||
// sqlite driver
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
//eventSinkDB is the default name of the events database
|
||||
eventSinkDB = "events.db"
|
||||
createTableQuery = "CREATE TABLE IF NOT EXISTS events " +
|
||||
"(id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
"activity INTEGER, " +
|
||||
"timestamp DATETIME, " +
|
||||
"initiator_id TEXT," +
|
||||
"account_id TEXT," +
|
||||
"meta TEXT," +
|
||||
" target_id TEXT);"
|
||||
)
|
||||
|
||||
// Store is the implementation of the activity.Store interface backed by SQLite
|
||||
type Store struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewSQLiteStore creates a new Store with an event table if not exists.
|
||||
func NewSQLiteStore(dataDir string) (*Store, error) {
|
||||
dbFile := filepath.Join(dataDir, eventSinkDB)
|
||||
db, err := sql.Open("sqlite3", dbFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = db.Exec(createTableQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Store{db: db}, nil
|
||||
}
|
||||
|
||||
func processResult(result *sql.Rows) ([]*activity.Event, error) {
|
||||
events := make([]*activity.Event, 0)
|
||||
for result.Next() {
|
||||
var id int64
|
||||
var operation activity.Activity
|
||||
var timestamp time.Time
|
||||
var initiator string
|
||||
var target string
|
||||
var account string
|
||||
var jsonMeta string
|
||||
err := result.Scan(&id, &operation, ×tamp, &initiator, &target, &account, &jsonMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
meta := make(map[string]any)
|
||||
if jsonMeta != "" {
|
||||
err = json.Unmarshal([]byte(jsonMeta), &meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
events = append(events, &activity.Event{
|
||||
Timestamp: timestamp,
|
||||
Activity: operation,
|
||||
ID: uint64(id),
|
||||
InitiatorID: initiator,
|
||||
TargetID: target,
|
||||
AccountID: account,
|
||||
Meta: meta,
|
||||
})
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// Get returns "limit" number of events from index ordered descending or ascending by a timestamp
|
||||
func (store *Store) Get(accountID string, offset, limit int, descending bool) ([]*activity.Event, error) {
|
||||
order := "DESC"
|
||||
if !descending {
|
||||
order = "ASC"
|
||||
}
|
||||
stmt, err := store.db.Prepare(fmt.Sprintf("SELECT id, activity, timestamp, initiator_id, target_id, account_id, meta"+
|
||||
" FROM events WHERE account_id = ? ORDER BY timestamp %s LIMIT ? OFFSET ?;", order))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := stmt.Query(accountID, limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer result.Close() //nolint
|
||||
return processResult(result)
|
||||
}
|
||||
|
||||
// Save an event in the SQLite events table
|
||||
func (store *Store) Save(event *activity.Event) (*activity.Event, error) {
|
||||
|
||||
stmt, err := store.db.Prepare("INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id, meta) VALUES(?, ?, ?, ?, ?, ?)")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var jsonMeta string
|
||||
if event.Meta != nil {
|
||||
metaBytes, err := json.Marshal(event.Meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jsonMeta = string(metaBytes)
|
||||
}
|
||||
|
||||
result, err := stmt.Exec(event.Activity, event.Timestamp, event.InitiatorID, event.TargetID, event.AccountID, jsonMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
eventCopy := event.Copy()
|
||||
eventCopy.ID = uint64(id)
|
||||
return eventCopy, nil
|
||||
}
|
||||
|
||||
// Close the Store
|
||||
func (store *Store) Close() error {
|
||||
if store.db != nil {
|
||||
return store.db.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
52
management/server/activity/sqlite/sqlite_test.go
Normal file
52
management/server/activity/sqlite/sqlite_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewSQLiteStore(t *testing.T) {
|
||||
dataDir := t.TempDir()
|
||||
store, err := NewSQLiteStore(dataDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
accountID := "account_1"
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err = store.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.PeerAddedByUser,
|
||||
InitiatorID: "user_" + fmt.Sprint(i),
|
||||
TargetID: "peer_" + fmt.Sprint(i),
|
||||
AccountID: accountID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
result, err := store.Get(accountID, 0, 10, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Len(t, result, 10)
|
||||
assert.True(t, result[0].Timestamp.Before(result[len(result)-1].Timestamp))
|
||||
|
||||
result, err = store.Get(accountID, 0, 5, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Len(t, result, 5)
|
||||
assert.True(t, result[0].Timestamp.After(result[len(result)-1].Timestamp))
|
||||
}
|
||||
@@ -78,15 +78,7 @@ func getPeersCustomZone(account *Account, dnsDomain string) nbdns.CustomZone {
|
||||
}
|
||||
|
||||
func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup {
|
||||
groupList := make(lookupMap)
|
||||
for groupID, group := range account.Groups {
|
||||
for _, id := range group.Peers {
|
||||
if id == peerID {
|
||||
groupList[groupID] = struct{}{}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
groupList := account.getPeerGroups(peerID)
|
||||
|
||||
var peerNSGroups []*nbdns.NameServerGroup
|
||||
|
||||
|
||||
33
management/server/event.go
Normal file
33
management/server/event.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
)
|
||||
|
||||
// GetEvents returns a list of activity events of an account
|
||||
func (am *DefaultAccountManager) GetEvents(accountID, userID string) ([]*activity.Event, error) {
|
||||
events, err := am.eventStore.Get(accountID, 0, 10000, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// this is a workaround for duplicate activity.UserJoined events that might occur when a user redeems invite.
|
||||
// we will need to find a better way to handle this.
|
||||
filtered := make([]*activity.Event, 0)
|
||||
dups := make(map[string]struct{})
|
||||
for _, event := range events {
|
||||
if event.Activity == activity.UserJoined {
|
||||
key := event.TargetID + event.InitiatorID + event.AccountID + fmt.Sprint(event.Activity)
|
||||
_, duplicate := dups[key]
|
||||
if duplicate {
|
||||
continue
|
||||
} else {
|
||||
dups[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
filtered = append(filtered, event)
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
@@ -105,6 +105,19 @@ func restore(file string) (*FileStore, error) {
|
||||
if len(existingLabels) != len(account.Peers) {
|
||||
addPeerLabelsToAccount(account, existingLabels)
|
||||
}
|
||||
|
||||
allGroup, err := account.GetGroupAll()
|
||||
if err != nil {
|
||||
log.Errorf("unable to find the All group, this should happen only when migrate from a version that didn't support groups. Error: %v", err)
|
||||
// if the All group didn't exist we probably don't have routes to update
|
||||
continue
|
||||
}
|
||||
|
||||
for _, route := range account.Routes {
|
||||
if len(route.Groups) == 0 {
|
||||
route.Groups = []string{allGroup.ID}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we need this persist to apply changes we made to account.Peers (we set them to Disconnected)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package server
|
||||
|
||||
import "github.com/netbirdio/netbird/management/server/status"
|
||||
import (
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Group of the peers for ACL
|
||||
type Group struct {
|
||||
@@ -34,6 +38,11 @@ type GroupUpdateOperation struct {
|
||||
Values []string
|
||||
}
|
||||
|
||||
// EventMeta returns activity event meta related to the group
|
||||
func (g *Group) EventMeta() map[string]any {
|
||||
return map[string]any{"name": g.Name}
|
||||
}
|
||||
|
||||
func (g *Group) Copy() *Group {
|
||||
return &Group{
|
||||
ID: g.ID,
|
||||
@@ -62,7 +71,7 @@ func (am *DefaultAccountManager) GetGroup(accountID, groupID string) (*Group, er
|
||||
}
|
||||
|
||||
// SaveGroup object of the peers
|
||||
func (am *DefaultAccountManager) SaveGroup(accountID string, group *Group) error {
|
||||
func (am *DefaultAccountManager) SaveGroup(accountID, userID string, group *Group) error {
|
||||
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
@@ -71,7 +80,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID string, group *Group) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, exists := account.Groups[group.ID]
|
||||
account.Groups[group.ID] = group
|
||||
|
||||
account.Network.IncSerial()
|
||||
@@ -79,6 +88,20 @@ func (am *DefaultAccountManager) SaveGroup(accountID string, group *Group) error
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
_, err = am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.GroupCreated,
|
||||
InitiatorID: userID,
|
||||
TargetID: group.ID,
|
||||
AccountID: accountID,
|
||||
Meta: group.EventMeta(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return am.updateAccountPeers(account)
|
||||
}
|
||||
|
||||
|
||||
@@ -435,10 +435,7 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot
|
||||
|
||||
func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfig {
|
||||
netmask, _ := network.Net.Mask.Size()
|
||||
fqdn := ""
|
||||
if dnsName != "" {
|
||||
fqdn = peer.DNSLabel + "." + dnsName
|
||||
}
|
||||
fqdn := peer.FQDN(dnsName)
|
||||
return &proto.PeerConfig{
|
||||
Address: fmt.Sprintf("%s/%d", peer.IP.String(), netmask), // take it from the network
|
||||
SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled},
|
||||
@@ -449,10 +446,7 @@ func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfi
|
||||
func toRemotePeerConfig(peers []*Peer, dnsName string) []*proto.RemotePeerConfig {
|
||||
remotePeers := []*proto.RemotePeerConfig{}
|
||||
for _, rPeer := range peers {
|
||||
fqdn := ""
|
||||
if dnsName != "" {
|
||||
fqdn = rPeer.DNSLabel + "." + dnsName
|
||||
}
|
||||
fqdn := rPeer.FQDN(dnsName)
|
||||
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
|
||||
WgPubKey: rPeer.Key,
|
||||
AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)},
|
||||
|
||||
@@ -18,6 +18,8 @@ tags:
|
||||
description: Interact with and view information about routes.
|
||||
- name: DNS
|
||||
description: Interact with and view information about DNS configuration.
|
||||
- name: Events
|
||||
description: View information about the account and network events.
|
||||
components:
|
||||
schemas:
|
||||
User:
|
||||
@@ -45,12 +47,12 @@ components:
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- email
|
||||
- name
|
||||
- role
|
||||
- auto_groups
|
||||
- status
|
||||
- id
|
||||
- email
|
||||
- name
|
||||
- role
|
||||
- auto_groups
|
||||
- status
|
||||
UserRequest:
|
||||
type: object
|
||||
properties:
|
||||
@@ -96,8 +98,8 @@ components:
|
||||
description: Peer's hostname
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- id
|
||||
- name
|
||||
Peer:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/PeerMinimum'
|
||||
@@ -140,15 +142,15 @@ components:
|
||||
description: 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
|
||||
type: string
|
||||
required:
|
||||
- ip
|
||||
- connected
|
||||
- last_seen
|
||||
- os
|
||||
- version
|
||||
- groups
|
||||
- ssh_enabled
|
||||
- hostname
|
||||
- dns_label
|
||||
- ip
|
||||
- connected
|
||||
- last_seen
|
||||
- os
|
||||
- version
|
||||
- groups
|
||||
- ssh_enabled
|
||||
- hostname
|
||||
- dns_label
|
||||
SetupKey:
|
||||
type: object
|
||||
properties:
|
||||
@@ -193,19 +195,23 @@ components:
|
||||
description: Setup key last update date
|
||||
type: string
|
||||
format: date-time
|
||||
usage_limit:
|
||||
description: A number of times this key can be used. The value of 0 indicates the unlimited usage.
|
||||
type: integer
|
||||
required:
|
||||
- id
|
||||
- key
|
||||
- name
|
||||
- expires
|
||||
- type
|
||||
- valid
|
||||
- revoked
|
||||
- used_times
|
||||
- last_used
|
||||
- state
|
||||
- auto_groups
|
||||
- updated_at
|
||||
- id
|
||||
- key
|
||||
- name
|
||||
- expires
|
||||
- type
|
||||
- valid
|
||||
- revoked
|
||||
- used_times
|
||||
- last_used
|
||||
- state
|
||||
- auto_groups
|
||||
- updated_at
|
||||
- usage_limit
|
||||
SetupKeyRequest:
|
||||
type: object
|
||||
properties:
|
||||
@@ -226,12 +232,16 @@ components:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
usage_limit:
|
||||
description: A number of times this key can be used. The value of 0 indicates the unlimited usage.
|
||||
type: integer
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
- expires_in
|
||||
- revoked
|
||||
- auto_groups
|
||||
- usage_limit
|
||||
GroupMinimum:
|
||||
type: object
|
||||
properties:
|
||||
@@ -245,9 +255,9 @@ components:
|
||||
description: Count of peers associated to the group
|
||||
type: integer
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- peers_count
|
||||
- id
|
||||
- name
|
||||
- peers_count
|
||||
Group:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/GroupMinimum'
|
||||
@@ -259,7 +269,7 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/PeerMinimum'
|
||||
required:
|
||||
- peers
|
||||
- peers
|
||||
PatchMinimum:
|
||||
type: object
|
||||
properties:
|
||||
@@ -303,10 +313,10 @@ components:
|
||||
description: Rule flow, currently, only "bidirect" for bi-directional traffic is accepted
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- description
|
||||
- disabled
|
||||
- flow
|
||||
- name
|
||||
- description
|
||||
- disabled
|
||||
- flow
|
||||
Rule:
|
||||
allOf:
|
||||
- type: object
|
||||
@@ -315,7 +325,7 @@ components:
|
||||
description: Rule ID
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- id
|
||||
- $ref: '#/components/schemas/RuleMinimum'
|
||||
- type: object
|
||||
properties:
|
||||
@@ -330,8 +340,8 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/GroupMinimum'
|
||||
required:
|
||||
- sources
|
||||
- destinations
|
||||
- sources
|
||||
- destinations
|
||||
RulePatchOperation:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/PatchMinimum'
|
||||
@@ -371,6 +381,11 @@ components:
|
||||
masquerade:
|
||||
description: Indicate if peer should masquerade traffic to this route's prefix
|
||||
type: boolean
|
||||
groups:
|
||||
description: Route group tag groups
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- description
|
||||
@@ -380,6 +395,7 @@ components:
|
||||
- network
|
||||
- metric
|
||||
- masquerade
|
||||
- groups
|
||||
Route:
|
||||
allOf:
|
||||
- type: object
|
||||
@@ -402,7 +418,7 @@ components:
|
||||
path:
|
||||
description: Route field to update in form /<field>
|
||||
type: string
|
||||
enum: [ "network","network_id","description","enabled","peer","metric","masquerade" ]
|
||||
enum: [ "network","network_id","description","enabled","peer","metric","masquerade", "groups" ]
|
||||
required:
|
||||
- path
|
||||
Nameserver:
|
||||
@@ -414,7 +430,7 @@ components:
|
||||
ns_type:
|
||||
description: Nameserver Type
|
||||
type: string
|
||||
enum: ["udp"]
|
||||
enum: [ "udp" ]
|
||||
port:
|
||||
description: Nameserver Port
|
||||
type: integer
|
||||
@@ -484,32 +500,72 @@ components:
|
||||
path:
|
||||
description: Nameserver group field to update in form /<field>
|
||||
type: string
|
||||
enum: [ "name", "description", "enabled", "groups", "nameservers", "primary", "domains" ]
|
||||
enum: [ "name", "description", "enabled", "groups", "nameservers", "primary", "domains" ]
|
||||
required:
|
||||
- path
|
||||
|
||||
Event:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: Event unique identifier
|
||||
type: string
|
||||
timestamp:
|
||||
description: The date and time when the event occurred
|
||||
type: string
|
||||
format: date-time
|
||||
activity:
|
||||
description: The activity that occurred during the event
|
||||
type: string
|
||||
activity_code:
|
||||
description: The string code of the activity that occurred during the event
|
||||
type: string
|
||||
enum: [ "user.peer.delete", "user.join", "user.invite", "user.peer.add",
|
||||
"setupkey.peer.add", "setupkey.add", "setupkey.update", "setupkey.revoke", "setupkey.overuse",
|
||||
"rule.add", "rule.delete", "rule.update",
|
||||
"group.add", "group.update",
|
||||
"account.create",
|
||||
]
|
||||
initiator_id:
|
||||
description: The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
|
||||
type: string
|
||||
target_id:
|
||||
description: The ID of the target of the event. E.g., an ID of the peer that a user removed.
|
||||
type: string
|
||||
meta:
|
||||
description: The metadata of the event
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- timestamp
|
||||
- activity
|
||||
- activity_code
|
||||
- initiator_id
|
||||
- target_id
|
||||
- meta
|
||||
responses:
|
||||
not_found:
|
||||
description: Resource not found
|
||||
content: {}
|
||||
content: { }
|
||||
validation_failed_simple:
|
||||
description: Validation failed
|
||||
content: {}
|
||||
content: { }
|
||||
bad_request:
|
||||
description: Bad Request
|
||||
content: {}
|
||||
content: { }
|
||||
internal_error:
|
||||
description: Internal Server Error
|
||||
content: { }
|
||||
validation_failed:
|
||||
description: Validation failed
|
||||
content: {}
|
||||
content: { }
|
||||
forbidden:
|
||||
description: Forbidden
|
||||
content: {}
|
||||
content: { }
|
||||
requires_authentication:
|
||||
description: Requires authentication
|
||||
content: {}
|
||||
content: { }
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
@@ -521,9 +577,9 @@ paths:
|
||||
/api/users:
|
||||
get:
|
||||
summary: Returns a list of all users
|
||||
tags: [Users]
|
||||
tags: [ Users ]
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- BearerAuth: [ ]
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON array of Users
|
||||
@@ -544,7 +600,7 @@ paths:
|
||||
/api/users/:
|
||||
post:
|
||||
summary: Create a User (invite)
|
||||
tags: [ Users]
|
||||
tags: [ Users ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
requestBody:
|
||||
@@ -571,7 +627,7 @@ paths:
|
||||
/api/users/{id}:
|
||||
put:
|
||||
summary: Update information about a User
|
||||
tags: [ Users]
|
||||
tags: [ Users ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -605,9 +661,9 @@ paths:
|
||||
/api/peers:
|
||||
get:
|
||||
summary: Returns a list of all peers
|
||||
tags: [Peers]
|
||||
tags: [ Peers ]
|
||||
security:
|
||||
- BearerAuth: []
|
||||
- BearerAuth: [ ]
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON Array of Peers
|
||||
@@ -628,7 +684,7 @@ paths:
|
||||
/api/peers/{id}:
|
||||
get:
|
||||
summary: Get information about a peer
|
||||
tags: [Peers]
|
||||
tags: [ Peers ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -655,7 +711,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
summary: Update information about a peer
|
||||
tags: [Peers]
|
||||
tags: [ Peers ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -696,7 +752,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a peer
|
||||
tags: [Peers]
|
||||
tags: [ Peers ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -709,7 +765,7 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: Delete status code
|
||||
content: {}
|
||||
content: { }
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
@@ -721,7 +777,7 @@ paths:
|
||||
/api/setup-keys:
|
||||
get:
|
||||
summary: Returns a list of all Setup Keys
|
||||
tags: [Setup Keys]
|
||||
tags: [ Setup Keys ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
responses:
|
||||
@@ -743,7 +799,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
post:
|
||||
summary: Creates a Setup Key
|
||||
tags: [Setup Keys]
|
||||
tags: [ Setup Keys ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
requestBody:
|
||||
@@ -770,7 +826,7 @@ paths:
|
||||
/api/setup-keys/{id}:
|
||||
get:
|
||||
summary: Get information about a Setup Key
|
||||
tags: [Setup Keys]
|
||||
tags: [ Setup Keys ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -797,7 +853,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
summary: Update information about a Setup Key
|
||||
tags: [Setup Keys]
|
||||
tags: [ Setup Keys ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -830,7 +886,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a Setup Key
|
||||
tags: [Setup Keys]
|
||||
tags: [ Setup Keys ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -843,7 +899,7 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: Delete status code
|
||||
content: {}
|
||||
content: { }
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
@@ -855,7 +911,7 @@ paths:
|
||||
/api/groups:
|
||||
get:
|
||||
summary: Returns a list of all Groups
|
||||
tags: [Groups]
|
||||
tags: [ Groups ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
responses:
|
||||
@@ -877,7 +933,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
post:
|
||||
summary: Creates a Group
|
||||
tags: [Groups]
|
||||
tags: [ Groups ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
requestBody:
|
||||
@@ -913,7 +969,7 @@ paths:
|
||||
/api/groups/{id}:
|
||||
get:
|
||||
summary: Get information about a Group
|
||||
tags: [Groups]
|
||||
tags: [ Groups ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -940,7 +996,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
summary: Update/Replace a Group
|
||||
tags: [Groups]
|
||||
tags: [ Groups ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -1015,7 +1071,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a Group
|
||||
tags: [Groups]
|
||||
tags: [ Groups ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -1028,7 +1084,7 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: Delete status code
|
||||
content: {}
|
||||
content: { }
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
@@ -1040,7 +1096,7 @@ paths:
|
||||
/api/rules:
|
||||
get:
|
||||
summary: Returns a list of all Rules
|
||||
tags: [Rules]
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
responses:
|
||||
@@ -1062,7 +1118,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
post:
|
||||
summary: Creates a Rule
|
||||
tags: [Rules]
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
requestBody:
|
||||
@@ -1092,7 +1148,7 @@ paths:
|
||||
/api/rules/{id}:
|
||||
get:
|
||||
summary: Get information about a Rules
|
||||
tags: [Rules]
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -1119,7 +1175,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
put:
|
||||
summary: Update/Replace a Rule
|
||||
tags: [Rules]
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -1198,7 +1254,7 @@ paths:
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
delete:
|
||||
summary: Delete a Rule
|
||||
tags: [Rules]
|
||||
tags: [ Rules ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
parameters:
|
||||
@@ -1211,7 +1267,7 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: Delete status code
|
||||
content: {}
|
||||
content: { }
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
@@ -1559,5 +1615,28 @@ paths:
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/events:
|
||||
get:
|
||||
summary: Returns a list of all events
|
||||
tags: [ Events ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON Array of Events
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Event'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
@@ -11,6 +11,25 @@ const (
|
||||
BearerAuthScopes = "BearerAuth.Scopes"
|
||||
)
|
||||
|
||||
// Defines values for EventActivityCode.
|
||||
const (
|
||||
EventActivityCodeAccountCreate EventActivityCode = "account.create"
|
||||
EventActivityCodeGroupAdd EventActivityCode = "group.add"
|
||||
EventActivityCodeGroupUpdate EventActivityCode = "group.update"
|
||||
EventActivityCodeRuleAdd EventActivityCode = "rule.add"
|
||||
EventActivityCodeRuleDelete EventActivityCode = "rule.delete"
|
||||
EventActivityCodeRuleUpdate EventActivityCode = "rule.update"
|
||||
EventActivityCodeSetupkeyAdd EventActivityCode = "setupkey.add"
|
||||
EventActivityCodeSetupkeyOveruse EventActivityCode = "setupkey.overuse"
|
||||
EventActivityCodeSetupkeyPeerAdd EventActivityCode = "setupkey.peer.add"
|
||||
EventActivityCodeSetupkeyRevoke EventActivityCode = "setupkey.revoke"
|
||||
EventActivityCodeSetupkeyUpdate EventActivityCode = "setupkey.update"
|
||||
EventActivityCodeUserInvite EventActivityCode = "user.invite"
|
||||
EventActivityCodeUserJoin EventActivityCode = "user.join"
|
||||
EventActivityCodeUserPeerAdd EventActivityCode = "user.peer.add"
|
||||
EventActivityCodeUserPeerDelete EventActivityCode = "user.peer.delete"
|
||||
)
|
||||
|
||||
// Defines values for GroupPatchOperationOp.
|
||||
const (
|
||||
GroupPatchOperationOpAdd GroupPatchOperationOp = "add"
|
||||
@@ -65,6 +84,7 @@ const (
|
||||
const (
|
||||
RoutePatchOperationPathDescription RoutePatchOperationPath = "description"
|
||||
RoutePatchOperationPathEnabled RoutePatchOperationPath = "enabled"
|
||||
RoutePatchOperationPathGroups RoutePatchOperationPath = "groups"
|
||||
RoutePatchOperationPathMasquerade RoutePatchOperationPath = "masquerade"
|
||||
RoutePatchOperationPathMetric RoutePatchOperationPath = "metric"
|
||||
RoutePatchOperationPathNetwork RoutePatchOperationPath = "network"
|
||||
@@ -96,6 +116,33 @@ const (
|
||||
UserStatusInvited UserStatus = "invited"
|
||||
)
|
||||
|
||||
// Event defines model for Event.
|
||||
type Event struct {
|
||||
// Activity The activity that occurred during the event
|
||||
Activity string `json:"activity"`
|
||||
|
||||
// ActivityCode The string code of the activity that occurred during the event
|
||||
ActivityCode EventActivityCode `json:"activity_code"`
|
||||
|
||||
// Id Event unique identifier
|
||||
Id string `json:"id"`
|
||||
|
||||
// InitiatorId The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
|
||||
InitiatorId string `json:"initiator_id"`
|
||||
|
||||
// Meta The metadata of the event
|
||||
Meta map[string]string `json:"meta"`
|
||||
|
||||
// TargetId The ID of the target of the event. E.g., an ID of the peer that a user removed.
|
||||
TargetId string `json:"target_id"`
|
||||
|
||||
// Timestamp The date and time when the event occurred
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// EventActivityCode The string code of the activity that occurred during the event
|
||||
type EventActivityCode string
|
||||
|
||||
// Group defines model for Group.
|
||||
type Group struct {
|
||||
// Id Group ID
|
||||
@@ -296,6 +343,9 @@ type Route struct {
|
||||
// Enabled Route status
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
// Groups Route group tag groups
|
||||
Groups []string `json:"groups"`
|
||||
|
||||
// Id Route Id
|
||||
Id string `json:"id"`
|
||||
|
||||
@@ -344,6 +394,9 @@ type RouteRequest struct {
|
||||
// Enabled Route status
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
// Groups Route group tag groups
|
||||
Groups []string `json:"groups"`
|
||||
|
||||
// Masquerade Indicate if peer should masquerade traffic to this route's prefix
|
||||
Masquerade bool `json:"masquerade"`
|
||||
|
||||
@@ -449,6 +502,9 @@ type SetupKey struct {
|
||||
// UpdatedAt Setup key last update date
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// UsageLimit A number of times this key can be used. The value of 0 indicates the unlimited usage.
|
||||
UsageLimit int `json:"usage_limit"`
|
||||
|
||||
// UsedTimes Usage count of setup key
|
||||
UsedTimes int `json:"used_times"`
|
||||
|
||||
@@ -472,6 +528,9 @@ type SetupKeyRequest struct {
|
||||
|
||||
// Type Setup key type, one-off for single time usage and reusable
|
||||
Type string `json:"type"`
|
||||
|
||||
// UsageLimit A number of times this key can be used. The value of 0 indicates the unlimited usage.
|
||||
UsageLimit int `json:"usage_limit"`
|
||||
}
|
||||
|
||||
// User defines model for User.
|
||||
|
||||
69
management/server/http/events.go
Normal file
69
management/server/http/events.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/http/api"
|
||||
"github.com/netbirdio/netbird/management/server/http/util"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Events HTTP handler
|
||||
type Events struct {
|
||||
accountManager server.AccountManager
|
||||
authAudience string
|
||||
jwtExtractor jwtclaims.ClaimsExtractor
|
||||
}
|
||||
|
||||
// NewEvents creates a new Events HTTP handler
|
||||
func NewEvents(accountManager server.AccountManager, authAudience string) *Events {
|
||||
return &Events{
|
||||
accountManager: accountManager,
|
||||
authAudience: authAudience,
|
||||
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
|
||||
}
|
||||
}
|
||||
|
||||
// GetEvents list of the given account
|
||||
func (h *Events) GetEvents(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
accountEvents, err := h.accountManager.GetEvents(account.Id, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
}
|
||||
events := make([]*api.Event, 0)
|
||||
for _, e := range accountEvents {
|
||||
events = append(events, toEventResponse(e))
|
||||
}
|
||||
|
||||
util.WriteJSONObject(w, events)
|
||||
}
|
||||
|
||||
func toEventResponse(event *activity.Event) *api.Event {
|
||||
meta := make(map[string]string)
|
||||
if event.Meta != nil {
|
||||
for s, a := range event.Meta {
|
||||
meta[s] = fmt.Sprintf("%v", a)
|
||||
}
|
||||
}
|
||||
return &api.Event{
|
||||
Id: fmt.Sprint(event.ID),
|
||||
InitiatorId: event.InitiatorID,
|
||||
Activity: event.Activity.Message(),
|
||||
ActivityCode: api.EventActivityCode(event.Activity.StringCode()),
|
||||
TargetId: event.TargetID,
|
||||
Timestamp: event.Timestamp,
|
||||
Meta: meta,
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func (h *Groups) GetAllGroupsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// UpdateGroupHandler handles update to a group identified by a given ID
|
||||
func (h *Groups) UpdateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -102,7 +102,7 @@ func (h *Groups) UpdateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Peers: peerIPsToKeys(account, req.Peers),
|
||||
}
|
||||
|
||||
if err := h.accountManager.SaveGroup(account.Id, &group); err != nil {
|
||||
if err := h.accountManager.SaveGroup(account.Id, user.Id, &group); err != nil {
|
||||
log.Errorf("failed updating group %s under account %s %v", groupID, account.Id, err)
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -219,7 +219,7 @@ func (h *Groups) PatchGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// CreateGroupHandler handles group creation request
|
||||
func (h *Groups) CreateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -243,7 +243,7 @@ func (h *Groups) CreateGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Peers: peerIPsToKeys(account, req.Peers),
|
||||
}
|
||||
|
||||
err = h.accountManager.SaveGroup(account.Id, &group)
|
||||
err = h.accountManager.SaveGroup(account.Id, user.Id, &group)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
|
||||
@@ -29,7 +29,7 @@ var TestPeers = map[string]*server.Peer{
|
||||
func initGroupTestData(user *server.User, groups ...*server.Group) *Groups {
|
||||
return &Groups{
|
||||
accountManager: &mock_server.MockAccountManager{
|
||||
SaveGroupFunc: func(accountID string, group *server.Group) error {
|
||||
SaveGroupFunc: func(accountID, userID string, group *server.Group) error {
|
||||
if !strings.HasPrefix(group.ID, "id-") {
|
||||
group.ID = "id-was-set"
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience
|
||||
userHandler := NewUserHandler(accountManager, authAudience)
|
||||
routesHandler := NewRoutes(accountManager, authAudience)
|
||||
nameserversHandler := NewNameservers(accountManager, authAudience)
|
||||
eventsHandler := NewEvents(accountManager, authAudience)
|
||||
|
||||
apiHandler.HandleFunc("/peers", peersHandler.GetPeers).Methods("GET", "OPTIONS")
|
||||
apiHandler.HandleFunc("/peers/{id}", peersHandler.HandlePeer).
|
||||
@@ -81,6 +82,8 @@ func APIHandler(accountManager s.AccountManager, authIssuer string, authAudience
|
||||
apiHandler.HandleFunc("/dns/nameservers/{id}", nameserversHandler.GetNameserverGroupHandler).Methods("GET", "OPTIONS")
|
||||
apiHandler.HandleFunc("/dns/nameservers/{id}", nameserversHandler.DeleteNameserverGroupHandler).Methods("DELETE", "OPTIONS")
|
||||
|
||||
apiHandler.HandleFunc("/events", eventsHandler.GetEvents).Methods("GET", "OPTIONS")
|
||||
|
||||
err = apiHandler.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||
methods, err := route.GetMethods()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"math/big"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Jwks is a collection of JSONWebKeys obtained from Config.HttpServerConfig.AuthKeysLocation
|
||||
// Jwks is a collection of JSONWebKey obtained from Config.HttpServerConfig.AuthKeysLocation
|
||||
type Jwks struct {
|
||||
Keys []JSONWebKeys `json:"keys"`
|
||||
Keys []JSONWebKey `json:"keys"`
|
||||
}
|
||||
|
||||
// JSONWebKeys is a representation of a Jason Web Key
|
||||
type JSONWebKeys struct {
|
||||
// JSONWebKey is a representation of a Jason Web Key
|
||||
type JSONWebKey struct {
|
||||
Kty string `json:"kty"`
|
||||
Kid string `json:"kid"`
|
||||
Use string `json:"use"`
|
||||
@@ -45,7 +54,7 @@ func NewJwtMiddleware(issuer string, audience string, keysLocation string) (*JWT
|
||||
|
||||
cert, err := getPemCert(token, keys)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert))
|
||||
@@ -74,18 +83,80 @@ func getPemKeys(keysLocation string) (*Jwks, error) {
|
||||
}
|
||||
|
||||
func getPemCert(token *jwt.Token, jwks *Jwks) (string, error) {
|
||||
// todo as we load the jkws when the server is starting, we should build a JKS map with the pem cert at the boot time
|
||||
cert := ""
|
||||
|
||||
for k := range jwks.Keys {
|
||||
if token.Header["kid"] == jwks.Keys[k].Kid {
|
||||
cert = "-----BEGIN CERTIFICATE-----\n" + jwks.Keys[k].X5c[0] + "\n-----END CERTIFICATE-----"
|
||||
if token.Header["kid"] != jwks.Keys[k].Kid {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(jwks.Keys[k].X5c) != 0 {
|
||||
cert = "-----BEGIN CERTIFICATE-----\n" + jwks.Keys[k].X5c[0] + "\n-----END CERTIFICATE-----"
|
||||
return cert, nil
|
||||
}
|
||||
log.Debugf("generating validation pem from JWK")
|
||||
return generatePemFromJWK(jwks.Keys[k])
|
||||
}
|
||||
|
||||
if cert == "" {
|
||||
err := errors.New("unable to find appropriate key")
|
||||
return cert, err
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
return "", errors.New("unable to find appropriate key")
|
||||
}
|
||||
|
||||
func generatePemFromJWK(jwk JSONWebKey) (string, error) {
|
||||
decodedModulus, err := base64.RawURLEncoding.DecodeString(jwk.N)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to decode JWK modulus, error: %s", err)
|
||||
}
|
||||
|
||||
intModules := big.NewInt(0)
|
||||
intModules.SetBytes(decodedModulus)
|
||||
|
||||
exponent, err := convertExponentStringToInt(jwk.E)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to decode JWK exponent, error: %s", err)
|
||||
}
|
||||
|
||||
publicKey := &rsa.PublicKey{
|
||||
N: intModules,
|
||||
E: exponent,
|
||||
}
|
||||
|
||||
derKey, err := x509.MarshalPKIXPublicKey(publicKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to convert public key to DER, error: %s", err)
|
||||
}
|
||||
|
||||
block := &pem.Block{
|
||||
Type: "RSA PUBLIC KEY",
|
||||
Bytes: derKey,
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
err = pem.Encode(&out, block)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to encode Pem block , error: %s", err)
|
||||
}
|
||||
|
||||
return out.String(), nil
|
||||
}
|
||||
|
||||
func convertExponentStringToInt(stringExponent string) (int, error) {
|
||||
decodedString, err := base64.StdEncoding.DecodeString(stringExponent)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
exponentBytes := decodedString
|
||||
if len(decodedString) < 8 {
|
||||
exponentBytes = make([]byte, 8-len(decodedString), 8)
|
||||
exponentBytes = append(exponentBytes, decodedString...)
|
||||
}
|
||||
|
||||
bytesReader := bytes.NewReader(exponentBytes)
|
||||
var exponent uint64
|
||||
err = binary.Read(bytesReader, binary.BigEndian, &exponent)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(exponent), nil
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ func (h *Peers) updatePeer(account *server.Account, peer *server.Peer, w http.Re
|
||||
util.WriteJSONObject(w, toPeerResponse(peer, account, dnsDomain))
|
||||
}
|
||||
|
||||
func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
||||
_, err := h.accountManager.DeletePeer(accountId, peer.Key)
|
||||
func (h *Peers) deletePeer(accountID, userID string, peer *server.Peer, w http.ResponseWriter, r *http.Request) {
|
||||
_, err := h.accountManager.DeletePeer(accountID, peer.Key, userID)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -56,7 +56,7 @@ func (h *Peers) deletePeer(accountId string, peer *server.Peer, w http.ResponseW
|
||||
|
||||
func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -78,7 +78,7 @@ func (h *Peers) HandlePeer(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodDelete:
|
||||
h.deletePeer(account.Id, peer, w, r)
|
||||
h.deletePeer(account.Id, user.Id, peer, w, r)
|
||||
return
|
||||
case http.MethodPut:
|
||||
h.updatePeer(account, peer, w, r)
|
||||
@@ -143,9 +143,10 @@ func toPeerResponse(peer *server.Peer, account *server.Account, dnsDomain string
|
||||
}
|
||||
}
|
||||
}
|
||||
fqdn := peer.DNSLabel
|
||||
if dnsDomain != "" {
|
||||
fqdn = peer.DNSLabel + "." + dnsDomain
|
||||
|
||||
fqdn := peer.FQDN(dnsDomain)
|
||||
if fqdn == "" {
|
||||
fqdn = peer.DNSLabel
|
||||
}
|
||||
return &api.Peer{
|
||||
Id: peer.IP.String(),
|
||||
|
||||
@@ -89,7 +89,7 @@ func (h *Routes) CreateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
newRoute, err := h.accountManager.CreateRoute(account.Id, newPrefix.String(), peerKey, req.Description, req.NetworkId, req.Masquerade, req.Metric, req.Enabled)
|
||||
newRoute, err := h.accountManager.CreateRoute(account.Id, newPrefix.String(), peerKey, req.Description, req.NetworkId, req.Masquerade, req.Metric, req.Groups, req.Enabled)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -162,6 +162,7 @@ func (h *Routes) UpdateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Metric: req.Metric,
|
||||
Description: req.Description,
|
||||
Enabled: req.Enabled,
|
||||
Groups: req.Groups,
|
||||
}
|
||||
|
||||
err = h.accountManager.SaveRoute(account.Id, newRoute)
|
||||
@@ -298,6 +299,16 @@ func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Type: server.UpdateRouteEnabled,
|
||||
Values: patch.Value,
|
||||
})
|
||||
case api.RoutePatchOperationPathGroups:
|
||||
if patch.Op != api.RoutePatchOperationOpReplace {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument,
|
||||
"groups field only accepts replace operation, got %s", patch.Op), w)
|
||||
return
|
||||
}
|
||||
operations = append(operations, server.RouteUpdateOperation{
|
||||
Type: server.UpdateRouteGroups,
|
||||
Values: patch.Value,
|
||||
})
|
||||
default:
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "invalid patch path"), w)
|
||||
return
|
||||
@@ -383,5 +394,6 @@ func toRouteResponse(account *server.Account, serverRoute *route.Route) *api.Rou
|
||||
NetworkType: serverRoute.NetworkType.String(),
|
||||
Masquerade: serverRoute.Masquerade,
|
||||
Metric: serverRoute.Metric,
|
||||
Groups: serverRoute.Groups,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ const (
|
||||
notFoundPeerID = "100.64.0.200"
|
||||
existingPeerKey = "existingPeerKey"
|
||||
testAccountID = "test_id"
|
||||
existingGroupID = "testGroup"
|
||||
)
|
||||
|
||||
var baseExistingRoute = &route.Route{
|
||||
@@ -39,6 +40,7 @@ var baseExistingRoute = &route.Route{
|
||||
Metric: 9999,
|
||||
Masquerade: false,
|
||||
Enabled: true,
|
||||
Groups: []string{existingGroupID},
|
||||
}
|
||||
|
||||
var testingAccount = &server.Account{
|
||||
@@ -64,7 +66,7 @@ func initRoutesTestData() *Routes {
|
||||
}
|
||||
return nil, status.Errorf(status.NotFound, "route with ID %s not found", routeID)
|
||||
},
|
||||
CreateRouteFunc: func(accountID string, network, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error) {
|
||||
CreateRouteFunc: func(accountID string, network, peer, description, netID string, masquerade bool, metric int, groups []string, enabled bool) (*route.Route, error) {
|
||||
networkType, p, _ := route.ParseNetwork(network)
|
||||
return &route.Route{
|
||||
ID: existingRouteID,
|
||||
@@ -75,6 +77,7 @@ func initRoutesTestData() *Routes {
|
||||
Description: description,
|
||||
Masquerade: masquerade,
|
||||
Enabled: enabled,
|
||||
Groups: groups,
|
||||
}, nil
|
||||
},
|
||||
SaveRouteFunc: func(_ string, _ *route.Route) error {
|
||||
@@ -116,6 +119,8 @@ func initRoutesTestData() *Routes {
|
||||
routeToUpdate.Masquerade, _ = strconv.ParseBool(operation.Values[0])
|
||||
case server.UpdateRouteEnabled:
|
||||
routeToUpdate.Enabled, _ = strconv.ParseBool(operation.Values[0])
|
||||
case server.UpdateRouteGroups:
|
||||
routeToUpdate.Groups = operation.Values
|
||||
default:
|
||||
return nil, fmt.Errorf("no operation")
|
||||
}
|
||||
@@ -181,7 +186,7 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/routes",
|
||||
requestBody: bytes.NewBuffer(
|
||||
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID))),
|
||||
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID))),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: true,
|
||||
expectedRoute: &api.Route{
|
||||
@@ -193,13 +198,14 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
NetworkType: route.IPv4NetworkString,
|
||||
Masquerade: false,
|
||||
Enabled: false,
|
||||
Groups: []string{existingGroupID},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "POST Not Found Peer",
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/routes",
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", notFoundPeerID)),
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", notFoundPeerID, existingGroupID)),
|
||||
expectedStatus: http.StatusNotFound,
|
||||
expectedBody: false,
|
||||
},
|
||||
@@ -207,7 +213,7 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
name: "POST Invalid Network Identifier",
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/routes",
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
@@ -215,7 +221,7 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
name: "POST Invalid Network",
|
||||
requestType: http.MethodPost,
|
||||
requestPath: "/api/routes",
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
@@ -223,7 +229,7 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
name: "PUT OK",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/routes/" + existingRouteID,
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID)),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: true,
|
||||
expectedRoute: &api.Route{
|
||||
@@ -235,13 +241,14 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
NetworkType: route.IPv4NetworkString,
|
||||
Masquerade: false,
|
||||
Enabled: false,
|
||||
Groups: []string{existingGroupID},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PUT Not Found Route",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/routes/" + notFoundRouteID,
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID)),
|
||||
expectedStatus: http.StatusNotFound,
|
||||
expectedBody: false,
|
||||
},
|
||||
@@ -249,7 +256,7 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
name: "PUT Not Found Peer",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/routes/" + existingRouteID,
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", notFoundPeerID)),
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", notFoundPeerID, existingGroupID)),
|
||||
expectedStatus: http.StatusNotFound,
|
||||
expectedBody: false,
|
||||
},
|
||||
@@ -257,7 +264,7 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
name: "PUT Invalid Network Identifier",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/routes/" + existingRouteID,
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"12345678901234567890qwertyuiopqwertyuiop1\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
@@ -265,7 +272,7 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
name: "PUT Invalid Network",
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/routes/" + existingRouteID,
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\"}", existingPeerID)),
|
||||
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/34\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", existingPeerID, existingGroupID)),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedBody: false,
|
||||
},
|
||||
@@ -285,6 +292,7 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
Masquerade: baseExistingRoute.Masquerade,
|
||||
Enabled: baseExistingRoute.Enabled,
|
||||
Metric: baseExistingRoute.Metric,
|
||||
Groups: baseExistingRoute.Groups,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -304,6 +312,7 @@ func TestRoutesHandlers(t *testing.T) {
|
||||
Masquerade: baseExistingRoute.Masquerade,
|
||||
Enabled: baseExistingRoute.Enabled,
|
||||
Metric: baseExistingRoute.Metric,
|
||||
Groups: baseExistingRoute.Groups,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -52,7 +52,7 @@ func (h *Rules) GetAllRulesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// UpdateRuleHandler handles update to a rule identified by a given ID
|
||||
func (h *Rules) UpdateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -109,7 +109,7 @@ func (h *Rules) UpdateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = h.accountManager.SaveRule(account.Id, &rule)
|
||||
err = h.accountManager.SaveRule(account.Id, user.Id, &rule)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -267,7 +267,7 @@ func (h *Rules) PatchRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// CreateRuleHandler handles rule creation request
|
||||
func (h *Rules) CreateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -312,7 +312,7 @@ func (h *Rules) CreateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = h.accountManager.SaveRule(account.Id, &rule)
|
||||
err = h.accountManager.SaveRule(account.Id, user.Id, &rule)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -326,7 +326,7 @@ func (h *Rules) CreateRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// DeleteRuleHandler handles rule deletion request
|
||||
func (h *Rules) DeleteRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -339,7 +339,7 @@ func (h *Rules) DeleteRuleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = h.accountManager.DeleteRule(aID, rID)
|
||||
err = h.accountManager.DeleteRule(aID, rID, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
func initRulesTestData(rules ...*server.Rule) *Rules {
|
||||
return &Rules{
|
||||
accountManager: &mock_server.MockAccountManager{
|
||||
SaveRuleFunc: func(_ string, rule *server.Rule) error {
|
||||
SaveRuleFunc: func(_, _ string, rule *server.Rule) error {
|
||||
if !strings.HasPrefix(rule.ID, "id-") {
|
||||
rule.ID = "id-was-set"
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewSetupKeysHandler(accountManager server.AccountManager, authAudience stri
|
||||
// CreateSetupKeyHandler is a POST requests that creates a new SetupKey
|
||||
func (h *SetupKeys) CreateSetupKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -50,7 +50,7 @@ func (h *SetupKeys) CreateSetupKeyHandler(w http.ResponseWriter, r *http.Request
|
||||
|
||||
if !(server.SetupKeyType(req.Type) == server.SetupKeyReusable ||
|
||||
server.SetupKeyType(req.Type) == server.SetupKeyOneOff) {
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "unknown setup key type %s", string(req.Type)), w)
|
||||
util.WriteError(status.Errorf(status.InvalidArgument, "unknown setup key type %s", req.Type), w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func (h *SetupKeys) CreateSetupKeyHandler(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
setupKey, err := h.accountManager.CreateSetupKey(account.Id, req.Name, server.SetupKeyType(req.Type), expiresIn,
|
||||
req.AutoGroups)
|
||||
req.AutoGroups, req.UsageLimit, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -98,7 +98,7 @@ func (h *SetupKeys) GetSetupKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// UpdateSetupKeyHandler is a PUT request to update server.SetupKey
|
||||
func (h *SetupKeys) UpdateSetupKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -134,7 +134,7 @@ func (h *SetupKeys) UpdateSetupKeyHandler(w http.ResponseWriter, r *http.Request
|
||||
newKey.Name = req.Name
|
||||
newKey.Id = keyID
|
||||
|
||||
newKey, err = h.accountManager.SaveSetupKey(account.Id, newKey)
|
||||
newKey, err = h.accountManager.SaveSetupKey(account.Id, newKey, user.Id)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -201,5 +201,6 @@ func toResponseBody(key *server.SetupKey) *api.SetupKey {
|
||||
State: state,
|
||||
AutoGroups: key.AutoGroups,
|
||||
UpdatedAt: key.UpdatedAt,
|
||||
UsageLimit: key.UsageLimit,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ func initSetupKeysTestMetaData(defaultKey *server.SetupKey, newKey *server.Setup
|
||||
"id-all": {ID: "id-all", Name: "All"}},
|
||||
}, user, nil
|
||||
},
|
||||
CreateSetupKeyFunc: func(_ string, keyName string, typ server.SetupKeyType, _ time.Duration, _ []string) (*server.SetupKey, error) {
|
||||
CreateSetupKeyFunc: func(_ string, keyName string, typ server.SetupKeyType, _ time.Duration, _ []string,
|
||||
_ int, _ string) (*server.SetupKey, error) {
|
||||
if keyName == newKey.Name || typ != newKey.Type {
|
||||
return newKey, nil
|
||||
}
|
||||
@@ -63,7 +64,7 @@ func initSetupKeysTestMetaData(defaultKey *server.SetupKey, newKey *server.Setup
|
||||
}
|
||||
},
|
||||
|
||||
SaveSetupKeyFunc: func(accountID string, key *server.SetupKey) (*server.SetupKey, error) {
|
||||
SaveSetupKeyFunc: func(accountID string, key *server.SetupKey, _ string) (*server.SetupKey, error) {
|
||||
if key.Id == updatedSetupKey.Id {
|
||||
return updatedSetupKey, nil
|
||||
}
|
||||
@@ -93,7 +94,8 @@ func TestSetupKeysHandlers(t *testing.T) {
|
||||
|
||||
adminUser := server.NewAdminUser("test_user")
|
||||
|
||||
newSetupKey := server.GenerateSetupKey(newSetupKeyName, server.SetupKeyReusable, 0, []string{"group-1"})
|
||||
newSetupKey := server.GenerateSetupKey(newSetupKeyName, server.SetupKeyReusable, 0, []string{"group-1"},
|
||||
server.SetupKeyUnlimitedUsage)
|
||||
updatedDefaultSetupKey := defaultSetupKey.Copy()
|
||||
updatedDefaultSetupKey.AutoGroups = []string{"group-1"}
|
||||
updatedDefaultSetupKey.Name = updatedSetupKeyName
|
||||
|
||||
@@ -81,7 +81,7 @@ func (h *UserHandler) CreateUserHandler(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
claims := h.jwtExtractor.ExtractClaimsFromRequestContext(r, h.authAudience)
|
||||
account, _, err := h.accountManager.GetAccountFromToken(claims)
|
||||
account, user, err := h.accountManager.GetAccountFromToken(claims)
|
||||
if err != nil {
|
||||
util.WriteError(err, w)
|
||||
return
|
||||
@@ -99,7 +99,7 @@ func (h *UserHandler) CreateUserHandler(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
newUser, err := h.accountManager.CreateUser(account.Id, &server.UserInfo{
|
||||
newUser, err := h.accountManager.CreateUser(account.Id, user.Id, &server.UserInfo{
|
||||
Email: req.Email,
|
||||
Name: *req.Name,
|
||||
Role: req.Role,
|
||||
|
||||
@@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
activity "github.com/netbirdio/netbird/management/server/activity/sqlite"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -403,7 +404,12 @@ func startManagement(t *testing.T, port int, config *Config) (*grpc.Server, erro
|
||||
return nil, err
|
||||
}
|
||||
peersUpdateManager := NewPeersUpdateManager()
|
||||
accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "")
|
||||
eventStore, err := activity.NewSQLiteStore(t.TempDir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accountManager, err := BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package server_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
activity "github.com/netbirdio/netbird/management/server/activity/sqlite"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
@@ -493,7 +494,12 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) {
|
||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
||||
}
|
||||
peersUpdateManager := server.NewPeersUpdateManager()
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "")
|
||||
eventStore, err := activity.NewSQLiteStore(config.Datadir)
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating a event store: %s: %v", config.Datadir, err)
|
||||
}
|
||||
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "",
|
||||
eventStore)
|
||||
if err != nil {
|
||||
log.Fatalf("failed creating a manager: %v", err)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package mock_server
|
||||
import (
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -11,9 +12,10 @@ import (
|
||||
)
|
||||
|
||||
type MockAccountManager struct {
|
||||
GetOrCreateAccountByUserFunc func(userId, domain string) (*server.Account, error)
|
||||
GetAccountByUserFunc func(userId string) (*server.Account, error)
|
||||
CreateSetupKeyFunc func(accountId string, keyName string, keyType server.SetupKeyType, expiresIn time.Duration, autoGroups []string) (*server.SetupKey, error)
|
||||
GetOrCreateAccountByUserFunc func(userId, domain string) (*server.Account, error)
|
||||
GetAccountByUserFunc func(userId string) (*server.Account, error)
|
||||
CreateSetupKeyFunc func(accountId string, keyName string, keyType server.SetupKeyType,
|
||||
expiresIn time.Duration, autoGroups []string, usageLimit int, userID string) (*server.SetupKey, error)
|
||||
GetSetupKeyFunc func(accountID, userID, keyID string) (*server.SetupKey, error)
|
||||
GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error)
|
||||
IsUserAdminFunc func(claims jwtclaims.AuthorizationClaims) (bool, error)
|
||||
@@ -21,13 +23,13 @@ type MockAccountManager struct {
|
||||
GetPeerFunc func(peerKey string) (*server.Peer, error)
|
||||
GetPeersFunc func(accountID, userID string) ([]*server.Peer, error)
|
||||
MarkPeerConnectedFunc func(peerKey string, connected bool) error
|
||||
DeletePeerFunc func(accountId string, peerKey string) (*server.Peer, error)
|
||||
DeletePeerFunc func(accountID, peerKey, userID string) (*server.Peer, error)
|
||||
GetPeerByIPFunc func(accountId string, peerIP string) (*server.Peer, error)
|
||||
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
|
||||
GetPeerNetworkFunc func(peerKey string) (*server.Network, error)
|
||||
AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, error)
|
||||
GetGroupFunc func(accountID, groupID string) (*server.Group, error)
|
||||
SaveGroupFunc func(accountID string, group *server.Group) error
|
||||
SaveGroupFunc func(accountID, userID string, group *server.Group) error
|
||||
UpdateGroupFunc func(accountID string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error)
|
||||
DeleteGroupFunc func(accountID, groupID string) error
|
||||
ListGroupsFunc func(accountID string) ([]*server.Group, error)
|
||||
@@ -35,21 +37,21 @@ type MockAccountManager struct {
|
||||
GroupDeletePeerFunc func(accountID, groupID, peerKey string) error
|
||||
GroupListPeersFunc func(accountID, groupID string) ([]*server.Peer, error)
|
||||
GetRuleFunc func(accountID, ruleID, userID string) (*server.Rule, error)
|
||||
SaveRuleFunc func(accountID string, rule *server.Rule) error
|
||||
SaveRuleFunc func(accountID, userID string, rule *server.Rule) error
|
||||
UpdateRuleFunc func(accountID string, ruleID string, operations []server.RuleUpdateOperation) (*server.Rule, error)
|
||||
DeleteRuleFunc func(accountID, ruleID string) error
|
||||
DeleteRuleFunc func(accountID, ruleID, userID string) error
|
||||
ListRulesFunc func(accountID, userID string) ([]*server.Rule, error)
|
||||
GetUsersFromAccountFunc func(accountID, userID string) ([]*server.UserInfo, error)
|
||||
UpdatePeerMetaFunc func(peerKey string, meta server.PeerSystemMeta) error
|
||||
UpdatePeerSSHKeyFunc func(peerKey string, sshKey string) error
|
||||
UpdatePeerFunc func(accountID string, peer *server.Peer) (*server.Peer, error)
|
||||
CreateRouteFunc func(accountID string, prefix, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error)
|
||||
CreateRouteFunc func(accountID string, prefix, peer, description, netID string, masquerade bool, metric int, groups []string, enabled bool) (*route.Route, error)
|
||||
GetRouteFunc func(accountID, routeID, userID string) (*route.Route, error)
|
||||
SaveRouteFunc func(accountID string, route *route.Route) error
|
||||
UpdateRouteFunc func(accountID string, routeID string, operations []server.RouteUpdateOperation) (*route.Route, error)
|
||||
DeleteRouteFunc func(accountID, routeID string) error
|
||||
ListRoutesFunc func(accountID, userID string) ([]*route.Route, error)
|
||||
SaveSetupKeyFunc func(accountID string, key *server.SetupKey) (*server.SetupKey, error)
|
||||
SaveSetupKeyFunc func(accountID string, key *server.SetupKey, userID string) (*server.SetupKey, error)
|
||||
ListSetupKeysFunc func(accountID, userID string) ([]*server.SetupKey, error)
|
||||
SaveUserFunc func(accountID string, user *server.User) (*server.UserInfo, error)
|
||||
GetNameServerGroupFunc func(accountID, nsGroupID string) (*nbdns.NameServerGroup, error)
|
||||
@@ -58,9 +60,10 @@ type MockAccountManager struct {
|
||||
UpdateNameServerGroupFunc func(accountID, nsGroupID string, operations []server.NameServerGroupUpdateOperation) (*nbdns.NameServerGroup, error)
|
||||
DeleteNameServerGroupFunc func(accountID, nsGroupID string) error
|
||||
ListNameServerGroupsFunc func(accountID string) ([]*nbdns.NameServerGroup, error)
|
||||
CreateUserFunc func(accountID string, key *server.UserInfo) (*server.UserInfo, error)
|
||||
CreateUserFunc func(accountID, userID string, key *server.UserInfo) (*server.UserInfo, error)
|
||||
GetAccountFromTokenFunc func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error)
|
||||
GetDNSDomainFunc func() string
|
||||
GetEventsFunc func(accountID, userID string) ([]*activity.Event, error)
|
||||
}
|
||||
|
||||
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
||||
@@ -72,9 +75,9 @@ func (am *MockAccountManager) GetUsersFromAccount(accountID string, userID strin
|
||||
}
|
||||
|
||||
// DeletePeer mock implementation of DeletePeer from server.AccountManager interface
|
||||
func (am *MockAccountManager) DeletePeer(accountId string, peerKey string) (*server.Peer, error) {
|
||||
func (am *MockAccountManager) DeletePeer(accountID, peerKey, userID string) (*server.Peer, error) {
|
||||
if am.DeletePeerFunc != nil {
|
||||
return am.DeletePeerFunc(accountId, peerKey)
|
||||
return am.DeletePeerFunc(accountID, peerKey, userID)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeletePeer is not implemented")
|
||||
}
|
||||
@@ -102,14 +105,16 @@ func (am *MockAccountManager) GetAccountByUser(userId string) (*server.Account,
|
||||
|
||||
// CreateSetupKey mock implementation of CreateSetupKey from server.AccountManager interface
|
||||
func (am *MockAccountManager) CreateSetupKey(
|
||||
accountId string,
|
||||
accountID string,
|
||||
keyName string,
|
||||
keyType server.SetupKeyType,
|
||||
expiresIn time.Duration,
|
||||
autoGroups []string,
|
||||
usageLimit int,
|
||||
userID string,
|
||||
) (*server.SetupKey, error) {
|
||||
if am.CreateSetupKeyFunc != nil {
|
||||
return am.CreateSetupKeyFunc(accountId, keyName, keyType, expiresIn, autoGroups)
|
||||
return am.CreateSetupKeyFunc(accountID, keyName, keyType, expiresIn, autoGroups, usageLimit, userID)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreateSetupKey is not implemented")
|
||||
}
|
||||
@@ -196,9 +201,9 @@ func (am *MockAccountManager) GetGroup(accountID, groupID string) (*server.Group
|
||||
}
|
||||
|
||||
// SaveGroup mock implementation of SaveGroup from server.AccountManager interface
|
||||
func (am *MockAccountManager) SaveGroup(accountID string, group *server.Group) error {
|
||||
func (am *MockAccountManager) SaveGroup(accountID, userID string, group *server.Group) error {
|
||||
if am.SaveGroupFunc != nil {
|
||||
return am.SaveGroupFunc(accountID, group)
|
||||
return am.SaveGroupFunc(accountID, userID, group)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method SaveGroup is not implemented")
|
||||
}
|
||||
@@ -260,9 +265,9 @@ func (am *MockAccountManager) GetRule(accountID, ruleID, userID string) (*server
|
||||
}
|
||||
|
||||
// SaveRule mock implementation of SaveRule from server.AccountManager interface
|
||||
func (am *MockAccountManager) SaveRule(accountID string, rule *server.Rule) error {
|
||||
func (am *MockAccountManager) SaveRule(accountID, userID string, rule *server.Rule) error {
|
||||
if am.SaveRuleFunc != nil {
|
||||
return am.SaveRuleFunc(accountID, rule)
|
||||
return am.SaveRuleFunc(accountID, userID, rule)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method SaveRule is not implemented")
|
||||
}
|
||||
@@ -276,9 +281,9 @@ func (am *MockAccountManager) UpdateRule(accountID string, ruleID string, operat
|
||||
}
|
||||
|
||||
// DeleteRule mock implementation of DeleteRule from server.AccountManager interface
|
||||
func (am *MockAccountManager) DeleteRule(accountID, ruleID string) error {
|
||||
func (am *MockAccountManager) DeleteRule(accountID, ruleID, userID string) error {
|
||||
if am.DeleteRuleFunc != nil {
|
||||
return am.DeleteRuleFunc(accountID, ruleID)
|
||||
return am.DeleteRuleFunc(accountID, ruleID, userID)
|
||||
}
|
||||
return status.Errorf(codes.Unimplemented, "method DeleteRule is not implemented")
|
||||
}
|
||||
@@ -324,9 +329,9 @@ func (am *MockAccountManager) UpdatePeer(accountID string, peer *server.Peer) (*
|
||||
}
|
||||
|
||||
// CreateRoute mock implementation of CreateRoute from server.AccountManager interface
|
||||
func (am *MockAccountManager) CreateRoute(accountID string, network, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error) {
|
||||
func (am *MockAccountManager) CreateRoute(accountID string, network, peer, description, netID string, masquerade bool, metric int, groups []string, enabled bool) (*route.Route, error) {
|
||||
if am.CreateRouteFunc != nil {
|
||||
return am.CreateRouteFunc(accountID, network, peer, description, netID, masquerade, metric, enabled)
|
||||
return am.CreateRouteFunc(accountID, network, peer, description, netID, masquerade, metric, groups, enabled)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreateRoute is not implemented")
|
||||
}
|
||||
@@ -372,9 +377,9 @@ func (am *MockAccountManager) ListRoutes(accountID, userID string) ([]*route.Rou
|
||||
}
|
||||
|
||||
// SaveSetupKey mocks SaveSetupKey of the AccountManager interface
|
||||
func (am *MockAccountManager) SaveSetupKey(accountID string, key *server.SetupKey) (*server.SetupKey, error) {
|
||||
func (am *MockAccountManager) SaveSetupKey(accountID string, key *server.SetupKey, userID string) (*server.SetupKey, error) {
|
||||
if am.SaveSetupKeyFunc != nil {
|
||||
return am.SaveSetupKeyFunc(accountID, key)
|
||||
return am.SaveSetupKeyFunc(accountID, key, userID)
|
||||
}
|
||||
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SaveSetupKey is not implemented")
|
||||
@@ -455,9 +460,9 @@ func (am *MockAccountManager) ListNameServerGroups(accountID string) ([]*nbdns.N
|
||||
}
|
||||
|
||||
// CreateUser mocks CreateUser of the AccountManager interface
|
||||
func (am *MockAccountManager) CreateUser(accountID string, invite *server.UserInfo) (*server.UserInfo, error) {
|
||||
func (am *MockAccountManager) CreateUser(accountID, userID string, invite *server.UserInfo) (*server.UserInfo, error) {
|
||||
if am.CreateUserFunc != nil {
|
||||
return am.CreateUserFunc(accountID, invite)
|
||||
return am.CreateUserFunc(accountID, userID, invite)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CreateUser is not implemented")
|
||||
}
|
||||
@@ -476,7 +481,7 @@ func (am *MockAccountManager) GetPeers(accountID, userID string) ([]*server.Peer
|
||||
if am.GetAccountFromTokenFunc != nil {
|
||||
return am.GetPeersFunc(accountID, userID)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeersFunc is not implemented")
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeers is not implemented")
|
||||
}
|
||||
|
||||
// GetDNSDomain mocks GetDNSDomain of the AccountManager interface
|
||||
@@ -486,3 +491,11 @@ func (am *MockAccountManager) GetDNSDomain() string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetEvents mocks GetEvents of the AccountManager interface
|
||||
func (am *MockAccountManager) GetEvents(accountID, userID string) ([]*activity.Event, error) {
|
||||
if am.GetEventsFunc != nil {
|
||||
return am.GetEventsFunc(accountID, userID)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetEvents is not implemented")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
activity "github.com/netbirdio/netbird/management/server/activity/sqlite"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/netip"
|
||||
"testing"
|
||||
@@ -1056,7 +1057,11 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "")
|
||||
eventStore, err := activity.NewSQLiteStore(t.TempDir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore)
|
||||
}
|
||||
|
||||
func createNSStore(t *testing.T) (Store, error) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
"net"
|
||||
"strings"
|
||||
@@ -73,6 +75,19 @@ func (p *Peer) Copy() *Peer {
|
||||
}
|
||||
}
|
||||
|
||||
// FQDN returns peers FQDN combined of the peer's DNS label and the system's DNS domain
|
||||
func (p *Peer) FQDN(dnsDomain string) string {
|
||||
if dnsDomain == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", p.DNSLabel, dnsDomain)
|
||||
}
|
||||
|
||||
// EventMeta returns activity event meta related to the peer
|
||||
func (p *Peer) EventMeta(dnsDomain string) map[string]any {
|
||||
return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP}
|
||||
}
|
||||
|
||||
// Copy PeerStatus
|
||||
func (p *PeerStatus) Copy() *PeerStatus {
|
||||
return &PeerStatus{
|
||||
@@ -120,7 +135,7 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, er
|
||||
|
||||
// fetch all the peers that have access to the user's peers
|
||||
for _, peer := range peers {
|
||||
aclPeers := am.getPeersByACL(account, peer.Key)
|
||||
aclPeers := account.getPeersByACL(peer.Key)
|
||||
for _, p := range aclPeers {
|
||||
peersMap[p.Key] = p
|
||||
}
|
||||
@@ -216,7 +231,7 @@ func (am *DefaultAccountManager) UpdatePeer(accountID string, update *Peer) (*Pe
|
||||
}
|
||||
|
||||
// DeletePeer removes peer from the account by its IP
|
||||
func (am *DefaultAccountManager) DeletePeer(accountID string, peerPubKey string) (*Peer, error) {
|
||||
func (am *DefaultAccountManager) DeletePeer(accountID, peerPubKey, userID string) (*Peer, error) {
|
||||
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
@@ -262,6 +277,18 @@ func (am *DefaultAccountManager) DeletePeer(accountID string, peerPubKey string)
|
||||
}
|
||||
|
||||
am.peersUpdateManager.CloseChannel(peerPubKey)
|
||||
event := &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
AccountID: account.Id,
|
||||
InitiatorID: userID,
|
||||
TargetID: peer.IP.String(),
|
||||
Activity: activity.PeerRemovedByUser,
|
||||
Meta: peer.EventMeta(am.GetDNSDomain()),
|
||||
}
|
||||
_, err = am.eventStore.Save(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
@@ -293,8 +320,8 @@ func (am *DefaultAccountManager) GetNetworkMap(peerPubKey string) (*NetworkMap,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aclPeers := am.getPeersByACL(account, peerPubKey)
|
||||
routesUpdate := account.GetPeersRoutes(append(aclPeers, account.Peers[peerPubKey]))
|
||||
aclPeers := account.getPeersByACL(peerPubKey)
|
||||
routesUpdate := account.getRoutesToSync(peerPubKey, aclPeers)
|
||||
|
||||
var zones []nbdns.CustomZone
|
||||
peersCustomZone := getPeersCustomZone(account, am.dnsDomain)
|
||||
@@ -359,6 +386,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opEvent := &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
AccountID: account.Id,
|
||||
}
|
||||
|
||||
if !addedByUser {
|
||||
// validate the setup key if adding with a key
|
||||
sk, err := account.FindSetupKey(upperKey)
|
||||
@@ -371,6 +403,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*
|
||||
}
|
||||
|
||||
account.SetupKeys[sk.Key] = sk.IncrementUsage()
|
||||
opEvent.InitiatorID = sk.Id
|
||||
opEvent.Activity = activity.PeerAddedWithSetupKey
|
||||
} else {
|
||||
opEvent.InitiatorID = userID
|
||||
opEvent.Activity = activity.PeerAddedByUser
|
||||
}
|
||||
|
||||
takenIps := account.getTakenIPs()
|
||||
@@ -436,6 +473,13 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opEvent.TargetID = newPeer.IP.String()
|
||||
opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain())
|
||||
_, err = am.eventStore.Save(opEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newPeer, nil
|
||||
}
|
||||
|
||||
@@ -515,9 +559,9 @@ func (am *DefaultAccountManager) UpdatePeerMeta(peerPubKey string, meta PeerSyst
|
||||
}
|
||||
|
||||
// getPeersByACL returns all peers that given peer has access to.
|
||||
func (am *DefaultAccountManager) getPeersByACL(account *Account, peerPubKey string) []*Peer {
|
||||
func (a *Account) getPeersByACL(peerPubKey string) []*Peer {
|
||||
var peers []*Peer
|
||||
srcRules, dstRules := account.GetPeerRules(peerPubKey)
|
||||
srcRules, dstRules := a.GetPeerRules(peerPubKey)
|
||||
|
||||
groups := map[string]*Group{}
|
||||
for _, r := range srcRules {
|
||||
@@ -526,7 +570,7 @@ func (am *DefaultAccountManager) getPeersByACL(account *Account, peerPubKey stri
|
||||
}
|
||||
if r.Flow == TrafficFlowBidirect {
|
||||
for _, gid := range r.Destination {
|
||||
if group, ok := account.Groups[gid]; ok {
|
||||
if group, ok := a.Groups[gid]; ok {
|
||||
groups[gid] = group
|
||||
}
|
||||
}
|
||||
@@ -539,7 +583,7 @@ func (am *DefaultAccountManager) getPeersByACL(account *Account, peerPubKey stri
|
||||
}
|
||||
if r.Flow == TrafficFlowBidirect {
|
||||
for _, gid := range r.Source {
|
||||
if group, ok := account.Groups[gid]; ok {
|
||||
if group, ok := a.Groups[gid]; ok {
|
||||
groups[gid] = group
|
||||
}
|
||||
}
|
||||
@@ -549,13 +593,13 @@ func (am *DefaultAccountManager) getPeersByACL(account *Account, peerPubKey stri
|
||||
peersSet := make(map[string]struct{})
|
||||
for _, g := range groups {
|
||||
for _, pid := range g.Peers {
|
||||
peer, ok := account.Peers[pid]
|
||||
peer, ok := a.Peers[pid]
|
||||
if !ok {
|
||||
log.Warnf(
|
||||
"peer %s found in group %s but doesn't belong to account %s",
|
||||
pid,
|
||||
g.ID,
|
||||
account.Id,
|
||||
a.Id,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -87,9 +87,9 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
expectedId := "test_account"
|
||||
userId := "account_creator"
|
||||
account, err := createAccount(manager, expectedId, userId, "")
|
||||
expectedID := "test_account"
|
||||
userID := "account_creator"
|
||||
account, err := createAccount(manager, expectedID, userID, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -134,13 +134,13 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
rules, err := manager.ListRules(account.Id, userId)
|
||||
rules, err := manager.ListRules(account.Id, userID)
|
||||
if err != nil {
|
||||
t.Errorf("expecting to get a list of rules, got failure %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = manager.DeleteRule(account.Id, rules[0].ID)
|
||||
err = manager.DeleteRule(account.Id, rules[0].ID, userID)
|
||||
if err != nil {
|
||||
t.Errorf("expecting to delete 1 group, got failure %v", err)
|
||||
return
|
||||
@@ -159,12 +159,12 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
|
||||
group1.Peers = append(group1.Peers, peerKey1.PublicKey().String())
|
||||
group2.Peers = append(group2.Peers, peerKey2.PublicKey().String())
|
||||
|
||||
err = manager.SaveGroup(account.Id, &group1)
|
||||
err = manager.SaveGroup(account.Id, userID, &group1)
|
||||
if err != nil {
|
||||
t.Errorf("expecting group1 to be added, got failure %v", err)
|
||||
return
|
||||
}
|
||||
err = manager.SaveGroup(account.Id, &group2)
|
||||
err = manager.SaveGroup(account.Id, userID, &group2)
|
||||
if err != nil {
|
||||
t.Errorf("expecting group2 to be added, got failure %v", err)
|
||||
return
|
||||
@@ -174,7 +174,7 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
|
||||
rule.Source = append(rule.Source, group1.ID)
|
||||
rule.Destination = append(rule.Destination, group2.ID)
|
||||
rule.Flow = TrafficFlowBidirect
|
||||
err = manager.SaveRule(account.Id, &rule)
|
||||
err = manager.SaveRule(account.Id, userID, &rule)
|
||||
if err != nil {
|
||||
t.Errorf("expecting rule to be added, got failure %v", err)
|
||||
return
|
||||
@@ -222,7 +222,7 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) {
|
||||
}
|
||||
|
||||
rule.Disabled = true
|
||||
err = manager.SaveRule(account.Id, &rule)
|
||||
err = manager.SaveRule(account.Id, userID, &rule)
|
||||
if err != nil {
|
||||
t.Errorf("expecting rule to be added, got failure %v", err)
|
||||
return
|
||||
|
||||
@@ -26,6 +26,8 @@ const (
|
||||
UpdateRouteEnabled
|
||||
// UpdateRouteNetworkIdentifier indicates a route net ID update operation
|
||||
UpdateRouteNetworkIdentifier
|
||||
// UpdateRouteGroups indicates a group list update operation
|
||||
UpdateRouteGroups
|
||||
)
|
||||
|
||||
// RouteUpdateOperationType operation type
|
||||
@@ -47,6 +49,8 @@ func (t RouteUpdateOperationType) String() string {
|
||||
return "UpdateRouteEnabled"
|
||||
case UpdateRouteNetworkIdentifier:
|
||||
return "UpdateRouteNetworkIdentifier"
|
||||
case UpdateRouteGroups:
|
||||
return "UpdateRouteGroups"
|
||||
default:
|
||||
return "InvalidOperation"
|
||||
}
|
||||
@@ -114,7 +118,7 @@ func (am *DefaultAccountManager) checkPrefixPeerExists(accountID, peer string, p
|
||||
}
|
||||
|
||||
// CreateRoute creates and saves a new route
|
||||
func (am *DefaultAccountManager) CreateRoute(accountID string, network, peer, description, netID string, masquerade bool, metric int, enabled bool) (*route.Route, error) {
|
||||
func (am *DefaultAccountManager) CreateRoute(accountID string, network, peer, description, netID string, masquerade bool, metric int, groups []string, enabled bool) (*route.Route, error) {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
@@ -148,6 +152,11 @@ func (am *DefaultAccountManager) CreateRoute(accountID string, network, peer, de
|
||||
return nil, status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d", route.MaxNetIDChar)
|
||||
}
|
||||
|
||||
err = validateGroups(groups, account.Groups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newRoute.Peer = peer
|
||||
newRoute.ID = xid.New().String()
|
||||
newRoute.Network = newPrefix
|
||||
@@ -157,6 +166,7 @@ func (am *DefaultAccountManager) CreateRoute(accountID string, network, peer, de
|
||||
newRoute.Masquerade = masquerade
|
||||
newRoute.Metric = metric
|
||||
newRoute.Enabled = enabled
|
||||
newRoute.Groups = groups
|
||||
|
||||
if account.Routes == nil {
|
||||
account.Routes = make(map[string]*route.Route)
|
||||
@@ -210,6 +220,11 @@ func (am *DefaultAccountManager) SaveRoute(accountID string, routeToSave *route.
|
||||
}
|
||||
}
|
||||
|
||||
err = validateGroups(routeToSave.Groups, account.Groups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account.Routes[routeToSave.ID] = routeToSave
|
||||
|
||||
account.Network.IncSerial()
|
||||
@@ -300,6 +315,12 @@ func (am *DefaultAccountManager) UpdateRoute(accountID, routeID string, operatio
|
||||
return nil, status.Errorf(status.InvalidArgument, "failed to parse enabled %s, not boolean", operation.Values[0])
|
||||
}
|
||||
newRoute.Enabled = enabled
|
||||
case UpdateRouteGroups:
|
||||
err = validateGroups(operation.Values, account.Groups)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoute.Groups = operation.Values
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
activity "github.com/netbirdio/netbird/management/server/activity/sqlite"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
"github.com/rs/xid"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -8,8 +9,14 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const peer1Key = "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8="
|
||||
const peer2Key = "/yF0+vCfv+mRR5k0dca0TrGdO/oiNeAI58gToZm5NyI="
|
||||
const (
|
||||
peer1Key = "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8="
|
||||
peer2Key = "/yF0+vCfv+mRR5k0dca0TrGdO/oiNeAI58gToZm5NyI="
|
||||
routeGroup1 = "routeGroup1"
|
||||
routeGroup2 = "routeGroup2"
|
||||
routeInvalidGroup1 = "routeInvalidGroup1"
|
||||
userID = "testingUser"
|
||||
)
|
||||
|
||||
func TestCreateRoute(t *testing.T) {
|
||||
|
||||
@@ -21,6 +28,7 @@ func TestCreateRoute(t *testing.T) {
|
||||
masquerade bool
|
||||
metric int
|
||||
enabled bool
|
||||
groups []string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
@@ -40,6 +48,7 @@ func TestCreateRoute(t *testing.T) {
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
@@ -52,10 +61,11 @@ func TestCreateRoute(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Bad Prefix",
|
||||
name: "Bad Prefix Should Fail",
|
||||
inputArgs: input{
|
||||
network: "192.168.0.0/34",
|
||||
netID: "happy",
|
||||
@@ -64,12 +74,13 @@ func TestCreateRoute(t *testing.T) {
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Bad Peer",
|
||||
name: "Bad Peer Should Fail",
|
||||
inputArgs: input{
|
||||
network: "192.168.0.0/16",
|
||||
netID: "happy",
|
||||
@@ -78,12 +89,13 @@ func TestCreateRoute(t *testing.T) {
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Empty Peer",
|
||||
name: "Empty Peer Should Create",
|
||||
inputArgs: input{
|
||||
network: "192.168.0.0/16",
|
||||
netID: "happy",
|
||||
@@ -92,6 +104,7 @@ func TestCreateRoute(t *testing.T) {
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: false,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
@@ -104,10 +117,11 @@ func TestCreateRoute(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: false,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Large Metric",
|
||||
name: "Large Metric Should Fail",
|
||||
inputArgs: input{
|
||||
network: "192.168.0.0/16",
|
||||
peer: peer1Key,
|
||||
@@ -116,12 +130,13 @@ func TestCreateRoute(t *testing.T) {
|
||||
masquerade: false,
|
||||
metric: 99999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Small Metric",
|
||||
name: "Small Metric Should Fail",
|
||||
inputArgs: input{
|
||||
network: "192.168.0.0/16",
|
||||
netID: "happy",
|
||||
@@ -130,12 +145,13 @@ func TestCreateRoute(t *testing.T) {
|
||||
masquerade: false,
|
||||
metric: 0,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Large NetID",
|
||||
name: "Large NetID Should Fail",
|
||||
inputArgs: input{
|
||||
network: "192.168.0.0/16",
|
||||
peer: peer1Key,
|
||||
@@ -144,12 +160,13 @@ func TestCreateRoute(t *testing.T) {
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Small NetID",
|
||||
name: "Small NetID Should Fail",
|
||||
inputArgs: input{
|
||||
network: "192.168.0.0/16",
|
||||
netID: "",
|
||||
@@ -158,6 +175,52 @@ func TestCreateRoute(t *testing.T) {
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeGroup1},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Empty Group List Should Fail",
|
||||
inputArgs: input{
|
||||
network: "192.168.0.0/16",
|
||||
netID: "NewId",
|
||||
peer: peer1Key,
|
||||
description: "",
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Empty Group ID string Should Fail",
|
||||
inputArgs: input{
|
||||
network: "192.168.0.0/16",
|
||||
netID: "NewId",
|
||||
peer: peer1Key,
|
||||
description: "",
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{""},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid Group Should Fail",
|
||||
inputArgs: input{
|
||||
network: "192.168.0.0/16",
|
||||
netID: "NewId",
|
||||
peer: peer1Key,
|
||||
description: "",
|
||||
masquerade: false,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: []string{routeInvalidGroup1},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
shouldCreate: false,
|
||||
@@ -183,6 +246,7 @@ func TestCreateRoute(t *testing.T) {
|
||||
testCase.inputArgs.netID,
|
||||
testCase.inputArgs.masquerade,
|
||||
testCase.inputArgs.metric,
|
||||
testCase.inputArgs.groups,
|
||||
testCase.inputArgs.enabled,
|
||||
)
|
||||
|
||||
@@ -220,6 +284,7 @@ func TestSaveRoute(t *testing.T) {
|
||||
newPeer *string
|
||||
newMetric *int
|
||||
newPrefix *netip.Prefix
|
||||
newGroups []string
|
||||
skipCopying bool
|
||||
shouldCreate bool
|
||||
errFunc require.ErrorAssertionFunc
|
||||
@@ -237,10 +302,12 @@ func TestSaveRoute(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newPeer: &validPeer,
|
||||
newMetric: &validMetric,
|
||||
newPrefix: &validPrefix,
|
||||
newGroups: []string{routeGroup2},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
expectedRoute: &route.Route{
|
||||
@@ -253,10 +320,11 @@ func TestSaveRoute(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: validMetric,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Bad Prefix",
|
||||
name: "Bad Prefix Should Fail",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
@@ -267,12 +335,13 @@ func TestSaveRoute(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newPrefix: &invalidPrefix,
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Bad Peer",
|
||||
name: "Bad Peer Should Fail",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
@@ -283,12 +352,13 @@ func TestSaveRoute(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newPeer: &invalidPeer,
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid Metric",
|
||||
name: "Invalid Metric Should Fail",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
@@ -299,12 +369,13 @@ func TestSaveRoute(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newMetric: &invalidMetric,
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid NetID",
|
||||
name: "Invalid NetID Should Fail",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
@@ -315,12 +386,13 @@ func TestSaveRoute(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newMetric: &invalidMetric,
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Nil Route",
|
||||
name: "Nil Route Should Fail",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
@@ -331,10 +403,62 @@ func TestSaveRoute(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
skipCopying: true,
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Empty Group List Should Fail",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
NetID: validNetID,
|
||||
NetworkType: route.IPv4Network,
|
||||
Peer: peer1Key,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newGroups: []string{},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Empty Group ID String Should Fail",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
NetID: validNetID,
|
||||
NetworkType: route.IPv4Network,
|
||||
Peer: peer1Key,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newGroups: []string{""},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid Group Should Fail",
|
||||
existingRoute: &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
NetID: validNetID,
|
||||
NetworkType: route.IPv4Network,
|
||||
Peer: peer1Key,
|
||||
Description: "super",
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
newGroups: []string{routeInvalidGroup1},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
@@ -370,6 +494,10 @@ func TestSaveRoute(t *testing.T) {
|
||||
if testCase.newPrefix != nil {
|
||||
routeToSave.Network = *testCase.newPrefix
|
||||
}
|
||||
|
||||
if testCase.newGroups != nil {
|
||||
routeToSave.Groups = testCase.newGroups
|
||||
}
|
||||
}
|
||||
|
||||
err = am.SaveRoute(account.Id, routeToSave)
|
||||
@@ -409,6 +537,7 @@ func TestUpdateRoute(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
@@ -423,7 +552,7 @@ func TestUpdateRoute(t *testing.T) {
|
||||
name: "Happy Path Single OPS",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRoutePeer,
|
||||
Values: []string{peer2Key},
|
||||
},
|
||||
@@ -440,40 +569,45 @@ func TestUpdateRoute(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Happy Path Multiple OPS",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteDescription,
|
||||
Values: []string{"great"},
|
||||
},
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteNetwork,
|
||||
Values: []string{"192.168.0.0/24"},
|
||||
},
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRoutePeer,
|
||||
Values: []string{peer2Key},
|
||||
},
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteMetric,
|
||||
Values: []string{"3030"},
|
||||
},
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteMasquerade,
|
||||
Values: []string{"true"},
|
||||
},
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteEnabled,
|
||||
Values: []string{"false"},
|
||||
},
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteNetworkIdentifier,
|
||||
Values: []string{"megaRoute"},
|
||||
},
|
||||
{
|
||||
Type: UpdateRouteGroups,
|
||||
Values: []string{routeGroup2},
|
||||
},
|
||||
},
|
||||
errFunc: require.NoError,
|
||||
shouldCreate: true,
|
||||
@@ -487,23 +621,24 @@ func TestUpdateRoute(t *testing.T) {
|
||||
Masquerade: true,
|
||||
Metric: 3030,
|
||||
Enabled: false,
|
||||
Groups: []string{routeGroup2},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Empty Values",
|
||||
name: "Empty Values Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRoutePeer,
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Multiple Values",
|
||||
name: "Multiple Values Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRoutePeer,
|
||||
Values: []string{peer2Key, peer1Key},
|
||||
},
|
||||
@@ -511,10 +646,10 @@ func TestUpdateRoute(t *testing.T) {
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Bad Prefix",
|
||||
name: "Bad Prefix Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteNetwork,
|
||||
Values: []string{"192.168.0.0/34"},
|
||||
},
|
||||
@@ -522,10 +657,10 @@ func TestUpdateRoute(t *testing.T) {
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Bad Peer",
|
||||
name: "Bad Peer Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRoutePeer,
|
||||
Values: []string{"non existing Peer"},
|
||||
},
|
||||
@@ -536,7 +671,7 @@ func TestUpdateRoute(t *testing.T) {
|
||||
name: "Empty Peer",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRoutePeer,
|
||||
Values: []string{""},
|
||||
},
|
||||
@@ -553,13 +688,14 @@ func TestUpdateRoute(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Large Network ID",
|
||||
name: "Large Network ID Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteNetworkIdentifier,
|
||||
Values: []string{"12345678901234567890qwertyuiopqwertyuiop1"},
|
||||
},
|
||||
@@ -567,10 +703,10 @@ func TestUpdateRoute(t *testing.T) {
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Empty Network ID",
|
||||
name: "Empty Network ID Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteNetworkIdentifier,
|
||||
Values: []string{""},
|
||||
},
|
||||
@@ -578,10 +714,10 @@ func TestUpdateRoute(t *testing.T) {
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid Metric",
|
||||
name: "Invalid Metric Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteMetric,
|
||||
Values: []string{"999999"},
|
||||
},
|
||||
@@ -589,16 +725,27 @@ func TestUpdateRoute(t *testing.T) {
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid Boolean",
|
||||
name: "Invalid Boolean Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteMasquerade,
|
||||
Values: []string{"yes"},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid Group Should Fail",
|
||||
existingRoute: existingRoute,
|
||||
operations: []RouteUpdateOperation{
|
||||
{
|
||||
Type: UpdateRouteGroups,
|
||||
Values: []string{routeInvalidGroup1},
|
||||
},
|
||||
},
|
||||
errFunc: require.Error,
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
@@ -686,7 +833,6 @@ func TestDeleteRoute(t *testing.T) {
|
||||
func TestGetNetworkMap_RouteSync(t *testing.T) {
|
||||
// no routes for peer in different groups
|
||||
// no routes when route is deleted
|
||||
|
||||
baseRoute := &route.Route{
|
||||
ID: "testingRoute",
|
||||
Network: netip.MustParsePrefix("192.168.0.0/16"),
|
||||
@@ -697,6 +843,7 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
|
||||
Masquerade: false,
|
||||
Metric: 9999,
|
||||
Enabled: true,
|
||||
Groups: []string{routeGroup1},
|
||||
}
|
||||
|
||||
am, err := createRouterManager(t)
|
||||
@@ -714,7 +861,7 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
|
||||
require.Len(t, newAccountRoutes.Routes, 0, "new accounts should have no routes")
|
||||
|
||||
createdRoute, err := am.CreateRoute(account.Id, baseRoute.Network.String(), baseRoute.Peer,
|
||||
baseRoute.Description, baseRoute.NetID, baseRoute.Masquerade, baseRoute.Metric, false)
|
||||
baseRoute.Description, baseRoute.NetID, baseRoute.Masquerade, baseRoute.Metric, baseRoute.Groups, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
noDisabledRoutes, err := am.GetNetworkMap(peer1Key)
|
||||
@@ -734,7 +881,14 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
|
||||
|
||||
peer2Routes, err := am.GetNetworkMap(peer2Key)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer2Routes.Routes, 1, "we should receive one route for peer2")
|
||||
require.Len(t, peer2Routes.Routes, 0, "no routes for peers not in the distribution group")
|
||||
|
||||
err = am.GroupAddPeer(account.Id, routeGroup1, peer2Key)
|
||||
require.NoError(t, err)
|
||||
|
||||
peer2Routes, err = am.GetNetworkMap(peer2Key)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peer2Routes.Routes, 1, "we should receive one route")
|
||||
require.True(t, peer1Routes.Routes[0].IsEqual(peer2Routes.Routes[0]), "routes should be the same for peers in the same group")
|
||||
|
||||
newGroup := &Group{
|
||||
@@ -742,7 +896,7 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
|
||||
Name: "peer1 group",
|
||||
Peers: []string{peer1Key},
|
||||
}
|
||||
err = am.SaveGroup(account.Id, newGroup)
|
||||
err = am.SaveGroup(account.Id, userID, newGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
rules, err := am.ListRules(account.Id, "testingUser")
|
||||
@@ -755,10 +909,10 @@ func TestGetNetworkMap_RouteSync(t *testing.T) {
|
||||
newRule.Source = []string{newGroup.ID}
|
||||
newRule.Destination = []string{newGroup.ID}
|
||||
|
||||
err = am.SaveRule(account.Id, newRule)
|
||||
err = am.SaveRule(account.Id, userID, newRule)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = am.DeleteRule(account.Id, defaultRule.ID)
|
||||
err = am.DeleteRule(account.Id, defaultRule.ID, userID)
|
||||
require.NoError(t, err)
|
||||
|
||||
peer1GroupRoutes, err := am.GetNetworkMap(peer1Key)
|
||||
@@ -783,7 +937,11 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "")
|
||||
eventStore, err := activity.NewSQLiteStore(t.TempDir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildManager(store, NewPeersUpdateManager(), nil, "", "", eventStore)
|
||||
}
|
||||
|
||||
func createRouterStore(t *testing.T) (Store, error) {
|
||||
@@ -827,7 +985,6 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
||||
}
|
||||
|
||||
accountID := "testingAcc"
|
||||
userID := "testingUser"
|
||||
domain := "example.com"
|
||||
|
||||
account := newAccountWithId(accountID, userID, domain)
|
||||
@@ -844,6 +1001,26 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newGroup := &Group{
|
||||
ID: routeGroup1,
|
||||
Name: routeGroup1,
|
||||
Peers: []string{peer1Key},
|
||||
}
|
||||
err = am.SaveGroup(accountID, userID, newGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newGroup = &Group{
|
||||
ID: routeGroup2,
|
||||
Name: routeGroup2,
|
||||
Peers: []string{peer1Key},
|
||||
}
|
||||
|
||||
err = am.SaveGroup(accountID, userID, newGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return am.Store.GetAccount(account.Id)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TrafficFlowType defines allowed direction of the traffic in the rule
|
||||
@@ -87,6 +89,11 @@ func (r *Rule) Copy() *Rule {
|
||||
}
|
||||
}
|
||||
|
||||
// EventMeta returns activity event meta related to this rule
|
||||
func (r *Rule) EventMeta() map[string]any {
|
||||
return map[string]any{"name": r.Name}
|
||||
}
|
||||
|
||||
// GetRule of ACL from the store
|
||||
func (am *DefaultAccountManager) GetRule(accountID, ruleID, userID string) (*Rule, error) {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
@@ -115,7 +122,7 @@ func (am *DefaultAccountManager) GetRule(accountID, ruleID, userID string) (*Rul
|
||||
}
|
||||
|
||||
// SaveRule of ACL in the store
|
||||
func (am *DefaultAccountManager) SaveRule(accountID string, rule *Rule) error {
|
||||
func (am *DefaultAccountManager) SaveRule(accountID, userID string, rule *Rule) error {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
@@ -124,6 +131,8 @@ func (am *DefaultAccountManager) SaveRule(accountID string, rule *Rule) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, exists := account.Rules[rule.ID]
|
||||
|
||||
account.Rules[rule.ID] = rule
|
||||
|
||||
account.Network.IncSerial()
|
||||
@@ -131,6 +140,24 @@ func (am *DefaultAccountManager) SaveRule(accountID string, rule *Rule) error {
|
||||
return err
|
||||
}
|
||||
|
||||
action := activity.RuleAdded
|
||||
if exists {
|
||||
action = activity.RuleUpdated
|
||||
}
|
||||
|
||||
_, err = am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: action,
|
||||
InitiatorID: userID,
|
||||
TargetID: rule.ID,
|
||||
AccountID: accountID,
|
||||
Meta: rule.EventMeta(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return am.updateAccountPeers(account)
|
||||
}
|
||||
|
||||
@@ -210,7 +237,7 @@ func (am *DefaultAccountManager) UpdateRule(accountID string, ruleID string,
|
||||
}
|
||||
|
||||
// DeleteRule of ACL from the store
|
||||
func (am *DefaultAccountManager) DeleteRule(accountID, ruleID string) error {
|
||||
func (am *DefaultAccountManager) DeleteRule(accountID, ruleID, userID string) error {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
@@ -219,6 +246,10 @@ func (am *DefaultAccountManager) DeleteRule(accountID, ruleID string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
rule := account.Rules[ruleID]
|
||||
if rule == nil {
|
||||
return status.Errorf(status.NotFound, "rule with ID %s doesn't exist", ruleID)
|
||||
}
|
||||
delete(account.Rules, ruleID)
|
||||
|
||||
account.Network.IncSerial()
|
||||
@@ -226,6 +257,19 @@ func (am *DefaultAccountManager) DeleteRule(accountID, ruleID string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.RuleRemoved,
|
||||
InitiatorID: userID,
|
||||
TargetID: ruleID,
|
||||
AccountID: accountID,
|
||||
Meta: rule.EventMeta(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return am.updateAccountPeers(account)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/status"
|
||||
"hash/fnv"
|
||||
"strconv"
|
||||
@@ -20,7 +21,11 @@ const (
|
||||
DefaultSetupKeyDuration = 24 * 30 * time.Hour
|
||||
// DefaultSetupKeyName is a default name of the default setup key
|
||||
DefaultSetupKeyName = "Default key"
|
||||
// SetupKeyUnlimitedUsage indicates an unlimited usage of a setup key
|
||||
SetupKeyUnlimitedUsage = 0
|
||||
)
|
||||
|
||||
const (
|
||||
// UpdateSetupKeyName indicates a setup key name update operation
|
||||
UpdateSetupKeyName SetupKeyUpdateOperationType = iota
|
||||
// UpdateSetupKeyRevoked indicates a setup key revoked filed update operation
|
||||
@@ -75,6 +80,9 @@ type SetupKey struct {
|
||||
LastUsed time.Time
|
||||
// AutoGroups is a list of Group IDs that are auto assigned to a Peer when it uses this key to register
|
||||
AutoGroups []string
|
||||
// UsageLimit indicates the number of times this key can be used to enroll a machine.
|
||||
// The value of 0 indicates the unlimited usage.
|
||||
UsageLimit int
|
||||
}
|
||||
|
||||
// Copy copies SetupKey to a new object
|
||||
@@ -96,15 +104,24 @@ func (key *SetupKey) Copy() *SetupKey {
|
||||
UsedTimes: key.UsedTimes,
|
||||
LastUsed: key.LastUsed,
|
||||
AutoGroups: autoGroups,
|
||||
UsageLimit: key.UsageLimit,
|
||||
}
|
||||
}
|
||||
|
||||
// EventMeta returns activity event meta related to the setup key
|
||||
func (key *SetupKey) EventMeta() map[string]any {
|
||||
return map[string]any{"name": key.Name, "type": key.Type, "key": key.HiddenCopy(1).Key}
|
||||
}
|
||||
|
||||
// HiddenCopy returns a copy of the key with a Key value hidden with "*" and a 5 character prefix.
|
||||
// E.g., "831F6*******************************"
|
||||
func (key *SetupKey) HiddenCopy() *SetupKey {
|
||||
func (key *SetupKey) HiddenCopy(length int) *SetupKey {
|
||||
k := key.Copy()
|
||||
prefix := k.Key[0:5]
|
||||
k.Key = prefix + strings.Repeat("*", utf8.RuneCountInString(key.Key)-len(prefix))
|
||||
if length > utf8.RuneCountInString(key.Key) {
|
||||
length = utf8.RuneCountInString(key.Key) - len(prefix)
|
||||
}
|
||||
k.Key = prefix + strings.Repeat("*", length)
|
||||
return k
|
||||
}
|
||||
|
||||
@@ -131,14 +148,23 @@ func (key *SetupKey) IsExpired() bool {
|
||||
return time.Now().After(key.ExpiresAt)
|
||||
}
|
||||
|
||||
// IsOverUsed if key was used too many times
|
||||
// IsOverUsed if the key was used too many times. SetupKey.UsageLimit == 0 indicates the unlimited usage.
|
||||
func (key *SetupKey) IsOverUsed() bool {
|
||||
return key.Type == SetupKeyOneOff && key.UsedTimes >= 1
|
||||
limit := key.UsageLimit
|
||||
if key.Type == SetupKeyOneOff {
|
||||
limit = 1
|
||||
}
|
||||
return limit > 0 && key.UsedTimes >= limit
|
||||
}
|
||||
|
||||
// GenerateSetupKey generates a new setup key
|
||||
func GenerateSetupKey(name string, t SetupKeyType, validFor time.Duration, autoGroups []string) *SetupKey {
|
||||
func GenerateSetupKey(name string, t SetupKeyType, validFor time.Duration, autoGroups []string,
|
||||
usageLimit int) *SetupKey {
|
||||
key := strings.ToUpper(uuid.New().String())
|
||||
limit := usageLimit
|
||||
if t == SetupKeyOneOff {
|
||||
limit = 1
|
||||
}
|
||||
return &SetupKey{
|
||||
Id: strconv.Itoa(int(Hash(key))),
|
||||
Key: key,
|
||||
@@ -150,12 +176,14 @@ func GenerateSetupKey(name string, t SetupKeyType, validFor time.Duration, autoG
|
||||
Revoked: false,
|
||||
UsedTimes: 0,
|
||||
AutoGroups: autoGroups,
|
||||
UsageLimit: limit,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateDefaultSetupKey generates a default setup key
|
||||
// GenerateDefaultSetupKey generates a default reusable setup key with an unlimited usage and 30 days expiration
|
||||
func GenerateDefaultSetupKey() *SetupKey {
|
||||
return GenerateSetupKey(DefaultSetupKeyName, SetupKeyReusable, DefaultSetupKeyDuration, []string{})
|
||||
return GenerateSetupKey(DefaultSetupKeyName, SetupKeyReusable, DefaultSetupKeyDuration, []string{},
|
||||
SetupKeyUnlimitedUsage)
|
||||
}
|
||||
|
||||
func Hash(s string) uint32 {
|
||||
@@ -170,7 +198,7 @@ func Hash(s string) uint32 {
|
||||
// CreateSetupKey generates a new setup key with a given name, type, list of groups IDs to auto-assign to peers registered with this key,
|
||||
// and adds it to the specified account. A list of autoGroups IDs can be empty.
|
||||
func (am *DefaultAccountManager) CreateSetupKey(accountID string, keyName string, keyType SetupKeyType,
|
||||
expiresIn time.Duration, autoGroups []string) (*SetupKey, error) {
|
||||
expiresIn time.Duration, autoGroups []string, usageLimit int, userID string) (*SetupKey, error) {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
@@ -190,14 +218,25 @@ func (am *DefaultAccountManager) CreateSetupKey(accountID string, keyName string
|
||||
}
|
||||
}
|
||||
|
||||
setupKey := GenerateSetupKey(keyName, keyType, keyDuration, autoGroups)
|
||||
setupKey := GenerateSetupKey(keyName, keyType, keyDuration, autoGroups, usageLimit)
|
||||
account.SetupKeys[setupKey.Key] = setupKey
|
||||
|
||||
err = am.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(status.Internal, "failed adding account key")
|
||||
}
|
||||
|
||||
_, err = am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.SetupKeyCreated,
|
||||
InitiatorID: userID,
|
||||
TargetID: setupKey.Id,
|
||||
AccountID: accountID,
|
||||
Meta: setupKey.EventMeta(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return setupKey, nil
|
||||
}
|
||||
|
||||
@@ -205,7 +244,7 @@ func (am *DefaultAccountManager) CreateSetupKey(accountID string, keyName string
|
||||
// Due to the unique nature of a SetupKey certain properties must not be overwritten
|
||||
// (e.g. the key itself, creation date, ID, etc).
|
||||
// These properties are overwritten: Name, AutoGroups, Revoked. The rest is copied from the existing key.
|
||||
func (am *DefaultAccountManager) SaveSetupKey(accountID string, keyToSave *SetupKey) (*SetupKey, error) {
|
||||
func (am *DefaultAccountManager) SaveSetupKey(accountID string, keyToSave *SetupKey, userID string) (*SetupKey, error) {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
@@ -242,6 +281,20 @@ func (am *DefaultAccountManager) SaveSetupKey(accountID string, keyToSave *Setup
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !oldKey.Revoked && newKey.Revoked {
|
||||
_, err = am.eventStore.Save(&activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.SetupKeyRevoked,
|
||||
InitiatorID: userID,
|
||||
TargetID: newKey.Id,
|
||||
AccountID: accountID,
|
||||
Meta: newKey.EventMeta(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return newKey, am.updateAccountPeers(account)
|
||||
}
|
||||
|
||||
@@ -263,7 +316,7 @@ func (am *DefaultAccountManager) ListSetupKeys(accountID, userID string) ([]*Set
|
||||
for _, key := range account.SetupKeys {
|
||||
var k *SetupKey
|
||||
if !user.IsAdmin() {
|
||||
k = key.HiddenCopy()
|
||||
k = key.HiddenCopy(999)
|
||||
} else {
|
||||
k = key.Copy()
|
||||
}
|
||||
@@ -305,7 +358,7 @@ func (am *DefaultAccountManager) GetSetupKey(accountID, userID, keyID string) (*
|
||||
}
|
||||
|
||||
if !user.IsAdmin() {
|
||||
foundKey = foundKey.HiddenCopy()
|
||||
foundKey = foundKey.HiddenCopy(999)
|
||||
}
|
||||
|
||||
return foundKey, nil
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = manager.SaveGroup(account.Id, &Group{
|
||||
err = manager.SaveGroup(account.Id, userID, &Group{
|
||||
ID: "group_1",
|
||||
Name: "group_name_1",
|
||||
Peers: []string{},
|
||||
@@ -32,7 +32,8 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
|
||||
expiresIn := time.Hour
|
||||
keyName := "my-test-key"
|
||||
|
||||
key, err := manager.CreateSetupKey(account.Id, keyName, SetupKeyReusable, expiresIn, []string{})
|
||||
key, err := manager.CreateSetupKey(account.Id, keyName, SetupKeyReusable, expiresIn, []string{},
|
||||
SetupKeyUnlimitedUsage, userID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -45,7 +46,7 @@ func TestDefaultAccountManager_SaveSetupKey(t *testing.T) {
|
||||
Name: newKeyName,
|
||||
Revoked: revoked,
|
||||
AutoGroups: autoGroups,
|
||||
})
|
||||
}, userID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -66,7 +67,7 @@ func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = manager.SaveGroup(account.Id, &Group{
|
||||
err = manager.SaveGroup(account.Id, userID, &Group{
|
||||
ID: "group_1",
|
||||
Name: "group_name_1",
|
||||
Peers: []string{},
|
||||
@@ -75,7 +76,7 @@ func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = manager.SaveGroup(account.Id, &Group{
|
||||
err = manager.SaveGroup(account.Id, userID, &Group{
|
||||
ID: "group_2",
|
||||
Name: "group_name_2",
|
||||
Peers: []string{},
|
||||
@@ -120,7 +121,7 @@ func TestDefaultAccountManager_CreateSetupKey(t *testing.T) {
|
||||
for _, tCase := range []testCase{testCase1, testCase2} {
|
||||
t.Run(tCase.name, func(t *testing.T) {
|
||||
key, err := manager.CreateSetupKey(account.Id, tCase.expectedKeyName, SetupKeyReusable, expiresIn,
|
||||
tCase.expectedGroups)
|
||||
tCase.expectedGroups, SetupKeyUnlimitedUsage, userID)
|
||||
|
||||
if tCase.expectedFailure {
|
||||
if err == nil {
|
||||
@@ -168,7 +169,7 @@ func TestGenerateSetupKey(t *testing.T) {
|
||||
expectedUpdatedAt := time.Now()
|
||||
var expectedAutoGroups []string
|
||||
|
||||
key := GenerateSetupKey(expectedName, SetupKeyOneOff, time.Hour, []string{})
|
||||
key := GenerateSetupKey(expectedName, SetupKeyOneOff, time.Hour, []string{}, SetupKeyUnlimitedUsage)
|
||||
|
||||
assertKey(t, key, expectedName, expectedRevoke, expectedType, expectedUsedTimes, expectedCreatedAt,
|
||||
expectedExpiresAt, strconv.Itoa(int(Hash(key.Key))), expectedUpdatedAt, expectedAutoGroups)
|
||||
@@ -176,33 +177,33 @@ func TestGenerateSetupKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetupKey_IsValid(t *testing.T) {
|
||||
validKey := GenerateSetupKey("valid key", SetupKeyOneOff, time.Hour, []string{})
|
||||
validKey := GenerateSetupKey("valid key", SetupKeyOneOff, time.Hour, []string{}, SetupKeyUnlimitedUsage)
|
||||
if !validKey.IsValid() {
|
||||
t.Errorf("expected key to be valid, got invalid %v", validKey)
|
||||
}
|
||||
|
||||
// expired
|
||||
expiredKey := GenerateSetupKey("invalid key", SetupKeyOneOff, -time.Hour, []string{})
|
||||
expiredKey := GenerateSetupKey("invalid key", SetupKeyOneOff, -time.Hour, []string{}, SetupKeyUnlimitedUsage)
|
||||
if expiredKey.IsValid() {
|
||||
t.Errorf("expected key to be invalid due to expiration, got valid %v", expiredKey)
|
||||
}
|
||||
|
||||
// revoked
|
||||
revokedKey := GenerateSetupKey("invalid key", SetupKeyOneOff, time.Hour, []string{})
|
||||
revokedKey := GenerateSetupKey("invalid key", SetupKeyOneOff, time.Hour, []string{}, SetupKeyUnlimitedUsage)
|
||||
revokedKey.Revoked = true
|
||||
if revokedKey.IsValid() {
|
||||
t.Errorf("expected revoked key to be invalid, got valid %v", revokedKey)
|
||||
}
|
||||
|
||||
// overused
|
||||
overUsedKey := GenerateSetupKey("invalid key", SetupKeyOneOff, time.Hour, []string{})
|
||||
overUsedKey := GenerateSetupKey("invalid key", SetupKeyOneOff, time.Hour, []string{}, SetupKeyUnlimitedUsage)
|
||||
overUsedKey.UsedTimes = 1
|
||||
if overUsedKey.IsValid() {
|
||||
t.Errorf("expected overused key to be invalid, got valid %v", overUsedKey)
|
||||
}
|
||||
|
||||
// overused
|
||||
reusableKey := GenerateSetupKey("valid key", SetupKeyReusable, time.Hour, []string{})
|
||||
reusableKey := GenerateSetupKey("valid key", SetupKeyReusable, time.Hour, []string{}, SetupKeyUnlimitedUsage)
|
||||
reusableKey.UsedTimes = 99
|
||||
if !reusableKey.IsValid() {
|
||||
t.Errorf("expected reusable key to be valid when used many times, got valid %v", reusableKey)
|
||||
@@ -257,7 +258,7 @@ func assertKey(t *testing.T, key *SetupKey, expectedName string, expectedRevoke
|
||||
|
||||
func TestSetupKey_Copy(t *testing.T) {
|
||||
|
||||
key := GenerateSetupKey("key name", SetupKeyOneOff, time.Hour, []string{})
|
||||
key := GenerateSetupKey("key name", SetupKeyOneOff, time.Hour, []string{}, SetupKeyUnlimitedUsage)
|
||||
keyCopy := key.Copy()
|
||||
|
||||
assertKey(t, keyCopy, key.Name, key.Revoked, string(key.Type), key.UsedTimes, key.CreatedAt, key.ExpiresAt, key.Id,
|
||||
|
||||
@@ -2,11 +2,12 @@ package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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/status"
|
||||
"strings"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -117,7 +118,7 @@ func NewAdminUser(id string) *User {
|
||||
}
|
||||
|
||||
// CreateUser creates a new user under the given account. Effectively this is a user invite.
|
||||
func (am *DefaultAccountManager) CreateUser(accountID string, invite *UserInfo) (*UserInfo, error) {
|
||||
func (am *DefaultAccountManager) CreateUser(accountID, userID string, invite *UserInfo) (*UserInfo, error) {
|
||||
unlock := am.Store.AcquireAccountLock(accountID)
|
||||
defer unlock()
|
||||
|
||||
@@ -176,6 +177,19 @@ func (am *DefaultAccountManager) CreateUser(accountID string, invite *UserInfo)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
event := &activity.Event{
|
||||
Timestamp: time.Now(),
|
||||
Activity: activity.UserInvited,
|
||||
AccountID: account.Id,
|
||||
TargetID: newUser.Id,
|
||||
InitiatorID: userID,
|
||||
}
|
||||
|
||||
_, err = am.eventStore.Save(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newUser.toUserInfo(idpUser)
|
||||
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ type Route struct {
|
||||
Masquerade bool
|
||||
Metric int
|
||||
Enabled bool
|
||||
Groups []string
|
||||
}
|
||||
|
||||
// Copy copies a route object
|
||||
@@ -87,6 +88,7 @@ func (r *Route) Copy() *Route {
|
||||
Metric: r.Metric,
|
||||
Masquerade: r.Masquerade,
|
||||
Enabled: r.Enabled,
|
||||
Groups: r.Groups,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +102,8 @@ func (r *Route) IsEqual(other *Route) bool {
|
||||
other.Peer == r.Peer &&
|
||||
other.Metric == r.Metric &&
|
||||
other.Masquerade == r.Masquerade &&
|
||||
other.Enabled == r.Enabled
|
||||
other.Enabled == r.Enabled &&
|
||||
compareGroupsList(r.Groups, other.Groups)
|
||||
}
|
||||
|
||||
// ParseNetwork Parses a network prefix string and returns a netip.Prefix object and if is invalid, IPv4 or IPv6
|
||||
@@ -122,3 +125,28 @@ func ParseNetwork(networkString string) (NetworkType, netip.Prefix, error) {
|
||||
|
||||
return IPv4Network, masked, nil
|
||||
}
|
||||
|
||||
func compareGroupsList(list, other []string) bool {
|
||||
if len(list) != len(other) {
|
||||
return false
|
||||
}
|
||||
for _, id := range list {
|
||||
match := false
|
||||
for _, otherID := range other {
|
||||
if id == otherID {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetHAUniqueID returns a highly available route ID by combining Network ID and Network range address
|
||||
func GetHAUniqueID(input *Route) string {
|
||||
return input.NetID + "-" + input.Network.String()
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -231,7 +230,7 @@ func copySymLink(source, dest string) error {
|
||||
|
||||
func cpDir(src string, dst string) error {
|
||||
var err error
|
||||
var fds []os.FileInfo
|
||||
var fds []os.DirEntry
|
||||
var srcinfo os.FileInfo
|
||||
|
||||
if srcinfo, err = os.Stat(src); err != nil {
|
||||
@@ -242,7 +241,7 @@ func cpDir(src string, dst string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if fds, err = ioutil.ReadDir(src); err != nil {
|
||||
if fds, err = os.ReadDir(src); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fd := range fds {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
@@ -25,7 +24,7 @@ var _ = Describe("Client", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
tmpDir, err = ioutil.TempDir("", "wiretrustee_util_test_tmp_*")
|
||||
tmpDir, err = os.MkdirTemp("", "wiretrustee_util_test_tmp_*")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user