mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-30 22:26:42 +00:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8d03d8c91 | ||
|
|
74ff2619d0 | ||
|
|
40bea645e9 | ||
|
|
e7d52beeab | ||
|
|
7a5c6b24ae | ||
|
|
90c2093018 | ||
|
|
06318a15e1 | ||
|
|
eeb38b7ecf | ||
|
|
e59d2317fe | ||
|
|
ee6be58a67 | ||
|
|
a9f5fad625 | ||
|
|
c979a4e9fb | ||
|
|
f2fc0df104 | ||
|
|
87cc53b743 | ||
|
|
7d8a69cc0c | ||
|
|
e4de1d75de | ||
|
|
73e57f17ea | ||
|
|
46f5f148da | ||
|
|
32880c56a4 | ||
|
|
2b90ff8c24 | ||
|
|
b8599f634c | ||
|
|
659110f0d5 | ||
|
|
4ad14cb46b | ||
|
|
3c485dc7a1 | ||
|
|
f7e6cdcbf0 | ||
|
|
af6fdd3af2 | ||
|
|
5781ec7a8e | ||
|
|
1219006a6e | ||
|
|
4791e41004 | ||
|
|
9131069d12 | ||
|
|
26bbc33e7a | ||
|
|
35bc493cc3 | ||
|
|
e26ec0b937 | ||
|
|
a952e7c72f | ||
|
|
22f69d7852 | ||
|
|
b23011fbe8 | ||
|
|
6ad3894a51 | ||
|
|
c81b83b346 | ||
|
|
8c5c6815e0 | ||
|
|
0c470e7838 | ||
|
|
8118d60ffb | ||
|
|
1956ca169e | ||
|
|
830dee1771 | ||
|
|
c08a96770e | ||
|
|
c6bf1c7f26 | ||
|
|
5f499d66b2 | ||
|
|
7c065bd9fc | ||
|
|
ab849f0942 | ||
|
|
aa1d31bde6 | ||
|
|
5b4dc4dd47 | ||
|
|
1324169ebb | ||
|
|
732afd8393 | ||
|
|
da7b6b11ad | ||
|
|
e260270825 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.go text eol=lf
|
||||||
5
.github/workflows/golang-test-darwin.yml
vendored
5
.github/workflows/golang-test-darwin.yml
vendored
@@ -12,6 +12,9 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
store: ['jsonfile', 'sqlite']
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
@@ -33,4 +36,4 @@ jobs:
|
|||||||
run: go mod tidy
|
run: go mod tidy
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -exec 'sudo --preserve-env=CI' -timeout 5m -p 1 ./...
|
run: NETBIRD_STORE_ENGINE=${{ matrix.store }} go test -exec 'sudo --preserve-env=CI' -timeout 5m -p 1 ./...
|
||||||
|
|||||||
18
.github/workflows/golang-test-linux.yml
vendored
18
.github/workflows/golang-test-linux.yml
vendored
@@ -15,6 +15,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
arch: ['386','amd64']
|
arch: ['386','amd64']
|
||||||
|
store: ['jsonfile', 'sqlite']
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
@@ -41,17 +42,16 @@ jobs:
|
|||||||
run: go mod tidy
|
run: go mod tidy
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} go test -exec 'sudo --preserve-env=CI' -timeout 5m -p 1 ./...
|
run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} NETBIRD_STORE_ENGINE=${{ matrix.store }} go test -exec 'sudo --preserve-env=CI' -timeout 5m -p 1 ./...
|
||||||
|
|
||||||
test_client_on_docker:
|
test_client_on_docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.20.x"
|
go-version: "1.20.x"
|
||||||
|
|
||||||
|
|
||||||
- name: Cache Go modules
|
- name: Cache Go modules
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
@@ -64,7 +64,7 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: sudo apt update && sudo apt install -y -q libgtk-3-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
|
- name: Install modules
|
||||||
run: go mod tidy
|
run: go mod tidy
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
run: CGO_ENABLED=0 go test -c -o nftablesmanager-testing.bin ./client/firewall/nftables/...
|
run: CGO_ENABLED=0 go test -c -o nftablesmanager-testing.bin ./client/firewall/nftables/...
|
||||||
|
|
||||||
- name: Generate Engine Test bin
|
- name: Generate Engine Test bin
|
||||||
run: CGO_ENABLED=0 go test -c -o engine-testing.bin ./client/internal
|
run: CGO_ENABLED=1 go test -c -o engine-testing.bin ./client/internal
|
||||||
|
|
||||||
- name: Generate Peer Test bin
|
- name: Generate Peer Test bin
|
||||||
run: CGO_ENABLED=0 go test -c -o peer-testing.bin ./client/internal/peer/...
|
run: CGO_ENABLED=0 go test -c -o peer-testing.bin ./client/internal/peer/...
|
||||||
@@ -95,15 +95,17 @@ jobs:
|
|||||||
- name: Run Iface tests in docker
|
- name: Run Iface tests in docker
|
||||||
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/iface --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/iface-testing.bin -test.timeout 5m -test.parallel 1
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/iface --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/iface-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
|
|
||||||
|
|
||||||
- name: Run RouteManager tests in docker
|
- name: Run RouteManager tests in docker
|
||||||
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal/routemanager --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/routemanager-testing.bin -test.timeout 5m -test.parallel 1
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal/routemanager --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/routemanager-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
|
|
||||||
- name: Run nftables Manager tests in docker
|
- name: Run nftables Manager tests in docker
|
||||||
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/firewall --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/nftablesmanager-testing.bin -test.timeout 5m -test.parallel 1
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/firewall --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/nftablesmanager-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
|
|
||||||
- name: Run Engine tests in docker
|
- name: Run Engine tests in docker with file store
|
||||||
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/engine-testing.bin -test.timeout 5m -test.parallel 1
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal -e NETBIRD_STORE_ENGINE="jsonfile" --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/engine-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
|
|
||||||
|
- name: Run Engine tests in docker with sqlite store
|
||||||
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal -e NETBIRD_STORE_ENGINE="sqlite" --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/engine-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
|
|
||||||
- name: Run Peer tests in docker
|
- name: Run Peer tests in docker
|
||||||
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal/peer --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/peer-testing.bin -test.timeout 5m -test.parallel 1
|
run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal/peer --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/peer-testing.bin -test.timeout 5m -test.parallel 1
|
||||||
4
.github/workflows/golang-test-windows.yml
vendored
4
.github/workflows/golang-test-windows.yml
vendored
@@ -39,7 +39,9 @@ jobs:
|
|||||||
|
|
||||||
- run: mv ${{ env.downloadPath }}/wintun/bin/amd64/wintun.dll 'C:\Windows\System32\'
|
- run: mv ${{ env.downloadPath }}/wintun/bin/amd64/wintun.dll 'C:\Windows\System32\'
|
||||||
|
|
||||||
- run: choco install -y sysinternals
|
- run: choco install -y sysinternals --ignore-checksums
|
||||||
|
- run: choco install -y mingw
|
||||||
|
|
||||||
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOMODCACHE=C:\Users\runneradmin\go\pkg\mod
|
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOMODCACHE=C:\Users\runneradmin\go\pkg\mod
|
||||||
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOCACHE=C:\Users\runneradmin\AppData\Local\go-build
|
- run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOCACHE=C:\Users\runneradmin\AppData\Local\go-build
|
||||||
|
|
||||||
|
|||||||
18
.github/workflows/golangci-lint.yml
vendored
18
.github/workflows/golangci-lint.yml
vendored
@@ -1,12 +1,23 @@
|
|||||||
name: golangci-lint
|
name: golangci-lint
|
||||||
on: [pull_request]
|
on: [pull_request]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
golangci:
|
golangci:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||||
name: lint
|
name: lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -14,7 +25,12 @@ jobs:
|
|||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: "1.20.x"
|
go-version: "1.20.x"
|
||||||
|
cache: false
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: sudo apt update && sudo apt install -y -q libgtk-3-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
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
args: --timeout=12m
|
||||||
44
.github/workflows/test-infrastructure-files.yml
vendored
44
.github/workflows/test-infrastructure-files.yml
vendored
@@ -8,6 +8,8 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'infrastructure_files/**'
|
- 'infrastructure_files/**'
|
||||||
- '.github/workflows/test-infrastructure-files.yml'
|
- '.github/workflows/test-infrastructure-files.yml'
|
||||||
|
- 'management/cmd/**'
|
||||||
|
- 'signal/cmd/**'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }}
|
||||||
@@ -56,6 +58,8 @@ jobs:
|
|||||||
CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id
|
CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id
|
||||||
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
||||||
CI_NETBIRD_AUTH_SUPPORTED_SCOPES: "openid profile email offline_access api email_verified"
|
CI_NETBIRD_AUTH_SUPPORTED_SCOPES: "openid profile email offline_access api email_verified"
|
||||||
|
CI_NETBIRD_STORE_CONFIG_ENGINE: "sqlite"
|
||||||
|
CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH: false
|
||||||
|
|
||||||
- name: check values
|
- name: check values
|
||||||
working-directory: infrastructure_files
|
working-directory: infrastructure_files
|
||||||
@@ -81,6 +85,8 @@ jobs:
|
|||||||
CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id
|
CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id
|
||||||
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret
|
||||||
CI_NETBIRD_SIGNAL_PORT: 12345
|
CI_NETBIRD_SIGNAL_PORT: 12345
|
||||||
|
CI_NETBIRD_STORE_CONFIG_ENGINE: "sqlite"
|
||||||
|
CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH: false
|
||||||
|
|
||||||
run: |
|
run: |
|
||||||
grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
||||||
@@ -97,7 +103,9 @@ jobs:
|
|||||||
grep NETBIRD_TOKEN_SOURCE docker-compose.yml | grep $CI_NETBIRD_TOKEN_SOURCE
|
grep NETBIRD_TOKEN_SOURCE docker-compose.yml | grep $CI_NETBIRD_TOKEN_SOURCE
|
||||||
grep AuthUserIDClaim management.json | grep $CI_NETBIRD_AUTH_USER_ID_CLAIM
|
grep AuthUserIDClaim management.json | grep $CI_NETBIRD_AUTH_USER_ID_CLAIM
|
||||||
grep -A 3 DeviceAuthorizationFlow management.json | grep -A 1 ProviderConfig | grep Audience | grep $CI_NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE
|
grep -A 3 DeviceAuthorizationFlow management.json | grep -A 1 ProviderConfig | grep Audience | grep $CI_NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE
|
||||||
grep -A 8 DeviceAuthorizationFlow management.json | grep -A 6 ProviderConfig | grep Scope | grep "$CI_NETBIRD_AUTH_DEVICE_AUTH_SCOPE"
|
grep -A 3 DeviceAuthorizationFlow management.json | grep -A 1 ProviderConfig | grep Audience | grep $CI_NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE
|
||||||
|
grep Engine management.json | grep "$CI_NETBIRD_STORE_CONFIG_ENGINE"
|
||||||
|
grep IdpSignKeyRefreshEnabled management.json | grep "$CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH"
|
||||||
grep UseIDToken management.json | grep false
|
grep UseIDToken management.json | grep false
|
||||||
grep -A 1 IdpManagerConfig management.json | grep ManagerType | grep $CI_NETBIRD_MGMT_IDP
|
grep -A 1 IdpManagerConfig management.json | grep ManagerType | grep $CI_NETBIRD_MGMT_IDP
|
||||||
grep -A 3 IdpManagerConfig management.json | grep -A 1 ClientConfig | grep Issuer | grep $CI_NETBIRD_AUTH_AUTHORITY
|
grep -A 3 IdpManagerConfig management.json | grep -A 1 ClientConfig | grep Issuer | grep $CI_NETBIRD_AUTH_AUTHORITY
|
||||||
@@ -105,12 +113,34 @@ jobs:
|
|||||||
grep -A 5 IdpManagerConfig management.json | grep -A 3 ClientConfig | grep ClientID | grep $CI_NETBIRD_IDP_MGMT_CLIENT_ID
|
grep -A 5 IdpManagerConfig management.json | grep -A 3 ClientConfig | grep ClientID | grep $CI_NETBIRD_IDP_MGMT_CLIENT_ID
|
||||||
grep -A 6 IdpManagerConfig management.json | grep -A 4 ClientConfig | grep ClientSecret | grep $CI_NETBIRD_IDP_MGMT_CLIENT_SECRET
|
grep -A 6 IdpManagerConfig management.json | grep -A 4 ClientConfig | grep ClientSecret | grep $CI_NETBIRD_IDP_MGMT_CLIENT_SECRET
|
||||||
grep -A 7 IdpManagerConfig management.json | grep -A 5 ClientConfig | grep GrantType | grep client_credentials
|
grep -A 7 IdpManagerConfig management.json | grep -A 5 ClientConfig | grep GrantType | grep client_credentials
|
||||||
grep -A 2 PKCEAuthorizationFlow management.json | grep -A 1 ProviderConfig | grep Audience | grep $CI_NETBIRD_AUTH_AUDIENCE
|
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep Audience | grep $CI_NETBIRD_AUTH_AUDIENCE
|
||||||
grep -A 3 PKCEAuthorizationFlow management.json | grep -A 2 ProviderConfig | grep ClientID | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep ClientID | grep $CI_NETBIRD_AUTH_CLIENT_ID
|
||||||
grep -A 4 PKCEAuthorizationFlow management.json | grep -A 3 ProviderConfig | grep ClientSecret | grep $CI_NETBIRD_AUTH_CLIENT_SECRET
|
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep ClientSecret | grep $CI_NETBIRD_AUTH_CLIENT_SECRET
|
||||||
grep -A 5 PKCEAuthorizationFlow management.json | grep -A 4 ProviderConfig | grep AuthorizationEndpoint | grep $CI_NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT
|
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep AuthorizationEndpoint | grep $CI_NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT
|
||||||
grep -A 6 PKCEAuthorizationFlow management.json | grep -A 5 ProviderConfig | grep TokenEndpoint | grep $CI_NETBIRD_AUTH_TOKEN_ENDPOINT
|
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep TokenEndpoint | grep $CI_NETBIRD_AUTH_TOKEN_ENDPOINT
|
||||||
grep -A 7 PKCEAuthorizationFlow management.json | grep -A 6 ProviderConfig | grep Scope | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES"
|
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep Scope | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES"
|
||||||
|
grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep -A 3 RedirectURLs | grep "http://localhost:53000"
|
||||||
|
|
||||||
|
- name: Install modules
|
||||||
|
run: go mod tidy
|
||||||
|
|
||||||
|
- name: Build management binary
|
||||||
|
working-directory: management
|
||||||
|
run: CGO_ENABLED=1 go build -o netbird-mgmt main.go
|
||||||
|
|
||||||
|
- name: Build management docker image
|
||||||
|
working-directory: management
|
||||||
|
run: |
|
||||||
|
docker build -t netbirdio/management:latest .
|
||||||
|
|
||||||
|
- name: Build signal binary
|
||||||
|
working-directory: signal
|
||||||
|
run: CGO_ENABLED=0 go build -o netbird-signal main.go
|
||||||
|
|
||||||
|
- name: Build signal docker image
|
||||||
|
working-directory: signal
|
||||||
|
run: |
|
||||||
|
docker build -t netbirdio/signal:latest .
|
||||||
|
|
||||||
- name: run docker compose up
|
- name: run docker compose up
|
||||||
working-directory: infrastructure_files
|
working-directory: infrastructure_files
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,3 +20,4 @@ infrastructure_files/setup.env
|
|||||||
infrastructure_files/setup-*.env
|
infrastructure_files/setup-*.env
|
||||||
.vscode
|
.vscode
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
*.db
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ If you haven't already, join our slack workspace [here](https://join.slack.com/t
|
|||||||
- [Test suite](#test-suite)
|
- [Test suite](#test-suite)
|
||||||
- [Checklist before submitting a PR](#checklist-before-submitting-a-pr)
|
- [Checklist before submitting a PR](#checklist-before-submitting-a-pr)
|
||||||
- [Other project repositories](#other-project-repositories)
|
- [Other project repositories](#other-project-repositories)
|
||||||
- [Checklist before submitting a new node](#checklist-before-submitting-a-new-node)
|
|
||||||
- [Contributor License Agreement](#contributor-license-agreement)
|
- [Contributor License Agreement](#contributor-license-agreement)
|
||||||
|
|
||||||
## Code of conduct
|
## Code of conduct
|
||||||
@@ -70,7 +69,7 @@ dependencies are installed. Here is a short guide on how that can be done.
|
|||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
#### Go 1.19
|
#### Go 1.21
|
||||||
|
|
||||||
Follow the installation guide from https://go.dev/
|
Follow the installation guide from https://go.dev/
|
||||||
|
|
||||||
@@ -139,15 +138,14 @@ checked out and set up:
|
|||||||
### Build and start
|
### Build and start
|
||||||
#### Client
|
#### 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:
|
To start NetBird, execute:
|
||||||
```
|
```
|
||||||
cd client
|
cd client
|
||||||
# bash wireguard_nt.sh # if windows
|
CGO_ENABLED=0 go build .
|
||||||
go build .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> Windows clients have a Wireguard driver requirement. You can downlowd the wintun driver from https://www.wintun.net/builds/wintun-0.14.1.zip, after decompressing, you can copy the file `windtun\bin\ARCH\wintun.dll` to the same path as your binary file or to `C:\Windows\System32\wintun.dll`.
|
||||||
|
|
||||||
To start NetBird the client in the foreground:
|
To start NetBird the client in the foreground:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ func (a *Auth) login(urlOpener URLOpener) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, error) {
|
func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, error) {
|
||||||
oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config)
|
oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -83,6 +84,7 @@ var loginCmd = &cobra.Command{
|
|||||||
SetupKey: setupKey,
|
SetupKey: setupKey,
|
||||||
PreSharedKey: preSharedKey,
|
PreSharedKey: preSharedKey,
|
||||||
ManagementUrl: managementURL,
|
ManagementUrl: managementURL,
|
||||||
|
IsLinuxDesktopClient: isLinuxRunningDesktop(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var loginErr error
|
var loginErr error
|
||||||
@@ -163,7 +165,7 @@ func foregroundLogin(ctx context.Context, cmd *cobra.Command, config *internal.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *internal.Config) (*auth.TokenInfo, error) {
|
func foregroundGetTokenInfo(ctx context.Context, cmd *cobra.Command, config *internal.Config) (*auth.TokenInfo, error) {
|
||||||
oAuthFlow, err := auth.NewOAuthFlow(ctx, config)
|
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, isLinuxRunningDesktop())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -202,3 +204,8 @@ func openURL(cmd *cobra.Command, verificationURIComplete, userCode string) {
|
|||||||
"https://docs.netbird.io/how-to/register-machines-using-setup-keys")
|
"https://docs.netbird.io/how-to/register-machines-using-setup-keys")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isLinuxRunningDesktop checks if a Linux OS is running desktop environment
|
||||||
|
func isLinuxRunningDesktop() bool {
|
||||||
|
return os.Getenv("DESKTOP_SESSION") != "" || os.Getenv("XDG_CURRENT_DESKTOP") != ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func startManagement(t *testing.T, config *mgmt.Config) (*grpc.Server, net.Liste
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
store, err := mgmt.NewFileStore(config.Datadir, nil)
|
store, err := mgmt.NewStoreFromJson(config.Datadir, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
|
|||||||
NatExternalIPs: natExternalIPs,
|
NatExternalIPs: natExternalIPs,
|
||||||
CleanNATExternalIPs: natExternalIPs != nil && len(natExternalIPs) == 0,
|
CleanNATExternalIPs: natExternalIPs != nil && len(natExternalIPs) == 0,
|
||||||
CustomDNSAddress: customDNSAddressConverted,
|
CustomDNSAddress: customDNSAddressConverted,
|
||||||
|
IsLinuxDesktopClient: isLinuxRunningDesktop(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var loginErr error
|
var loginErr error
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func Create(wgIface iFaceMapper, ipv6Supported bool) (*Manager, error) {
|
|||||||
|
|
||||||
// AddFiltering rule to the firewall
|
// AddFiltering rule to the firewall
|
||||||
//
|
//
|
||||||
// If comment is empty rule ID is used as comment
|
// Comment will be ignored because some system this feature is not supported
|
||||||
func (m *Manager) AddFiltering(
|
func (m *Manager) AddFiltering(
|
||||||
ip net.IP,
|
ip net.IP,
|
||||||
protocol fw.Protocol,
|
protocol fw.Protocol,
|
||||||
@@ -123,9 +123,6 @@ func (m *Manager) AddFiltering(
|
|||||||
ipsetName = m.transformIPsetName(ipsetName, sPortVal, dPortVal)
|
ipsetName = m.transformIPsetName(ipsetName, sPortVal, dPortVal)
|
||||||
|
|
||||||
ruleID := uuid.New().String()
|
ruleID := uuid.New().String()
|
||||||
if comment == "" {
|
|
||||||
comment = ruleID
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipsetName != "" {
|
if ipsetName != "" {
|
||||||
rs, rsExists := m.rulesets[ipsetName]
|
rs, rsExists := m.rulesets[ipsetName]
|
||||||
@@ -157,8 +154,7 @@ func (m *Manager) AddFiltering(
|
|||||||
// this is new ipset so we need to create firewall rule for it
|
// this is new ipset so we need to create firewall rule for it
|
||||||
}
|
}
|
||||||
|
|
||||||
specs := m.filterRuleSpecs("filter", ip, string(protocol), sPortVal, dPortVal,
|
specs := m.filterRuleSpecs(ip, string(protocol), sPortVal, dPortVal, direction, action, ipsetName)
|
||||||
direction, action, comment, ipsetName)
|
|
||||||
|
|
||||||
if direction == fw.RuleDirectionOUT {
|
if direction == fw.RuleDirectionOUT {
|
||||||
ok, err := client.Exists("filter", ChainOutputFilterName, specs...)
|
ok, err := client.Exists("filter", ChainOutputFilterName, specs...)
|
||||||
@@ -283,7 +279,7 @@ func (m *Manager) AllowNetbird() error {
|
|||||||
fw.RuleDirectionIN,
|
fw.RuleDirectionIN,
|
||||||
fw.ActionAccept,
|
fw.ActionAccept,
|
||||||
"",
|
"",
|
||||||
"allow netbird interface traffic",
|
"",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to allow netbird interface traffic: %w", err)
|
return fmt.Errorf("failed to allow netbird interface traffic: %w", err)
|
||||||
@@ -296,7 +292,7 @@ func (m *Manager) AllowNetbird() error {
|
|||||||
fw.RuleDirectionOUT,
|
fw.RuleDirectionOUT,
|
||||||
fw.ActionAccept,
|
fw.ActionAccept,
|
||||||
"",
|
"",
|
||||||
"allow netbird interface traffic",
|
"",
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -362,9 +358,7 @@ func (m *Manager) reset(client *iptables.IPTables, table string) error {
|
|||||||
|
|
||||||
// filterRuleSpecs returns the specs of a filtering rule
|
// filterRuleSpecs returns the specs of a filtering rule
|
||||||
func (m *Manager) filterRuleSpecs(
|
func (m *Manager) filterRuleSpecs(
|
||||||
table string, ip net.IP, protocol string, sPort, dPort string,
|
ip net.IP, protocol string, sPort, dPort string, direction fw.RuleDirection, action fw.Action, ipsetName string,
|
||||||
direction fw.RuleDirection, action fw.Action, comment string,
|
|
||||||
ipsetName string,
|
|
||||||
) (specs []string) {
|
) (specs []string) {
|
||||||
matchByIP := true
|
matchByIP := true
|
||||||
// don't use IP matching if IP is ip 0.0.0.0
|
// don't use IP matching if IP is ip 0.0.0.0
|
||||||
@@ -398,8 +392,7 @@ func (m *Manager) filterRuleSpecs(
|
|||||||
if dPort != "" {
|
if dPort != "" {
|
||||||
specs = append(specs, "--dport", dPort)
|
specs = append(specs, "--dport", dPort)
|
||||||
}
|
}
|
||||||
specs = append(specs, "-j", m.actionToStr(action))
|
return append(specs, "-j", m.actionToStr(action))
|
||||||
return append(specs, "-m", "comment", "--comment", comment)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rawClient returns corresponding iptables client for the given ip
|
// rawClient returns corresponding iptables client for the given ip
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
gstatus "google.golang.org/grpc/status"
|
gstatus "google.golang.org/grpc/status"
|
||||||
|
|
||||||
@@ -63,14 +64,16 @@ func (t TokenInfo) GetTokenToUse() string {
|
|||||||
// and if that also fails, the authentication process is deemed unsuccessful
|
// and if that also fails, the authentication process is deemed unsuccessful
|
||||||
//
|
//
|
||||||
// On Linux distros without desktop environment support, it only tries to initialize the Device Code Flow
|
// On Linux distros without desktop environment support, it only tries to initialize the Device Code Flow
|
||||||
func NewOAuthFlow(ctx context.Context, config *internal.Config) (OAuthFlow, error) {
|
func NewOAuthFlow(ctx context.Context, config *internal.Config, isLinuxDesktopClient bool) (OAuthFlow, error) {
|
||||||
if runtime.GOOS == "linux" && !isLinuxRunningDesktop() {
|
if runtime.GOOS == "linux" && !isLinuxDesktopClient {
|
||||||
return authenticateWithDeviceCodeFlow(ctx, config)
|
return authenticateWithDeviceCodeFlow(ctx, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
pkceFlow, err := authenticateWithPKCEFlow(ctx, config)
|
pkceFlow, err := authenticateWithPKCEFlow(ctx, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// fallback to device code flow
|
// fallback to device code flow
|
||||||
|
log.Debugf("failed to initialize pkce authentication with error: %v\n", err)
|
||||||
|
log.Debug("falling back to device code flow")
|
||||||
return authenticateWithDeviceCodeFlow(ctx, config)
|
return authenticateWithDeviceCodeFlow(ctx, config)
|
||||||
}
|
}
|
||||||
return pkceFlow, nil
|
return pkceFlow, nil
|
||||||
|
|||||||
@@ -197,7 +197,13 @@ func (p *PKCEAuthorizationFlow) parseOAuthToken(token *oauth2.Token) (TokenInfo,
|
|||||||
tokenInfo.IDToken = idToken
|
tokenInfo.IDToken = idToken
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := isValidAccessToken(tokenInfo.GetTokenToUse(), p.providerConfig.Audience); err != nil {
|
// if a provider doesn't support an audience, use the Client ID for token verification
|
||||||
|
audience := p.providerConfig.Audience
|
||||||
|
if audience == "" {
|
||||||
|
audience = p.providerConfig.ClientID
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := isValidAccessToken(tokenInfo.GetTokenToUse(), audience); err != nil {
|
||||||
return TokenInfo{}, fmt.Errorf("validate access token failed with error: %v", err)
|
return TokenInfo{}, fmt.Errorf("validate access token failed with error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,15 +43,14 @@ func isValidAccessToken(token string, audience string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Audience claim of JWT can be a string or an array of strings
|
// Audience claim of JWT can be a string or an array of strings
|
||||||
typ := reflect.TypeOf(claims.Audience)
|
switch aud := claims.Audience.(type) {
|
||||||
switch typ.Kind() {
|
case string:
|
||||||
case reflect.String:
|
if aud == audience {
|
||||||
if claims.Audience == audience {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case reflect.Slice:
|
case []interface{}:
|
||||||
for _, aud := range claims.Audience.([]interface{}) {
|
for _, audItem := range aud {
|
||||||
if audience == aud {
|
if audStr, ok := audItem.(string); ok && audStr == audience {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,8 +58,3 @@ func isValidAccessToken(token string, audience string) error {
|
|||||||
|
|
||||||
return fmt.Errorf("invalid JWT token audience field")
|
return fmt.Errorf("invalid JWT token audience field")
|
||||||
}
|
}
|
||||||
|
|
||||||
// isLinuxRunningDesktop checks if a Linux OS is running desktop environment
|
|
||||||
func isLinuxRunningDesktop() bool {
|
|
||||||
return os.Getenv("DESKTOP_SESSION") != "" || os.Getenv("XDG_CURRENT_DESKTOP") != ""
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
mgm "github.com/netbirdio/netbird/management/client"
|
mgm "github.com/netbirdio/netbird/management/client"
|
||||||
mgmProto "github.com/netbirdio/netbird/management/proto"
|
mgmProto "github.com/netbirdio/netbird/management/proto"
|
||||||
signal "github.com/netbirdio/netbird/signal/client"
|
signal "github.com/netbirdio/netbird/signal/client"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunClient with main logic.
|
// RunClient with main logic.
|
||||||
@@ -43,6 +44,8 @@ func RunClientMobile(ctx context.Context, config *Config, statusRecorder *peer.S
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status, mobileDependency MobileDependency) error {
|
func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status, mobileDependency MobileDependency) error {
|
||||||
|
log.Infof("starting NetBird client version %s", version.NetbirdVersion())
|
||||||
|
|
||||||
backOff := &backoff.ExponentialBackOff{
|
backOff := &backoff.ExponentialBackOff{
|
||||||
InitialInterval: time.Second,
|
InitialInterval: time.Second,
|
||||||
RandomizationFactor: 1,
|
RandomizationFactor: 1,
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ func dnsConfigToHostDNSConfig(dnsConfig nbdns.Config, ip string, port int) hostD
|
|||||||
for _, domain := range nsConfig.Domains {
|
for _, domain := range nsConfig.Domains {
|
||||||
config.domains = append(config.domains, domainConfig{
|
config.domains = append(config.domains, domainConfig{
|
||||||
domain: strings.TrimSuffix(domain, "."),
|
domain: strings.TrimSuffix(domain, "."),
|
||||||
matchOnly: true,
|
matchOnly: !nsConfig.SearchDomainsEnabled,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,11 @@ const (
|
|||||||
interfaceConfigPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"
|
interfaceConfigPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"
|
||||||
interfaceConfigNameServerKey = "NameServer"
|
interfaceConfigNameServerKey = "NameServer"
|
||||||
interfaceConfigSearchListKey = "SearchList"
|
interfaceConfigSearchListKey = "SearchList"
|
||||||
tcpipParametersPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type registryConfigurator struct {
|
type registryConfigurator struct {
|
||||||
guid string
|
guid string
|
||||||
routingAll bool
|
routingAll bool
|
||||||
existingSearchDomains []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHostManager(wgInterface WGIface) (hostManager, error) {
|
func newHostManager(wgInterface WGIface) (hostManager, error) {
|
||||||
@@ -148,30 +146,11 @@ func (r *registryConfigurator) restoreHostDNS() error {
|
|||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.updateSearchDomains([]string{})
|
return r.deleteInterfaceRegistryKeyProperty(interfaceConfigSearchListKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *registryConfigurator) updateSearchDomains(domains []string) error {
|
func (r *registryConfigurator) updateSearchDomains(domains []string) error {
|
||||||
value, err := getLocalMachineRegistryKeyStringValue(tcpipParametersPath, interfaceConfigSearchListKey)
|
err := r.setInterfaceRegistryKeyStringValue(interfaceConfigSearchListKey, strings.Join(domains, ","))
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to get current search domains failed with error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
valueList := strings.Split(value, ",")
|
|
||||||
setExisting := false
|
|
||||||
if len(r.existingSearchDomains) == 0 {
|
|
||||||
r.existingSearchDomains = valueList
|
|
||||||
setExisting = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(domains) == 0 && setExisting {
|
|
||||||
log.Infof("added %d search domains to the registry. Domain list: %s", len(domains), domains)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newList := append(r.existingSearchDomains, domains...)
|
|
||||||
|
|
||||||
err = setLocalMachineRegistryKeyStringValue(tcpipParametersPath, interfaceConfigSearchListKey, strings.Join(newList, ","))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("adding search domain failed with error: %s", err)
|
return fmt.Errorf("adding search domain failed with error: %s", err)
|
||||||
}
|
}
|
||||||
@@ -235,33 +214,3 @@ func removeRegistryKeyFromDNSPolicyConfig(regKeyPath string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLocalMachineRegistryKeyStringValue(keyPath, key string) (string, error) {
|
|
||||||
regKey, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.QUERY_VALUE)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("unable to open existing key from registry, key path: HKEY_LOCAL_MACHINE\\%s, error: %s", keyPath, err)
|
|
||||||
}
|
|
||||||
defer regKey.Close()
|
|
||||||
|
|
||||||
val, _, err := regKey.GetStringValue(key)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("getting %s value for key path HKEY_LOCAL_MACHINE\\%s failed with error: %s", key, keyPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setLocalMachineRegistryKeyStringValue(keyPath, key, value string) error {
|
|
||||||
regKey, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.SET_VALUE)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to open existing key from registry, key path: HKEY_LOCAL_MACHINE\\%s, error: %s", keyPath, err)
|
|
||||||
}
|
|
||||||
defer regKey.Close()
|
|
||||||
|
|
||||||
err = regKey.SetStringValue(key, value)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("setting %s value %s for key path HKEY_LOCAL_MACHINE\\%s failed with error: %s", key, value, keyPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -716,6 +716,7 @@ func toDNSConfig(protoDNSConfig *mgmProto.DNSConfig) nbdns.Config {
|
|||||||
dnsNSGroup := &nbdns.NameServerGroup{
|
dnsNSGroup := &nbdns.NameServerGroup{
|
||||||
Primary: nsGroup.GetPrimary(),
|
Primary: nsGroup.GetPrimary(),
|
||||||
Domains: nsGroup.GetDomains(),
|
Domains: nsGroup.GetDomains(),
|
||||||
|
SearchDomainsEnabled: nsGroup.GetSearchDomainsEnabled(),
|
||||||
}
|
}
|
||||||
for _, ns := range nsGroup.GetNameServers() {
|
for _, ns := range nsGroup.GetNameServers() {
|
||||||
dnsNS := nbdns.NameServer{
|
dnsNS := nbdns.NameServer{
|
||||||
|
|||||||
@@ -1039,10 +1039,11 @@ func startManagement(dataDir string) (*grpc.Server, string, error) {
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||||
store, err := server.NewFileStore(config.Datadir, nil)
|
store, err := server.NewStoreFromJson(config.Datadir, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
peersUpdateManager := server.NewPeersUpdateManager()
|
peersUpdateManager := server.NewPeersUpdateManager()
|
||||||
eventStore := &activity.InMemoryEventStore{}
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -106,9 +106,6 @@ func GetPKCEAuthorizationFlowInfo(ctx context.Context, privateKey string, mgmURL
|
|||||||
|
|
||||||
func isPKCEProviderConfigValid(config PKCEAuthProviderConfig) error {
|
func isPKCEProviderConfigValid(config PKCEAuthProviderConfig) error {
|
||||||
errorMSGFormat := "invalid provider configuration received from management: %s value is empty. Contact your NetBird administrator"
|
errorMSGFormat := "invalid provider configuration received from management: %s value is empty. Contact your NetBird administrator"
|
||||||
if config.Audience == "" {
|
|
||||||
return fmt.Errorf(errorMSGFormat, "Audience")
|
|
||||||
}
|
|
||||||
if config.ClientID == "" {
|
if config.ClientID == "" {
|
||||||
return fmt.Errorf(errorMSGFormat, "Client ID")
|
return fmt.Errorf(errorMSGFormat, "Client ID")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.21.9
|
// protoc v4.23.4
|
||||||
// source: daemon.proto
|
// source: daemon.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -42,6 +42,7 @@ type LoginRequest struct {
|
|||||||
// omits initialized empty slices due to omitempty tags
|
// omits initialized empty slices due to omitempty tags
|
||||||
CleanNATExternalIPs bool `protobuf:"varint,6,opt,name=cleanNATExternalIPs,proto3" json:"cleanNATExternalIPs,omitempty"`
|
CleanNATExternalIPs bool `protobuf:"varint,6,opt,name=cleanNATExternalIPs,proto3" json:"cleanNATExternalIPs,omitempty"`
|
||||||
CustomDNSAddress []byte `protobuf:"bytes,7,opt,name=customDNSAddress,proto3" json:"customDNSAddress,omitempty"`
|
CustomDNSAddress []byte `protobuf:"bytes,7,opt,name=customDNSAddress,proto3" json:"customDNSAddress,omitempty"`
|
||||||
|
IsLinuxDesktopClient bool `protobuf:"varint,8,opt,name=isLinuxDesktopClient,proto3" json:"isLinuxDesktopClient,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *LoginRequest) Reset() {
|
func (x *LoginRequest) Reset() {
|
||||||
@@ -125,6 +126,13 @@ func (x *LoginRequest) GetCustomDNSAddress() []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *LoginRequest) GetIsLinuxDesktopClient() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsLinuxDesktopClient
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type LoginResponse struct {
|
type LoginResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -1043,7 +1051,7 @@ var file_daemon_proto_rawDesc = []byte{
|
|||||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||||
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||||
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||||
0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x96, 0x02, 0x0a, 0x0c, 0x4c, 0x6f,
|
0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xca, 0x02, 0x0a, 0x0c, 0x4c, 0x6f,
|
||||||
0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65,
|
0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65,
|
||||||
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65,
|
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65,
|
||||||
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61,
|
0x74, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61,
|
||||||
@@ -1061,128 +1069,131 @@ var file_daemon_proto_rawDesc = []byte{
|
|||||||
0x6e, 0x61, 0x6c, 0x49, 0x50, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
|
0x6e, 0x61, 0x6c, 0x49, 0x50, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
|
||||||
0x44, 0x4e, 0x53, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c,
|
0x44, 0x4e, 0x53, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c,
|
||||||
0x52, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x44, 0x4e, 0x53, 0x41, 0x64, 0x64, 0x72, 0x65,
|
0x52, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x44, 0x4e, 0x53, 0x41, 0x64, 0x64, 0x72, 0x65,
|
||||||
0x73, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70,
|
0x73, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x69, 0x73, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x44, 0x65, 0x73,
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f,
|
0x6b, 0x74, 0x6f, 0x70, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x65, 0x65,
|
0x52, 0x14, 0x69, 0x73, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70,
|
||||||
0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73,
|
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
||||||
0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73,
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6e, 0x65, 0x65, 0x64,
|
||||||
0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69,
|
0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x0d, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1a,
|
||||||
0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49,
|
0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x76, 0x65,
|
||||||
0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x18, 0x03, 0x20,
|
||||||
0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55,
|
0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
|
||||||
0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x31, 0x0a, 0x13, 0x57, 0x61,
|
0x6e, 0x55, 0x52, 0x49, 0x12, 0x38, 0x0a, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||||
0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
0x74, 0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18,
|
||||||
0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20,
|
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x16, 0x0a,
|
0x69, 0x6f, 0x6e, 0x55, 0x52, 0x49, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x31,
|
||||||
0x14, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73,
|
0x0a, 0x13, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64,
|
||||||
0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x64,
|
||||||
0x22, 0x3d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
0x65, 0x22, 0x16, 0x0a, 0x14, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69,
|
||||||
0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72,
|
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x0b, 0x0a, 0x09, 0x55, 0x70, 0x52,
|
||||||
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x67, 0x65,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70,
|
||||||
0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
|
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
|
||||||
0x82, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c,
|
||||||
0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01,
|
0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
|
||||||
0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32, 0x0a, 0x0a, 0x66, 0x75,
|
0x52, 0x11, 0x67, 0x65, 0x74, 0x46, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61,
|
||||||
0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12,
|
0x74, 0x75, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65,
|
||||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||||
0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x24,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x32,
|
||||||
0x0a, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
|
0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72,
|
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x75, 0x6c, 0x6c,
|
||||||
0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75,
|
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74,
|
||||||
0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x75, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x56, 0x65, 0x72, 0x73,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43,
|
0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x0d, 0x0a, 0x0b, 0x44, 0x6f, 0x77, 0x6e,
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a,
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x52,
|
||||||
0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x18, 0x01,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x6f,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, 0x0a, 0x11,
|
||||||
0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x69, 0x6c,
|
0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46,
|
0x65, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x55,
|
||||||
0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x03,
|
0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x22, 0x0a,
|
0x6d, 0x65, 0x6e, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x04, 0x20,
|
0x67, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65,
|
0x66, 0x69, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69,
|
||||||
0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x05, 0x20,
|
0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x46, 0x69, 0x6c,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c, 0x22, 0xcf, 0x02,
|
0x65, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65,
|
||||||
0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49,
|
0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72,
|
||||||
0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70,
|
0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52,
|
||||||
0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62,
|
0x4c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52,
|
||||||
0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75,
|
0x4c, 0x22, 0xcf, 0x02, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
|
||||||
0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61,
|
0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12,
|
||||||
0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75,
|
0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53,
|
||||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e,
|
||||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53,
|
0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x53,
|
||||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72,
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||||
0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65,
|
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18,
|
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63,
|
||||||
0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x12, 0x34, 0x0a,
|
0x6f, 0x6e, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12,
|
||||||
0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61,
|
0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
|
||||||
0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x6c, 0x6f,
|
0x52, 0x07, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72,
|
||||||
0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54,
|
0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63,
|
||||||
0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65,
|
0x74, 0x12, 0x34, 0x0a, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e,
|
||||||
0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20,
|
0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, 0x63, 0x65, 0x43, 0x61,
|
0x52, 0x15, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64,
|
||||||
0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66,
|
0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74,
|
||||||
0x71, 0x64, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22,
|
0x65, 0x49, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70,
|
||||||
0x76, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74,
|
0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49,
|
||||||
0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49,
|
0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12,
|
||||||
0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
|
0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66,
|
||||||
0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x72,
|
0x71, 0x64, 0x6e, 0x22, 0x76, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72,
|
||||||
0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01,
|
0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66,
|
0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18,
|
||||||
0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28,
|
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a,
|
||||||
0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x61,
|
0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65,
|
||||||
0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20,
|
0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e,
|
0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18,
|
||||||
0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e,
|
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x53,
|
||||||
0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52,
|
||||||
0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52, 0x4c,
|
0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09, 0x63,
|
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
|
0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x41, 0x0a, 0x0f, 0x4d, 0x61,
|
||||||
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01, 0x0a, 0x0a, 0x46, 0x75,
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a,
|
||||||
0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61,
|
0x03, 0x55, 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12,
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67,
|
0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0xef, 0x01,
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x6e, 0x61,
|
0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f,
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73,
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18,
|
||||||
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
|
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d,
|
||||||
0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c,
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f,
|
||||||
0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61,
|
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
|
||||||
0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53,
|
0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02,
|
||||||
0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x65,
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69,
|
||||||
0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61,
|
0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61,
|
||||||
0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61,
|
0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50,
|
||||||
0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
|
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
|
||||||
0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53,
|
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65,
|
||||||
0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32, 0xf7, 0x02, 0x0a, 0x0d,
|
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65,
|
||||||
0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a,
|
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18,
|
||||||
0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64,
|
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x32,
|
||||||
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0xf7, 0x02, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f,
|
0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57,
|
0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52,
|
||||||
0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69,
|
||||||
0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
||||||
0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52,
|
||||||
0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70,
|
||||||
0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61,
|
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64,
|
||||||
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||||
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74,
|
0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04,
|
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
|
||||||
0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f,
|
0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
|
||||||
0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||||
0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18,
|
0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
||||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e,
|
||||||
0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43,
|
||||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64,
|
||||||
|
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
|
||||||
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ message LoginRequest {
|
|||||||
|
|
||||||
bytes customDNSAddress = 7;
|
bytes customDNSAddress = 7;
|
||||||
|
|
||||||
|
bool isLinuxDesktopClient = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LoginResponse {
|
message LoginResponse {
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ func (s *Server) Login(callerCtx context.Context, msg *proto.LoginRequest) (*pro
|
|||||||
state.Set(internal.StatusConnecting)
|
state.Set(internal.StatusConnecting)
|
||||||
|
|
||||||
if msg.SetupKey == "" {
|
if msg.SetupKey == "" {
|
||||||
oAuthFlow, err := auth.NewOAuthFlow(ctx, config)
|
oAuthFlow, err := auth.NewOAuthFlow(ctx, config, msg.IsLinuxDesktopClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.Set(internal.StatusLoginFailed)
|
state.Set(internal.StatusLoginFailed)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
5
client/ui/build-ui-linux.sh
Normal file
5
client/ui/build-ui-linux.sh
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt remove gir1.2-appindicator3-0.1
|
||||||
|
sudo apt install -y libayatana-appindicator3-dev
|
||||||
|
go build
|
||||||
@@ -205,6 +205,7 @@ func (s *serviceClient) getSettingsForm() *widget.Form {
|
|||||||
ManagementUrl: s.iMngURL.Text,
|
ManagementUrl: s.iMngURL.Text,
|
||||||
AdminURL: s.iAdminURL.Text,
|
AdminURL: s.iAdminURL.Text,
|
||||||
PreSharedKey: s.iPreSharedKey.Text,
|
PreSharedKey: s.iPreSharedKey.Text,
|
||||||
|
IsLinuxDesktopClient: runtime.GOOS == "linux",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("login to management URL: %v", err)
|
log.Errorf("login to management URL: %v", err)
|
||||||
@@ -233,7 +234,9 @@ func (s *serviceClient) login() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
loginResp, err := conn.Login(s.ctx, &proto.LoginRequest{})
|
loginResp, err := conn.Login(s.ctx, &proto.LoginRequest{
|
||||||
|
IsLinuxDesktopClient: runtime.GOOS == "linux",
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("login to management URL with: %v", err)
|
log.Errorf("login to management URL with: %v", err)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -50,21 +50,25 @@ func ToNameServerType(typeString string) NameServerType {
|
|||||||
// NameServerGroup group of nameservers and with group ids
|
// NameServerGroup group of nameservers and with group ids
|
||||||
type NameServerGroup struct {
|
type NameServerGroup struct {
|
||||||
// ID identifier of group
|
// ID identifier of group
|
||||||
ID string
|
ID string `gorm:"primaryKey"`
|
||||||
|
// AccountID is a reference to Account that this object belongs
|
||||||
|
AccountID string `gorm:"index"`
|
||||||
// Name group name
|
// Name group name
|
||||||
Name string
|
Name string
|
||||||
// Description group description
|
// Description group description
|
||||||
Description string
|
Description string
|
||||||
// NameServers list of nameservers
|
// NameServers list of nameservers
|
||||||
NameServers []NameServer
|
NameServers []NameServer `gorm:"serializer:json"`
|
||||||
// Groups list of peer group IDs to distribute the nameservers information
|
// Groups list of peer group IDs to distribute the nameservers information
|
||||||
Groups []string
|
Groups []string `gorm:"serializer:json"`
|
||||||
// Primary indicates that the nameserver group is the primary resolver for any dns query
|
// Primary indicates that the nameserver group is the primary resolver for any dns query
|
||||||
Primary bool
|
Primary bool
|
||||||
// Domains indicate the dns query domains to use with this nameserver group
|
// Domains indicate the dns query domains to use with this nameserver group
|
||||||
Domains []string
|
Domains []string `gorm:"serializer:json"`
|
||||||
// Enabled group status
|
// Enabled group status
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
// SearchDomainsEnabled indicates whether to add match domains to search domains list or not
|
||||||
|
SearchDomainsEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameServer represents a DNS nameserver
|
// NameServer represents a DNS nameserver
|
||||||
@@ -139,6 +143,7 @@ func (g *NameServerGroup) Copy() *NameServerGroup {
|
|||||||
Enabled: g.Enabled,
|
Enabled: g.Enabled,
|
||||||
Primary: g.Primary,
|
Primary: g.Primary,
|
||||||
Domains: make([]string, len(g.Domains)),
|
Domains: make([]string, len(g.Domains)),
|
||||||
|
SearchDomainsEnabled: g.SearchDomainsEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(nsGroup.NameServers, g.NameServers)
|
copy(nsGroup.NameServers, g.NameServers)
|
||||||
@@ -154,6 +159,7 @@ func (g *NameServerGroup) IsEqual(other *NameServerGroup) bool {
|
|||||||
other.Name == g.Name &&
|
other.Name == g.Name &&
|
||||||
other.Description == g.Description &&
|
other.Description == g.Description &&
|
||||||
other.Primary == g.Primary &&
|
other.Primary == g.Primary &&
|
||||||
|
other.SearchDomainsEnabled == g.SearchDomainsEnabled &&
|
||||||
compareNameServerList(g.NameServers, other.NameServers) &&
|
compareNameServerList(g.NameServers, other.NameServers) &&
|
||||||
compareGroupsList(g.Groups, other.Groups) &&
|
compareGroupsList(g.Groups, other.Groups) &&
|
||||||
compareGroupsList(g.Domains, other.Domains)
|
compareGroupsList(g.Domains, other.Domains)
|
||||||
|
|||||||
18
go.mod
18
go.mod
@@ -17,8 +17,8 @@ require (
|
|||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/vishvananda/netlink v1.1.0
|
github.com/vishvananda/netlink v1.1.0
|
||||||
golang.org/x/crypto v0.9.0
|
golang.org/x/crypto v0.14.0
|
||||||
golang.org/x/sys v0.8.0
|
golang.org/x/sys v0.13.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675
|
golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211215182854-7a385b3431de
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||||
@@ -29,6 +29,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
fyne.io/fyne/v2 v2.1.4
|
fyne.io/fyne/v2 v2.1.4
|
||||||
|
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible
|
||||||
github.com/c-robinson/iplib v1.0.3
|
github.com/c-robinson/iplib v1.0.3
|
||||||
github.com/cilium/ebpf v0.10.0
|
github.com/cilium/ebpf v0.10.0
|
||||||
github.com/coreos/go-iptables v0.7.0
|
github.com/coreos/go-iptables v0.7.0
|
||||||
@@ -45,11 +46,12 @@ require (
|
|||||||
github.com/hashicorp/go-version v1.6.0
|
github.com/hashicorp/go-version v1.6.0
|
||||||
github.com/libp2p/go-netroute v0.2.0
|
github.com/libp2p/go-netroute v0.2.0
|
||||||
github.com/magiconair/properties v1.8.5
|
github.com/magiconair/properties v1.8.5
|
||||||
github.com/mattn/go-sqlite3 v1.14.16
|
github.com/mattn/go-sqlite3 v1.14.17
|
||||||
github.com/mdlayher/socket v0.4.0
|
github.com/mdlayher/socket v0.4.0
|
||||||
github.com/miekg/dns v1.1.43
|
github.com/miekg/dns v1.1.43
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
github.com/nadoo/ipset v0.5.0
|
github.com/nadoo/ipset v0.5.0
|
||||||
|
github.com/netbirdio/management-integrations/integrations v0.0.0-20231017101406-322cbabed3da
|
||||||
github.com/okta/okta-sdk-golang/v2 v2.18.0
|
github.com/okta/okta-sdk-golang/v2 v2.18.0
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pion/logging v0.2.2
|
github.com/pion/logging v0.2.2
|
||||||
@@ -67,12 +69,14 @@ require (
|
|||||||
goauthentik.io/api/v3 v3.2023051.3
|
goauthentik.io/api/v3 v3.2023051.3
|
||||||
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf
|
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
|
||||||
golang.org/x/net v0.10.0
|
golang.org/x/net v0.17.0
|
||||||
golang.org/x/oauth2 v0.8.0
|
golang.org/x/oauth2 v0.8.0
|
||||||
golang.org/x/sync v0.2.0
|
golang.org/x/sync v0.2.0
|
||||||
golang.org/x/term v0.8.0
|
golang.org/x/term v0.13.0
|
||||||
google.golang.org/api v0.126.0
|
google.golang.org/api v0.126.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
gorm.io/driver/sqlite v1.5.3
|
||||||
|
gorm.io/gorm v1.25.4
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -109,6 +113,8 @@ require (
|
|||||||
github.com/googleapis/gax-go/v2 v2.10.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.10.0 // indirect
|
||||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/josharian/native v1.0.0 // indirect
|
github.com/josharian/native v1.0.0 // indirect
|
||||||
github.com/kelseyhightower/envconfig v1.4.0 // indirect
|
github.com/kelseyhightower/envconfig v1.4.0 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
@@ -136,7 +142,7 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v1.11.1 // indirect
|
go.opentelemetry.io/otel/trace v1.11.1 // indirect
|
||||||
golang.org/x/image v0.5.0 // indirect
|
golang.org/x/image v0.5.0 // indirect
|
||||||
golang.org/x/mod v0.8.0 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||||
golang.org/x/tools v0.6.0 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
|
|||||||
32
go.sum
32
go.sum
@@ -61,6 +61,8 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
|
|||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible h1:hqcTK6ZISdip65SR792lwYJTa/axESA0889D3UlZbLo=
|
||||||
|
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible/go.mod h1:6B1nuc1MUs6c62ODZDl7hVE5Pv7O2XGSkgg2olnq34I=
|
||||||
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10=
|
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10=
|
||||||
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY=
|
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY=
|
||||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
@@ -381,6 +383,10 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
|
|||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
@@ -439,8 +445,8 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
|
|||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0=
|
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0=
|
||||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
@@ -489,6 +495,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
|
|||||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||||
github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
|
github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
|
||||||
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ=
|
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ=
|
||||||
|
github.com/netbirdio/management-integrations/integrations v0.0.0-20231017101406-322cbabed3da h1:S1RoPhLTw3+IhHGnyfcQlj4aqIIaQdVd3SqaiK+MYFY=
|
||||||
|
github.com/netbirdio/management-integrations/integrations v0.0.0-20231017101406-322cbabed3da/go.mod h1:KSqjzHcqlodTWiuap5lRXxt5KT3vtYRoksL0KIrTK40=
|
||||||
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g=
|
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g=
|
||||||
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||||
github.com/netbirdio/systray v0.0.0-20221012095658-dc8eda872c0c h1:wK/s4nyZj/GF/kFJQjX6nqNfE0G3gcqd6hhnPCyp4sw=
|
github.com/netbirdio/systray v0.0.0-20221012095658-dc8eda872c0c h1:wK/s4nyZj/GF/kFJQjX6nqNfE0G3gcqd6hhnPCyp4sw=
|
||||||
@@ -722,8 +730,8 @@ golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0
|
|||||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@@ -830,8 +838,9 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
|||||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -955,15 +964,17 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.1-0.20230222185716-a3b23cc77e89/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.1-0.20230222185716-a3b23cc77e89/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -977,8 +988,9 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@@ -1187,6 +1199,10 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/sqlite v1.5.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g=
|
||||||
|
gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||||
|
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
||||||
|
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||||
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY=
|
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY=
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ NETBIRD_MGMT_API_CERT_KEY_FILE="/etc/letsencrypt/live/$NETBIRD_LETSENCRYPT_DOMAI
|
|||||||
# 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
|
# 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_SINGLE_ACCOUNT_MODE_DOMAIN=$NETBIRD_DOMAIN
|
||||||
NETBIRD_MGMT_DNS_DOMAIN=${NETBIRD_MGMT_DNS_DOMAIN:-netbird.selfhosted}
|
NETBIRD_MGMT_DNS_DOMAIN=${NETBIRD_MGMT_DNS_DOMAIN:-netbird.selfhosted}
|
||||||
|
NETBIRD_MGMT_IDP_SIGNKEY_REFRESH=${NETBIRD_MGMT_IDP_SIGNKEY_REFRESH:-false}
|
||||||
|
|
||||||
# Signal
|
# Signal
|
||||||
NETBIRD_SIGNAL_PROTOCOL="http"
|
NETBIRD_SIGNAL_PROTOCOL="http"
|
||||||
@@ -46,6 +47,17 @@ NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken}
|
|||||||
# PKCE authorization flow
|
# PKCE authorization flow
|
||||||
NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS=${NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS:-"53000"}
|
NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS=${NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS:-"53000"}
|
||||||
NETBIRD_AUTH_PKCE_USE_ID_TOKEN=${NETBIRD_AUTH_PKCE_USE_ID_TOKEN:-false}
|
NETBIRD_AUTH_PKCE_USE_ID_TOKEN=${NETBIRD_AUTH_PKCE_USE_ID_TOKEN:-false}
|
||||||
|
NETBIRD_AUTH_PKCE_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
||||||
|
|
||||||
|
# Dashboard
|
||||||
|
|
||||||
|
# The default setting is to transmit the audience to the IDP during authorization. However,
|
||||||
|
# if your IDP does not have this capability, you can turn this off by setting it to false.
|
||||||
|
NETBIRD_DASH_AUTH_USE_AUDIENCE=${NETBIRD_DASH_AUTH_USE_AUDIENCE:-true}
|
||||||
|
NETBIRD_DASH_AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
||||||
|
|
||||||
|
# Store config
|
||||||
|
NETBIRD_STORE_CONFIG_ENGINE=${NETBIRD_STORE_CONFIG_ENGINE:-"jsonfile"}
|
||||||
|
|
||||||
# exports
|
# exports
|
||||||
export NETBIRD_DOMAIN
|
export NETBIRD_DOMAIN
|
||||||
@@ -78,6 +90,7 @@ export LETSENCRYPT_VOLUMESUFFIX
|
|||||||
export NETBIRD_DISABLE_ANONYMOUS_METRICS
|
export NETBIRD_DISABLE_ANONYMOUS_METRICS
|
||||||
export NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN
|
export NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN
|
||||||
export NETBIRD_MGMT_DNS_DOMAIN
|
export NETBIRD_MGMT_DNS_DOMAIN
|
||||||
|
export NETBIRD_MGMT_IDP_SIGNKEY_REFRESH
|
||||||
export NETBIRD_SIGNAL_PROTOCOL
|
export NETBIRD_SIGNAL_PROTOCOL
|
||||||
export NETBIRD_SIGNAL_PORT
|
export NETBIRD_SIGNAL_PORT
|
||||||
export NETBIRD_AUTH_USER_ID_CLAIM
|
export NETBIRD_AUTH_USER_ID_CLAIM
|
||||||
@@ -87,3 +100,7 @@ export NETBIRD_AUTH_DEVICE_AUTH_SCOPE
|
|||||||
export NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN
|
export NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN
|
||||||
export NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT
|
export NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT
|
||||||
export NETBIRD_AUTH_PKCE_USE_ID_TOKEN
|
export NETBIRD_AUTH_PKCE_USE_ID_TOKEN
|
||||||
|
export NETBIRD_AUTH_PKCE_AUDIENCE
|
||||||
|
export NETBIRD_DASH_AUTH_USE_AUDIENCE
|
||||||
|
export NETBIRD_DASH_AUTH_AUDIENCE
|
||||||
|
export NETBIRD_STORE_CONFIG_ENGINE
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
if ! which curl >/dev/null 2>&1; then
|
if ! which curl >/dev/null 2>&1; then
|
||||||
echo "This script uses curl fetch OpenID configuration from IDP."
|
echo "This script uses curl fetch OpenID configuration from IDP."
|
||||||
@@ -154,6 +155,8 @@ if [ -n "$NETBIRD_MGMT_IDP" ]; then
|
|||||||
export NETBIRD_IDP_MGMT_CLIENT_ID
|
export NETBIRD_IDP_MGMT_CLIENT_ID
|
||||||
export NETBIRD_IDP_MGMT_CLIENT_SECRET
|
export NETBIRD_IDP_MGMT_CLIENT_SECRET
|
||||||
export NETBIRD_IDP_MGMT_EXTRA_CONFIG=$EXTRA_CONFIG
|
export NETBIRD_IDP_MGMT_EXTRA_CONFIG=$EXTRA_CONFIG
|
||||||
|
else
|
||||||
|
export NETBIRD_IDP_MGMT_EXTRA_CONFIG={}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
IFS=',' read -r -a REDIRECT_URL_PORTS <<< "$NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS"
|
IFS=',' read -r -a REDIRECT_URL_PORTS <<< "$NETBIRD_AUTH_PKCE_REDIRECT_URL_PORTS"
|
||||||
@@ -164,8 +167,35 @@ done
|
|||||||
|
|
||||||
export NETBIRD_AUTH_PKCE_REDIRECT_URLS=${REDIRECT_URLS%,}
|
export NETBIRD_AUTH_PKCE_REDIRECT_URLS=${REDIRECT_URLS%,}
|
||||||
|
|
||||||
|
# Remove audience for providers that do not support it
|
||||||
|
if [ "$NETBIRD_DASH_AUTH_USE_AUDIENCE" = "false" ]; then
|
||||||
|
export NETBIRD_DASH_AUTH_AUDIENCE=none
|
||||||
|
export NETBIRD_AUTH_PKCE_AUDIENCE=
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read the encryption key
|
||||||
|
if test -f 'management.json'; then
|
||||||
|
encKey=$(jq -r ".DataStoreEncryptionKey" management.json)
|
||||||
|
if [[ "$encKey" != "null" ]]; then
|
||||||
|
export NETBIRD_DATASTORE_ENC_KEY=$encKey
|
||||||
|
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
env | grep NETBIRD
|
env | grep NETBIRD
|
||||||
|
|
||||||
|
bkp_postfix="$(date +%s)"
|
||||||
|
if test -f 'docker-compose.yml'; then
|
||||||
|
cp docker-compose.yml "docker-compose.yml.bkp.${bkp_postfix}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -f 'management.json'; then
|
||||||
|
cp management.json "management.json.bkp.${bkp_postfix}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -f 'turnserver.conf'; then
|
||||||
|
cp turnserver.conf "turnserver.conf.bpk.${bkp_postfix}"
|
||||||
|
fi
|
||||||
envsubst <docker-compose.yml.tmpl >docker-compose.yml
|
envsubst <docker-compose.yml.tmpl >docker-compose.yml
|
||||||
envsubst <management.json.tmpl >management.json
|
envsubst <management.json.tmpl | jq . >management.json
|
||||||
envsubst <turnserver.conf.tmpl >turnserver.conf
|
envsubst <turnserver.conf.tmpl >turnserver.conf
|
||||||
@@ -12,7 +12,7 @@ services:
|
|||||||
- NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
- NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
||||||
- NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
- NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
||||||
# OIDC
|
# OIDC
|
||||||
- AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
- AUTH_AUDIENCE=$NETBIRD_DASH_AUTH_AUDIENCE
|
||||||
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
||||||
- AUTH_CLIENT_SECRET=$NETBIRD_AUTH_CLIENT_SECRET
|
- AUTH_CLIENT_SECRET=$NETBIRD_AUTH_CLIENT_SECRET
|
||||||
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ services:
|
|||||||
- NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
- NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
||||||
- NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
- NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
|
||||||
# OIDC
|
# OIDC
|
||||||
- AUTH_AUDIENCE=$NETBIRD_AUTH_AUDIENCE
|
- AUTH_AUDIENCE=$NETBIRD_DASH_AUTH_AUDIENCE
|
||||||
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
||||||
- AUTH_CLIENT_SECRET=$NETBIRD_AUTH_CLIENT_SECRET
|
- AUTH_CLIENT_SECRET=$NETBIRD_AUTH_CLIENT_SECRET
|
||||||
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
|
||||||
@@ -20,6 +20,7 @@ services:
|
|||||||
- AUTH_SUPPORTED_SCOPES=$NETBIRD_AUTH_SUPPORTED_SCOPES
|
- AUTH_SUPPORTED_SCOPES=$NETBIRD_AUTH_SUPPORTED_SCOPES
|
||||||
- AUTH_REDIRECT_URI=$NETBIRD_AUTH_REDIRECT_URI
|
- AUTH_REDIRECT_URI=$NETBIRD_AUTH_REDIRECT_URI
|
||||||
- AUTH_SILENT_REDIRECT_URI=$NETBIRD_AUTH_SILENT_REDIRECT_URI
|
- AUTH_SILENT_REDIRECT_URI=$NETBIRD_AUTH_SILENT_REDIRECT_URI
|
||||||
|
- NETBIRD_TOKEN_SOURCE=$NETBIRD_TOKEN_SOURCE
|
||||||
# SSL
|
# SSL
|
||||||
- NGINX_SSL_PORT=443
|
- NGINX_SSL_PORT=443
|
||||||
# Letsencrypt
|
# Letsencrypt
|
||||||
|
|||||||
@@ -27,6 +27,10 @@
|
|||||||
"Password": null
|
"Password": null
|
||||||
},
|
},
|
||||||
"Datadir": "",
|
"Datadir": "",
|
||||||
|
"DataStoreEncryptionKey": "$NETBIRD_DATASTORE_ENC_KEY",
|
||||||
|
"StoreConfig": {
|
||||||
|
"Engine": "$NETBIRD_STORE_CONFIG_ENGINE"
|
||||||
|
},
|
||||||
"HttpConfig": {
|
"HttpConfig": {
|
||||||
"Address": "0.0.0.0:$NETBIRD_MGMT_API_PORT",
|
"Address": "0.0.0.0:$NETBIRD_MGMT_API_PORT",
|
||||||
"AuthIssuer": "$NETBIRD_AUTH_AUTHORITY",
|
"AuthIssuer": "$NETBIRD_AUTH_AUTHORITY",
|
||||||
@@ -35,6 +39,7 @@
|
|||||||
"AuthUserIDClaim": "$NETBIRD_AUTH_USER_ID_CLAIM",
|
"AuthUserIDClaim": "$NETBIRD_AUTH_USER_ID_CLAIM",
|
||||||
"CertFile":"$NETBIRD_MGMT_API_CERT_FILE",
|
"CertFile":"$NETBIRD_MGMT_API_CERT_FILE",
|
||||||
"CertKey":"$NETBIRD_MGMT_API_CERT_KEY_FILE",
|
"CertKey":"$NETBIRD_MGMT_API_CERT_KEY_FILE",
|
||||||
|
"IdpSignKeyRefreshEnabled": $NETBIRD_MGMT_IDP_SIGNKEY_REFRESH,
|
||||||
"OIDCConfigEndpoint":"$NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT"
|
"OIDCConfigEndpoint":"$NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT"
|
||||||
},
|
},
|
||||||
"IdpManagerConfig": {
|
"IdpManagerConfig": {
|
||||||
@@ -46,25 +51,33 @@
|
|||||||
"ClientSecret": "$NETBIRD_IDP_MGMT_CLIENT_SECRET",
|
"ClientSecret": "$NETBIRD_IDP_MGMT_CLIENT_SECRET",
|
||||||
"GrantType": "client_credentials"
|
"GrantType": "client_credentials"
|
||||||
},
|
},
|
||||||
"ExtraConfig": $NETBIRD_IDP_MGMT_EXTRA_CONFIG
|
"ExtraConfig": $NETBIRD_IDP_MGMT_EXTRA_CONFIG,
|
||||||
|
"Auth0ClientCredentials": null,
|
||||||
|
"AzureClientCredentials": null,
|
||||||
|
"KeycloakClientCredentials": null,
|
||||||
|
"ZitadelClientCredentials": null
|
||||||
},
|
},
|
||||||
"DeviceAuthorizationFlow": {
|
"DeviceAuthorizationFlow": {
|
||||||
"Provider": "$NETBIRD_AUTH_DEVICE_AUTH_PROVIDER",
|
"Provider": "$NETBIRD_AUTH_DEVICE_AUTH_PROVIDER",
|
||||||
"ProviderConfig": {
|
"ProviderConfig": {
|
||||||
"Audience": "$NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE",
|
"Audience": "$NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE",
|
||||||
|
"AuthorizationEndpoint": "",
|
||||||
"Domain": "$NETBIRD_AUTH0_DOMAIN",
|
"Domain": "$NETBIRD_AUTH0_DOMAIN",
|
||||||
"ClientID": "$NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID",
|
"ClientID": "$NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID",
|
||||||
|
"ClientSecret": "",
|
||||||
"TokenEndpoint": "$NETBIRD_AUTH_TOKEN_ENDPOINT",
|
"TokenEndpoint": "$NETBIRD_AUTH_TOKEN_ENDPOINT",
|
||||||
"DeviceAuthEndpoint": "$NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT",
|
"DeviceAuthEndpoint": "$NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT",
|
||||||
"Scope": "$NETBIRD_AUTH_DEVICE_AUTH_SCOPE",
|
"Scope": "$NETBIRD_AUTH_DEVICE_AUTH_SCOPE",
|
||||||
"UseIDToken": $NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN
|
"UseIDToken": $NETBIRD_AUTH_DEVICE_AUTH_USE_ID_TOKEN,
|
||||||
|
"RedirectURLs": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"PKCEAuthorizationFlow": {
|
"PKCEAuthorizationFlow": {
|
||||||
"ProviderConfig": {
|
"ProviderConfig": {
|
||||||
"Audience": "$NETBIRD_AUTH_AUDIENCE",
|
"Audience": "$NETBIRD_AUTH_PKCE_AUDIENCE",
|
||||||
"ClientID": "$NETBIRD_AUTH_CLIENT_ID",
|
"ClientID": "$NETBIRD_AUTH_CLIENT_ID",
|
||||||
"ClientSecret": "$NETBIRD_AUTH_CLIENT_SECRET",
|
"ClientSecret": "$NETBIRD_AUTH_CLIENT_SECRET",
|
||||||
|
"Domain": "",
|
||||||
"AuthorizationEndpoint": "$NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT",
|
"AuthorizationEndpoint": "$NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT",
|
||||||
"TokenEndpoint": "$NETBIRD_AUTH_TOKEN_ENDPOINT",
|
"TokenEndpoint": "$NETBIRD_AUTH_TOKEN_ENDPOINT",
|
||||||
"Scope": "$NETBIRD_AUTH_SUPPORTED_SCOPES",
|
"Scope": "$NETBIRD_AUTH_SUPPORTED_SCOPES",
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ NETBIRD_DOMAIN=""
|
|||||||
# e.g., https://example.eu.auth0.com/.well-known/openid-configuration
|
# e.g., https://example.eu.auth0.com/.well-known/openid-configuration
|
||||||
# -------------------------------------------
|
# -------------------------------------------
|
||||||
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT=""
|
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT=""
|
||||||
|
# The default setting is to transmit the audience to the IDP during authorization. However,
|
||||||
|
# if your IDP does not have this capability, you can turn this off by setting it to false.
|
||||||
|
#NETBIRD_DASH_AUTH_USE_AUDIENCE=false
|
||||||
NETBIRD_AUTH_AUDIENCE=""
|
NETBIRD_AUTH_AUDIENCE=""
|
||||||
# e.g. netbird-client
|
# e.g. netbird-client
|
||||||
NETBIRD_AUTH_CLIENT_ID=""
|
NETBIRD_AUTH_CLIENT_ID=""
|
||||||
@@ -50,6 +53,8 @@ NETBIRD_MGMT_IDP="none"
|
|||||||
# Some IDPs requires different client id and client secret for management api
|
# Some IDPs requires different client id and client secret for management api
|
||||||
NETBIRD_IDP_MGMT_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
NETBIRD_IDP_MGMT_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
|
||||||
NETBIRD_IDP_MGMT_CLIENT_SECRET=""
|
NETBIRD_IDP_MGMT_CLIENT_SECRET=""
|
||||||
|
# With some IDPs may be needed enabling automatic refresh of signing keys on expire
|
||||||
|
# NETBIRD_MGMT_IDP_SIGNKEY_REFRESH=false
|
||||||
# NETBIRD_IDP_MGMT_EXTRA_ variables. See https://docs.netbird.io/selfhosted/identity-providers for more information about your IDP of choice.
|
# NETBIRD_IDP_MGMT_EXTRA_ variables. See https://docs.netbird.io/selfhosted/identity-providers for more information about your IDP of choice.
|
||||||
# -------------------------------------------
|
# -------------------------------------------
|
||||||
# Letsencrypt
|
# Letsencrypt
|
||||||
|
|||||||
@@ -23,3 +23,5 @@ NETBIRD_MGMT_IDP=$CI_NETBIRD_MGMT_IDP
|
|||||||
NETBIRD_IDP_MGMT_CLIENT_ID=$CI_NETBIRD_IDP_MGMT_CLIENT_ID
|
NETBIRD_IDP_MGMT_CLIENT_ID=$CI_NETBIRD_IDP_MGMT_CLIENT_ID
|
||||||
NETBIRD_IDP_MGMT_CLIENT_SECRET=$CI_NETBIRD_IDP_MGMT_CLIENT_SECRET
|
NETBIRD_IDP_MGMT_CLIENT_SECRET=$CI_NETBIRD_IDP_MGMT_CLIENT_SECRET
|
||||||
NETBIRD_SIGNAL_PORT=12345
|
NETBIRD_SIGNAL_PORT=12345
|
||||||
|
NETBIRD_STORE_CONFIG_ENGINE=$CI_NETBIRD_STORE_CONFIG_ENGINE
|
||||||
|
NETBIRD_MGMT_IDP_SIGNKEY_REFRESH=$CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH
|
||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
"github.com/netbirdio/netbird/management/proto"
|
|
||||||
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||||
mgmt "github.com/netbirdio/netbird/management/server"
|
mgmt "github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/mock_server"
|
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||||
@@ -53,7 +52,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
store, err := mgmt.NewFileStore(config.Datadir, nil)
|
store, err := mgmt.NewStoreFromJson(config.Datadir, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -95,8 +94,8 @@ func startMockManagement(t *testing.T) (*grpc.Server, net.Listener, *mock_server
|
|||||||
}
|
}
|
||||||
|
|
||||||
mgmtMockServer := &mock_server.ManagementServiceServerMock{
|
mgmtMockServer := &mock_server.ManagementServiceServerMock{
|
||||||
GetServerKeyFunc: func(context.Context, *proto.Empty) (*proto.ServerKeyResponse, error) {
|
GetServerKeyFunc: func(context.Context, *mgmtProto.Empty) (*mgmtProto.ServerKeyResponse, error) {
|
||||||
response := &proto.ServerKeyResponse{
|
response := &mgmtProto.ServerKeyResponse{
|
||||||
Key: serverKey.PublicKey().String(),
|
Key: serverKey.PublicKey().String(),
|
||||||
}
|
}
|
||||||
return response, nil
|
return response, nil
|
||||||
@@ -300,19 +299,19 @@ func Test_SystemMetaDataFromClient(t *testing.T) {
|
|||||||
log.Fatalf("error while getting server public key from testclient, %v", err)
|
log.Fatalf("error while getting server public key from testclient, %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var actualMeta *proto.PeerSystemMeta
|
var actualMeta *mgmtProto.PeerSystemMeta
|
||||||
var actualValidKey string
|
var actualValidKey string
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
mgmtMockServer.LoginFunc = func(ctx context.Context, msg *proto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
mgmtMockServer.LoginFunc = func(ctx context.Context, msg *mgmtProto.EncryptedMessage) (*mgmtProto.EncryptedMessage, error) {
|
||||||
peerKey, err := wgtypes.ParseKey(msg.GetWgPubKey())
|
peerKey, err := wgtypes.ParseKey(msg.GetWgPubKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("error while parsing peer's Wireguard public key %s on Sync request.", msg.WgPubKey)
|
log.Warnf("error while parsing peer's Wireguard public key %s on Sync request.", msg.WgPubKey)
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "provided wgPubKey %s is invalid", msg.WgPubKey)
|
return nil, status.Errorf(codes.InvalidArgument, "provided wgPubKey %s is invalid", msg.WgPubKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
loginReq := &proto.LoginRequest{}
|
loginReq := &mgmtProto.LoginRequest{}
|
||||||
err = encryption.DecryptMessage(peerKey, serverKey, msg.Body, loginReq)
|
err = encryption.DecryptMessage(peerKey, serverKey, msg.Body, loginReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -322,7 +321,7 @@ func Test_SystemMetaDataFromClient(t *testing.T) {
|
|||||||
actualValidKey = loginReq.GetSetupKey()
|
actualValidKey = loginReq.GetSetupKey()
|
||||||
wg.Done()
|
wg.Done()
|
||||||
|
|
||||||
loginResp := &proto.LoginResponse{}
|
loginResp := &mgmtProto.LoginResponse{}
|
||||||
encryptedResp, err := encryption.EncryptMessage(peerKey, serverKey, loginResp)
|
encryptedResp, err := encryption.EncryptMessage(peerKey, serverKey, loginResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -343,7 +342,7 @@ func Test_SystemMetaDataFromClient(t *testing.T) {
|
|||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
expectedMeta := &proto.PeerSystemMeta{
|
expectedMeta := &mgmtProto.PeerSystemMeta{
|
||||||
Hostname: info.Hostname,
|
Hostname: info.Hostname,
|
||||||
GoOS: info.GoOS,
|
GoOS: info.GoOS,
|
||||||
Kernel: info.Kernel,
|
Kernel: info.Kernel,
|
||||||
@@ -374,12 +373,12 @@ func Test_GetDeviceAuthorizationFlow(t *testing.T) {
|
|||||||
log.Fatalf("error while creating testClient: %v", err)
|
log.Fatalf("error while creating testClient: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedFlowInfo := &proto.DeviceAuthorizationFlow{
|
expectedFlowInfo := &mgmtProto.DeviceAuthorizationFlow{
|
||||||
Provider: 0,
|
Provider: 0,
|
||||||
ProviderConfig: &proto.ProviderConfig{ClientID: "client"},
|
ProviderConfig: &mgmtProto.ProviderConfig{ClientID: "client"},
|
||||||
}
|
}
|
||||||
|
|
||||||
mgmtMockServer.GetDeviceAuthorizationFlowFunc = func(ctx context.Context, req *mgmtProto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
mgmtMockServer.GetDeviceAuthorizationFlowFunc = func(ctx context.Context, req *mgmtProto.EncryptedMessage) (*mgmtProto.EncryptedMessage, error) {
|
||||||
encryptedResp, err := encryption.EncryptMessage(serverKey, client.key, expectedFlowInfo)
|
encryptedResp, err := encryption.EncryptMessage(serverKey, client.key, expectedFlowInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -418,14 +417,14 @@ func Test_GetPKCEAuthorizationFlow(t *testing.T) {
|
|||||||
log.Fatalf("error while creating testClient: %v", err)
|
log.Fatalf("error while creating testClient: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedFlowInfo := &proto.PKCEAuthorizationFlow{
|
expectedFlowInfo := &mgmtProto.PKCEAuthorizationFlow{
|
||||||
ProviderConfig: &proto.ProviderConfig{
|
ProviderConfig: &mgmtProto.ProviderConfig{
|
||||||
ClientID: "client",
|
ClientID: "client",
|
||||||
ClientSecret: "secret",
|
ClientSecret: "secret",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
mgmtMockServer.GetPKCEAuthorizationFlowFunc = func(ctx context.Context, req *mgmtProto.EncryptedMessage) (*proto.EncryptedMessage, error) {
|
mgmtMockServer.GetPKCEAuthorizationFlowFunc = func(ctx context.Context, req *mgmtProto.EncryptedMessage) (*mgmtProto.EncryptedMessage, error) {
|
||||||
encryptedResp, err := encryption.EncryptMessage(serverKey, client.key, expectedFlowInfo)
|
encryptedResp, err := encryption.EncryptMessage(serverKey, client.key, expectedFlowInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE
|
|||||||
transportOption,
|
transportOption,
|
||||||
grpc.WithBlock(),
|
grpc.WithBlock(),
|
||||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||||
Time: 15 * time.Second,
|
Time: 30 * time.Second,
|
||||||
Timeout: 10 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ var (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
store, err := server.NewFileStore(config.Datadir, appMetrics)
|
store, err := server.NewStore(config.StoreConfig.Engine, config.Datadir, appMetrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err)
|
return fmt.Errorf("failed creating Store: %s: %v", config.Datadir, err)
|
||||||
}
|
}
|
||||||
@@ -148,8 +148,8 @@ var (
|
|||||||
return fmt.Errorf("failed to initialize database: %s", err)
|
return fmt.Errorf("failed to initialize database: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if key != "" {
|
if config.DataStoreEncryptionKey != key {
|
||||||
log.Debugf("update config with activity store key")
|
log.Infof("update config with activity store key")
|
||||||
config.DataStoreEncryptionKey = key
|
config.DataStoreEncryptionKey = key
|
||||||
err := updateMgmtConfig(mgmtConfig, config)
|
err := updateMgmtConfig(mgmtConfig, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -466,7 +466,7 @@ func loadMgmtConfig(mgmtConfigPath string) (*server.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateMgmtConfig(path string, config *server.Config) error {
|
func updateMgmtConfig(path string, config *server.Config) error {
|
||||||
return util.WriteJson(path, config)
|
return util.DirectWriteJson(path, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDCConfigResponse used for parsing OIDC config response
|
// OIDCConfigResponse used for parsing OIDC config response
|
||||||
|
|||||||
66
management/cmd/migration_down.go
Normal file
66
management/cmd/migration_down.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var shortDown = "Rollback SQLite store to JSON file store. Please make a backup of the SQLite file before running this command."
|
||||||
|
|
||||||
|
var downCmd = &cobra.Command{
|
||||||
|
Use: "downgrade [--datadir directory] [--log-file console]",
|
||||||
|
Aliases: []string{"down"},
|
||||||
|
Short: shortDown,
|
||||||
|
Long: shortDown +
|
||||||
|
"\n\n" +
|
||||||
|
"This command reads the content of {datadir}/store.db and migrates it to {datadir}/store.json that can be used by File store driver.",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
flag.Parse()
|
||||||
|
err := util.InitLog(logLevel, logFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed initializing log %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sqliteStorePath := path.Join(mgmtDataDir, "store.db")
|
||||||
|
if _, err := os.Stat(sqliteStorePath); errors.Is(err, os.ErrNotExist) {
|
||||||
|
return fmt.Errorf("%s doesn't exist, couldn't continue the operation", sqliteStorePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStorePath := path.Join(mgmtDataDir, "store.json")
|
||||||
|
if _, err := os.Stat(fileStorePath); err == nil {
|
||||||
|
return fmt.Errorf("%s already exists, couldn't continue the operation", fileStorePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlstore, err := server.NewSqliteStore(mgmtDataDir, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed creating file store: %s: %v", mgmtDataDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sqliteStoreAccounts := len(sqlstore.GetAllAccounts())
|
||||||
|
log.Infof("%d account will be migrated from sqlite store %s to file store %s",
|
||||||
|
sqliteStoreAccounts, sqliteStorePath, fileStorePath)
|
||||||
|
|
||||||
|
store, err := server.NewFilestoreFromSqliteStore(sqlstore, mgmtDataDir, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed creating file store: %s: %v", mgmtDataDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fsStoreAccounts := len(store.GetAllAccounts())
|
||||||
|
if fsStoreAccounts != sqliteStoreAccounts {
|
||||||
|
return fmt.Errorf("failed to migrate accounts from sqlite to file[]. Expected accounts: %d, got: %d",
|
||||||
|
sqliteStoreAccounts, fsStoreAccounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Migration finished successfully")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
66
management/cmd/migration_up.go
Normal file
66
management/cmd/migration_up.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var shortUp = "Migrate JSON file store to SQLite store. Please make a backup of the JSON file before running this command."
|
||||||
|
|
||||||
|
var upCmd = &cobra.Command{
|
||||||
|
Use: "upgrade [--datadir directory] [--log-file console]",
|
||||||
|
Aliases: []string{"up"},
|
||||||
|
Short: shortUp,
|
||||||
|
Long: shortUp +
|
||||||
|
"\n\n" +
|
||||||
|
"This command reads the content of {datadir}/store.json and migrates it to {datadir}/store.db that can be used by SQLite store driver.",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
flag.Parse()
|
||||||
|
err := util.InitLog(logLevel, logFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed initializing log %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStorePath := path.Join(mgmtDataDir, "store.json")
|
||||||
|
if _, err := os.Stat(fileStorePath); errors.Is(err, os.ErrNotExist) {
|
||||||
|
return fmt.Errorf("%s doesn't exist, couldn't continue the operation", fileStorePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlStorePath := path.Join(mgmtDataDir, "store.db")
|
||||||
|
if _, err := os.Stat(sqlStorePath); err == nil {
|
||||||
|
return fmt.Errorf("%s already exists, couldn't continue the operation", sqlStorePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
fstore, err := server.NewFileStore(mgmtDataDir, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed creating file store: %s: %v", mgmtDataDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fsStoreAccounts := len(fstore.GetAllAccounts())
|
||||||
|
log.Infof("%d account will be migrated from file store %s to sqlite store %s",
|
||||||
|
fsStoreAccounts, fileStorePath, sqlStorePath)
|
||||||
|
|
||||||
|
store, err := server.NewSqliteStoreFromFileStore(fstore, mgmtDataDir, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed creating file store: %s: %v", mgmtDataDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sqliteStoreAccounts := len(store.GetAllAccounts())
|
||||||
|
if fsStoreAccounts != sqliteStoreAccounts {
|
||||||
|
return fmt.Errorf("failed to migrate accounts from file to sqlite. Expected accounts: %d, got: %d",
|
||||||
|
fsStoreAccounts, sqliteStoreAccounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Migration finished successfully")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -34,6 +34,12 @@ var (
|
|||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
migrationCmd = &cobra.Command{
|
||||||
|
Use: "sqlite-migration",
|
||||||
|
Short: "Contains sub-commands to perform JSON file store to SQLite store migration and rollback",
|
||||||
|
Long: "",
|
||||||
|
SilenceUsage: true,
|
||||||
|
}
|
||||||
// Execution control channel for stopCh signal
|
// Execution control channel for stopCh signal
|
||||||
stopCh chan int
|
stopCh chan int
|
||||||
)
|
)
|
||||||
@@ -63,6 +69,14 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
|
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "")
|
||||||
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the the log will be output to stdout")
|
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the the log will be output to stdout")
|
||||||
rootCmd.AddCommand(mgmtCmd)
|
rootCmd.AddCommand(mgmtCmd)
|
||||||
|
|
||||||
|
migrationCmd.PersistentFlags().StringVar(&mgmtDataDir, "datadir", defaultMgmtDataDir, "server data directory location")
|
||||||
|
migrationCmd.MarkFlagRequired("datadir") //nolint
|
||||||
|
|
||||||
|
migrationCmd.AddCommand(upCmd)
|
||||||
|
migrationCmd.AddCommand(downCmd)
|
||||||
|
|
||||||
|
rootCmd.AddCommand(migrationCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupCloseHandler handles SIGTERM signal and exits with success
|
// SetupCloseHandler handles SIGTERM signal and exits with success
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.21.12
|
// protoc v3.21.9
|
||||||
// source: management.proto
|
// source: management.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -2002,6 +2002,7 @@ type NameServerGroup struct {
|
|||||||
NameServers []*NameServer `protobuf:"bytes,1,rep,name=NameServers,proto3" json:"NameServers,omitempty"`
|
NameServers []*NameServer `protobuf:"bytes,1,rep,name=NameServers,proto3" json:"NameServers,omitempty"`
|
||||||
Primary bool `protobuf:"varint,2,opt,name=Primary,proto3" json:"Primary,omitempty"`
|
Primary bool `protobuf:"varint,2,opt,name=Primary,proto3" json:"Primary,omitempty"`
|
||||||
Domains []string `protobuf:"bytes,3,rep,name=Domains,proto3" json:"Domains,omitempty"`
|
Domains []string `protobuf:"bytes,3,rep,name=Domains,proto3" json:"Domains,omitempty"`
|
||||||
|
SearchDomainsEnabled bool `protobuf:"varint,4,opt,name=SearchDomainsEnabled,proto3" json:"SearchDomainsEnabled,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NameServerGroup) Reset() {
|
func (x *NameServerGroup) Reset() {
|
||||||
@@ -2057,6 +2058,13 @@ func (x *NameServerGroup) GetDomains() []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *NameServerGroup) GetSearchDomainsEnabled() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.SearchDomainsEnabled
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// NameServer represents a dns.NameServer
|
// NameServer represents a dns.NameServer
|
||||||
type NameServer struct {
|
type NameServer struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
@@ -2444,73 +2452,76 @@ var file_management_proto_rawDesc = []byte{
|
|||||||
0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x04,
|
0x52, 0x05, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x54, 0x54, 0x4c, 0x18, 0x04,
|
||||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x44, 0x61,
|
0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x44, 0x61,
|
||||||
0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x22,
|
0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x44, 0x61, 0x74, 0x61, 0x22,
|
||||||
0x7f, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x6f,
|
0xb3, 0x01, 0x0a, 0x0f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72,
|
||||||
0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
0x6f, 0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65,
|
||||||
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52,
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||||
0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07,
|
0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a,
|
||||||
0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50,
|
0x07, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
|
||||||
0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
||||||
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73,
|
0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||||
0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0e,
|
0x73, 0x12, 0x32, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69,
|
||||||
0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16,
|
0x6e, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06,
|
0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x6e,
|
||||||
0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03,
|
0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x48, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72,
|
||||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0xf0, 0x02, 0x0a, 0x0c, 0x46,
|
0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x50,
|
0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20,
|
||||||
0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65,
|
0x01, 0x28, 0x03, 0x52, 0x06, 0x4e, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x50,
|
||||||
0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
0xf0, 0x02, 0x0a, 0x0c, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65,
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65,
|
0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x44, 0x69, 0x72, 0x65,
|
0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x50, 0x12, 0x40, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65,
|
||||||
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18,
|
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x6d, 0x61,
|
||||||
0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c,
|
||||||
|
0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||||
|
0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x06, 0x41, 0x63,
|
||||||
|
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x6d, 0x61, 0x6e,
|
||||||
|
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c,
|
||||||
|
0x52, 0x75, 0x6c, 0x65, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74,
|
||||||
|
0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18,
|
||||||
|
0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||||
0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e,
|
0x6e, 0x74, 0x2e, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e,
|
||||||
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d,
|
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63,
|
||||||
0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e,
|
0x6f, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x32, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x46, 0x69,
|
0x52, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
|
||||||
0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
0x69, 0x6f, 0x6e, 0x12, 0x06, 0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f,
|
||||||
0x63, 0x6f, 0x6c, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x12, 0x0a,
|
0x55, 0x54, 0x10, 0x01, 0x22, 0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a,
|
||||||
0x04, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x6f, 0x72,
|
0x0a, 0x06, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52,
|
||||||
0x74, 0x22, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x06,
|
0x4f, 0x50, 0x10, 0x01, 0x22, 0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
|
||||||
0x0a, 0x02, 0x49, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x22,
|
0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a,
|
||||||
0x1e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x43,
|
0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12,
|
||||||
0x45, 0x50, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x01, 0x22,
|
0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50,
|
||||||
0x3c, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55,
|
0x10, 0x04, 0x32, 0xd1, 0x03, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
|
||||||
0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10,
|
0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69,
|
||||||
0x01, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44,
|
0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
|
||||||
0x50, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x43, 0x4d, 0x50, 0x10, 0x04, 0x32, 0xd1, 0x03,
|
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a,
|
||||||
0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76,
|
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63,
|
||||||
0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d,
|
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12,
|
||||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
|
0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e,
|
|
||||||
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
|
||||||
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79,
|
|
||||||
0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
|
||||||
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
|
||||||
0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e,
|
|
||||||
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00,
|
|
||||||
0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b,
|
|
||||||
0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
|
|
||||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
|
||||||
0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70,
|
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c,
|
|
||||||
0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
|
||||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
|
||||||
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x1a, 0x47,
|
|
||||||
0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
|
|
||||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
|
|
||||||
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
|
|
||||||
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
|
||||||
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65,
|
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65,
|
||||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x50, 0x4b,
|
0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
|
||||||
0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46,
|
0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
||||||
0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
|
0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65,
|
||||||
0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||||
0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45,
|
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e,
|
||||||
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22,
|
0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65,
|
||||||
0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69,
|
||||||
0x74, 0x6f, 0x33,
|
0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61,
|
||||||
|
0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00,
|
||||||
|
0x12, 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74,
|
||||||
|
0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c,
|
||||||
|
0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72,
|
||||||
|
0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d,
|
||||||
|
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
|
||||||
|
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x18,
|
||||||
|
0x47, 0x65, 0x74, 0x50, 0x4b, 0x43, 0x45, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
|
||||||
|
0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||||
|
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d,
|
||||||
|
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
|
||||||
|
0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73,
|
||||||
|
0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
|
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -317,6 +317,7 @@ message NameServerGroup {
|
|||||||
repeated NameServer NameServers = 1;
|
repeated NameServer NameServers = 1;
|
||||||
bool Primary = 2;
|
bool Primary = 2;
|
||||||
repeated string Domains = 3;
|
repeated string Domains = 3;
|
||||||
|
bool SearchDomainsEnabled = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameServer represents a dns.NameServer
|
// NameServer represents a dns.NameServer
|
||||||
|
|||||||
@@ -62,12 +62,9 @@ type AccountManager interface {
|
|||||||
GetAccountFromPAT(pat string) (*Account, *User, *PersonalAccessToken, error)
|
GetAccountFromPAT(pat string) (*Account, *User, *PersonalAccessToken, error)
|
||||||
MarkPATUsed(tokenID string) error
|
MarkPATUsed(tokenID string) error
|
||||||
GetUser(claims jwtclaims.AuthorizationClaims) (*User, error)
|
GetUser(claims jwtclaims.AuthorizationClaims) (*User, error)
|
||||||
AccountExists(accountId string) (*bool, error)
|
|
||||||
GetPeerByKey(peerKey string) (*Peer, error)
|
|
||||||
GetPeers(accountID, userID string) ([]*Peer, error)
|
GetPeers(accountID, userID string) ([]*Peer, error)
|
||||||
MarkPeerConnected(peerKey string, connected bool) error
|
MarkPeerConnected(peerKey string, connected bool) error
|
||||||
DeletePeer(accountID, peerID, userID string) (*Peer, error)
|
DeletePeer(accountID, peerID, userID string) error
|
||||||
GetPeerByIP(accountId string, peerIP string) (*Peer, error)
|
|
||||||
UpdatePeer(accountID, userID string, peer *Peer) (*Peer, error)
|
UpdatePeer(accountID, userID string, peer *Peer) (*Peer, error)
|
||||||
GetNetworkMap(peerID string) (*NetworkMap, error)
|
GetNetworkMap(peerID string) (*NetworkMap, error)
|
||||||
GetPeerNetwork(peerID string) (*Network, error)
|
GetPeerNetwork(peerID string) (*Network, error)
|
||||||
@@ -83,19 +80,18 @@ type AccountManager interface {
|
|||||||
DeleteGroup(accountId, userId, groupID string) error
|
DeleteGroup(accountId, userId, groupID string) error
|
||||||
ListGroups(accountId string) ([]*Group, error)
|
ListGroups(accountId string) ([]*Group, error)
|
||||||
GroupAddPeer(accountId, groupID, peerID string) error
|
GroupAddPeer(accountId, groupID, peerID string) error
|
||||||
GroupDeletePeer(accountId, groupID, peerKey string) error
|
GroupDeletePeer(accountId, groupID, peerID string) error
|
||||||
GroupListPeers(accountId, groupID string) ([]*Peer, error)
|
|
||||||
GetPolicy(accountID, policyID, userID string) (*Policy, error)
|
GetPolicy(accountID, policyID, userID string) (*Policy, error)
|
||||||
SavePolicy(accountID, userID string, policy *Policy) error
|
SavePolicy(accountID, userID string, policy *Policy) error
|
||||||
DeletePolicy(accountID, policyID, userID string) error
|
DeletePolicy(accountID, policyID, userID string) error
|
||||||
ListPolicies(accountID, userID string) ([]*Policy, error)
|
ListPolicies(accountID, userID string) ([]*Policy, error)
|
||||||
GetRoute(accountID, routeID, userID string) (*route.Route, error)
|
GetRoute(accountID, routeID, userID string) (*route.Route, error)
|
||||||
CreateRoute(accountID string, prefix, peerID, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error)
|
CreateRoute(accountID, prefix, peerID string, peerGroupIDs []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error)
|
||||||
SaveRoute(accountID, userID string, route *route.Route) error
|
SaveRoute(accountID, userID string, route *route.Route) error
|
||||||
DeleteRoute(accountID, routeID, userID string) error
|
DeleteRoute(accountID, routeID, userID string) error
|
||||||
ListRoutes(accountID, userID string) ([]*route.Route, error)
|
ListRoutes(accountID, userID string) ([]*route.Route, error)
|
||||||
GetNameServerGroup(accountID, nsGroupID string) (*nbdns.NameServerGroup, error)
|
GetNameServerGroup(accountID, nsGroupID string) (*nbdns.NameServerGroup, error)
|
||||||
CreateNameServerGroup(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string) (*nbdns.NameServerGroup, error)
|
CreateNameServerGroup(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string, searchDomainsEnabled bool) (*nbdns.NameServerGroup, error)
|
||||||
SaveNameServerGroup(accountID, userID string, nsGroupToSave *nbdns.NameServerGroup) error
|
SaveNameServerGroup(accountID, userID string, nsGroupToSave *nbdns.NameServerGroup) error
|
||||||
DeleteNameServerGroup(accountID, nsGroupID, userID string) error
|
DeleteNameServerGroup(accountID, nsGroupID, userID string) error
|
||||||
ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error)
|
ListNameServerGroups(accountID string) ([]*nbdns.NameServerGroup, error)
|
||||||
@@ -107,6 +103,7 @@ type AccountManager interface {
|
|||||||
UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error)
|
UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error)
|
||||||
LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) // used by peer gRPC API
|
LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) // used by peer gRPC API
|
||||||
SyncPeer(sync PeerSync) (*Peer, *NetworkMap, error) // used by peer gRPC API
|
SyncPeer(sync PeerSync) (*Peer, *NetworkMap, error) // used by peer gRPC API
|
||||||
|
GetAllConnectedPeers() (map[string]struct{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultAccountManager struct {
|
type DefaultAccountManager struct {
|
||||||
@@ -168,24 +165,33 @@ func (s *Settings) Copy() *Settings {
|
|||||||
|
|
||||||
// Account represents a unique account of the system
|
// Account represents a unique account of the system
|
||||||
type Account struct {
|
type Account struct {
|
||||||
Id string
|
// we have to name column to aid as it collides with Network.Id when work with associations
|
||||||
|
Id string `gorm:"primaryKey"`
|
||||||
|
|
||||||
// User.Id it was created by
|
// User.Id it was created by
|
||||||
CreatedBy string
|
CreatedBy string
|
||||||
Domain string
|
Domain string `gorm:"index"`
|
||||||
DomainCategory string
|
DomainCategory string
|
||||||
IsDomainPrimaryAccount bool
|
IsDomainPrimaryAccount bool
|
||||||
SetupKeys map[string]*SetupKey
|
SetupKeys map[string]*SetupKey `gorm:"-"`
|
||||||
Network *Network
|
SetupKeysG []SetupKey `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
Peers map[string]*Peer
|
Network *Network `gorm:"embedded;embeddedPrefix:network_"`
|
||||||
Users map[string]*User
|
Peers map[string]*Peer `gorm:"-"`
|
||||||
Groups map[string]*Group
|
PeersG []Peer `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
Rules map[string]*Rule
|
Users map[string]*User `gorm:"-"`
|
||||||
Policies []*Policy
|
UsersG []User `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
Routes map[string]*route.Route
|
Groups map[string]*Group `gorm:"-"`
|
||||||
NameServerGroups map[string]*nbdns.NameServerGroup
|
GroupsG []Group `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
DNSSettings *DNSSettings
|
Rules map[string]*Rule `gorm:"-"`
|
||||||
|
RulesG []Rule `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
|
Policies []*Policy `gorm:"foreignKey:AccountID;references:id"`
|
||||||
|
Routes map[string]*route.Route `gorm:"-"`
|
||||||
|
RoutesG []route.Route `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
|
NameServerGroups map[string]*nbdns.NameServerGroup `gorm:"-"`
|
||||||
|
NameServerGroupsG []nbdns.NameServerGroup `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||||
|
DNSSettings DNSSettings `gorm:"embedded;embeddedPrefix:dns_settings_"`
|
||||||
// Settings is a dictionary of Account settings
|
// Settings is a dictionary of Account settings
|
||||||
Settings *Settings
|
Settings *Settings `gorm:"embedded;embeddedPrefix:settings_"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
@@ -204,7 +210,7 @@ type UserInfo struct {
|
|||||||
// from the ACL peers that have distribution groups associated with the peer ID.
|
// from the ACL peers that have distribution groups associated with the peer ID.
|
||||||
// Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
|
// Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
|
||||||
func (a *Account) getRoutesToSync(peerID string, aclPeers []*Peer) []*route.Route {
|
func (a *Account) getRoutesToSync(peerID string, aclPeers []*Peer) []*route.Route {
|
||||||
routes, peerDisabledRoutes := a.getEnabledAndDisabledRoutesByPeer(peerID)
|
routes, peerDisabledRoutes := a.getRoutingPeerRoutes(peerID)
|
||||||
peerRoutesMembership := make(lookupMap)
|
peerRoutesMembership := make(lookupMap)
|
||||||
for _, r := range append(routes, peerDisabledRoutes...) {
|
for _, r := range append(routes, peerDisabledRoutes...) {
|
||||||
peerRoutesMembership[route.GetHAUniqueID(r)] = struct{}{}
|
peerRoutesMembership[route.GetHAUniqueID(r)] = struct{}{}
|
||||||
@@ -212,7 +218,7 @@ func (a *Account) getRoutesToSync(peerID string, aclPeers []*Peer) []*route.Rout
|
|||||||
|
|
||||||
groupListMap := a.getPeerGroups(peerID)
|
groupListMap := a.getPeerGroups(peerID)
|
||||||
for _, peer := range aclPeers {
|
for _, peer := range aclPeers {
|
||||||
activeRoutes, _ := a.getEnabledAndDisabledRoutesByPeer(peer.ID)
|
activeRoutes, _ := a.getRoutingPeerRoutes(peer.ID)
|
||||||
groupFilteredRoutes := a.filterRoutesByGroups(activeRoutes, groupListMap)
|
groupFilteredRoutes := a.filterRoutesByGroups(activeRoutes, groupListMap)
|
||||||
filteredRoutes := a.filterRoutesFromPeersOfSameHAGroup(groupFilteredRoutes, peerRoutesMembership)
|
filteredRoutes := a.filterRoutesFromPeersOfSameHAGroup(groupFilteredRoutes, peerRoutesMembership)
|
||||||
routes = append(routes, filteredRoutes...)
|
routes = append(routes, filteredRoutes...)
|
||||||
@@ -248,29 +254,63 @@ func (a *Account) filterRoutesByGroups(routes []*route.Route, groupListMap looku
|
|||||||
return filteredRoutes
|
return filteredRoutes
|
||||||
}
|
}
|
||||||
|
|
||||||
// getEnabledAndDisabledRoutesByPeer returns the enabled and disabled lists of routes that belong to a peer.
|
// getRoutingPeerRoutes returns the enabled and disabled lists of routes that the given routing peer serves
|
||||||
// Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
|
// Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
|
||||||
func (a *Account) getEnabledAndDisabledRoutesByPeer(peerID string) ([]*route.Route, []*route.Route) {
|
// If the given is not a routing peer, then the lists are empty.
|
||||||
var enabledRoutes []*route.Route
|
func (a *Account) getRoutingPeerRoutes(peerID string) (enabledRoutes []*route.Route, disabledRoutes []*route.Route) {
|
||||||
var disabledRoutes []*route.Route
|
|
||||||
for _, r := range a.Routes {
|
|
||||||
if r.Peer == peerID {
|
|
||||||
// We need to set Peer.Key instead of Peer.ID because this object will be sent to agents as part of a network map.
|
|
||||||
// Ideally we should have a separate field for that, but fine for now.
|
|
||||||
peer := a.GetPeer(peerID)
|
peer := a.GetPeer(peerID)
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
log.Errorf("route %s has peer %s that doesn't exist under account %s", r.ID, peerID, a.Id)
|
log.Errorf("peer %s that doesn't exist under account %s", peerID, a.Id)
|
||||||
continue
|
return enabledRoutes, disabledRoutes
|
||||||
}
|
}
|
||||||
raut := r.Copy()
|
|
||||||
raut.Peer = peer.Key
|
// currently we support only linux routing peers
|
||||||
|
if peer.Meta.GoOS != "linux" {
|
||||||
|
return enabledRoutes, disabledRoutes
|
||||||
|
}
|
||||||
|
|
||||||
|
seenRoute := make(map[string]struct{})
|
||||||
|
|
||||||
|
takeRoute := func(r *route.Route, id string) {
|
||||||
|
if _, ok := seenRoute[r.ID]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seenRoute[r.ID] = struct{}{}
|
||||||
|
|
||||||
if r.Enabled {
|
if r.Enabled {
|
||||||
enabledRoutes = append(enabledRoutes, raut)
|
r.Peer = peer.Key
|
||||||
|
enabledRoutes = append(enabledRoutes, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
disabledRoutes = append(disabledRoutes, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range a.Routes {
|
||||||
|
for _, groupID := range r.PeerGroups {
|
||||||
|
group := a.GetGroup(groupID)
|
||||||
|
if group == nil {
|
||||||
|
log.Errorf("route %s has peers group %s that doesn't exist under account %s", r.ID, groupID, a.Id)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
disabledRoutes = append(disabledRoutes, raut)
|
for _, id := range group.Peers {
|
||||||
|
if id != peerID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newPeerRoute := r.Copy()
|
||||||
|
newPeerRoute.Peer = id
|
||||||
|
newPeerRoute.PeerGroups = nil
|
||||||
|
newPeerRoute.ID = r.ID + ":" + id // we have to provide unique route id when distribute network map
|
||||||
|
takeRoute(newPeerRoute, id)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if r.Peer == peerID {
|
||||||
|
takeRoute(r.Copy(), peerID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return enabledRoutes, disabledRoutes
|
return enabledRoutes, disabledRoutes
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,17 +326,6 @@ func (a *Account) GetRoutesByPrefix(prefix netip.Prefix) []*route.Route {
|
|||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerByIP returns peer by it's IP if exists under account or nil otherwise
|
|
||||||
func (a *Account) GetPeerByIP(peerIP string) *Peer {
|
|
||||||
for _, peer := range a.Peers {
|
|
||||||
if peerIP == peer.IP.String() {
|
|
||||||
return peer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroup returns a group by ID if exists, nil otherwise
|
// GetGroup returns a group by ID if exists, nil otherwise
|
||||||
func (a *Account) GetGroup(groupID string) *Group {
|
func (a *Account) GetGroup(groupID string) *Group {
|
||||||
return a.Groups[groupID]
|
return a.Groups[groupID]
|
||||||
@@ -316,7 +345,7 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap {
|
|||||||
}
|
}
|
||||||
peersToConnect = append(peersToConnect, p)
|
peersToConnect = append(peersToConnect, p)
|
||||||
}
|
}
|
||||||
// Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID.
|
|
||||||
routesUpdate := a.getRoutesToSync(peerID, peersToConnect)
|
routesUpdate := a.getRoutesToSync(peerID, peersToConnect)
|
||||||
|
|
||||||
dnsManagementStatus := a.getPeerDNSManagementStatus(peerID)
|
dnsManagementStatus := a.getPeerDNSManagementStatus(peerID)
|
||||||
@@ -493,7 +522,6 @@ func (a *Account) getUserGroups(userID string) ([]string, error) {
|
|||||||
func (a *Account) getPeerDNSManagementStatus(peerID string) bool {
|
func (a *Account) getPeerDNSManagementStatus(peerID string) bool {
|
||||||
peerGroups := a.getPeerGroups(peerID)
|
peerGroups := a.getPeerGroups(peerID)
|
||||||
enabled := true
|
enabled := true
|
||||||
if a.DNSSettings != nil {
|
|
||||||
for _, groupID := range a.DNSSettings.DisabledManagementGroups {
|
for _, groupID := range a.DNSSettings.DisabledManagementGroups {
|
||||||
_, found := peerGroups[groupID]
|
_, found := peerGroups[groupID]
|
||||||
if found {
|
if found {
|
||||||
@@ -501,7 +529,6 @@ func (a *Account) getPeerDNSManagementStatus(peerID string) bool {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return enabled
|
return enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,8 +604,8 @@ func (a *Account) Copy() *Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
routes := map[string]*route.Route{}
|
routes := map[string]*route.Route{}
|
||||||
for id, route := range a.Routes {
|
for id, r := range a.Routes {
|
||||||
routes[id] = route.Copy()
|
routes[id] = r.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
nsGroups := map[string]*nbdns.NameServerGroup{}
|
nsGroups := map[string]*nbdns.NameServerGroup{}
|
||||||
@@ -586,10 +613,7 @@ func (a *Account) Copy() *Account {
|
|||||||
nsGroups[id] = nsGroup.Copy()
|
nsGroups[id] = nsGroup.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
var dnsSettings *DNSSettings
|
dnsSettings := a.DNSSettings.Copy()
|
||||||
if a.DNSSettings != nil {
|
|
||||||
dnsSettings = a.DNSSettings.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
var settings *Settings
|
var settings *Settings
|
||||||
if a.Settings != nil {
|
if a.Settings != nil {
|
||||||
@@ -927,6 +951,28 @@ func (am *DefaultAccountManager) warmupIDPCache() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
log.Infof("%d entries received from IdP management", len(userData))
|
||||||
|
|
||||||
|
// If the Identity Provider does not support writing AppMetadata,
|
||||||
|
// in cases like this, we expect it to return all users in an "unset" field.
|
||||||
|
// We iterate over the users in the "unset" field, look up their AccountID in our store, and
|
||||||
|
// update their AppMetadata with the AccountID.
|
||||||
|
if unsetData, ok := userData[idp.UnsetAccountID]; ok {
|
||||||
|
for _, user := range unsetData {
|
||||||
|
accountID, err := am.Store.GetAccountByUser(user.ID)
|
||||||
|
if err == nil {
|
||||||
|
data := userData[accountID.Id]
|
||||||
|
if data == nil {
|
||||||
|
data = make([]*idp.UserData, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
user.AppMetadata.WTAccountID = accountID.Id
|
||||||
|
|
||||||
|
userData[accountID.Id] = append(data, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(userData, idp.UnsetAccountID)
|
||||||
|
|
||||||
for accountID, users := range userData {
|
for accountID, users := range userData {
|
||||||
err = am.cacheManager.Set(am.ctx, accountID, users, cacheStore.WithExpiration(cacheEntryExpiration()))
|
err = am.cacheManager.Set(am.ctx, accountID, users, cacheStore.WithExpiration(cacheEntryExpiration()))
|
||||||
@@ -994,7 +1040,37 @@ func (am *DefaultAccountManager) addAccountIDToIDPAppMeta(userID string, account
|
|||||||
|
|
||||||
func (am *DefaultAccountManager) loadAccount(_ context.Context, accountID interface{}) ([]*idp.UserData, error) {
|
func (am *DefaultAccountManager) loadAccount(_ context.Context, accountID interface{}) ([]*idp.UserData, error) {
|
||||||
log.Debugf("account %s not found in cache, reloading", accountID)
|
log.Debugf("account %s not found in cache, reloading", accountID)
|
||||||
return am.idpManager.GetAccount(fmt.Sprintf("%v", accountID))
|
accountIDString := fmt.Sprintf("%v", accountID)
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(accountIDString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userData, err := am.idpManager.GetAccount(accountIDString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debugf("%d entries received from IdP management", len(userData))
|
||||||
|
|
||||||
|
dataMap := make(map[string]*idp.UserData, len(userData))
|
||||||
|
for _, datum := range userData {
|
||||||
|
dataMap[datum.ID] = datum
|
||||||
|
}
|
||||||
|
|
||||||
|
matchedUserData := make([]*idp.UserData, 0)
|
||||||
|
for _, user := range account.Users {
|
||||||
|
if user.IsServiceUser {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
datum, ok := dataMap[user.Id]
|
||||||
|
if !ok {
|
||||||
|
log.Warnf("user %s not found in IDP", user.Id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matchedUserData = append(matchedUserData, datum)
|
||||||
|
}
|
||||||
|
return matchedUserData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) lookupUserInCacheByEmail(email string, accountID string) (*idp.UserData, error) {
|
func (am *DefaultAccountManager) lookupUserInCacheByEmail(email string, accountID string) (*idp.UserData, error) {
|
||||||
@@ -1243,7 +1319,6 @@ func (am *DefaultAccountManager) redeemInvite(account *Account, userID string) e
|
|||||||
|
|
||||||
// MarkPATUsed marks a personal access token as used
|
// MarkPATUsed marks a personal access token as used
|
||||||
func (am *DefaultAccountManager) MarkPATUsed(tokenID string) error {
|
func (am *DefaultAccountManager) MarkPATUsed(tokenID string) error {
|
||||||
unlock := am.Store.AcquireGlobalLock()
|
|
||||||
|
|
||||||
user, err := am.Store.GetUserByTokenID(tokenID)
|
user, err := am.Store.GetUserByTokenID(tokenID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1255,8 +1330,7 @@ func (am *DefaultAccountManager) MarkPATUsed(tokenID string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
unlock()
|
unlock := am.Store.AcquireAccountLock(account.Id)
|
||||||
unlock = am.Store.AcquireAccountLock(account.Id)
|
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
account, err = am.Store.GetAccountByUser(user.Id)
|
account, err = am.Store.GetAccountByUser(user.Id)
|
||||||
@@ -1383,9 +1457,7 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat
|
|||||||
if err := am.Store.SaveAccount(account); err != nil {
|
if err := am.Store.SaveAccount(account); err != nil {
|
||||||
log.Errorf("failed to save account: %v", err)
|
log.Errorf("failed to save account: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if err := am.updateAccountPeers(account); err != nil {
|
am.updateAccountPeers(account)
|
||||||
log.Errorf("failed updating account peers while updating user %s", account.Id)
|
|
||||||
}
|
|
||||||
for _, g := range addNewGroups {
|
for _, g := range addNewGroups {
|
||||||
if group := account.GetGroup(g); group != nil {
|
if group := account.GetGroup(g); group != nil {
|
||||||
am.storeEvent(user.Id, user.Id, account.Id, activity.GroupAddedToUser,
|
am.storeEvent(user.Id, user.Id, account.Id, activity.GroupAddedToUser,
|
||||||
@@ -1491,31 +1563,16 @@ func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(claims jwtcla
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllConnectedPeers returns connected peers based on peersUpdateManager.GetAllConnectedPeers()
|
||||||
|
func (am *DefaultAccountManager) GetAllConnectedPeers() (map[string]struct{}, error) {
|
||||||
|
return am.peersUpdateManager.GetAllConnectedPeers(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func isDomainValid(domain string) bool {
|
func isDomainValid(domain string) bool {
|
||||||
re := regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`)
|
re := regexp.MustCompile(`^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`)
|
||||||
return re.Match([]byte(domain))
|
return re.Match([]byte(domain))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountExists checks whether account exists (returns true) or not (returns false)
|
|
||||||
func (am *DefaultAccountManager) AccountExists(accountID string) (*bool, error) {
|
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
|
||||||
defer unlock()
|
|
||||||
|
|
||||||
var res bool
|
|
||||||
_, err := am.Store.GetAccount(accountID)
|
|
||||||
if err != nil {
|
|
||||||
if s, ok := status.FromError(err); ok && s.Type() == status.NotFound {
|
|
||||||
res = false
|
|
||||||
return &res, nil
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res = true
|
|
||||||
return &res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDNSDomain returns the configured dnsDomain
|
// GetDNSDomain returns the configured dnsDomain
|
||||||
func (am *DefaultAccountManager) GetDNSDomain() string {
|
func (am *DefaultAccountManager) GetDNSDomain() string {
|
||||||
return am.dnsDomain
|
return am.dnsDomain
|
||||||
@@ -1565,7 +1622,7 @@ func newAccountWithId(accountID, userID, domain string) *Account {
|
|||||||
setupKeys := map[string]*SetupKey{}
|
setupKeys := map[string]*SetupKey{}
|
||||||
nameServersGroups := make(map[string]*nbdns.NameServerGroup)
|
nameServersGroups := make(map[string]*nbdns.NameServerGroup)
|
||||||
users[userID] = NewAdminUser(userID)
|
users[userID] = NewAdminUser(userID)
|
||||||
dnsSettings := &DNSSettings{
|
dnsSettings := DNSSettings{
|
||||||
DisabledManagementGroups: make([]string, 0),
|
DisabledManagementGroups: make([]string, 0),
|
||||||
}
|
}
|
||||||
log.Debugf("created new account %s", accountID)
|
log.Debugf("created new account %s", accountID)
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
|
|||||||
netIP := net.IP{100, 64, 0, 0}
|
netIP := net.IP{100, 64, 0, 0}
|
||||||
netMask := net.IPMask{255, 255, 0, 0}
|
netMask := net.IPMask{255, 255, 0, 0}
|
||||||
network := &Network{
|
network := &Network{
|
||||||
Id: "network",
|
Identifier: "network",
|
||||||
Net: net.IPNet{IP: netIP, Mask: netMask},
|
Net: net.IPNet{IP: netIP, Mask: netMask},
|
||||||
Dns: "netbird.selfhosted",
|
Dns: "netbird.selfhosted",
|
||||||
Serial: 0,
|
Serial: 0,
|
||||||
@@ -476,7 +476,7 @@ func TestDefaultAccountManager_GetGroupsFromTheToken(t *testing.T) {
|
|||||||
// as initAccount was created without account id we have to take the id after account initialization
|
// as initAccount was created without account id we have to take the id after account initialization
|
||||||
// that happens inside the GetAccountByUserOrAccountID where the id is getting generated
|
// that happens inside the GetAccountByUserOrAccountID where the id is getting generated
|
||||||
// it is important to set the id as it help to avoid creating additional account with empty Id and re-pointing indices to it
|
// it is important to set the id as it help to avoid creating additional account with empty Id and re-pointing indices to it
|
||||||
initAccount.Id = acc.Id
|
initAccount = acc
|
||||||
|
|
||||||
claims := jwtclaims.AuthorizationClaims{
|
claims := jwtclaims.AuthorizationClaims{
|
||||||
AccountId: accountID, // is empty as it is based on accountID right after initialization of initAccount
|
AccountId: accountID, // is empty as it is based on accountID right after initialization of initAccount
|
||||||
@@ -706,30 +706,6 @@ func createAccount(am *DefaultAccountManager, accountID, userID, domain string)
|
|||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountManager_AccountExists(t *testing.T) {
|
|
||||||
manager, err := createManager(t)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedId := "test_account"
|
|
||||||
userId := "account_creator"
|
|
||||||
_, err = createAccount(manager, expectedId, userId, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
exists, err := manager.AccountExists(expectedId)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !*exists {
|
|
||||||
t.Errorf("expected account to exist after creation, got false")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccountManager_GetAccount(t *testing.T) {
|
func TestAccountManager_GetAccount(t *testing.T) {
|
||||||
manager, err := createManager(t)
|
manager, err := createManager(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1049,7 +1025,6 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
|||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("delete peer update", func(t *testing.T) {
|
t.Run("delete peer update", func(t *testing.T) {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -1062,7 +1037,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if _, err := manager.DeletePeer(account.Id, peer3.ID, userID); err != nil {
|
if err := manager.DeletePeer(account.Id, peer3.ID, userID); err != nil {
|
||||||
t.Errorf("delete peer: %v", err)
|
t.Errorf("delete peer: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1129,7 +1104,7 @@ func TestAccountManager_DeletePeer(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = manager.DeletePeer(account.Id, peerKey, userID)
|
err = manager.DeletePeer(account.Id, peerKey, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1261,7 +1236,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
account := &Account{
|
account := &Account{
|
||||||
Peers: map[string]*Peer{
|
Peers: map[string]*Peer{
|
||||||
"peer-1": {Key: "peer-1"}, "peer-2": {Key: "peer-2"}, "peer-3": {Key: "peer-1"},
|
"peer-1": {Key: "peer-1", Meta: PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: PeerSystemMeta{GoOS: "linux"}},
|
||||||
},
|
},
|
||||||
Groups: map[string]*Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}},
|
Groups: map[string]*Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}},
|
||||||
Routes: map[string]*route.Route{
|
Routes: map[string]*route.Route{
|
||||||
@@ -1333,7 +1308,7 @@ func TestAccount_Copy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Network: &Network{
|
Network: &Network{
|
||||||
Id: "net1",
|
Identifier: "net1",
|
||||||
},
|
},
|
||||||
Peers: map[string]*Peer{
|
Peers: map[string]*Peer{
|
||||||
"peer1": {
|
"peer1": {
|
||||||
@@ -1386,6 +1361,7 @@ func TestAccount_Copy(t *testing.T) {
|
|||||||
Routes: map[string]*route.Route{
|
Routes: map[string]*route.Route{
|
||||||
"route1": {
|
"route1": {
|
||||||
ID: "route1",
|
ID: "route1",
|
||||||
|
PeerGroups: []string{},
|
||||||
Groups: []string{"group1"},
|
Groups: []string{"group1"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1397,7 +1373,7 @@ func TestAccount_Copy(t *testing.T) {
|
|||||||
NameServers: []nbdns.NameServer{},
|
NameServers: []nbdns.NameServer{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DNSSettings: &DNSSettings{DisabledManagementGroups: []string{}},
|
DNSSettings: DNSSettings{DisabledManagementGroups: []string{}},
|
||||||
Settings: &Settings{},
|
Settings: &Settings{},
|
||||||
}
|
}
|
||||||
err := hasNilField(account)
|
err := hasNilField(account)
|
||||||
@@ -1423,6 +1399,10 @@ func hasNilField(x interface{}) error {
|
|||||||
rv := reflect.ValueOf(x)
|
rv := reflect.ValueOf(x)
|
||||||
rv = rv.Elem()
|
rv = rv.Elem()
|
||||||
for i := 0; i < rv.NumField(); i++ {
|
for i := 0; i < rv.NumField(); i++ {
|
||||||
|
// skip gorm internal fields
|
||||||
|
if json, ok := rv.Type().Field(i).Tag.Lookup("json"); ok && json == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if f := rv.Field(i); f.IsValid() {
|
if f := rv.Field(i); f.IsValid() {
|
||||||
k := f.Kind()
|
k := f.Kind()
|
||||||
switch k {
|
switch k {
|
||||||
@@ -2068,7 +2048,7 @@ func createManager(t *testing.T) (*DefaultAccountManager, error) {
|
|||||||
|
|
||||||
func createStore(t *testing.T) (Store, error) {
|
func createStore(t *testing.T) (Store, error) {
|
||||||
dataDir := t.TempDir()
|
dataDir := t.TempDir()
|
||||||
store, err := NewFileStore(dataDir, nil)
|
store, err := NewStoreFromJson(dataDir, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ type Event struct {
|
|||||||
ID uint64
|
ID uint64
|
||||||
// InitiatorID is the ID of an object that initiated the event (e.g., a user)
|
// InitiatorID is the ID of an object that initiated the event (e.g., a user)
|
||||||
InitiatorID string
|
InitiatorID string
|
||||||
// InitiatorEmail is the email address of an object that initiated the event. This will be set on deleted users only
|
// InitiatorName is the name of an object that initiated the event.
|
||||||
|
InitiatorName string
|
||||||
|
// InitiatorEmail is the email address of an object that initiated the event.
|
||||||
InitiatorEmail string
|
InitiatorEmail string
|
||||||
// TargetID is the ID of an object that was effected by the event (e.g., a peer)
|
// TargetID is the ID of an object that was effected by the event (e.g., a peer)
|
||||||
TargetID string
|
TargetID string
|
||||||
@@ -42,6 +44,7 @@ func (e *Event) Copy() *Event {
|
|||||||
Activity: e.Activity,
|
Activity: e.Activity,
|
||||||
ID: e.ID,
|
ID: e.ID,
|
||||||
InitiatorID: e.InitiatorID,
|
InitiatorID: e.InitiatorID,
|
||||||
|
InitiatorName: e.InitiatorName,
|
||||||
InitiatorEmail: e.InitiatorEmail,
|
InitiatorEmail: e.InitiatorEmail,
|
||||||
TargetID: e.TargetID,
|
TargetID: e.TargetID,
|
||||||
AccountID: e.AccountID,
|
AccountID: e.AccountID,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
var iv = []byte{10, 22, 13, 79, 05, 8, 52, 91, 87, 98, 88, 98, 35, 25, 13, 05}
|
var iv = []byte{10, 22, 13, 79, 05, 8, 52, 91, 87, 98, 88, 98, 35, 25, 13, 05}
|
||||||
|
|
||||||
type EmailEncrypt struct {
|
type FieldEncrypt struct {
|
||||||
block cipher.Block
|
block cipher.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ func GenerateKey() (string, error) {
|
|||||||
return readableKey, nil
|
return readableKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEmailEncrypt(key string) (*EmailEncrypt, error) {
|
func NewFieldEncrypt(key string) (*FieldEncrypt, error) {
|
||||||
binKey, err := base64.StdEncoding.DecodeString(key)
|
binKey, err := base64.StdEncoding.DecodeString(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -35,14 +35,14 @@ func NewEmailEncrypt(key string) (*EmailEncrypt, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ec := &EmailEncrypt{
|
ec := &FieldEncrypt{
|
||||||
block: block,
|
block: block,
|
||||||
}
|
}
|
||||||
|
|
||||||
return ec, nil
|
return ec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *EmailEncrypt) Encrypt(payload string) string {
|
func (ec *FieldEncrypt) Encrypt(payload string) string {
|
||||||
plainText := pkcs5Padding([]byte(payload))
|
plainText := pkcs5Padding([]byte(payload))
|
||||||
cipherText := make([]byte, len(plainText))
|
cipherText := make([]byte, len(plainText))
|
||||||
cbc := cipher.NewCBCEncrypter(ec.block, iv)
|
cbc := cipher.NewCBCEncrypter(ec.block, iv)
|
||||||
@@ -50,7 +50,7 @@ func (ec *EmailEncrypt) Encrypt(payload string) string {
|
|||||||
return base64.StdEncoding.EncodeToString(cipherText)
|
return base64.StdEncoding.EncodeToString(cipherText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *EmailEncrypt) Decrypt(data string) (string, error) {
|
func (ec *FieldEncrypt) Decrypt(data string) (string, error) {
|
||||||
cipherText, err := base64.StdEncoding.DecodeString(data)
|
cipherText, err := base64.StdEncoding.DecodeString(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ func TestGenerateKey(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to generate key: %s", err)
|
t.Fatalf("failed to generate key: %s", err)
|
||||||
}
|
}
|
||||||
ee, err := NewEmailEncrypt(key)
|
ee, err := NewFieldEncrypt(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to init email encryption: %s", err)
|
t.Fatalf("failed to init email encryption: %s", err)
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ func TestCorruptKey(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to generate key: %s", err)
|
t.Fatalf("failed to generate key: %s", err)
|
||||||
}
|
}
|
||||||
ee, err := NewEmailEncrypt(key)
|
ee, err := NewFieldEncrypt(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to init email encryption: %s", err)
|
t.Fatalf("failed to init email encryption: %s", err)
|
||||||
}
|
}
|
||||||
@@ -51,13 +51,13 @@ func TestCorruptKey(t *testing.T) {
|
|||||||
t.Fatalf("failed to generate key: %s", err)
|
t.Fatalf("failed to generate key: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ee, err = NewEmailEncrypt(newKey)
|
ee, err = NewFieldEncrypt(newKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to init email encryption: %s", err)
|
t.Fatalf("failed to init email encryption: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := ee.Decrypt(encrypted)
|
res, _ := ee.Decrypt(encrypted)
|
||||||
if err == nil || res == testData {
|
if res == testData {
|
||||||
t.Fatalf("incorrect decryption, the result is: %s", res)
|
t.Fatalf("incorrect decryption, the result is: %s", res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3" // sqlite driver
|
_ "github.com/mattn/go-sqlite3"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
@@ -25,16 +25,16 @@ const (
|
|||||||
"meta TEXT," +
|
"meta TEXT," +
|
||||||
" target_id TEXT);"
|
" target_id TEXT);"
|
||||||
|
|
||||||
creatTableAccountEmailQuery = `CREATE TABLE IF NOT EXISTS deleted_users (id TEXT NOT NULL, email TEXT NOT NULL);`
|
creatTableDeletedUsersQuery = `CREATE TABLE IF NOT EXISTS deleted_users (id TEXT NOT NULL, email TEXT NOT NULL, name TEXT);`
|
||||||
|
|
||||||
selectDescQuery = `SELECT events.id, activity, timestamp, initiator_id, i.email as "initiator_email", target_id, t.email as "target_email", account_id, meta
|
selectDescQuery = `SELECT events.id, activity, timestamp, initiator_id, i.name as "initiator_name", i.email as "initiator_email", target_id, t.name as "target_name", t.email as "target_email", account_id, meta
|
||||||
FROM events
|
FROM events
|
||||||
LEFT JOIN deleted_users i ON events.initiator_id = i.id
|
LEFT JOIN deleted_users i ON events.initiator_id = i.id
|
||||||
LEFT JOIN deleted_users t ON events.target_id = t.id
|
LEFT JOIN deleted_users t ON events.target_id = t.id
|
||||||
WHERE account_id = ?
|
WHERE account_id = ?
|
||||||
ORDER BY timestamp DESC LIMIT ? OFFSET ?;`
|
ORDER BY timestamp DESC LIMIT ? OFFSET ?;`
|
||||||
|
|
||||||
selectAscQuery = `SELECT events.id, activity, timestamp, initiator_id, i.email as "initiator_email", target_id, t.email as "target_email", account_id, meta
|
selectAscQuery = `SELECT events.id, activity, timestamp, initiator_id, i.name as "initiator_name", i.email as "initiator_email", target_id, t.name as "target_name", t.email as "target_email", account_id, meta
|
||||||
FROM events
|
FROM events
|
||||||
LEFT JOIN deleted_users i ON events.initiator_id = i.id
|
LEFT JOIN deleted_users i ON events.initiator_id = i.id
|
||||||
LEFT JOIN deleted_users t ON events.target_id = t.id
|
LEFT JOIN deleted_users t ON events.target_id = t.id
|
||||||
@@ -44,13 +44,16 @@ const (
|
|||||||
insertQuery = "INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id, meta) " +
|
insertQuery = "INSERT INTO events(activity, timestamp, initiator_id, target_id, account_id, meta) " +
|
||||||
"VALUES(?, ?, ?, ?, ?, ?)"
|
"VALUES(?, ?, ?, ?, ?, ?)"
|
||||||
|
|
||||||
insertDeleteUserQuery = `INSERT INTO deleted_users(id, email) VALUES(?, ?)`
|
insertDeleteUserQuery = `INSERT INTO deleted_users(id, email, name) VALUES(?, ?, ?)`
|
||||||
|
|
||||||
|
fallbackName = "unknown"
|
||||||
|
fallbackEmail = "unknown@unknown.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Store is the implementation of the activity.Store interface backed by SQLite
|
// Store is the implementation of the activity.Store interface backed by SQLite
|
||||||
type Store struct {
|
type Store struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
emailEncrypt *EmailEncrypt
|
fieldEncrypt *FieldEncrypt
|
||||||
|
|
||||||
insertStatement *sql.Stmt
|
insertStatement *sql.Stmt
|
||||||
selectAscStatement *sql.Stmt
|
selectAscStatement *sql.Stmt
|
||||||
@@ -66,65 +69,82 @@ func NewSQLiteStore(dataDir string, encryptionKey string) (*Store, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
crypt, err := NewEmailEncrypt(encryptionKey)
|
crypt, err := NewFieldEncrypt(encryptionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.Exec(createTableQuery)
|
_, err = db.Exec(createTableQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.Exec(creatTableAccountEmailQuery)
|
_, err = db.Exec(creatTableDeletedUsersQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = updateDeletedUsersTable(db)
|
||||||
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
insertStmt, err := db.Prepare(insertQuery)
|
insertStmt, err := db.Prepare(insertQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
selectDescStmt, err := db.Prepare(selectDescQuery)
|
selectDescStmt, err := db.Prepare(selectDescQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
selectAscStmt, err := db.Prepare(selectAscQuery)
|
selectAscStmt, err := db.Prepare(selectAscQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteUserStmt, err := db.Prepare(insertDeleteUserQuery)
|
deleteUserStmt, err := db.Prepare(insertDeleteUserQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = db.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &Store{
|
s := &Store{
|
||||||
db: db,
|
db: db,
|
||||||
emailEncrypt: crypt,
|
fieldEncrypt: crypt,
|
||||||
insertStatement: insertStmt,
|
insertStatement: insertStmt,
|
||||||
selectDescStatement: selectDescStmt,
|
selectDescStatement: selectDescStmt,
|
||||||
selectAscStatement: selectAscStmt,
|
selectAscStatement: selectAscStmt,
|
||||||
deleteUserStmt: deleteUserStmt,
|
deleteUserStmt: deleteUserStmt,
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *Store) processResult(result *sql.Rows) ([]*activity.Event, error) {
|
func (store *Store) processResult(result *sql.Rows) ([]*activity.Event, error) {
|
||||||
events := make([]*activity.Event, 0)
|
events := make([]*activity.Event, 0)
|
||||||
|
var cryptErr error
|
||||||
for result.Next() {
|
for result.Next() {
|
||||||
var id int64
|
var id int64
|
||||||
var operation activity.Activity
|
var operation activity.Activity
|
||||||
var timestamp time.Time
|
var timestamp time.Time
|
||||||
var initiator string
|
var initiator string
|
||||||
|
var initiatorName *string
|
||||||
var initiatorEmail *string
|
var initiatorEmail *string
|
||||||
var target string
|
var target string
|
||||||
|
var targetUserName *string
|
||||||
var targetEmail *string
|
var targetEmail *string
|
||||||
var account string
|
var account string
|
||||||
var jsonMeta string
|
var jsonMeta string
|
||||||
err := result.Scan(&id, &operation, ×tamp, &initiator, &initiatorEmail, &target, &targetEmail, &account, &jsonMeta)
|
err := result.Scan(&id, &operation, ×tamp, &initiator, &initiatorName, &initiatorEmail, &target, &targetUserName, &targetEmail, &account, &jsonMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -137,11 +157,21 @@ func (store *Store) processResult(result *sql.Rows) ([]*activity.Event, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if targetEmail != nil {
|
if targetUserName != nil {
|
||||||
email, err := store.emailEncrypt.Decrypt(*targetEmail)
|
name, err := store.fieldEncrypt.Decrypt(*targetUserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to decrypt email address for target id: %s", target)
|
cryptErr = fmt.Errorf("failed to decrypt username for target id: %s", target)
|
||||||
meta["email"] = ""
|
meta["username"] = fallbackName
|
||||||
|
} else {
|
||||||
|
meta["username"] = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetEmail != nil {
|
||||||
|
email, err := store.fieldEncrypt.Decrypt(*targetEmail)
|
||||||
|
if err != nil {
|
||||||
|
cryptErr = fmt.Errorf("failed to decrypt email address for target id: %s", target)
|
||||||
|
meta["email"] = fallbackEmail
|
||||||
} else {
|
} else {
|
||||||
meta["email"] = email
|
meta["email"] = email
|
||||||
}
|
}
|
||||||
@@ -157,10 +187,21 @@ func (store *Store) processResult(result *sql.Rows) ([]*activity.Event, error) {
|
|||||||
Meta: meta,
|
Meta: meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
if initiatorEmail != nil {
|
if initiatorName != nil {
|
||||||
email, err := store.emailEncrypt.Decrypt(*initiatorEmail)
|
name, err := store.fieldEncrypt.Decrypt(*initiatorName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to decrypt email address of initiator: %s", initiator)
|
cryptErr = fmt.Errorf("failed to decrypt username of initiator: %s", initiator)
|
||||||
|
event.InitiatorName = fallbackName
|
||||||
|
} else {
|
||||||
|
event.InitiatorName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if initiatorEmail != nil {
|
||||||
|
email, err := store.fieldEncrypt.Decrypt(*initiatorEmail)
|
||||||
|
if err != nil {
|
||||||
|
cryptErr = fmt.Errorf("failed to decrypt email address of initiator: %s", initiator)
|
||||||
|
event.InitiatorEmail = fallbackEmail
|
||||||
} else {
|
} else {
|
||||||
event.InitiatorEmail = email
|
event.InitiatorEmail = email
|
||||||
}
|
}
|
||||||
@@ -169,6 +210,10 @@ func (store *Store) processResult(result *sql.Rows) ([]*activity.Event, error) {
|
|||||||
events = append(events, event)
|
events = append(events, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cryptErr != nil {
|
||||||
|
log.Warnf("%s", cryptErr)
|
||||||
|
}
|
||||||
|
|
||||||
return events, nil
|
return events, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +236,7 @@ func (store *Store) Get(accountID string, offset, limit int, descending bool) ([
|
|||||||
// Save an event in the SQLite events table end encrypt the "email" element in meta map
|
// Save an event in the SQLite events table end encrypt the "email" element in meta map
|
||||||
func (store *Store) Save(event *activity.Event) (*activity.Event, error) {
|
func (store *Store) Save(event *activity.Event) (*activity.Event, error) {
|
||||||
var jsonMeta string
|
var jsonMeta string
|
||||||
meta, err := store.saveDeletedUserEmailInEncrypted(event)
|
meta, err := store.saveDeletedUserEmailAndNameInEncrypted(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -219,26 +264,31 @@ func (store *Store) Save(event *activity.Event) (*activity.Event, error) {
|
|||||||
return eventCopy, nil
|
return eventCopy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveDeletedUserEmailInEncrypted if the meta contains email then store it in encrypted way and delete this item from
|
// saveDeletedUserEmailAndNameInEncrypted if the meta contains email and name then store it in encrypted way and delete
|
||||||
// meta map
|
// this item from meta map
|
||||||
func (store *Store) saveDeletedUserEmailInEncrypted(event *activity.Event) (map[string]any, error) {
|
func (store *Store) saveDeletedUserEmailAndNameInEncrypted(event *activity.Event) (map[string]any, error) {
|
||||||
email, ok := event.Meta["email"]
|
email, ok := event.Meta["email"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return event.Meta, nil
|
return event.Meta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(event.Meta, "email")
|
name, ok := event.Meta["name"]
|
||||||
|
if !ok {
|
||||||
|
return event.Meta, nil
|
||||||
|
}
|
||||||
|
|
||||||
encrypted := store.emailEncrypt.Encrypt(fmt.Sprintf("%s", email))
|
encryptedEmail := store.fieldEncrypt.Encrypt(fmt.Sprintf("%s", email))
|
||||||
_, err := store.deleteUserStmt.Exec(event.TargetID, encrypted)
|
encryptedName := store.fieldEncrypt.Encrypt(fmt.Sprintf("%s", name))
|
||||||
|
_, err := store.deleteUserStmt.Exec(event.TargetID, encryptedEmail, encryptedName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(event.Meta) == 1 {
|
if len(event.Meta) == 2 {
|
||||||
return nil, nil // nolint
|
return nil, nil // nolint
|
||||||
}
|
}
|
||||||
delete(event.Meta, "email")
|
delete(event.Meta, "email")
|
||||||
|
delete(event.Meta, "name")
|
||||||
return event.Meta, nil
|
return event.Meta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,3 +299,44 @@ func (store *Store) Close() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateDeletedUsersTable(db *sql.DB) error {
|
||||||
|
log.Debugf("check deleted_users table version")
|
||||||
|
rows, err := db.Query(`PRAGMA table_info(deleted_users);`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
found := false
|
||||||
|
for rows.Next() {
|
||||||
|
var (
|
||||||
|
cid int
|
||||||
|
name string
|
||||||
|
dataType string
|
||||||
|
notNull int
|
||||||
|
dfltVal sql.NullString
|
||||||
|
pk int
|
||||||
|
)
|
||||||
|
err := rows.Scan(&cid, &name, &dataType, ¬Null, &dfltVal, &pk)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if name == "name" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rows.Err()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("update delted_users table")
|
||||||
|
_, err = db.Exec(`ALTER TABLE deleted_users ADD COLUMN name TEXT;`)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ type Config struct {
|
|||||||
DeviceAuthorizationFlow *DeviceAuthorizationFlow
|
DeviceAuthorizationFlow *DeviceAuthorizationFlow
|
||||||
|
|
||||||
PKCEAuthorizationFlow *PKCEAuthorizationFlow
|
PKCEAuthorizationFlow *PKCEAuthorizationFlow
|
||||||
|
|
||||||
|
StoreConfig StoreConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthAudiences returns the audience from the http config and device authorization flow config
|
// GetAuthAudiences returns the audience from the http config and device authorization flow config
|
||||||
@@ -136,6 +138,11 @@ type ProviderConfig struct {
|
|||||||
RedirectURLs []string
|
RedirectURLs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StoreConfig contains Store configuration
|
||||||
|
type StoreConfig struct {
|
||||||
|
Engine StoreEngine
|
||||||
|
}
|
||||||
|
|
||||||
// validateURL validates input http url
|
// validateURL validates input http url
|
||||||
func validateURL(httpURL string) bool {
|
func validateURL(httpURL string) bool {
|
||||||
_, err := url.ParseRequestURI(httpURL)
|
_, err := url.ParseRequestURI(httpURL)
|
||||||
|
|||||||
@@ -20,23 +20,15 @@ type lookupMap map[string]struct{}
|
|||||||
// DNSSettings defines dns settings at the account level
|
// DNSSettings defines dns settings at the account level
|
||||||
type DNSSettings struct {
|
type DNSSettings struct {
|
||||||
// DisabledManagementGroups groups whose DNS management is disabled
|
// DisabledManagementGroups groups whose DNS management is disabled
|
||||||
DisabledManagementGroups []string
|
DisabledManagementGroups []string `gorm:"serializer:json"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy returns a copy of the DNS settings
|
// Copy returns a copy of the DNS settings
|
||||||
func (d *DNSSettings) Copy() *DNSSettings {
|
func (d DNSSettings) Copy() DNSSettings {
|
||||||
settings := &DNSSettings{
|
settings := DNSSettings{
|
||||||
DisabledManagementGroups: make([]string, 0),
|
DisabledManagementGroups: make([]string, len(d.DisabledManagementGroups)),
|
||||||
}
|
}
|
||||||
|
copy(settings.DisabledManagementGroups, d.DisabledManagementGroups)
|
||||||
if d == nil {
|
|
||||||
return settings
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.DisabledManagementGroups != nil && len(d.DisabledManagementGroups) > 0 {
|
|
||||||
settings.DisabledManagementGroups = d.DisabledManagementGroups[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings
|
return settings
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,12 +50,8 @@ func (am *DefaultAccountManager) GetDNSSettings(accountID string, userID string)
|
|||||||
if !user.IsAdmin() {
|
if !user.IsAdmin() {
|
||||||
return nil, status.Errorf(status.PermissionDenied, "only admins are allowed to view DNS settings")
|
return nil, status.Errorf(status.PermissionDenied, "only admins are allowed to view DNS settings")
|
||||||
}
|
}
|
||||||
|
dnsSettings := account.DNSSettings.Copy()
|
||||||
if account.DNSSettings == nil {
|
return &dnsSettings, nil
|
||||||
return &DNSSettings{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return account.DNSSettings.Copy(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveDNSSettings validates a user role and updates the account's DNS settings
|
// SaveDNSSettings validates a user role and updates the account's DNS settings
|
||||||
@@ -96,11 +84,7 @@ func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
oldSettings := &DNSSettings{}
|
oldSettings := account.DNSSettings.Copy()
|
||||||
if account.DNSSettings != nil {
|
|
||||||
oldSettings = account.DNSSettings.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
account.DNSSettings = dnsSettingsToSave.Copy()
|
account.DNSSettings = dnsSettingsToSave.Copy()
|
||||||
|
|
||||||
account.Network.IncSerial()
|
account.Network.IncSerial()
|
||||||
@@ -122,7 +106,9 @@ func (am *DefaultAccountManager) SaveDNSSettings(accountID string, userID string
|
|||||||
am.storeEvent(userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta)
|
am.storeEvent(userID, accountID, accountID, activity.GroupRemovedFromDisabledManagementGroups, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
return am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig {
|
func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig {
|
||||||
@@ -146,6 +132,7 @@ func toProtocolDNSConfig(update nbdns.Config) *proto.DNSConfig {
|
|||||||
protoGroup := &proto.NameServerGroup{
|
protoGroup := &proto.NameServerGroup{
|
||||||
Primary: nsGroup.Primary,
|
Primary: nsGroup.Primary,
|
||||||
Domains: nsGroup.Domains,
|
Domains: nsGroup.Domains,
|
||||||
|
SearchDomainsEnabled: nsGroup.SearchDomainsEnabled,
|
||||||
}
|
}
|
||||||
for _, ns := range nsGroup.NameServers {
|
for _, ns := range nsGroup.NameServers {
|
||||||
protoNS := &proto.NameServer{
|
protoNS := &proto.NameServer{
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func TestGetDNSSettings(t *testing.T) {
|
|||||||
t.Fatal("DNS settings for new accounts shouldn't return nil")
|
t.Fatal("DNS settings for new accounts shouldn't return nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
account.DNSSettings = &DNSSettings{
|
account.DNSSettings = DNSSettings{
|
||||||
DisabledManagementGroups: []string{group1ID},
|
DisabledManagementGroups: []string{group1ID},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
|||||||
|
|
||||||
func createDNSStore(t *testing.T) (Store, error) {
|
func createDNSStore(t *testing.T) (Store, error) {
|
||||||
dataDir := t.TempDir()
|
dataDir := t.TempDir()
|
||||||
store, err := NewFileStore(dataDir, nil)
|
store, err := NewStoreFromJson(dataDir, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ func (e *EphemeralManager) cleanup() {
|
|||||||
|
|
||||||
for id, p := range deletePeers {
|
for id, p := range deletePeers {
|
||||||
log.Debugf("delete ephemeral peer: %s", id)
|
log.Debugf("delete ephemeral peer: %s", id)
|
||||||
_, err := e.accountManager.DeletePeer(p.account.Id, id, activity.SystemInitiator)
|
err := e.accountManager.DeletePeer(p.account.Id, id, activity.SystemInitiator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Tracef("failed to delete ephemeral peer: %s", err)
|
log.Tracef("failed to delete ephemeral peer: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ type MocAccountManager struct {
|
|||||||
store *MockStore
|
store *MockStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a MocAccountManager) DeletePeer(accountID, peerID, userID string) (*Peer, error) {
|
func (a MocAccountManager) DeletePeer(accountID, peerID, userID string) error {
|
||||||
delete(a.store.account.Peers, peerID)
|
delete(a.store.account.Peers, peerID)
|
||||||
return nil, nil //nolint:nilnil
|
return nil //nolint:nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewManager(t *testing.T) {
|
func TestNewManager(t *testing.T) {
|
||||||
|
|||||||
@@ -54,6 +54,25 @@ func NewFileStore(dataDir string, metrics telemetry.AppMetrics) (*FileStore, err
|
|||||||
return fs, nil
|
return fs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewFilestoreFromSqliteStore restores a store from Sqlite and stores to Filestore json in the file located in datadir
|
||||||
|
func NewFilestoreFromSqliteStore(sqlitestore *SqliteStore, dataDir string, metrics telemetry.AppMetrics) (*FileStore, error) {
|
||||||
|
store, err := NewFileStore(dataDir, metrics)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.SaveInstallationID(sqlitestore.GetInstallationID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, account := range sqlitestore.GetAllAccounts() {
|
||||||
|
store.Accounts[account.Id] = account
|
||||||
|
}
|
||||||
|
|
||||||
|
return store, store.persist(store.storeFile)
|
||||||
|
}
|
||||||
|
|
||||||
// restore the state of the store from the file.
|
// restore the state of the store from the file.
|
||||||
// Creates a new empty store file if doesn't exist
|
// Creates a new empty store file if doesn't exist
|
||||||
func restore(file string) (*FileStore, error) {
|
func restore(file string) (*FileStore, error) {
|
||||||
@@ -111,10 +130,6 @@ func restore(file string) (*FileStore, error) {
|
|||||||
for _, peer := range account.Peers {
|
for _, peer := range account.Peers {
|
||||||
store.PeerKeyID2AccountID[peer.Key] = accountID
|
store.PeerKeyID2AccountID[peer.Key] = accountID
|
||||||
store.PeerID2AccountID[peer.ID] = accountID
|
store.PeerID2AccountID[peer.ID] = accountID
|
||||||
// reset all peers to status = Disconnected
|
|
||||||
if peer.Status != nil && peer.Status.Connected {
|
|
||||||
peer.Status.Connected = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for _, user := range account.Users {
|
for _, user := range account.Users {
|
||||||
store.UserID2AccountID[user.Id] = accountID
|
store.UserID2AccountID[user.Id] = accountID
|
||||||
@@ -599,3 +614,8 @@ func (s *FileStore) Close() error {
|
|||||||
|
|
||||||
return s.persist(s.storeFile)
|
return s.persist(s.storeFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStoreEngine returns FileStoreEngine
|
||||||
|
func (s *FileStore) GetStoreEngine() StoreEngine {
|
||||||
|
return FileStoreEngine
|
||||||
|
}
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ func TestFileStore_GetAccount(t *testing.T) {
|
|||||||
assert.Equal(t, expected.DomainCategory, account.DomainCategory)
|
assert.Equal(t, expected.DomainCategory, account.DomainCategory)
|
||||||
assert.Equal(t, expected.Domain, account.Domain)
|
assert.Equal(t, expected.Domain, account.Domain)
|
||||||
assert.Equal(t, expected.CreatedBy, account.CreatedBy)
|
assert.Equal(t, expected.CreatedBy, account.CreatedBy)
|
||||||
assert.Equal(t, expected.Network.Id, account.Network.Id)
|
assert.Equal(t, expected.Network.Identifier, account.Network.Identifier)
|
||||||
assert.Len(t, account.Peers, len(expected.Peers))
|
assert.Len(t, account.Peers, len(expected.Peers))
|
||||||
assert.Len(t, account.Users, len(expected.Users))
|
assert.Len(t, account.Users, len(expected.Users))
|
||||||
assert.Len(t, account.SetupKeys, len(expected.SetupKeys))
|
assert.Len(t, account.SetupKeys, len(expected.SetupKeys))
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ type Group struct {
|
|||||||
// ID of the group
|
// ID of the group
|
||||||
ID string
|
ID string
|
||||||
|
|
||||||
|
// AccountID is a reference to Account that this object belongs
|
||||||
|
AccountID string `json:"-" gorm:"index"`
|
||||||
|
|
||||||
// Name visible in the UI
|
// Name visible in the UI
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
@@ -30,7 +33,7 @@ type Group struct {
|
|||||||
Issued string
|
Issued string
|
||||||
|
|
||||||
// Peers list of the group
|
// Peers list of the group
|
||||||
Peers []string
|
Peers []string `gorm:"serializer:json"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventMeta returns activity event meta related to the group
|
// EventMeta returns activity event meta related to the group
|
||||||
@@ -84,10 +87,7 @@ func (am *DefaultAccountManager) SaveGroup(accountID, userID string, newGroup *G
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// the following snippet tracks the activity and stores the group events in the event store.
|
// the following snippet tracks the activity and stores the group events in the event store.
|
||||||
// It has to happen after all the operations have been successfully performed.
|
// It has to happen after all the operations have been successfully performed.
|
||||||
@@ -229,7 +229,9 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string)
|
|||||||
|
|
||||||
am.storeEvent(userId, groupID, accountId, activity.GroupDeleted, g.EventMeta())
|
am.storeEvent(userId, groupID, accountId, activity.GroupDeleted, g.EventMeta())
|
||||||
|
|
||||||
return am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListGroups objects of the peers
|
// ListGroups objects of the peers
|
||||||
@@ -281,11 +283,13 @@ func (am *DefaultAccountManager) GroupAddPeer(accountID, groupID, peerID string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupDeletePeer removes peer from the group
|
// GroupDeletePeer removes peer from the group
|
||||||
func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey string) error {
|
func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerID string) error {
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
@@ -301,7 +305,7 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey str
|
|||||||
|
|
||||||
account.Network.IncSerial()
|
account.Network.IncSerial()
|
||||||
for i, itemID := range group.Peers {
|
for i, itemID := range group.Peers {
|
||||||
if itemID == peerKey {
|
if itemID == peerID {
|
||||||
group.Peers = append(group.Peers[:i], group.Peers[i+1:]...)
|
group.Peers = append(group.Peers[:i], group.Peers[i+1:]...)
|
||||||
if err := am.Store.SaveAccount(account); err != nil {
|
if err := am.Store.SaveAccount(account); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -309,31 +313,7 @@ func (am *DefaultAccountManager) GroupDeletePeer(accountID, groupID, peerKey str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
}
|
|
||||||
|
return nil
|
||||||
// GroupListPeers returns list of the peers from the group
|
|
||||||
func (am *DefaultAccountManager) GroupListPeers(accountID, groupID string) ([]*Peer, error) {
|
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
|
||||||
defer unlock()
|
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Errorf(status.NotFound, "account not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
group, ok := account.Groups[groupID]
|
|
||||||
if !ok {
|
|
||||||
return nil, status.Errorf(status.NotFound, "group with ID %s not found", groupID)
|
|
||||||
}
|
|
||||||
|
|
||||||
peers := make([]*Peer, 0, len(account.Groups))
|
|
||||||
for _, peerID := range group.Peers {
|
|
||||||
p, ok := account.Peers[peerID]
|
|
||||||
if ok {
|
|
||||||
peers = append(peers, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return peers, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
|||||||
|
|
||||||
groupForRoute := &Group{
|
groupForRoute := &Group{
|
||||||
"grp-for-route",
|
"grp-for-route",
|
||||||
|
"account-id",
|
||||||
"Group for route",
|
"Group for route",
|
||||||
GroupIssuedAPI,
|
GroupIssuedAPI,
|
||||||
make([]string, 0),
|
make([]string, 0),
|
||||||
@@ -87,6 +88,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
|||||||
|
|
||||||
groupForNameServerGroups := &Group{
|
groupForNameServerGroups := &Group{
|
||||||
"grp-for-name-server-grp",
|
"grp-for-name-server-grp",
|
||||||
|
"account-id",
|
||||||
"Group for name server groups",
|
"Group for name server groups",
|
||||||
GroupIssuedAPI,
|
GroupIssuedAPI,
|
||||||
make([]string, 0),
|
make([]string, 0),
|
||||||
@@ -94,6 +96,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
|||||||
|
|
||||||
groupForPolicies := &Group{
|
groupForPolicies := &Group{
|
||||||
"grp-for-policies",
|
"grp-for-policies",
|
||||||
|
"account-id",
|
||||||
"Group for policies",
|
"Group for policies",
|
||||||
GroupIssuedAPI,
|
GroupIssuedAPI,
|
||||||
make([]string, 0),
|
make([]string, 0),
|
||||||
@@ -101,6 +104,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
|||||||
|
|
||||||
groupForSetupKeys := &Group{
|
groupForSetupKeys := &Group{
|
||||||
"grp-for-keys",
|
"grp-for-keys",
|
||||||
|
"account-id",
|
||||||
"Group for setup keys",
|
"Group for setup keys",
|
||||||
GroupIssuedAPI,
|
GroupIssuedAPI,
|
||||||
make([]string, 0),
|
make([]string, 0),
|
||||||
@@ -108,6 +112,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
|
|||||||
|
|
||||||
groupForUsers := &Group{
|
groupForUsers := &Group{
|
||||||
"grp-for-users",
|
"grp-for-users",
|
||||||
|
"account-id",
|
||||||
"Group for users",
|
"Group for users",
|
||||||
GroupIssuedAPI,
|
GroupIssuedAPI,
|
||||||
make([]string, 0),
|
make([]string, 0),
|
||||||
|
|||||||
@@ -159,6 +159,11 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi
|
|||||||
select {
|
select {
|
||||||
// condition when there are some updates
|
// condition when there are some updates
|
||||||
case update, open := <-updates:
|
case update, open := <-updates:
|
||||||
|
|
||||||
|
if s.appMetrics != nil {
|
||||||
|
s.appMetrics.GRPCMetrics().UpdateChannelQueueLength(len(updates) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
if !open {
|
if !open {
|
||||||
log.Debugf("updates channel for peer %s was closed", peerKey.String())
|
log.Debugf("updates channel for peer %s was closed", peerKey.String())
|
||||||
s.cancelPeerRoutines(peer)
|
s.cancelPeerRoutines(peer)
|
||||||
|
|||||||
@@ -745,7 +745,13 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
example: true
|
example: true
|
||||||
peer:
|
peer:
|
||||||
description: Peer Identifier associated with route
|
description: Peer Identifier associated with route. This property can not be set together with `peer_groups`
|
||||||
|
type: string
|
||||||
|
example: chacbco6lnnbn6cg5s91
|
||||||
|
peer_groups:
|
||||||
|
description: Peers Group Identifier associated with route. This property can not be set together with `peer`
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
type: string
|
type: string
|
||||||
example: chacbco6lnnbn6cg5s91
|
example: chacbco6lnnbn6cg5s91
|
||||||
network:
|
network:
|
||||||
@@ -773,7 +779,9 @@ components:
|
|||||||
- description
|
- description
|
||||||
- network_id
|
- network_id
|
||||||
- enabled
|
- enabled
|
||||||
- peer
|
# Only one property has to be set
|
||||||
|
#- peer
|
||||||
|
#- peer_groups
|
||||||
- network
|
- network
|
||||||
- metric
|
- metric
|
||||||
- masquerade
|
- masquerade
|
||||||
@@ -849,13 +857,17 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
example: true
|
example: true
|
||||||
domains:
|
domains:
|
||||||
description: Nameserver group domain list
|
description: Nameserver group match domain list
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
maxLength: 255
|
maxLength: 255
|
||||||
example: "example.com"
|
example: "example.com"
|
||||||
|
search_domains_enabled:
|
||||||
|
description: Nameserver group search domain status for match domains. It should be true only if domains list is not empty.
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- description
|
- description
|
||||||
@@ -864,6 +876,7 @@ components:
|
|||||||
- groups
|
- groups
|
||||||
- primary
|
- primary
|
||||||
- domains
|
- domains
|
||||||
|
- search_domains_enabled
|
||||||
NameserverGroup:
|
NameserverGroup:
|
||||||
allOf:
|
allOf:
|
||||||
- type: object
|
- type: object
|
||||||
@@ -922,6 +935,10 @@ components:
|
|||||||
description: The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
|
description: The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
|
||||||
type: string
|
type: string
|
||||||
example: google-oauth2|123456789012345678901
|
example: google-oauth2|123456789012345678901
|
||||||
|
initiator_name:
|
||||||
|
description: The name of the initiator of the event.
|
||||||
|
type: string
|
||||||
|
example: John Doe
|
||||||
initiator_email:
|
initiator_email:
|
||||||
description: The e-mail address of the initiator of the event. E.g., an e-mail of a user that triggered the event.
|
description: The e-mail address of the initiator of the event. E.g., an e-mail of a user that triggered the event.
|
||||||
type: string
|
type: string
|
||||||
@@ -942,6 +959,7 @@ components:
|
|||||||
- activity
|
- activity
|
||||||
- activity_code
|
- activity_code
|
||||||
- initiator_id
|
- initiator_id
|
||||||
|
- initiator_name
|
||||||
- initiator_email
|
- initiator_email
|
||||||
- target_id
|
- target_id
|
||||||
- meta
|
- meta
|
||||||
@@ -1139,8 +1157,8 @@ paths:
|
|||||||
'500':
|
'500':
|
||||||
"$ref": "#/components/responses/internal_error"
|
"$ref": "#/components/responses/internal_error"
|
||||||
delete:
|
delete:
|
||||||
summary: Block a User
|
summary: Delete a User
|
||||||
description: This method blocks a user from accessing the system, but leaves the IDP user intact.
|
description: This method removes a user from accessing the system. For this leaves the IDP user intact unless the `--user-delete-from-idp` is passed to management startup.
|
||||||
tags: [ Users ]
|
tags: [ Users ]
|
||||||
security:
|
security:
|
||||||
- BearerAuth: [ ]
|
- BearerAuth: [ ]
|
||||||
|
|||||||
@@ -170,6 +170,9 @@ type Event struct {
|
|||||||
// InitiatorId The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
|
// InitiatorId The ID of the initiator of the event. E.g., an ID of a user that triggered the event.
|
||||||
InitiatorId string `json:"initiator_id"`
|
InitiatorId string `json:"initiator_id"`
|
||||||
|
|
||||||
|
// InitiatorName The name of the initiator of the event.
|
||||||
|
InitiatorName string `json:"initiator_name"`
|
||||||
|
|
||||||
// Meta The metadata of the event
|
// Meta The metadata of the event
|
||||||
Meta map[string]string `json:"meta"`
|
Meta map[string]string `json:"meta"`
|
||||||
|
|
||||||
@@ -245,7 +248,7 @@ type NameserverGroup struct {
|
|||||||
// Description Nameserver group description
|
// Description Nameserver group description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
|
||||||
// Domains Nameserver group domain list
|
// Domains Nameserver group match domain list
|
||||||
Domains []string `json:"domains"`
|
Domains []string `json:"domains"`
|
||||||
|
|
||||||
// Enabled Nameserver group status
|
// Enabled Nameserver group status
|
||||||
@@ -265,6 +268,9 @@ type NameserverGroup struct {
|
|||||||
|
|
||||||
// Primary Nameserver group primary status
|
// Primary Nameserver group primary status
|
||||||
Primary bool `json:"primary"`
|
Primary bool `json:"primary"`
|
||||||
|
|
||||||
|
// SearchDomainsEnabled Nameserver group search domain status for match domains. It should be true only if domains list is not empty.
|
||||||
|
SearchDomainsEnabled bool `json:"search_domains_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameserverGroupRequest defines model for NameserverGroupRequest.
|
// NameserverGroupRequest defines model for NameserverGroupRequest.
|
||||||
@@ -272,7 +278,7 @@ type NameserverGroupRequest struct {
|
|||||||
// Description Nameserver group description
|
// Description Nameserver group description
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
|
||||||
// Domains Nameserver group domain list
|
// Domains Nameserver group match domain list
|
||||||
Domains []string `json:"domains"`
|
Domains []string `json:"domains"`
|
||||||
|
|
||||||
// Enabled Nameserver group status
|
// Enabled Nameserver group status
|
||||||
@@ -289,6 +295,9 @@ type NameserverGroupRequest struct {
|
|||||||
|
|
||||||
// Primary Nameserver group primary status
|
// Primary Nameserver group primary status
|
||||||
Primary bool `json:"primary"`
|
Primary bool `json:"primary"`
|
||||||
|
|
||||||
|
// SearchDomainsEnabled Nameserver group search domain status for match domains. It should be true only if domains list is not empty.
|
||||||
|
SearchDomainsEnabled bool `json:"search_domains_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peer defines model for Peer.
|
// Peer defines model for Peer.
|
||||||
@@ -596,8 +605,11 @@ type Route struct {
|
|||||||
// NetworkType Network type indicating if it is IPv4 or IPv6
|
// NetworkType Network type indicating if it is IPv4 or IPv6
|
||||||
NetworkType string `json:"network_type"`
|
NetworkType string `json:"network_type"`
|
||||||
|
|
||||||
// Peer Peer Identifier associated with route
|
// Peer Peer Identifier associated with route. This property can not be set together with `peer_groups`
|
||||||
Peer string `json:"peer"`
|
Peer *string `json:"peer,omitempty"`
|
||||||
|
|
||||||
|
// PeerGroups Peers Group Identifier associated with route. This property can not be set together with `peer`
|
||||||
|
PeerGroups *[]string `json:"peer_groups,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RouteRequest defines model for RouteRequest.
|
// RouteRequest defines model for RouteRequest.
|
||||||
@@ -623,8 +635,11 @@ type RouteRequest struct {
|
|||||||
// NetworkId Route network identifier, to group HA routes
|
// NetworkId Route network identifier, to group HA routes
|
||||||
NetworkId string `json:"network_id"`
|
NetworkId string `json:"network_id"`
|
||||||
|
|
||||||
// Peer Peer Identifier associated with route
|
// Peer Peer Identifier associated with route. This property can not be set together with `peer_groups`
|
||||||
Peer string `json:"peer"`
|
Peer *string `json:"peer,omitempty"`
|
||||||
|
|
||||||
|
// PeerGroups Peers Group Identifier associated with route. This property can not be set together with `peer`
|
||||||
|
PeerGroups *[]string `json:"peer_groups,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule defines model for Rule.
|
// Rule defines model for Rule.
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const (
|
|||||||
testDNSSettingsUserID = "test_user"
|
testDNSSettingsUserID = "test_user"
|
||||||
)
|
)
|
||||||
|
|
||||||
var baseExistingDNSSettings = &server.DNSSettings{
|
var baseExistingDNSSettings = server.DNSSettings{
|
||||||
DisabledManagementGroups: []string{testDNSSettingsExistingGroup},
|
DisabledManagementGroups: []string{testDNSSettingsExistingGroup},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ func initDNSSettingsTestData() *DNSSettingsHandler {
|
|||||||
return &DNSSettingsHandler{
|
return &DNSSettingsHandler{
|
||||||
accountManager: &mock_server.MockAccountManager{
|
accountManager: &mock_server.MockAccountManager{
|
||||||
GetDNSSettingsFunc: func(accountID string, userID string) (*server.DNSSettings, error) {
|
GetDNSSettingsFunc: func(accountID string, userID string) (*server.DNSSettings, error) {
|
||||||
return testingDNSSettingsAccount.DNSSettings, nil
|
return &testingDNSSettingsAccount.DNSSettings, nil
|
||||||
},
|
},
|
||||||
SaveDNSSettingsFunc: func(accountID string, userID string, dnsSettingsToSave *server.DNSSettings) error {
|
SaveDNSSettingsFunc: func(accountID string, userID string, dnsSettingsToSave *server.DNSSettings) error {
|
||||||
if dnsSettingsToSave != nil {
|
if dnsSettingsToSave != nil {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func (h *EventsHandler) GetAllEvents(w http.ResponseWriter, r *http.Request) {
|
|||||||
events[i] = toEventResponse(e)
|
events[i] = toEventResponse(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.fillEventsWithInitiatorEmail(events, account.Id, user.Id)
|
err = h.fillEventsWithUserInfo(events, account.Id, user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteError(err, w)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
@@ -59,8 +59,8 @@ func (h *EventsHandler) GetAllEvents(w http.ResponseWriter, r *http.Request) {
|
|||||||
util.WriteJSONObject(w, events)
|
util.WriteJSONObject(w, events)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *EventsHandler) fillEventsWithInitiatorEmail(events []*api.Event, accountId, userId string) error {
|
func (h *EventsHandler) fillEventsWithUserInfo(events []*api.Event, accountId, userId string) error {
|
||||||
// build email map based on users
|
// build email, name maps based on users
|
||||||
userInfos, err := h.accountManager.GetUsersFromAccount(accountId, userId)
|
userInfos, err := h.accountManager.GetUsersFromAccount(accountId, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get users from account: %s", err)
|
log.Errorf("failed to get users from account: %s", err)
|
||||||
@@ -68,19 +68,39 @@ func (h *EventsHandler) fillEventsWithInitiatorEmail(events []*api.Event, accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
emails := make(map[string]string)
|
emails := make(map[string]string)
|
||||||
|
names := make(map[string]string)
|
||||||
for _, ui := range userInfos {
|
for _, ui := range userInfos {
|
||||||
emails[ui.ID] = ui.Email
|
emails[ui.ID] = ui.Email
|
||||||
|
names[ui.ID] = ui.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill event with email of initiator
|
|
||||||
var ok bool
|
var ok bool
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
|
// fill initiator
|
||||||
if event.InitiatorEmail == "" {
|
if event.InitiatorEmail == "" {
|
||||||
event.InitiatorEmail, ok = emails[event.InitiatorId]
|
event.InitiatorEmail, ok = emails[event.InitiatorId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Warnf("failed to resolve email for initiator: %s", event.InitiatorId)
|
log.Warnf("failed to resolve email for initiator: %s", event.InitiatorId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if event.InitiatorName == "" {
|
||||||
|
// here to allowed to be empty because in the first release we did not store the name
|
||||||
|
event.InitiatorName = names[event.InitiatorId]
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill target meta
|
||||||
|
email, ok := emails[event.TargetId]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event.Meta["email"] = email
|
||||||
|
|
||||||
|
username, ok := names[event.TargetId]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event.Meta["username"] = username
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -95,6 +115,7 @@ func toEventResponse(event *activity.Event) *api.Event {
|
|||||||
e := &api.Event{
|
e := &api.Event{
|
||||||
Id: fmt.Sprint(event.ID),
|
Id: fmt.Sprint(event.ID),
|
||||||
InitiatorId: event.InitiatorID,
|
InitiatorId: event.InitiatorID,
|
||||||
|
InitiatorName: event.InitiatorName,
|
||||||
InitiatorEmail: event.InitiatorEmail,
|
InitiatorEmail: event.InitiatorEmail,
|
||||||
Activity: event.Activity.Message(),
|
Activity: event.Activity.Message(),
|
||||||
ActivityCode: api.EventActivityCode(event.Activity.StringCode()),
|
ActivityCode: api.EventActivityCode(event.Activity.StringCode()),
|
||||||
|
|||||||
@@ -53,14 +53,6 @@ func initGroupTestData(user *server.User, groups ...*server.Group) *GroupsHandle
|
|||||||
Issued: server.GroupIssuedAPI,
|
Issued: server.GroupIssuedAPI,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
|
||||||
for _, peer := range TestPeers {
|
|
||||||
if peer.IP.String() == peerIP {
|
|
||||||
return peer, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("peer not found")
|
|
||||||
},
|
|
||||||
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
return &server.Account{
|
return &server.Account{
|
||||||
Id: claims.AccountId,
|
Id: claims.AccountId,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
|
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
s "github.com/netbirdio/netbird/management/server"
|
s "github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/http/middleware"
|
"github.com/netbirdio/netbird/management/server/http/middleware"
|
||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
@@ -58,6 +59,7 @@ func APIHandler(accountManager s.AccountManager, jwtValidator jwtclaims.JWTValid
|
|||||||
AuthCfg: authCfg,
|
AuthCfg: authCfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
integrations.RegisterHandlers(api.Router, accountManager)
|
||||||
api.addAccountsEndpoint()
|
api.addAccountsEndpoint()
|
||||||
api.addPeersEndpoint()
|
api.addPeersEndpoint()
|
||||||
api.addUsersEndpoint()
|
api.addUsersEndpoint()
|
||||||
@@ -73,8 +75,8 @@ func APIHandler(accountManager s.AccountManager, jwtValidator jwtclaims.JWTValid
|
|||||||
|
|
||||||
err := api.Router.Walk(func(route *mux.Route, _ *mux.Router, _ []*mux.Route) error {
|
err := api.Router.Walk(func(route *mux.Route, _ *mux.Router, _ []*mux.Route) error {
|
||||||
methods, err := route.GetMethods()
|
methods, err := route.GetMethods()
|
||||||
if err != nil {
|
if err != nil { // we may have wildcard routes from integrations without methods, skip them for now
|
||||||
return err
|
methods = []string{}
|
||||||
}
|
}
|
||||||
for _, method := range methods {
|
for _, method := range methods {
|
||||||
template, err := route.GetPathTemplate()
|
template, err := route.GetPathTemplate()
|
||||||
|
|||||||
@@ -57,10 +57,17 @@ func NewAuthMiddleware(getAccountFromPAT GetAccountFromPATFunc, validateAndParse
|
|||||||
func (m *AuthMiddleware) Handler(h http.Handler) http.Handler {
|
func (m *AuthMiddleware) Handler(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
auth := strings.Split(r.Header.Get("Authorization"), " ")
|
auth := strings.Split(r.Header.Get("Authorization"), " ")
|
||||||
authType := auth[0]
|
authType := strings.ToLower(auth[0])
|
||||||
switch strings.ToLower(authType) {
|
|
||||||
|
// fallback to token when receive pat as bearer
|
||||||
|
if len(auth) >= 2 && authType == "bearer" && strings.HasPrefix(auth[1], "nbp_") {
|
||||||
|
authType = "token"
|
||||||
|
auth[0] = authType
|
||||||
|
}
|
||||||
|
|
||||||
|
switch authType {
|
||||||
case "bearer":
|
case "bearer":
|
||||||
err := m.CheckJWTFromRequest(w, r)
|
err := m.checkJWTFromRequest(w, r, auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error when validating JWT claims: %s", err.Error())
|
log.Errorf("Error when validating JWT claims: %s", err.Error())
|
||||||
util.WriteError(status.Errorf(status.Unauthorized, "token invalid"), w)
|
util.WriteError(status.Errorf(status.Unauthorized, "token invalid"), w)
|
||||||
@@ -68,7 +75,7 @@ func (m *AuthMiddleware) Handler(h http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
h.ServeHTTP(w, r)
|
h.ServeHTTP(w, r)
|
||||||
case "token":
|
case "token":
|
||||||
err := m.CheckPATFromRequest(w, r)
|
err := m.checkPATFromRequest(w, r, auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Error when validating PAT claims: %s", err.Error())
|
log.Debugf("Error when validating PAT claims: %s", err.Error())
|
||||||
util.WriteError(status.Errorf(status.Unauthorized, "token invalid"), w)
|
util.WriteError(status.Errorf(status.Unauthorized, "token invalid"), w)
|
||||||
@@ -83,9 +90,8 @@ func (m *AuthMiddleware) Handler(h http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckJWTFromRequest checks if the JWT is valid
|
// CheckJWTFromRequest checks if the JWT is valid
|
||||||
func (m *AuthMiddleware) CheckJWTFromRequest(w http.ResponseWriter, r *http.Request) error {
|
func (m *AuthMiddleware) checkJWTFromRequest(w http.ResponseWriter, r *http.Request, auth []string) error {
|
||||||
|
token, err := getTokenFromJWTRequest(auth)
|
||||||
token, err := getTokenFromJWTRequest(r)
|
|
||||||
|
|
||||||
// If an error occurs, call the error handler and return an error
|
// If an error occurs, call the error handler and return an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -110,8 +116,8 @@ func (m *AuthMiddleware) CheckJWTFromRequest(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckPATFromRequest checks if the PAT is valid
|
// CheckPATFromRequest checks if the PAT is valid
|
||||||
func (m *AuthMiddleware) CheckPATFromRequest(w http.ResponseWriter, r *http.Request) error {
|
func (m *AuthMiddleware) checkPATFromRequest(w http.ResponseWriter, r *http.Request, auth []string) error {
|
||||||
token, err := getTokenFromPATRequest(r)
|
token, err := getTokenFromPATRequest(auth)
|
||||||
|
|
||||||
// If an error occurs, call the error handler and return an error
|
// If an error occurs, call the error handler and return an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -143,16 +149,9 @@ func (m *AuthMiddleware) CheckPATFromRequest(w http.ResponseWriter, r *http.Requ
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTokenFromJWTRequest is a "TokenExtractor" that takes a give request and extracts
|
// getTokenFromJWTRequest is a "TokenExtractor" that takes auth header parts and extracts
|
||||||
// the JWT token from the Authorization header.
|
// the JWT token from the Authorization header.
|
||||||
func getTokenFromJWTRequest(r *http.Request) (string, error) {
|
func getTokenFromJWTRequest(authHeaderParts []string) (string, error) {
|
||||||
authHeader := r.Header.Get("Authorization")
|
|
||||||
if authHeader == "" {
|
|
||||||
return "", nil // No error, just no token
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make this a bit more robust, parsing-wise
|
|
||||||
authHeaderParts := strings.Fields(authHeader)
|
|
||||||
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
|
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
|
||||||
return "", errors.New("Authorization header format must be Bearer {token}")
|
return "", errors.New("Authorization header format must be Bearer {token}")
|
||||||
}
|
}
|
||||||
@@ -160,16 +159,9 @@ func getTokenFromJWTRequest(r *http.Request) (string, error) {
|
|||||||
return authHeaderParts[1], nil
|
return authHeaderParts[1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTokenFromPATRequest is a "TokenExtractor" that takes a give request and extracts
|
// getTokenFromPATRequest is a "TokenExtractor" that takes auth header parts and extracts
|
||||||
// the PAT token from the Authorization header.
|
// the PAT token from the Authorization header.
|
||||||
func getTokenFromPATRequest(r *http.Request) (string, error) {
|
func getTokenFromPATRequest(authHeaderParts []string) (string, error) {
|
||||||
authHeader := r.Header.Get("Authorization")
|
|
||||||
if authHeader == "" {
|
|
||||||
return "", nil // No error, just no token
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make this a bit more robust, parsing-wise
|
|
||||||
authHeaderParts := strings.Fields(authHeader)
|
|
||||||
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "token" {
|
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "token" {
|
||||||
return "", errors.New("Authorization header format must be Token {token}")
|
return "", errors.New("Authorization header format must be Token {token}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const (
|
|||||||
domain = "domain"
|
domain = "domain"
|
||||||
userID = "userID"
|
userID = "userID"
|
||||||
tokenID = "tokenID"
|
tokenID = "tokenID"
|
||||||
PAT = "PAT"
|
PAT = "nbp_PAT"
|
||||||
JWT = "JWT"
|
JWT = "JWT"
|
||||||
wrongToken = "wrongToken"
|
wrongToken = "wrongToken"
|
||||||
)
|
)
|
||||||
@@ -82,6 +82,11 @@ func TestAuthMiddleware_Handler(t *testing.T) {
|
|||||||
authHeader: "Token " + wrongToken,
|
authHeader: "Token " + wrongToken,
|
||||||
expectedStatusCode: 401,
|
expectedStatusCode: 401,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Fallback to PAT Token",
|
||||||
|
authHeader: "Bearer " + PAT,
|
||||||
|
expectedStatusCode: 200,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Valid JWT Token",
|
name: "Valid JWT Token",
|
||||||
authHeader: "Bearer " + JWT,
|
authHeader: "Bearer " + JWT,
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func (h *NameserversHandler) CreateNameserverGroup(w http.ResponseWriter, r *htt
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nsGroup, err := h.accountManager.CreateNameServerGroup(account.Id, req.Name, req.Description, nsList, req.Groups, req.Primary, req.Domains, req.Enabled, user.Id)
|
nsGroup, err := h.accountManager.CreateNameServerGroup(account.Id, req.Name, req.Description, nsList, req.Groups, req.Primary, req.Domains, req.Enabled, user.Id, req.SearchDomainsEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteError(err, w)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
@@ -127,6 +127,7 @@ func (h *NameserversHandler) UpdateNameserverGroup(w http.ResponseWriter, r *htt
|
|||||||
NameServers: nsList,
|
NameServers: nsList,
|
||||||
Groups: req.Groups,
|
Groups: req.Groups,
|
||||||
Enabled: req.Enabled,
|
Enabled: req.Enabled,
|
||||||
|
SearchDomainsEnabled: req.SearchDomainsEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.accountManager.SaveNameServerGroup(account.Id, user.Id, updatedNSGroup)
|
err = h.accountManager.SaveNameServerGroup(account.Id, user.Id, updatedNSGroup)
|
||||||
@@ -224,5 +225,6 @@ func toNameserverGroupResponse(serverNSGroup *nbdns.NameServerGroup) *api.Namese
|
|||||||
Groups: serverNSGroup.Groups,
|
Groups: serverNSGroup.Groups,
|
||||||
Nameservers: nsList,
|
Nameservers: nsList,
|
||||||
Enabled: serverNSGroup.Enabled,
|
Enabled: serverNSGroup.Enabled,
|
||||||
|
SearchDomainsEnabled: serverNSGroup.SearchDomainsEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ func initNameserversTestData() *NameserversHandler {
|
|||||||
}
|
}
|
||||||
return nil, status.Errorf(status.NotFound, "nameserver group with ID %s not found", nsGroupID)
|
return nil, status.Errorf(status.NotFound, "nameserver group with ID %s not found", nsGroupID)
|
||||||
},
|
},
|
||||||
CreateNameServerGroupFunc: func(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, _ string) (*nbdns.NameServerGroup, error) {
|
CreateNameServerGroupFunc: func(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, _ string, searchDomains bool) (*nbdns.NameServerGroup, error) {
|
||||||
return &nbdns.NameServerGroup{
|
return &nbdns.NameServerGroup{
|
||||||
ID: existingNSGroupID,
|
ID: existingNSGroupID,
|
||||||
Name: name,
|
Name: name,
|
||||||
@@ -77,6 +77,7 @@ func initNameserversTestData() *NameserversHandler {
|
|||||||
Enabled: enabled,
|
Enabled: enabled,
|
||||||
Primary: primary,
|
Primary: primary,
|
||||||
Domains: domains,
|
Domains: domains,
|
||||||
|
SearchDomainsEnabled: searchDomains,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
DeleteNameServerGroupFunc: func(accountID, nsGroupID, _ string) error {
|
DeleteNameServerGroupFunc: func(accountID, nsGroupID, _ string) error {
|
||||||
|
|||||||
@@ -31,6 +31,24 @@ func NewPeersHandler(accountManager server.AccountManager, authCfg AuthCfg) *Pee
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *PeersHandler) checkPeerStatus(peer *server.Peer) (*server.Peer, error) {
|
||||||
|
peerToReturn := peer.Copy()
|
||||||
|
if peer.Status.Connected {
|
||||||
|
statuses, err := h.accountManager.GetAllConnectedPeers()
|
||||||
|
if err != nil {
|
||||||
|
return peerToReturn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Although we have online status in store we do not yet have an updated channel so have to show it as disconnected
|
||||||
|
// This may happen after server restart when not all peers are yet connected
|
||||||
|
if _, connected := statuses[peerToReturn.ID]; !connected {
|
||||||
|
peerToReturn.Status.Connected = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return peerToReturn, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *PeersHandler) getPeer(account *server.Account, peerID, userID string, w http.ResponseWriter) {
|
func (h *PeersHandler) getPeer(account *server.Account, peerID, userID string, w http.ResponseWriter) {
|
||||||
peer, err := h.accountManager.GetPeer(account.Id, peerID, userID)
|
peer, err := h.accountManager.GetPeer(account.Id, peerID, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -38,7 +56,13 @@ func (h *PeersHandler) getPeer(account *server.Account, peerID, userID string, w
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
util.WriteJSONObject(w, toPeerResponse(peer, account, h.accountManager.GetDNSDomain()))
|
peerToReturn, err := h.checkPeerStatus(peer)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.WriteJSONObject(w, toPeerResponse(peerToReturn, account, h.accountManager.GetDNSDomain()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, peerID string, w http.ResponseWriter, r *http.Request) {
|
func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, peerID string, w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -61,7 +85,7 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *PeersHandler) deletePeer(accountID, userID string, peerID string, w http.ResponseWriter) {
|
func (h *PeersHandler) deletePeer(accountID, userID string, peerID string, w http.ResponseWriter) {
|
||||||
_, err := h.accountManager.DeletePeer(accountID, peerID, userID)
|
err := h.accountManager.DeletePeer(accountID, peerID, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteError(err, w)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
@@ -120,7 +144,12 @@ func (h *PeersHandler) GetAllPeers(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
respBody := []*api.Peer{}
|
respBody := []*api.Peer{}
|
||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
respBody = append(respBody, toPeerResponse(peer, account, dnsDomain))
|
peerToReturn, err := h.checkPeerStatus(peer)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respBody = append(respBody, toPeerResponse(peerToReturn, account, dnsDomain))
|
||||||
}
|
}
|
||||||
util.WriteJSONObject(w, respBody)
|
util.WriteJSONObject(w, respBody)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package http
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -23,19 +24,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const testPeerID = "test_peer"
|
const testPeerID = "test_peer"
|
||||||
|
const noUpdateChannelTestPeerID = "no-update-channel"
|
||||||
|
|
||||||
func initTestMetaData(peers ...*server.Peer) *PeersHandler {
|
func initTestMetaData(peers ...*server.Peer) *PeersHandler {
|
||||||
return &PeersHandler{
|
return &PeersHandler{
|
||||||
accountManager: &mock_server.MockAccountManager{
|
accountManager: &mock_server.MockAccountManager{
|
||||||
UpdatePeerFunc: func(accountID, userID string, update *server.Peer) (*server.Peer, error) {
|
UpdatePeerFunc: func(accountID, userID string, update *server.Peer) (*server.Peer, error) {
|
||||||
p := peers[0].Copy()
|
var p *server.Peer
|
||||||
|
for _, peer := range peers {
|
||||||
|
if update.ID == peer.ID {
|
||||||
|
p = peer.Copy()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
p.SSHEnabled = update.SSHEnabled
|
p.SSHEnabled = update.SSHEnabled
|
||||||
p.LoginExpirationEnabled = update.LoginExpirationEnabled
|
p.LoginExpirationEnabled = update.LoginExpirationEnabled
|
||||||
p.Name = update.Name
|
p.Name = update.Name
|
||||||
return p, nil
|
return p, nil
|
||||||
},
|
},
|
||||||
GetPeerFunc: func(accountID, peerID, userID string) (*server.Peer, error) {
|
GetPeerFunc: func(accountID, peerID, userID string) (*server.Peer, error) {
|
||||||
return peers[0], nil
|
var p *server.Peer
|
||||||
|
for _, peer := range peers {
|
||||||
|
if peerID == peer.ID {
|
||||||
|
p = peer.Copy()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
},
|
},
|
||||||
GetPeersFunc: func(accountID, userID string) ([]*server.Peer, error) {
|
GetPeersFunc: func(accountID, userID string) ([]*server.Peer, error) {
|
||||||
return peers, nil
|
return peers, nil
|
||||||
@@ -57,6 +72,16 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler {
|
|||||||
},
|
},
|
||||||
}, user, nil
|
}, user, nil
|
||||||
},
|
},
|
||||||
|
GetAllConnectedPeersFunc: func() (map[string]struct{}, error) {
|
||||||
|
statuses := make(map[string]struct{})
|
||||||
|
for _, peer := range peers {
|
||||||
|
if peer.ID == noUpdateChannelTestPeerID {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
statuses[peer.ID] = struct{}{}
|
||||||
|
}
|
||||||
|
return statuses, nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
claimsExtractor: jwtclaims.NewClaimsExtractor(
|
||||||
jwtclaims.WithFromRequestContext(func(r *http.Request) jwtclaims.AuthorizationClaims {
|
jwtclaims.WithFromRequestContext(func(r *http.Request) jwtclaims.AuthorizationClaims {
|
||||||
@@ -79,7 +104,7 @@ func TestGetPeers(t *testing.T) {
|
|||||||
Key: "key",
|
Key: "key",
|
||||||
SetupKey: "setupkey",
|
SetupKey: "setupkey",
|
||||||
IP: net.ParseIP("100.64.0.1"),
|
IP: net.ParseIP("100.64.0.1"),
|
||||||
Status: &server.PeerStatus{},
|
Status: &server.PeerStatus{Connected: true},
|
||||||
Name: "PeerName",
|
Name: "PeerName",
|
||||||
LoginExpirationEnabled: false,
|
LoginExpirationEnabled: false,
|
||||||
Meta: server.PeerSystemMeta{
|
Meta: server.PeerSystemMeta{
|
||||||
@@ -93,11 +118,17 @@ func TestGetPeers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
peer1 := peer.Copy()
|
||||||
|
peer1.ID = noUpdateChannelTestPeerID
|
||||||
|
|
||||||
expectedUpdatedPeer := peer.Copy()
|
expectedUpdatedPeer := peer.Copy()
|
||||||
expectedUpdatedPeer.LoginExpirationEnabled = true
|
expectedUpdatedPeer.LoginExpirationEnabled = true
|
||||||
expectedUpdatedPeer.SSHEnabled = true
|
expectedUpdatedPeer.SSHEnabled = true
|
||||||
expectedUpdatedPeer.Name = "New Name"
|
expectedUpdatedPeer.Name = "New Name"
|
||||||
|
|
||||||
|
expectedPeer1 := peer1.Copy()
|
||||||
|
expectedPeer1.Status.Connected = false
|
||||||
|
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
name string
|
name string
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
@@ -116,13 +147,21 @@ func TestGetPeers(t *testing.T) {
|
|||||||
expectedPeer: peer,
|
expectedPeer: peer,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GetPeer",
|
name: "GetPeer with update channel",
|
||||||
requestType: http.MethodGet,
|
requestType: http.MethodGet,
|
||||||
requestPath: "/api/peers/" + testPeerID,
|
requestPath: "/api/peers/" + testPeerID,
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
expectedArray: false,
|
expectedArray: false,
|
||||||
expectedPeer: peer,
|
expectedPeer: peer,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "GetPeer with no update channel",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/peers/" + peer1.ID,
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedArray: false,
|
||||||
|
expectedPeer: expectedPeer1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "PutPeer",
|
name: "PutPeer",
|
||||||
requestType: http.MethodPut,
|
requestType: http.MethodPut,
|
||||||
@@ -136,7 +175,7 @@ func TestGetPeers(t *testing.T) {
|
|||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
p := initTestMetaData(peer)
|
p := initTestMetaData(peer, peer1)
|
||||||
|
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
@@ -171,6 +210,10 @@ func TestGetPeers(t *testing.T) {
|
|||||||
t.Fatalf("Sent content is not in correct json format; %v", err)
|
t.Fatalf("Sent content is not in correct json format; %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hardcode this check for now as we only have two peers in this suite
|
||||||
|
assert.Equal(t, len(respBody), 2)
|
||||||
|
assert.Equal(t, respBody[1].Connected, false)
|
||||||
|
|
||||||
got = respBody[0]
|
got = respBody[0]
|
||||||
} else {
|
} else {
|
||||||
got = &api.Peer{}
|
got = &api.Peer{}
|
||||||
@@ -180,12 +223,15 @@ func TestGetPeers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println(got)
|
||||||
|
|
||||||
assert.Equal(t, got.Name, tc.expectedPeer.Name)
|
assert.Equal(t, got.Name, tc.expectedPeer.Name)
|
||||||
assert.Equal(t, got.Version, tc.expectedPeer.Meta.WtVersion)
|
assert.Equal(t, got.Version, tc.expectedPeer.Meta.WtVersion)
|
||||||
assert.Equal(t, got.Ip, tc.expectedPeer.IP.String())
|
assert.Equal(t, got.Ip, tc.expectedPeer.IP.String())
|
||||||
assert.Equal(t, got.Os, "OS core")
|
assert.Equal(t, got.Os, "OS core")
|
||||||
assert.Equal(t, got.LoginExpirationEnabled, tc.expectedPeer.LoginExpirationEnabled)
|
assert.Equal(t, got.LoginExpirationEnabled, tc.expectedPeer.LoginExpirationEnabled)
|
||||||
assert.Equal(t, got.SshEnabled, tc.expectedPeer.SSHEnabled)
|
assert.Equal(t, got.SshEnabled, tc.expectedPeer.SSHEnabled)
|
||||||
|
assert.Equal(t, got.Connected, tc.expectedPeer.Status.Connected)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,33 @@ func (h *RoutesHandler) CreateRoute(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newRoute, err := h.accountManager.CreateRoute(account.Id, newPrefix.String(), req.Peer, req.Description, req.NetworkId, req.Masquerade, req.Metric, req.Groups, req.Enabled, user.Id)
|
peerId := ""
|
||||||
|
if req.Peer != nil {
|
||||||
|
peerId = *req.Peer
|
||||||
|
}
|
||||||
|
|
||||||
|
peerGroupIds := []string{}
|
||||||
|
if req.PeerGroups != nil {
|
||||||
|
peerGroupIds = *req.PeerGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peerId != "" && len(peerGroupIds) > 0) || (peerId == "" && len(peerGroupIds) == 0) {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "only one peer or peer_groups should be provided"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not allow non Linux peers
|
||||||
|
if peer := account.GetPeer(peerId); peer != nil {
|
||||||
|
if peer.Meta.GoOS != "linux" {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "non-linux peers are non supported as network routes"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newRoute, err := h.accountManager.CreateRoute(
|
||||||
|
account.Id, newPrefix.String(), peerId, peerGroupIds,
|
||||||
|
req.Description, req.NetworkId, req.Masquerade, req.Metric, req.Groups, req.Enabled, user.Id,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteError(err, w)
|
util.WriteError(err, w)
|
||||||
return
|
return
|
||||||
@@ -135,19 +161,49 @@ func (h *RoutesHandler) UpdateRoute(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.Peer != nil && req.PeerGroups != nil {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "only peer or peers_group should be provided"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Peer == nil && req.PeerGroups == nil {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "either peer or peers_group should be provided"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
peerID := ""
|
||||||
|
if req.Peer != nil {
|
||||||
|
peerID = *req.Peer
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not allow non Linux peers
|
||||||
|
if peer := account.GetPeer(peerID); peer != nil {
|
||||||
|
if peer.Meta.GoOS != "linux" {
|
||||||
|
util.WriteError(status.Errorf(status.InvalidArgument, "non-linux peers are non supported as network routes"), w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newRoute := &route.Route{
|
newRoute := &route.Route{
|
||||||
ID: routeID,
|
ID: routeID,
|
||||||
Network: newPrefix,
|
Network: newPrefix,
|
||||||
NetID: req.NetworkId,
|
NetID: req.NetworkId,
|
||||||
NetworkType: prefixType,
|
NetworkType: prefixType,
|
||||||
Masquerade: req.Masquerade,
|
Masquerade: req.Masquerade,
|
||||||
Peer: req.Peer,
|
|
||||||
Metric: req.Metric,
|
Metric: req.Metric,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
Enabled: req.Enabled,
|
Enabled: req.Enabled,
|
||||||
Groups: req.Groups,
|
Groups: req.Groups,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.Peer != nil {
|
||||||
|
newRoute.Peer = peerID
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.PeerGroups != nil {
|
||||||
|
newRoute.PeerGroups = *req.PeerGroups
|
||||||
|
}
|
||||||
|
|
||||||
err = h.accountManager.SaveRoute(account.Id, user.Id, newRoute)
|
err = h.accountManager.SaveRoute(account.Id, user.Id, newRoute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteError(err, w)
|
util.WriteError(err, w)
|
||||||
@@ -208,16 +264,21 @@ func (h *RoutesHandler) GetRoute(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func toRouteResponse(serverRoute *route.Route) *api.Route {
|
func toRouteResponse(serverRoute *route.Route) *api.Route {
|
||||||
return &api.Route{
|
route := &api.Route{
|
||||||
Id: serverRoute.ID,
|
Id: serverRoute.ID,
|
||||||
Description: serverRoute.Description,
|
Description: serverRoute.Description,
|
||||||
NetworkId: serverRoute.NetID,
|
NetworkId: serverRoute.NetID,
|
||||||
Enabled: serverRoute.Enabled,
|
Enabled: serverRoute.Enabled,
|
||||||
Peer: serverRoute.Peer,
|
Peer: &serverRoute.Peer,
|
||||||
Network: serverRoute.Network.String(),
|
Network: serverRoute.Network.String(),
|
||||||
NetworkType: serverRoute.NetworkType.String(),
|
NetworkType: serverRoute.NetworkType.String(),
|
||||||
Masquerade: serverRoute.Masquerade,
|
Masquerade: serverRoute.Masquerade,
|
||||||
Metric: serverRoute.Metric,
|
Metric: serverRoute.Metric,
|
||||||
Groups: serverRoute.Groups,
|
Groups: serverRoute.Groups,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(serverRoute.PeerGroups) > 0 {
|
||||||
|
route.PeerGroups = &serverRoute.PeerGroups
|
||||||
|
}
|
||||||
|
return route
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,15 +24,22 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
existingRouteID = "existingRouteID"
|
existingRouteID = "existingRouteID"
|
||||||
|
existingRouteID2 = "existingRouteID2" // for peer_groups test
|
||||||
notFoundRouteID = "notFoundRouteID"
|
notFoundRouteID = "notFoundRouteID"
|
||||||
existingPeerIP = "100.64.0.100"
|
existingPeerIP1 = "100.64.0.100"
|
||||||
existingPeerID = "peer-id"
|
existingPeerIP2 = "100.64.0.101"
|
||||||
notFoundPeerID = "nonExistingPeer"
|
notFoundPeerID = "nonExistingPeer"
|
||||||
existingPeerKey = "existingPeerKey"
|
existingPeerKey = "existingPeerKey"
|
||||||
|
nonLinuxExistingPeerKey = "darwinExistingPeerKey"
|
||||||
testAccountID = "test_id"
|
testAccountID = "test_id"
|
||||||
existingGroupID = "testGroup"
|
existingGroupID = "testGroup"
|
||||||
|
notFoundGroupID = "nonExistingGroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var emptyString = ""
|
||||||
|
var existingPeerID = "peer-id"
|
||||||
|
var nonLinuxExistingPeerID = "darwin-peer-id"
|
||||||
|
|
||||||
var baseExistingRoute = &route.Route{
|
var baseExistingRoute = &route.Route{
|
||||||
ID: existingRouteID,
|
ID: existingRouteID,
|
||||||
Description: "base route",
|
Description: "base route",
|
||||||
@@ -51,8 +58,19 @@ var testingAccount = &server.Account{
|
|||||||
Peers: map[string]*server.Peer{
|
Peers: map[string]*server.Peer{
|
||||||
existingPeerID: {
|
existingPeerID: {
|
||||||
Key: existingPeerKey,
|
Key: existingPeerKey,
|
||||||
IP: netip.MustParseAddr(existingPeerIP).AsSlice(),
|
IP: netip.MustParseAddr(existingPeerIP1).AsSlice(),
|
||||||
ID: existingPeerID,
|
ID: existingPeerID,
|
||||||
|
Meta: server.PeerSystemMeta{
|
||||||
|
GoOS: "linux",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nonLinuxExistingPeerID: {
|
||||||
|
Key: nonLinuxExistingPeerID,
|
||||||
|
IP: netip.MustParseAddr(existingPeerIP2).AsSlice(),
|
||||||
|
ID: nonLinuxExistingPeerID,
|
||||||
|
Meta: server.PeerSystemMeta{
|
||||||
|
GoOS: "darwin",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Users: map[string]*server.User{
|
Users: map[string]*server.User{
|
||||||
@@ -67,17 +85,26 @@ func initRoutesTestData() *RoutesHandler {
|
|||||||
if routeID == existingRouteID {
|
if routeID == existingRouteID {
|
||||||
return baseExistingRoute, nil
|
return baseExistingRoute, nil
|
||||||
}
|
}
|
||||||
|
if routeID == existingRouteID2 {
|
||||||
|
route := baseExistingRoute.Copy()
|
||||||
|
route.PeerGroups = []string{existingGroupID}
|
||||||
|
return route, nil
|
||||||
|
}
|
||||||
return nil, status.Errorf(status.NotFound, "route with ID %s not found", routeID)
|
return nil, status.Errorf(status.NotFound, "route with ID %s not found", routeID)
|
||||||
},
|
},
|
||||||
CreateRouteFunc: func(accountID string, network, peerID, description, netID string, masquerade bool, metric int, groups []string, enabled bool, _ string) (*route.Route, error) {
|
CreateRouteFunc: func(accountID, network, peerID string, peerGroups []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, _ string) (*route.Route, error) {
|
||||||
if peerID == notFoundPeerID {
|
if peerID == notFoundPeerID {
|
||||||
return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
||||||
}
|
}
|
||||||
|
if len(peerGroups) > 0 && peerGroups[0] == notFoundGroupID {
|
||||||
|
return nil, status.Errorf(status.InvalidArgument, "peer groups with ID %s not found", peerGroups[0])
|
||||||
|
}
|
||||||
networkType, p, _ := route.ParseNetwork(network)
|
networkType, p, _ := route.ParseNetwork(network)
|
||||||
return &route.Route{
|
return &route.Route{
|
||||||
ID: existingRouteID,
|
ID: existingRouteID,
|
||||||
NetID: netID,
|
NetID: netID,
|
||||||
Peer: peerID,
|
Peer: peerID,
|
||||||
|
PeerGroups: peerGroups,
|
||||||
Network: p,
|
Network: p,
|
||||||
NetworkType: networkType,
|
NetworkType: networkType,
|
||||||
Description: description,
|
Description: description,
|
||||||
@@ -98,15 +125,6 @@ func initRoutesTestData() *RoutesHandler {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
GetPeerByIPFunc: func(_ string, peerIP string) (*server.Peer, error) {
|
|
||||||
if peerIP != existingPeerID {
|
|
||||||
return nil, status.Errorf(status.NotFound, "Peer with ID %s not found", peerIP)
|
|
||||||
}
|
|
||||||
return &server.Peer{
|
|
||||||
Key: existingPeerKey,
|
|
||||||
IP: netip.MustParseAddr(existingPeerID).AsSlice(),
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
GetAccountFromTokenFunc: func(_ jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
|
||||||
return testingAccount, testingAccount.Users["test_user"], nil
|
return testingAccount, testingAccount.Users["test_user"], nil
|
||||||
},
|
},
|
||||||
@@ -124,6 +142,9 @@ func initRoutesTestData() *RoutesHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRoutesHandlers(t *testing.T) {
|
func TestRoutesHandlers(t *testing.T) {
|
||||||
|
baseExistingRouteWithPeerGroups := baseExistingRoute.Copy()
|
||||||
|
baseExistingRouteWithPeerGroups.PeerGroups = []string{existingGroupID}
|
||||||
|
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
name string
|
name string
|
||||||
expectedStatus int
|
expectedStatus int
|
||||||
@@ -147,6 +168,14 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
requestPath: "/api/routes/" + notFoundRouteID,
|
requestPath: "/api/routes/" + notFoundRouteID,
|
||||||
expectedStatus: http.StatusNotFound,
|
expectedStatus: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Get Existing Route with Peer Groups",
|
||||||
|
requestType: http.MethodGet,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID2,
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedRoute: toRouteResponse(baseExistingRouteWithPeerGroups),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Delete Existing Route",
|
name: "Delete Existing Route",
|
||||||
requestType: http.MethodDelete,
|
requestType: http.MethodDelete,
|
||||||
@@ -173,13 +202,21 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
Description: "Post",
|
Description: "Post",
|
||||||
NetworkId: "awesomeNet",
|
NetworkId: "awesomeNet",
|
||||||
Network: "192.168.0.0/16",
|
Network: "192.168.0.0/16",
|
||||||
Peer: existingPeerID,
|
Peer: &existingPeerID,
|
||||||
NetworkType: route.IPv4NetworkString,
|
NetworkType: route.IPv4NetworkString,
|
||||||
Masquerade: false,
|
Masquerade: false,
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
Groups: []string{existingGroupID},
|
Groups: []string{existingGroupID},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "POST Non Linux Peer",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/routes",
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", nonLinuxExistingPeerID, existingGroupID)),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "POST Not Found Peer",
|
name: "POST Not Found Peer",
|
||||||
requestType: http.MethodPost,
|
requestType: http.MethodPost,
|
||||||
@@ -204,6 +241,24 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
expectedStatus: http.StatusUnprocessableEntity,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "POST UnprocessableEntity when both peer and peer_groups are provided",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/routes",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"peer\":\"%s\",\"peer_groups\":[\"%s\"],\"groups\":[\"%s\"]}", existingPeerID, existingGroupID, existingGroupID))),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST UnprocessableEntity when no peer and peer_groups are provided",
|
||||||
|
requestType: http.MethodPost,
|
||||||
|
requestPath: "/api/routes",
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"groups\":[\"%s\"]}", existingPeerID))),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "PUT OK",
|
name: "PUT OK",
|
||||||
requestType: http.MethodPut,
|
requestType: http.MethodPut,
|
||||||
@@ -216,7 +271,27 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
Description: "Post",
|
Description: "Post",
|
||||||
NetworkId: "awesomeNet",
|
NetworkId: "awesomeNet",
|
||||||
Network: "192.168.0.0/16",
|
Network: "192.168.0.0/16",
|
||||||
Peer: existingPeerID,
|
Peer: &existingPeerID,
|
||||||
|
NetworkType: route.IPv4NetworkString,
|
||||||
|
Masquerade: false,
|
||||||
|
Enabled: false,
|
||||||
|
Groups: []string{existingGroupID},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT OK when peer_groups provided",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"peer_groups\":[\"%s\"],\"groups\":[\"%s\"]}", existingGroupID, existingGroupID)),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: true,
|
||||||
|
expectedRoute: &api.Route{
|
||||||
|
Id: existingRouteID,
|
||||||
|
Description: "Post",
|
||||||
|
NetworkId: "awesomeNet",
|
||||||
|
Network: "192.168.0.0/16",
|
||||||
|
Peer: &emptyString,
|
||||||
|
PeerGroups: &[]string{existingGroupID},
|
||||||
NetworkType: route.IPv4NetworkString,
|
NetworkType: route.IPv4NetworkString,
|
||||||
Masquerade: false,
|
Masquerade: false,
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
@@ -239,6 +314,14 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
expectedStatus: http.StatusUnprocessableEntity,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "PUT Non Linux Peer",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBufferString(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"Peer\":\"%s\",\"groups\":[\"%s\"]}", nonLinuxExistingPeerID, existingGroupID)),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "PUT Invalid Network Identifier",
|
name: "PUT Invalid Network Identifier",
|
||||||
requestType: http.MethodPut,
|
requestType: http.MethodPut,
|
||||||
@@ -255,6 +338,24 @@ func TestRoutesHandlers(t *testing.T) {
|
|||||||
expectedStatus: http.StatusUnprocessableEntity,
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
expectedBody: false,
|
expectedBody: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "PUT UnprocessableEntity when both peer and peer_groups are provided",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"peer\":\"%s\",\"peer_groups\":[\"%s\"],\"groups\":[\"%s\"]}", existingPeerID, existingGroupID, existingGroupID))),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PUT UnprocessableEntity when no peer and peer_groups are provided",
|
||||||
|
requestType: http.MethodPut,
|
||||||
|
requestPath: "/api/routes/" + existingRouteID,
|
||||||
|
requestBody: bytes.NewBuffer(
|
||||||
|
[]byte(fmt.Sprintf("{\"Description\":\"Post\",\"Network\":\"192.168.0.0/16\",\"network_id\":\"awesomeNet\",\"groups\":[\"%s\"]}", existingPeerID))),
|
||||||
|
expectedStatus: http.StatusUnprocessableEntity,
|
||||||
|
expectedBody: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
p := initRoutesTestData()
|
p := initRoutesTestData()
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ func WriteErrorResponse(errMsg string, httpStatus int, w http.ResponseWriter) {
|
|||||||
// WriteError converts an error to an JSON error response.
|
// WriteError converts an error to an JSON error response.
|
||||||
// If it is known internal error of type server.Error then it sets the messages from the error, a generic message otherwise
|
// If it is known internal error of type server.Error then it sets the messages from the error, a generic message otherwise
|
||||||
func WriteError(err error, w http.ResponseWriter) {
|
func WriteError(err error, w http.ResponseWriter) {
|
||||||
|
log.Errorf("got a handler error: %s", err.Error())
|
||||||
errStatus, ok := status.FromError(err)
|
errStatus, ok := status.FromError(err)
|
||||||
httpStatus := http.StatusInternalServerError
|
httpStatus := http.StatusInternalServerError
|
||||||
msg := "internal server error"
|
msg := "internal server error"
|
||||||
|
|||||||
@@ -210,47 +210,7 @@ func (ac *AuthentikCredentials) Authenticate() (JWTToken, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
||||||
func (am *AuthentikManager) UpdateUserAppMetadata(userID string, appMetadata AppMetadata) error {
|
func (am *AuthentikManager) UpdateUserAppMetadata(_ string, _ AppMetadata) error {
|
||||||
ctx, err := am.authenticationContext()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
userPk, err := strconv.ParseInt(userID, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var pendingInvite bool
|
|
||||||
if appMetadata.WTPendingInvite != nil {
|
|
||||||
pendingInvite = *appMetadata.WTPendingInvite
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedUserReq := api.PatchedUserRequest{
|
|
||||||
Attributes: map[string]interface{}{
|
|
||||||
wtAccountID: appMetadata.WTAccountID,
|
|
||||||
wtPendingInvite: pendingInvite,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, resp, err := am.apiClient.CoreApi.CoreUsersPartialUpdate(ctx, int32(userPk)).
|
|
||||||
PatchedUserRequest(patchedUserReq).
|
|
||||||
Execute()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if am.appMetrics != nil {
|
|
||||||
am.appMetrics.IDPMetrics().CountUpdateUserAppMetadata()
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
if am.appMetrics != nil {
|
|
||||||
am.appMetrics.IDPMetrics().CountRequestStatusError()
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unable to update user %s, statusCode %d", userID, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,41 +243,26 @@ func (am *AuthentikManager) GetUserDataByID(userID string, appMetadata AppMetada
|
|||||||
return nil, fmt.Errorf("unable to get user %s, statusCode %d", userID, resp.StatusCode)
|
return nil, fmt.Errorf("unable to get user %s, statusCode %d", userID, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseAuthentikUser(*user)
|
userData := parseAuthentikUser(*user)
|
||||||
|
userData.AppMetadata = appMetadata
|
||||||
|
|
||||||
|
return userData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccount returns all the users for a given profile.
|
// GetAccount returns all the users for a given profile.
|
||||||
func (am *AuthentikManager) GetAccount(accountID string) ([]*UserData, error) {
|
func (am *AuthentikManager) GetAccount(accountID string) ([]*UserData, error) {
|
||||||
ctx, err := am.authenticationContext()
|
users, err := am.getAllUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accountFilter := fmt.Sprintf("{%q:%q}", wtAccountID, accountID)
|
|
||||||
userList, resp, err := am.apiClient.CoreApi.CoreUsersList(ctx).Attributes(accountFilter).Execute()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if am.appMetrics != nil {
|
if am.appMetrics != nil {
|
||||||
am.appMetrics.IDPMetrics().CountGetAccount()
|
am.appMetrics.IDPMetrics().CountGetAccount()
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
for index, user := range users {
|
||||||
if am.appMetrics != nil {
|
user.AppMetadata.WTAccountID = accountID
|
||||||
am.appMetrics.IDPMetrics().CountRequestStatusError()
|
users[index] = user
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unable to get account %s users, statusCode %d", accountID, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
users := make([]*UserData, 0)
|
|
||||||
for _, user := range userList.Results {
|
|
||||||
userData, err := parseAuthentikUser(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
users = append(users, userData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, nil
|
return users, nil
|
||||||
@@ -326,20 +271,37 @@ func (am *AuthentikManager) GetAccount(accountID string) ([]*UserData, error) {
|
|||||||
// GetAllAccounts gets all registered accounts with corresponding user data.
|
// GetAllAccounts gets all registered accounts with corresponding user data.
|
||||||
// It returns a list of users indexed by accountID.
|
// It returns a list of users indexed by accountID.
|
||||||
func (am *AuthentikManager) GetAllAccounts() (map[string][]*UserData, error) {
|
func (am *AuthentikManager) GetAllAccounts() (map[string][]*UserData, error) {
|
||||||
|
users, err := am.getAllUsers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
indexedUsers := make(map[string][]*UserData)
|
||||||
|
indexedUsers[UnsetAccountID] = append(indexedUsers[UnsetAccountID], users...)
|
||||||
|
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountGetAllAccounts()
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexedUsers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAllUsers returns all users in a Authentik account.
|
||||||
|
func (am *AuthentikManager) getAllUsers() ([]*UserData, error) {
|
||||||
|
users := make([]*UserData, 0)
|
||||||
|
|
||||||
|
page := int32(1)
|
||||||
|
for {
|
||||||
ctx, err := am.authenticationContext()
|
ctx, err := am.authenticationContext()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userList, resp, err := am.apiClient.CoreApi.CoreUsersList(ctx).Execute()
|
userList, resp, err := am.apiClient.CoreApi.CoreUsersList(ctx).Page(page).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
|
|
||||||
if am.appMetrics != nil {
|
|
||||||
am.appMetrics.IDPMetrics().CountGetAllAccounts()
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
if am.appMetrics != nil {
|
if am.appMetrics != nil {
|
||||||
@@ -348,67 +310,23 @@ func (am *AuthentikManager) GetAllAccounts() (map[string][]*UserData, error) {
|
|||||||
return nil, fmt.Errorf("unable to get all accounts, statusCode %d", resp.StatusCode)
|
return nil, fmt.Errorf("unable to get all accounts, statusCode %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
indexedUsers := make(map[string][]*UserData)
|
|
||||||
for _, user := range userList.Results {
|
for _, user := range userList.Results {
|
||||||
userData, err := parseAuthentikUser(user)
|
users = append(users, parseAuthentikUser(user))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
accountID := userData.AppMetadata.WTAccountID
|
page = int32(userList.GetPagination().Next)
|
||||||
if accountID != "" {
|
if userList.GetPagination().Next == 0 {
|
||||||
if _, ok := indexedUsers[accountID]; !ok {
|
break
|
||||||
indexedUsers[accountID] = make([]*UserData, 0)
|
|
||||||
}
|
|
||||||
indexedUsers[accountID] = append(indexedUsers[accountID], userData)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexedUsers, nil
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser creates a new user in authentik Idp and sends an invitation.
|
// CreateUser creates a new user in authentik Idp and sends an invitation.
|
||||||
func (am *AuthentikManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
|
func (am *AuthentikManager) CreateUser(_, _, _, _ string) (*UserData, error) {
|
||||||
ctx, err := am.authenticationContext()
|
return nil, fmt.Errorf("method CreateUser not implemented")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
groupID, err := am.getUserGroupByName("netbird")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultBoolValue := true
|
|
||||||
createUserRequest := api.UserRequest{
|
|
||||||
Email: &email,
|
|
||||||
Name: name,
|
|
||||||
IsActive: &defaultBoolValue,
|
|
||||||
Groups: []string{groupID},
|
|
||||||
Username: email,
|
|
||||||
Attributes: map[string]interface{}{
|
|
||||||
wtAccountID: accountID,
|
|
||||||
wtPendingInvite: &defaultBoolValue,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
user, resp, err := am.apiClient.CoreApi.CoreUsersCreate(ctx).UserRequest(createUserRequest).Execute()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if am.appMetrics != nil {
|
|
||||||
am.appMetrics.IDPMetrics().CountCreateUser()
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusCreated {
|
|
||||||
if am.appMetrics != nil {
|
|
||||||
am.appMetrics.IDPMetrics().CountRequestStatusError()
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unable to create user, statusCode %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseAuthentikUser(*user)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail searches users with a given email.
|
// GetUserByEmail searches users with a given email.
|
||||||
@@ -438,11 +356,7 @@ func (am *AuthentikManager) GetUserByEmail(email string) ([]*UserData, error) {
|
|||||||
|
|
||||||
users := make([]*UserData, 0)
|
users := make([]*UserData, 0)
|
||||||
for _, user := range userList.Results {
|
for _, user := range userList.Results {
|
||||||
userData, err := parseAuthentikUser(user)
|
users = append(users, parseAuthentikUser(user))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
users = append(users, userData)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, nil
|
return users, nil
|
||||||
@@ -501,64 +415,10 @@ func (am *AuthentikManager) authenticationContext() (context.Context, error) {
|
|||||||
return context.WithValue(context.Background(), api.ContextAPIKeys, value), nil
|
return context.WithValue(context.Background(), api.ContextAPIKeys, value), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getUserGroupByName retrieves the user group for assigning new users.
|
func parseAuthentikUser(user api.User) *UserData {
|
||||||
// If the group is not found, a new group with the specified name will be created.
|
|
||||||
func (am *AuthentikManager) getUserGroupByName(name string) (string, error) {
|
|
||||||
ctx, err := am.authenticationContext()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
groupList, resp, err := am.apiClient.CoreApi.CoreGroupsList(ctx).Name(name).Execute()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if groupList != nil {
|
|
||||||
if len(groupList.Results) > 0 {
|
|
||||||
return groupList.Results[0].Pk, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createGroupRequest := api.GroupRequest{Name: name}
|
|
||||||
group, resp, err := am.apiClient.CoreApi.CoreGroupsCreate(ctx).GroupRequest(createGroupRequest).Execute()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusCreated {
|
|
||||||
return "", fmt.Errorf("unable to create user group, statusCode: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return group.Pk, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseAuthentikUser(user api.User) (*UserData, error) {
|
|
||||||
var attributes struct {
|
|
||||||
AccountID string `json:"wt_account_id"`
|
|
||||||
PendingInvite bool `json:"wt_pending_invite"`
|
|
||||||
}
|
|
||||||
|
|
||||||
helper := JsonParser{}
|
|
||||||
buf, err := helper.Marshal(user.Attributes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = helper.Unmarshal(buf, &attributes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &UserData{
|
return &UserData{
|
||||||
Email: *user.Email,
|
Email: *user.Email,
|
||||||
Name: user.Name,
|
Name: user.Name,
|
||||||
ID: strconv.FormatInt(int64(user.Pk), 10),
|
ID: strconv.FormatInt(int64(user.Pk), 10),
|
||||||
AppMetadata: AppMetadata{
|
}
|
||||||
WTAccountID: attributes.AccountID,
|
|
||||||
WTPendingInvite: &attributes.PendingInvite,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package idp
|
package idp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -11,18 +10,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const profileFields = "id,displayName,mail,userPrincipalName"
|
||||||
// azure extension properties template
|
|
||||||
wtAccountIDTpl = "extension_%s_wt_account_id"
|
|
||||||
wtPendingInviteTpl = "extension_%s_wt_pending_invite"
|
|
||||||
|
|
||||||
profileFields = "id,displayName,mail,userPrincipalName"
|
|
||||||
extensionFields = "id,name,targetObjects"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AzureManager azure manager client instance.
|
// AzureManager azure manager client instance.
|
||||||
type AzureManager struct {
|
type AzureManager struct {
|
||||||
@@ -58,21 +51,6 @@ type AzureCredentials struct {
|
|||||||
// azureProfile represents an azure user profile.
|
// azureProfile represents an azure user profile.
|
||||||
type azureProfile map[string]any
|
type azureProfile map[string]any
|
||||||
|
|
||||||
// passwordProfile represent authentication method for,
|
|
||||||
// newly created user profile.
|
|
||||||
type passwordProfile struct {
|
|
||||||
ForceChangePasswordNextSignIn bool `json:"forceChangePasswordNextSignIn"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// azureExtension represent custom attribute,
|
|
||||||
// that can be added to user objects in Azure Active Directory (AD).
|
|
||||||
type azureExtension struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
DataType string `json:"dataType"`
|
|
||||||
TargetObjects []string `json:"targetObjects"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAzureManager creates a new instance of the AzureManager.
|
// NewAzureManager creates a new instance of the AzureManager.
|
||||||
func NewAzureManager(config AzureClientConfig, appMetrics telemetry.AppMetrics) (*AzureManager, error) {
|
func NewAzureManager(config AzureClientConfig, appMetrics telemetry.AppMetrics) (*AzureManager, error) {
|
||||||
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
@@ -115,7 +93,7 @@ func NewAzureManager(config AzureClientConfig, appMetrics telemetry.AppMetrics)
|
|||||||
appMetrics: appMetrics,
|
appMetrics: appMetrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
manager := &AzureManager{
|
return &AzureManager{
|
||||||
ObjectID: config.ObjectID,
|
ObjectID: config.ObjectID,
|
||||||
ClientID: config.ClientID,
|
ClientID: config.ClientID,
|
||||||
GraphAPIEndpoint: config.GraphAPIEndpoint,
|
GraphAPIEndpoint: config.GraphAPIEndpoint,
|
||||||
@@ -123,14 +101,7 @@ func NewAzureManager(config AzureClientConfig, appMetrics telemetry.AppMetrics)
|
|||||||
credentials: credentials,
|
credentials: credentials,
|
||||||
helper: helper,
|
helper: helper,
|
||||||
appMetrics: appMetrics,
|
appMetrics: appMetrics,
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
err := manager.configureAppMetadata()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return manager, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// jwtStillValid returns true if the token still valid and have enough time to be used and get a response from azure.
|
// jwtStillValid returns true if the token still valid and have enough time to be used and get a response from azure.
|
||||||
@@ -236,44 +207,14 @@ func (ac *AzureCredentials) Authenticate() (JWTToken, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser creates a new user in azure AD Idp.
|
// CreateUser creates a new user in azure AD Idp.
|
||||||
func (am *AzureManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
|
func (am *AzureManager) CreateUser(_, _, _, _ string) (*UserData, error) {
|
||||||
payload, err := buildAzureCreateUserRequestPayload(email, name, accountID, am.ClientID)
|
return nil, fmt.Errorf("method CreateUser not implemented")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := am.post("users", payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if am.appMetrics != nil {
|
|
||||||
am.appMetrics.IDPMetrics().CountCreateUser()
|
|
||||||
}
|
|
||||||
|
|
||||||
var profile azureProfile
|
|
||||||
err = am.helper.Unmarshal(body, &profile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
wtAccountIDField := extensionName(wtAccountIDTpl, am.ClientID)
|
|
||||||
profile[wtAccountIDField] = accountID
|
|
||||||
|
|
||||||
wtPendingInviteField := extensionName(wtPendingInviteTpl, am.ClientID)
|
|
||||||
profile[wtPendingInviteField] = true
|
|
||||||
|
|
||||||
return profile.userData(am.ClientID), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserDataByID requests user data from keycloak via ID.
|
// GetUserDataByID requests user data from keycloak via ID.
|
||||||
func (am *AzureManager) GetUserDataByID(userID string, appMetadata AppMetadata) (*UserData, error) {
|
func (am *AzureManager) GetUserDataByID(userID string, appMetadata AppMetadata) (*UserData, error) {
|
||||||
wtAccountIDField := extensionName(wtAccountIDTpl, am.ClientID)
|
|
||||||
wtPendingInviteField := extensionName(wtPendingInviteTpl, am.ClientID)
|
|
||||||
selectFields := strings.Join([]string{profileFields, wtAccountIDField, wtPendingInviteField}, ",")
|
|
||||||
|
|
||||||
q := url.Values{}
|
q := url.Values{}
|
||||||
q.Add("$select", selectFields)
|
q.Add("$select", profileFields)
|
||||||
|
|
||||||
body, err := am.get("users/"+userID, q)
|
body, err := am.get("users/"+userID, q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -290,18 +231,17 @@ func (am *AzureManager) GetUserDataByID(userID string, appMetadata AppMetadata)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return profile.userData(am.ClientID), nil
|
userData := profile.userData()
|
||||||
|
userData.AppMetadata = appMetadata
|
||||||
|
|
||||||
|
return userData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail searches users with a given email.
|
// GetUserByEmail searches users with a given email.
|
||||||
// If no users have been found, this function returns an empty list.
|
// If no users have been found, this function returns an empty list.
|
||||||
func (am *AzureManager) GetUserByEmail(email string) ([]*UserData, error) {
|
func (am *AzureManager) GetUserByEmail(email string) ([]*UserData, error) {
|
||||||
wtAccountIDField := extensionName(wtAccountIDTpl, am.ClientID)
|
|
||||||
wtPendingInviteField := extensionName(wtPendingInviteTpl, am.ClientID)
|
|
||||||
selectFields := strings.Join([]string{profileFields, wtAccountIDField, wtPendingInviteField}, ",")
|
|
||||||
|
|
||||||
q := url.Values{}
|
q := url.Values{}
|
||||||
q.Add("$select", selectFields)
|
q.Add("$select", profileFields)
|
||||||
|
|
||||||
body, err := am.get("users/"+email, q)
|
body, err := am.get("users/"+email, q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -319,22 +259,14 @@ func (am *AzureManager) GetUserByEmail(email string) ([]*UserData, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
users := make([]*UserData, 0)
|
users := make([]*UserData, 0)
|
||||||
users = append(users, profile.userData(am.ClientID))
|
users = append(users, profile.userData())
|
||||||
|
|
||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccount returns all the users for a given profile.
|
// GetAccount returns all the users for a given profile.
|
||||||
func (am *AzureManager) GetAccount(accountID string) ([]*UserData, error) {
|
func (am *AzureManager) GetAccount(accountID string) ([]*UserData, error) {
|
||||||
wtAccountIDField := extensionName(wtAccountIDTpl, am.ClientID)
|
users, err := am.getAllUsers()
|
||||||
wtPendingInviteField := extensionName(wtPendingInviteTpl, am.ClientID)
|
|
||||||
selectFields := strings.Join([]string{profileFields, wtAccountIDField, wtPendingInviteField}, ",")
|
|
||||||
|
|
||||||
q := url.Values{}
|
|
||||||
q.Add("$select", selectFields)
|
|
||||||
q.Add("$filter", fmt.Sprintf("%s eq '%s'", wtAccountIDField, accountID))
|
|
||||||
|
|
||||||
body, err := am.get("users", q)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -343,15 +275,9 @@ func (am *AzureManager) GetAccount(accountID string) ([]*UserData, error) {
|
|||||||
am.appMetrics.IDPMetrics().CountGetAccount()
|
am.appMetrics.IDPMetrics().CountGetAccount()
|
||||||
}
|
}
|
||||||
|
|
||||||
var profiles struct{ Value []azureProfile }
|
for index, user := range users {
|
||||||
err = am.helper.Unmarshal(body, &profiles)
|
user.AppMetadata.WTAccountID = accountID
|
||||||
if err != nil {
|
users[index] = user
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
users := make([]*UserData, 0)
|
|
||||||
for _, profile := range profiles.Value {
|
|
||||||
users = append(users, profile.userData(am.ClientID))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, nil
|
return users, nil
|
||||||
@@ -360,91 +286,23 @@ func (am *AzureManager) GetAccount(accountID string) ([]*UserData, error) {
|
|||||||
// GetAllAccounts gets all registered accounts with corresponding user data.
|
// GetAllAccounts gets all registered accounts with corresponding user data.
|
||||||
// It returns a list of users indexed by accountID.
|
// It returns a list of users indexed by accountID.
|
||||||
func (am *AzureManager) GetAllAccounts() (map[string][]*UserData, error) {
|
func (am *AzureManager) GetAllAccounts() (map[string][]*UserData, error) {
|
||||||
wtAccountIDField := extensionName(wtAccountIDTpl, am.ClientID)
|
users, err := am.getAllUsers()
|
||||||
wtPendingInviteField := extensionName(wtPendingInviteTpl, am.ClientID)
|
|
||||||
selectFields := strings.Join([]string{profileFields, wtAccountIDField, wtPendingInviteField}, ",")
|
|
||||||
|
|
||||||
q := url.Values{}
|
|
||||||
q.Add("$select", selectFields)
|
|
||||||
|
|
||||||
body, err := am.get("users", q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if am.appMetrics != nil {
|
|
||||||
am.appMetrics.IDPMetrics().CountGetAllAccounts()
|
|
||||||
}
|
|
||||||
|
|
||||||
var profiles struct{ Value []azureProfile }
|
|
||||||
err = am.helper.Unmarshal(body, &profiles)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
indexedUsers := make(map[string][]*UserData)
|
indexedUsers := make(map[string][]*UserData)
|
||||||
for _, profile := range profiles.Value {
|
indexedUsers[UnsetAccountID] = append(indexedUsers[UnsetAccountID], users...)
|
||||||
userData := profile.userData(am.ClientID)
|
|
||||||
|
|
||||||
accountID := userData.AppMetadata.WTAccountID
|
|
||||||
if accountID != "" {
|
|
||||||
if _, ok := indexedUsers[accountID]; !ok {
|
|
||||||
indexedUsers[accountID] = make([]*UserData, 0)
|
|
||||||
}
|
|
||||||
indexedUsers[accountID] = append(indexedUsers[accountID], userData)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if am.appMetrics != nil {
|
||||||
|
am.appMetrics.IDPMetrics().CountGetAllAccounts()
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexedUsers, nil
|
return indexedUsers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserAppMetadata updates user app metadata based on userID.
|
// UpdateUserAppMetadata updates user app metadata based on userID.
|
||||||
func (am *AzureManager) UpdateUserAppMetadata(userID string, appMetadata AppMetadata) error {
|
func (am *AzureManager) UpdateUserAppMetadata(_ string, _ AppMetadata) error {
|
||||||
jwtToken, err := am.credentials.Authenticate()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
wtAccountIDField := extensionName(wtAccountIDTpl, am.ClientID)
|
|
||||||
wtPendingInviteField := extensionName(wtPendingInviteTpl, am.ClientID)
|
|
||||||
|
|
||||||
data, err := am.helper.Marshal(map[string]any{
|
|
||||||
wtAccountIDField: appMetadata.WTAccountID,
|
|
||||||
wtPendingInviteField: appMetadata.WTPendingInvite,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
payload := strings.NewReader(string(data))
|
|
||||||
|
|
||||||
reqURL := fmt.Sprintf("%s/users/%s", am.GraphAPIEndpoint, userID)
|
|
||||||
req, err := http.NewRequest(http.MethodPatch, reqURL, payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
|
||||||
req.Header.Add("content-type", "application/json")
|
|
||||||
|
|
||||||
log.Debugf("updating idp metadata for user %s", userID)
|
|
||||||
|
|
||||||
resp, err := am.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
if am.appMetrics != nil {
|
|
||||||
am.appMetrics.IDPMetrics().CountRequestError()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if am.appMetrics != nil {
|
|
||||||
am.appMetrics.IDPMetrics().CountUpdateUserAppMetadata()
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusNoContent {
|
|
||||||
return fmt.Errorf("unable to update the appMetadata, statusCode %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,7 +312,7 @@ func (am *AzureManager) InviteUserByID(_ string) error {
|
|||||||
return fmt.Errorf("method InviteUserByID not implemented")
|
return fmt.Errorf("method InviteUserByID not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUser from Azure
|
// DeleteUser from Azure.
|
||||||
func (am *AzureManager) DeleteUser(userID string) error {
|
func (am *AzureManager) DeleteUser(userID string) error {
|
||||||
jwtToken, err := am.credentials.Authenticate()
|
jwtToken, err := am.credentials.Authenticate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -491,79 +349,37 @@ func (am *AzureManager) DeleteUser(userID string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AzureManager) getUserExtensions() ([]azureExtension, error) {
|
// getAllUsers returns all users in an Azure AD account.
|
||||||
|
func (am *AzureManager) getAllUsers() ([]*UserData, error) {
|
||||||
|
users := make([]*UserData, 0)
|
||||||
|
|
||||||
q := url.Values{}
|
q := url.Values{}
|
||||||
q.Add("$select", extensionFields)
|
q.Add("$select", profileFields)
|
||||||
|
q.Add("$top", "500")
|
||||||
|
|
||||||
resource := fmt.Sprintf("applications/%s/extensionProperties", am.ObjectID)
|
for nextLink := "users"; nextLink != ""; {
|
||||||
body, err := am.get(resource, q)
|
body, err := am.get(nextLink, q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var extensions struct{ Value []azureExtension }
|
var profiles struct {
|
||||||
err = am.helper.Unmarshal(body, &extensions)
|
Value []azureProfile
|
||||||
|
NextLink string `json:"@odata.nextLink"`
|
||||||
|
}
|
||||||
|
err = am.helper.Unmarshal(body, &profiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return extensions.Value, nil
|
for _, profile := range profiles.Value {
|
||||||
}
|
users = append(users, profile.userData())
|
||||||
|
|
||||||
func (am *AzureManager) createUserExtension(name string) (*azureExtension, error) {
|
|
||||||
extension := azureExtension{
|
|
||||||
Name: name,
|
|
||||||
DataType: "string",
|
|
||||||
TargetObjects: []string{"User"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
payload, err := am.helper.Marshal(extension)
|
nextLink = profiles.NextLink
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource := fmt.Sprintf("applications/%s/extensionProperties", am.ObjectID)
|
return users, nil
|
||||||
body, err := am.post(resource, string(payload))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var userExtension azureExtension
|
|
||||||
err = am.helper.Unmarshal(body, &userExtension)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &userExtension, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// configureAppMetadata sets up app metadata extensions if they do not exists.
|
|
||||||
func (am *AzureManager) configureAppMetadata() error {
|
|
||||||
wtAccountIDField := extensionName(wtAccountIDTpl, am.ClientID)
|
|
||||||
wtPendingInviteField := extensionName(wtPendingInviteTpl, am.ClientID)
|
|
||||||
|
|
||||||
extensions, err := am.getUserExtensions()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the wt_account_id extension does not already exist, create it.
|
|
||||||
if !hasExtension(extensions, wtAccountIDField) {
|
|
||||||
_, err = am.createUserExtension(wtAccountID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the wt_pending_invite extension does not already exist, create it.
|
|
||||||
if !hasExtension(extensions, wtPendingInviteField) {
|
|
||||||
_, err = am.createUserExtension(wtPendingInvite)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get perform Get requests.
|
// get perform Get requests.
|
||||||
@@ -573,7 +389,14 @@ func (am *AzureManager) get(resource string, q url.Values) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
reqURL := fmt.Sprintf("%s/%s?%s", am.GraphAPIEndpoint, resource, q.Encode())
|
var reqURL string
|
||||||
|
if strings.HasPrefix(resource, "https") {
|
||||||
|
// Already an absolute URL for paging
|
||||||
|
reqURL = resource
|
||||||
|
} else {
|
||||||
|
reqURL = fmt.Sprintf("%s/%s?%s", am.GraphAPIEndpoint, resource, q.Encode())
|
||||||
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, reqURL, nil)
|
req, err := http.NewRequest(http.MethodGet, reqURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -602,44 +425,8 @@ func (am *AzureManager) get(resource string, q url.Values) ([]byte, error) {
|
|||||||
return io.ReadAll(resp.Body)
|
return io.ReadAll(resp.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// post perform Post requests.
|
|
||||||
func (am *AzureManager) post(resource string, body string) ([]byte, error) {
|
|
||||||
jwtToken, err := am.credentials.Authenticate()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqURL := fmt.Sprintf("%s/%s", am.GraphAPIEndpoint, resource)
|
|
||||||
req, err := http.NewRequest(http.MethodPost, reqURL, strings.NewReader(body))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
|
||||||
req.Header.Add("content-type", "application/json")
|
|
||||||
|
|
||||||
resp, err := am.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
if am.appMetrics != nil {
|
|
||||||
am.appMetrics.IDPMetrics().CountRequestError()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusCreated {
|
|
||||||
if am.appMetrics != nil {
|
|
||||||
am.appMetrics.IDPMetrics().CountRequestStatusError()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("unable to post %s, statusCode %d", reqURL, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return io.ReadAll(resp.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
// userData construct user data from keycloak profile.
|
// userData construct user data from keycloak profile.
|
||||||
func (ap azureProfile) userData(clientID string) *UserData {
|
func (ap azureProfile) userData() *UserData {
|
||||||
id, ok := ap["id"].(string)
|
id, ok := ap["id"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
id = ""
|
id = ""
|
||||||
@@ -655,66 +442,9 @@ func (ap azureProfile) userData(clientID string) *UserData {
|
|||||||
name = ""
|
name = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
accountIDField := extensionName(wtAccountIDTpl, clientID)
|
|
||||||
accountID, ok := ap[accountIDField].(string)
|
|
||||||
if !ok {
|
|
||||||
accountID = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingInviteField := extensionName(wtPendingInviteTpl, clientID)
|
|
||||||
pendingInvite, ok := ap[pendingInviteField].(bool)
|
|
||||||
if !ok {
|
|
||||||
pendingInvite = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return &UserData{
|
return &UserData{
|
||||||
Email: email,
|
Email: email,
|
||||||
Name: name,
|
Name: name,
|
||||||
ID: id,
|
ID: id,
|
||||||
AppMetadata: AppMetadata{
|
|
||||||
WTAccountID: accountID,
|
|
||||||
WTPendingInvite: &pendingInvite,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAzureCreateUserRequestPayload(email, name, accountID, clientID string) (string, error) {
|
|
||||||
wtAccountIDField := extensionName(wtAccountIDTpl, clientID)
|
|
||||||
wtPendingInviteField := extensionName(wtPendingInviteTpl, clientID)
|
|
||||||
|
|
||||||
req := &azureProfile{
|
|
||||||
"accountEnabled": true,
|
|
||||||
"displayName": name,
|
|
||||||
"mailNickName": strings.Join(strings.Split(name, " "), ""),
|
|
||||||
"userPrincipalName": email,
|
|
||||||
"passwordProfile": passwordProfile{
|
|
||||||
ForceChangePasswordNextSignIn: true,
|
|
||||||
Password: GeneratePassword(8, 1, 1, 1),
|
|
||||||
},
|
|
||||||
wtAccountIDField: accountID,
|
|
||||||
wtPendingInviteField: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
str, err := json.Marshal(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(str), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func extensionName(extensionTpl, clientID string) string {
|
|
||||||
clientID = strings.ReplaceAll(clientID, "-", "")
|
|
||||||
return fmt.Sprintf(extensionTpl, clientID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasExtension checks whether a given extension by name,
|
|
||||||
// exists in an list of extensions.
|
|
||||||
func hasExtension(extensions []azureExtension, name string) bool {
|
|
||||||
for _, ext := range extensions {
|
|
||||||
if ext.Name == name {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,15 +8,6 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockAzureCredentials struct {
|
|
||||||
jwtToken JWTToken
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *mockAzureCredentials) Authenticate() (JWTToken, error) {
|
|
||||||
return mc.jwtToken, mc.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAzureJwtStillValid(t *testing.T) {
|
func TestAzureJwtStillValid(t *testing.T) {
|
||||||
type jwtStillValidTest struct {
|
type jwtStillValidTest struct {
|
||||||
name string
|
name string
|
||||||
@@ -124,115 +115,9 @@ func TestAzureAuthenticate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAzureUpdateUserAppMetadata(t *testing.T) {
|
|
||||||
type updateUserAppMetadataTest struct {
|
|
||||||
name string
|
|
||||||
inputReqBody string
|
|
||||||
expectedReqBody string
|
|
||||||
appMetadata AppMetadata
|
|
||||||
statusCode int
|
|
||||||
helper ManagerHelper
|
|
||||||
managerCreds ManagerCredentials
|
|
||||||
assertErrFunc assert.ErrorAssertionFunc
|
|
||||||
assertErrFuncMessage string
|
|
||||||
}
|
|
||||||
|
|
||||||
appMetadata := AppMetadata{WTAccountID: "ok"}
|
|
||||||
|
|
||||||
updateUserAppMetadataTestCase1 := updateUserAppMetadataTest{
|
|
||||||
name: "Bad Authentication",
|
|
||||||
expectedReqBody: "",
|
|
||||||
appMetadata: appMetadata,
|
|
||||||
statusCode: 400,
|
|
||||||
helper: JsonParser{},
|
|
||||||
managerCreds: &mockAzureCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
err: fmt.Errorf("error"),
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.Error,
|
|
||||||
assertErrFuncMessage: "should return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUserAppMetadataTestCase2 := updateUserAppMetadataTest{
|
|
||||||
name: "Bad Status Code",
|
|
||||||
expectedReqBody: fmt.Sprintf("{\"extension__wt_account_id\":\"%s\",\"extension__wt_pending_invite\":null}", appMetadata.WTAccountID),
|
|
||||||
appMetadata: appMetadata,
|
|
||||||
statusCode: 400,
|
|
||||||
helper: JsonParser{},
|
|
||||||
managerCreds: &mockAzureCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.Error,
|
|
||||||
assertErrFuncMessage: "should return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUserAppMetadataTestCase3 := updateUserAppMetadataTest{
|
|
||||||
name: "Bad Response Parsing",
|
|
||||||
statusCode: 400,
|
|
||||||
helper: &mockJsonParser{marshalErrorString: "error"},
|
|
||||||
managerCreds: &mockAzureCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.Error,
|
|
||||||
assertErrFuncMessage: "should return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUserAppMetadataTestCase4 := updateUserAppMetadataTest{
|
|
||||||
name: "Good request",
|
|
||||||
expectedReqBody: fmt.Sprintf("{\"extension__wt_account_id\":\"%s\",\"extension__wt_pending_invite\":null}", appMetadata.WTAccountID),
|
|
||||||
appMetadata: appMetadata,
|
|
||||||
statusCode: 204,
|
|
||||||
helper: JsonParser{},
|
|
||||||
managerCreds: &mockAzureCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.NoError,
|
|
||||||
assertErrFuncMessage: "shouldn't return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
invite := true
|
|
||||||
updateUserAppMetadataTestCase5 := updateUserAppMetadataTest{
|
|
||||||
name: "Update Pending Invite",
|
|
||||||
expectedReqBody: fmt.Sprintf("{\"extension__wt_account_id\":\"%s\",\"extension__wt_pending_invite\":true}", appMetadata.WTAccountID),
|
|
||||||
appMetadata: AppMetadata{
|
|
||||||
WTAccountID: "ok",
|
|
||||||
WTPendingInvite: &invite,
|
|
||||||
},
|
|
||||||
statusCode: 204,
|
|
||||||
helper: JsonParser{},
|
|
||||||
managerCreds: &mockAzureCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.NoError,
|
|
||||||
assertErrFuncMessage: "shouldn't return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range []updateUserAppMetadataTest{updateUserAppMetadataTestCase1, updateUserAppMetadataTestCase2,
|
|
||||||
updateUserAppMetadataTestCase3, updateUserAppMetadataTestCase4, updateUserAppMetadataTestCase5} {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
reqClient := mockHTTPClient{
|
|
||||||
resBody: testCase.inputReqBody,
|
|
||||||
code: testCase.statusCode,
|
|
||||||
}
|
|
||||||
|
|
||||||
manager := &AzureManager{
|
|
||||||
httpClient: &reqClient,
|
|
||||||
credentials: testCase.managerCreds,
|
|
||||||
helper: testCase.helper,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := manager.UpdateUserAppMetadata("1", testCase.appMetadata)
|
|
||||||
testCase.assertErrFunc(t, err, testCase.assertErrFuncMessage)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.expectedReqBody, reqClient.reqBody, "request body should match")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAzureProfile(t *testing.T) {
|
func TestAzureProfile(t *testing.T) {
|
||||||
type azureProfileTest struct {
|
type azureProfileTest struct {
|
||||||
name string
|
name string
|
||||||
clientID string
|
|
||||||
invite bool
|
invite bool
|
||||||
inputProfile azureProfile
|
inputProfile azureProfile
|
||||||
expectedUserData UserData
|
expectedUserData UserData
|
||||||
@@ -240,90 +125,53 @@ func TestAzureProfile(t *testing.T) {
|
|||||||
|
|
||||||
azureProfileTestCase1 := azureProfileTest{
|
azureProfileTestCase1 := azureProfileTest{
|
||||||
name: "Good Request",
|
name: "Good Request",
|
||||||
clientID: "25d0b095-0484-40d2-9fd3-03f8f4abbb3c",
|
|
||||||
invite: false,
|
invite: false,
|
||||||
inputProfile: azureProfile{
|
inputProfile: azureProfile{
|
||||||
"id": "test1",
|
"id": "test1",
|
||||||
"displayName": "John Doe",
|
"displayName": "John Doe",
|
||||||
"userPrincipalName": "test1@test.com",
|
"userPrincipalName": "test1@test.com",
|
||||||
"extension_25d0b095048440d29fd303f8f4abbb3c_wt_account_id": "1",
|
|
||||||
"extension_25d0b095048440d29fd303f8f4abbb3c_wt_pending_invite": false,
|
|
||||||
},
|
},
|
||||||
expectedUserData: UserData{
|
expectedUserData: UserData{
|
||||||
Email: "test1@test.com",
|
Email: "test1@test.com",
|
||||||
Name: "John Doe",
|
Name: "John Doe",
|
||||||
ID: "test1",
|
ID: "test1",
|
||||||
AppMetadata: AppMetadata{
|
|
||||||
WTAccountID: "1",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
azureProfileTestCase2 := azureProfileTest{
|
azureProfileTestCase2 := azureProfileTest{
|
||||||
name: "Missing User ID",
|
name: "Missing User ID",
|
||||||
clientID: "25d0b095-0484-40d2-9fd3-03f8f4abbb3c",
|
|
||||||
invite: true,
|
invite: true,
|
||||||
inputProfile: azureProfile{
|
inputProfile: azureProfile{
|
||||||
"displayName": "John Doe",
|
"displayName": "John Doe",
|
||||||
"userPrincipalName": "test2@test.com",
|
"userPrincipalName": "test2@test.com",
|
||||||
"extension_25d0b095048440d29fd303f8f4abbb3c_wt_account_id": "1",
|
|
||||||
"extension_25d0b095048440d29fd303f8f4abbb3c_wt_pending_invite": true,
|
|
||||||
},
|
},
|
||||||
expectedUserData: UserData{
|
expectedUserData: UserData{
|
||||||
Email: "test2@test.com",
|
Email: "test2@test.com",
|
||||||
Name: "John Doe",
|
Name: "John Doe",
|
||||||
AppMetadata: AppMetadata{
|
|
||||||
WTAccountID: "1",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
azureProfileTestCase3 := azureProfileTest{
|
azureProfileTestCase3 := azureProfileTest{
|
||||||
name: "Missing User Name",
|
name: "Missing User Name",
|
||||||
clientID: "25d0b095-0484-40d2-9fd3-03f8f4abbb3c",
|
|
||||||
invite: false,
|
invite: false,
|
||||||
inputProfile: azureProfile{
|
inputProfile: azureProfile{
|
||||||
"id": "test3",
|
"id": "test3",
|
||||||
"userPrincipalName": "test3@test.com",
|
"userPrincipalName": "test3@test.com",
|
||||||
"extension_25d0b095048440d29fd303f8f4abbb3c_wt_account_id": "1",
|
|
||||||
"extension_25d0b095048440d29fd303f8f4abbb3c_wt_pending_invite": false,
|
|
||||||
},
|
},
|
||||||
expectedUserData: UserData{
|
expectedUserData: UserData{
|
||||||
ID: "test3",
|
ID: "test3",
|
||||||
Email: "test3@test.com",
|
Email: "test3@test.com",
|
||||||
AppMetadata: AppMetadata{
|
|
||||||
WTAccountID: "1",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
azureProfileTestCase4 := azureProfileTest{
|
for _, testCase := range []azureProfileTest{azureProfileTestCase1, azureProfileTestCase2, azureProfileTestCase3} {
|
||||||
name: "Missing Extension Fields",
|
|
||||||
clientID: "25d0b095-0484-40d2-9fd3-03f8f4abbb3c",
|
|
||||||
invite: false,
|
|
||||||
inputProfile: azureProfile{
|
|
||||||
"id": "test4",
|
|
||||||
"displayName": "John Doe",
|
|
||||||
"userPrincipalName": "test4@test.com",
|
|
||||||
},
|
|
||||||
expectedUserData: UserData{
|
|
||||||
ID: "test4",
|
|
||||||
Name: "John Doe",
|
|
||||||
Email: "test4@test.com",
|
|
||||||
AppMetadata: AppMetadata{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range []azureProfileTest{azureProfileTestCase1, azureProfileTestCase2, azureProfileTestCase3, azureProfileTestCase4} {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
testCase.expectedUserData.AppMetadata.WTPendingInvite = &testCase.invite
|
testCase.expectedUserData.AppMetadata.WTPendingInvite = &testCase.invite
|
||||||
userData := testCase.inputProfile.userData(testCase.clientID)
|
userData := testCase.inputProfile.userData()
|
||||||
|
|
||||||
assert.Equal(t, testCase.expectedUserData.ID, userData.ID, "User id should match")
|
assert.Equal(t, testCase.expectedUserData.ID, userData.ID, "User id should match")
|
||||||
assert.Equal(t, testCase.expectedUserData.Email, userData.Email, "User email should match")
|
assert.Equal(t, testCase.expectedUserData.Email, userData.Email, "User email should match")
|
||||||
assert.Equal(t, testCase.expectedUserData.Name, userData.Name, "User name should match")
|
assert.Equal(t, testCase.expectedUserData.Name, userData.Name, "User name should match")
|
||||||
assert.Equal(t, testCase.expectedUserData.AppMetadata.WTAccountID, userData.AppMetadata.WTAccountID, "Account id should match")
|
|
||||||
assert.Equal(t, testCase.expectedUserData.AppMetadata.WTPendingInvite, userData.AppMetadata.WTPendingInvite, "Pending invite should match")
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,14 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/oauth2/google"
|
"golang.org/x/oauth2/google"
|
||||||
admin "google.golang.org/api/admin/directory/v1"
|
admin "google.golang.org/api/admin/directory/v1"
|
||||||
"google.golang.org/api/googleapi"
|
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GoogleWorkspaceManager Google Workspace manager client instance.
|
// GoogleWorkspaceManager Google Workspace manager client instance.
|
||||||
@@ -73,17 +72,13 @@ func NewGoogleWorkspaceManager(config GoogleWorkspaceClientConfig, appMetrics te
|
|||||||
}
|
}
|
||||||
|
|
||||||
service, err := admin.NewService(context.Background(),
|
service, err := admin.NewService(context.Background(),
|
||||||
option.WithScopes(admin.AdminDirectoryUserScope, admin.AdminDirectoryUserschemaScope),
|
option.WithScopes(admin.AdminDirectoryUserReadonlyScope),
|
||||||
option.WithCredentials(adminCredentials),
|
option.WithCredentials(adminCredentials),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = configureAppMetadataSchema(service, config.CustomerID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &GoogleWorkspaceManager{
|
return &GoogleWorkspaceManager{
|
||||||
usersService: service.Users,
|
usersService: service.Users,
|
||||||
CustomerID: config.CustomerID,
|
CustomerID: config.CustomerID,
|
||||||
@@ -95,33 +90,13 @@ func NewGoogleWorkspaceManager(config GoogleWorkspaceClientConfig, appMetrics te
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
||||||
func (gm *GoogleWorkspaceManager) UpdateUserAppMetadata(userID string, appMetadata AppMetadata) error {
|
func (gm *GoogleWorkspaceManager) UpdateUserAppMetadata(_ string, _ AppMetadata) error {
|
||||||
metadata, err := gm.helper.Marshal(appMetadata)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
user := &admin.User{
|
|
||||||
CustomSchemas: map[string]googleapi.RawMessage{
|
|
||||||
"app_metadata": metadata,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = gm.usersService.Update(userID, user).Do()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if gm.appMetrics != nil {
|
|
||||||
gm.appMetrics.IDPMetrics().CountUpdateUserAppMetadata()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserDataByID requests user data from Google Workspace via ID.
|
// GetUserDataByID requests user data from Google Workspace via ID.
|
||||||
func (gm *GoogleWorkspaceManager) GetUserDataByID(userID string, appMetadata AppMetadata) (*UserData, error) {
|
func (gm *GoogleWorkspaceManager) GetUserDataByID(userID string, appMetadata AppMetadata) (*UserData, error) {
|
||||||
user, err := gm.usersService.Get(userID).Projection("full").Do()
|
user, err := gm.usersService.Get(userID).Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -130,105 +105,86 @@ func (gm *GoogleWorkspaceManager) GetUserDataByID(userID string, appMetadata App
|
|||||||
gm.appMetrics.IDPMetrics().CountGetUserDataByID()
|
gm.appMetrics.IDPMetrics().CountGetUserDataByID()
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseGoogleWorkspaceUser(user)
|
userData := parseGoogleWorkspaceUser(user)
|
||||||
|
userData.AppMetadata = appMetadata
|
||||||
|
|
||||||
|
return userData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccount returns all the users for a given profile.
|
// GetAccount returns all the users for a given profile.
|
||||||
func (gm *GoogleWorkspaceManager) GetAccount(accountID string) ([]*UserData, error) {
|
func (gm *GoogleWorkspaceManager) GetAccount(accountID string) ([]*UserData, error) {
|
||||||
query := fmt.Sprintf("app_metadata.wt_account_id=\"%s\"", accountID)
|
users, err := gm.getAllUsers()
|
||||||
usersList, err := gm.usersService.List().Customer(gm.CustomerID).Query(query).Projection("full").Do()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
usersData := make([]*UserData, 0)
|
if gm.appMetrics != nil {
|
||||||
for _, user := range usersList.Users {
|
gm.appMetrics.IDPMetrics().CountGetAccount()
|
||||||
userData, err := parseGoogleWorkspaceUser(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
usersData = append(usersData, userData)
|
for index, user := range users {
|
||||||
|
user.AppMetadata.WTAccountID = accountID
|
||||||
|
users[index] = user
|
||||||
}
|
}
|
||||||
|
|
||||||
return usersData, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllAccounts gets all registered accounts with corresponding user data.
|
// GetAllAccounts gets all registered accounts with corresponding user data.
|
||||||
// It returns a list of users indexed by accountID.
|
// It returns a list of users indexed by accountID.
|
||||||
func (gm *GoogleWorkspaceManager) GetAllAccounts() (map[string][]*UserData, error) {
|
func (gm *GoogleWorkspaceManager) GetAllAccounts() (map[string][]*UserData, error) {
|
||||||
usersList, err := gm.usersService.List().Customer(gm.CustomerID).Projection("full").Do()
|
users, err := gm.getAllUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
indexedUsers := make(map[string][]*UserData)
|
||||||
|
indexedUsers[UnsetAccountID] = append(indexedUsers[UnsetAccountID], users...)
|
||||||
|
|
||||||
if gm.appMetrics != nil {
|
if gm.appMetrics != nil {
|
||||||
gm.appMetrics.IDPMetrics().CountGetAllAccounts()
|
gm.appMetrics.IDPMetrics().CountGetAllAccounts()
|
||||||
}
|
}
|
||||||
|
|
||||||
indexedUsers := make(map[string][]*UserData)
|
|
||||||
for _, user := range usersList.Users {
|
|
||||||
userData, err := parseGoogleWorkspaceUser(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
accountID := userData.AppMetadata.WTAccountID
|
|
||||||
if accountID != "" {
|
|
||||||
if _, ok := indexedUsers[accountID]; !ok {
|
|
||||||
indexedUsers[accountID] = make([]*UserData, 0)
|
|
||||||
}
|
|
||||||
indexedUsers[accountID] = append(indexedUsers[accountID], userData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return indexedUsers, nil
|
return indexedUsers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getAllUsers returns all users in a Google Workspace account filtered by customer ID.
|
||||||
|
func (gm *GoogleWorkspaceManager) getAllUsers() ([]*UserData, error) {
|
||||||
|
users := make([]*UserData, 0)
|
||||||
|
pageToken := ""
|
||||||
|
for {
|
||||||
|
call := gm.usersService.List().Customer(gm.CustomerID).MaxResults(500)
|
||||||
|
if pageToken != "" {
|
||||||
|
call.PageToken(pageToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := call.Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range resp.Users {
|
||||||
|
users = append(users, parseGoogleWorkspaceUser(user))
|
||||||
|
}
|
||||||
|
|
||||||
|
pageToken = resp.NextPageToken
|
||||||
|
if pageToken == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateUser creates a new user in Google Workspace and sends an invitation.
|
// CreateUser creates a new user in Google Workspace and sends an invitation.
|
||||||
func (gm *GoogleWorkspaceManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
|
func (gm *GoogleWorkspaceManager) CreateUser(_, _, _, _ string) (*UserData, error) {
|
||||||
invite := true
|
return nil, fmt.Errorf("method CreateUser not implemented")
|
||||||
metadata := AppMetadata{
|
|
||||||
WTAccountID: accountID,
|
|
||||||
WTPendingInvite: &invite,
|
|
||||||
}
|
|
||||||
|
|
||||||
username := &admin.UserName{}
|
|
||||||
fields := strings.Fields(name)
|
|
||||||
if n := len(fields); n > 0 {
|
|
||||||
username.GivenName = strings.Join(fields[:n-1], " ")
|
|
||||||
username.FamilyName = fields[n-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, err := gm.helper.Marshal(metadata)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
user := &admin.User{
|
|
||||||
Name: username,
|
|
||||||
PrimaryEmail: email,
|
|
||||||
CustomSchemas: map[string]googleapi.RawMessage{
|
|
||||||
"app_metadata": payload,
|
|
||||||
},
|
|
||||||
Password: GeneratePassword(8, 1, 1, 1),
|
|
||||||
}
|
|
||||||
user, err = gm.usersService.Insert(user).Do()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if gm.appMetrics != nil {
|
|
||||||
gm.appMetrics.IDPMetrics().CountCreateUser()
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseGoogleWorkspaceUser(user)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail searches users with a given email.
|
// GetUserByEmail searches users with a given email.
|
||||||
// If no users have been found, this function returns an empty list.
|
// If no users have been found, this function returns an empty list.
|
||||||
func (gm *GoogleWorkspaceManager) GetUserByEmail(email string) ([]*UserData, error) {
|
func (gm *GoogleWorkspaceManager) GetUserByEmail(email string) ([]*UserData, error) {
|
||||||
user, err := gm.usersService.Get(email).Projection("full").Do()
|
user, err := gm.usersService.Get(email).Do()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -237,13 +193,8 @@ func (gm *GoogleWorkspaceManager) GetUserByEmail(email string) ([]*UserData, err
|
|||||||
gm.appMetrics.IDPMetrics().CountGetUserByEmail()
|
gm.appMetrics.IDPMetrics().CountGetUserByEmail()
|
||||||
}
|
}
|
||||||
|
|
||||||
userData, err := parseGoogleWorkspaceUser(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
users := make([]*UserData, 0)
|
users := make([]*UserData, 0)
|
||||||
users = append(users, userData)
|
users = append(users, parseGoogleWorkspaceUser(user))
|
||||||
|
|
||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
@@ -281,8 +232,7 @@ func getGoogleCredentials(serviceAccountKey string) (*google.Credentials, error)
|
|||||||
creds, err := google.CredentialsFromJSON(
|
creds, err := google.CredentialsFromJSON(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
decodeKey,
|
decodeKey,
|
||||||
admin.AdminDirectoryUserschemaScope,
|
admin.AdminDirectoryUserReadonlyScope,
|
||||||
admin.AdminDirectoryUserScope,
|
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// No need to fallback to the default Google credentials path
|
// No need to fallback to the default Google credentials path
|
||||||
@@ -294,8 +244,7 @@ func getGoogleCredentials(serviceAccountKey string) (*google.Credentials, error)
|
|||||||
|
|
||||||
creds, err = google.FindDefaultCredentials(
|
creds, err = google.FindDefaultCredentials(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
admin.AdminDirectoryUserschemaScope,
|
admin.AdminDirectoryUserReadonlyScope,
|
||||||
admin.AdminDirectoryUserScope,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -304,62 +253,11 @@ func getGoogleCredentials(serviceAccountKey string) (*google.Credentials, error)
|
|||||||
return creds, nil
|
return creds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// configureAppMetadataSchema create a custom schema for managing app metadata fields in Google Workspace.
|
|
||||||
func configureAppMetadataSchema(service *admin.Service, customerID string) error {
|
|
||||||
schemaList, err := service.Schemas.List(customerID).Do()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// checks if app_metadata schema is already created
|
|
||||||
for _, schema := range schemaList.Schemas {
|
|
||||||
if schema.SchemaName == "app_metadata" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new app_metadata schema
|
|
||||||
appMetadataSchema := &admin.Schema{
|
|
||||||
SchemaName: "app_metadata",
|
|
||||||
Fields: []*admin.SchemaFieldSpec{
|
|
||||||
{
|
|
||||||
FieldName: "wt_account_id",
|
|
||||||
FieldType: "STRING",
|
|
||||||
MultiValued: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
FieldName: "wt_pending_invite",
|
|
||||||
FieldType: "BOOL",
|
|
||||||
MultiValued: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, err = service.Schemas.Insert(customerID, appMetadataSchema).Do()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseGoogleWorkspaceUser parse google user to UserData.
|
// parseGoogleWorkspaceUser parse google user to UserData.
|
||||||
func parseGoogleWorkspaceUser(user *admin.User) (*UserData, error) {
|
func parseGoogleWorkspaceUser(user *admin.User) *UserData {
|
||||||
var appMetadata AppMetadata
|
|
||||||
|
|
||||||
// Get app metadata from custom schemas
|
|
||||||
if user.CustomSchemas != nil {
|
|
||||||
rawMessage := user.CustomSchemas["app_metadata"]
|
|
||||||
helper := JsonParser{}
|
|
||||||
|
|
||||||
if err := helper.Unmarshal(rawMessage, &appMetadata); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &UserData{
|
return &UserData{
|
||||||
ID: user.Id,
|
ID: user.Id,
|
||||||
Email: user.PrimaryEmail,
|
Email: user.PrimaryEmail,
|
||||||
Name: user.Name.FullName,
|
Name: user.Name.FullName,
|
||||||
AppMetadata: appMetadata,
|
}
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsetAccountID is a special key to map users without an account ID
|
||||||
|
UnsetAccountID = "unset"
|
||||||
|
)
|
||||||
|
|
||||||
// Manager idp manager interface
|
// Manager idp manager interface
|
||||||
type Manager interface {
|
type Manager interface {
|
||||||
UpdateUserAppMetadata(userId string, appMetadata AppMetadata) error
|
UpdateUserAppMetadata(userId string, appMetadata AppMetadata) error
|
||||||
@@ -38,10 +43,10 @@ type Config struct {
|
|||||||
ManagerType string
|
ManagerType string
|
||||||
ClientConfig *ClientConfig
|
ClientConfig *ClientConfig
|
||||||
ExtraConfig ExtraConfig
|
ExtraConfig ExtraConfig
|
||||||
Auth0ClientCredentials Auth0ClientConfig
|
Auth0ClientCredentials *Auth0ClientConfig
|
||||||
AzureClientCredentials AzureClientConfig
|
AzureClientCredentials *AzureClientConfig
|
||||||
KeycloakClientCredentials KeycloakClientConfig
|
KeycloakClientCredentials *KeycloakClientConfig
|
||||||
ZitadelClientCredentials ZitadelClientConfig
|
ZitadelClientCredentials *ZitadelClientConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// ManagerCredentials interface that authenticates using the credential of each type of idp
|
// ManagerCredentials interface that authenticates using the credential of each type of idp
|
||||||
@@ -97,7 +102,7 @@ func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error)
|
|||||||
case "auth0":
|
case "auth0":
|
||||||
auth0ClientConfig := config.Auth0ClientCredentials
|
auth0ClientConfig := config.Auth0ClientCredentials
|
||||||
if config.ClientConfig != nil {
|
if config.ClientConfig != nil {
|
||||||
auth0ClientConfig = Auth0ClientConfig{
|
auth0ClientConfig = &Auth0ClientConfig{
|
||||||
Audience: config.ExtraConfig["Audience"],
|
Audience: config.ExtraConfig["Audience"],
|
||||||
AuthIssuer: config.ClientConfig.Issuer,
|
AuthIssuer: config.ClientConfig.Issuer,
|
||||||
ClientID: config.ClientConfig.ClientID,
|
ClientID: config.ClientConfig.ClientID,
|
||||||
@@ -106,11 +111,11 @@ func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewAuth0Manager(auth0ClientConfig, appMetrics)
|
return NewAuth0Manager(*auth0ClientConfig, appMetrics)
|
||||||
case "azure":
|
case "azure":
|
||||||
azureClientConfig := config.AzureClientCredentials
|
azureClientConfig := config.AzureClientCredentials
|
||||||
if config.ClientConfig != nil {
|
if config.ClientConfig != nil {
|
||||||
azureClientConfig = AzureClientConfig{
|
azureClientConfig = &AzureClientConfig{
|
||||||
ClientID: config.ClientConfig.ClientID,
|
ClientID: config.ClientConfig.ClientID,
|
||||||
ClientSecret: config.ClientConfig.ClientSecret,
|
ClientSecret: config.ClientConfig.ClientSecret,
|
||||||
GrantType: config.ClientConfig.GrantType,
|
GrantType: config.ClientConfig.GrantType,
|
||||||
@@ -120,11 +125,11 @@ func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewAzureManager(azureClientConfig, appMetrics)
|
return NewAzureManager(*azureClientConfig, appMetrics)
|
||||||
case "keycloak":
|
case "keycloak":
|
||||||
keycloakClientConfig := config.KeycloakClientCredentials
|
keycloakClientConfig := config.KeycloakClientCredentials
|
||||||
if config.ClientConfig != nil {
|
if config.ClientConfig != nil {
|
||||||
keycloakClientConfig = KeycloakClientConfig{
|
keycloakClientConfig = &KeycloakClientConfig{
|
||||||
ClientID: config.ClientConfig.ClientID,
|
ClientID: config.ClientConfig.ClientID,
|
||||||
ClientSecret: config.ClientConfig.ClientSecret,
|
ClientSecret: config.ClientConfig.ClientSecret,
|
||||||
GrantType: config.ClientConfig.GrantType,
|
GrantType: config.ClientConfig.GrantType,
|
||||||
@@ -133,11 +138,11 @@ func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewKeycloakManager(keycloakClientConfig, appMetrics)
|
return NewKeycloakManager(*keycloakClientConfig, appMetrics)
|
||||||
case "zitadel":
|
case "zitadel":
|
||||||
zitadelClientConfig := config.ZitadelClientCredentials
|
zitadelClientConfig := config.ZitadelClientCredentials
|
||||||
if config.ClientConfig != nil {
|
if config.ClientConfig != nil {
|
||||||
zitadelClientConfig = ZitadelClientConfig{
|
zitadelClientConfig = &ZitadelClientConfig{
|
||||||
ClientID: config.ClientConfig.ClientID,
|
ClientID: config.ClientConfig.ClientID,
|
||||||
ClientSecret: config.ClientConfig.ClientSecret,
|
ClientSecret: config.ClientConfig.ClientSecret,
|
||||||
GrantType: config.ClientConfig.GrantType,
|
GrantType: config.ClientConfig.GrantType,
|
||||||
@@ -146,7 +151,7 @@ func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewZitadelManager(zitadelClientConfig, appMetrics)
|
return NewZitadelManager(*zitadelClientConfig, appMetrics)
|
||||||
case "authentik":
|
case "authentik":
|
||||||
authentikConfig := AuthentikClientConfig{
|
authentikConfig := AuthentikClientConfig{
|
||||||
Issuer: config.ClientConfig.Issuer,
|
Issuer: config.ClientConfig.Issuer,
|
||||||
@@ -171,7 +176,11 @@ func NewManager(config Config, appMetrics telemetry.AppMetrics) (Manager, error)
|
|||||||
CustomerID: config.ExtraConfig["CustomerId"],
|
CustomerID: config.ExtraConfig["CustomerId"],
|
||||||
}
|
}
|
||||||
return NewGoogleWorkspaceManager(googleClientConfig, appMetrics)
|
return NewGoogleWorkspaceManager(googleClientConfig, appMetrics)
|
||||||
|
case "jumpcloud":
|
||||||
|
jumpcloudConfig := JumpCloudClientConfig{
|
||||||
|
APIToken: config.ExtraConfig["ApiToken"],
|
||||||
|
}
|
||||||
|
return NewJumpCloudManager(jumpcloudConfig, appMetrics)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid manager type: %s", config.ManagerType)
|
return nil, fmt.Errorf("invalid manager type: %s", config.ManagerType)
|
||||||
}
|
}
|
||||||
|
|||||||
257
management/server/idp/jumpcloud.go
Normal file
257
management/server/idp/jumpcloud.go
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v1 "github.com/TheJumpCloud/jcapi-go/v1"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
contentType = "application/json"
|
||||||
|
accept = "application/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JumpCloudManager JumpCloud manager client instance.
|
||||||
|
type JumpCloudManager struct {
|
||||||
|
client *v1.APIClient
|
||||||
|
apiToken string
|
||||||
|
httpClient ManagerHTTPClient
|
||||||
|
credentials ManagerCredentials
|
||||||
|
helper ManagerHelper
|
||||||
|
appMetrics telemetry.AppMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
// JumpCloudClientConfig JumpCloud manager client configurations.
|
||||||
|
type JumpCloudClientConfig struct {
|
||||||
|
APIToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
// JumpCloudCredentials JumpCloud authentication information.
|
||||||
|
type JumpCloudCredentials struct {
|
||||||
|
clientConfig JumpCloudClientConfig
|
||||||
|
helper ManagerHelper
|
||||||
|
httpClient ManagerHTTPClient
|
||||||
|
appMetrics telemetry.AppMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJumpCloudManager creates a new instance of the JumpCloudManager.
|
||||||
|
func NewJumpCloudManager(config JumpCloudClientConfig, appMetrics telemetry.AppMetrics) (*JumpCloudManager, error) {
|
||||||
|
httpTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
httpTransport.MaxIdleConns = 5
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
Transport: httpTransport,
|
||||||
|
}
|
||||||
|
helper := JsonParser{}
|
||||||
|
|
||||||
|
if config.APIToken == "" {
|
||||||
|
return nil, fmt.Errorf("jumpCloud IdP configuration is incomplete, ApiToken is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := v1.NewAPIClient(v1.NewConfiguration())
|
||||||
|
credentials := &JumpCloudCredentials{
|
||||||
|
clientConfig: config,
|
||||||
|
httpClient: httpClient,
|
||||||
|
helper: helper,
|
||||||
|
appMetrics: appMetrics,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &JumpCloudManager{
|
||||||
|
client: client,
|
||||||
|
apiToken: config.APIToken,
|
||||||
|
httpClient: httpClient,
|
||||||
|
credentials: credentials,
|
||||||
|
helper: helper,
|
||||||
|
appMetrics: appMetrics,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate retrieves access token to use the JumpCloud user API.
|
||||||
|
func (jc *JumpCloudCredentials) Authenticate() (JWTToken, error) {
|
||||||
|
return JWTToken{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jm *JumpCloudManager) authenticationContext() context.Context {
|
||||||
|
return context.WithValue(context.Background(), v1.ContextAPIKey, v1.APIKey{
|
||||||
|
Key: jm.apiToken,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
||||||
|
func (jm *JumpCloudManager) UpdateUserAppMetadata(_ string, _ AppMetadata) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserDataByID requests user data from JumpCloud via ID.
|
||||||
|
func (jm *JumpCloudManager) GetUserDataByID(userID string, appMetadata AppMetadata) (*UserData, error) {
|
||||||
|
authCtx := jm.authenticationContext()
|
||||||
|
user, resp, err := jm.client.SystemusersApi.SystemusersGet(authCtx, userID, contentType, accept, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
if jm.appMetrics != nil {
|
||||||
|
jm.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to get user %s, statusCode %d", userID, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jm.appMetrics != nil {
|
||||||
|
jm.appMetrics.IDPMetrics().CountGetUserDataByID()
|
||||||
|
}
|
||||||
|
|
||||||
|
userData := parseJumpCloudUser(user)
|
||||||
|
userData.AppMetadata = appMetadata
|
||||||
|
|
||||||
|
return userData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccount returns all the users for a given profile.
|
||||||
|
func (jm *JumpCloudManager) GetAccount(accountID string) ([]*UserData, error) {
|
||||||
|
authCtx := jm.authenticationContext()
|
||||||
|
userList, resp, err := jm.client.SearchApi.SearchSystemusersPost(authCtx, contentType, accept, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
if jm.appMetrics != nil {
|
||||||
|
jm.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to get account %s users, statusCode %d", accountID, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jm.appMetrics != nil {
|
||||||
|
jm.appMetrics.IDPMetrics().CountGetAccount()
|
||||||
|
}
|
||||||
|
|
||||||
|
users := make([]*UserData, 0)
|
||||||
|
for _, user := range userList.Results {
|
||||||
|
userData := parseJumpCloudUser(user)
|
||||||
|
userData.AppMetadata.WTAccountID = accountID
|
||||||
|
|
||||||
|
users = append(users, userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllAccounts gets all registered accounts with corresponding user data.
|
||||||
|
// It returns a list of users indexed by accountID.
|
||||||
|
func (jm *JumpCloudManager) GetAllAccounts() (map[string][]*UserData, error) {
|
||||||
|
authCtx := jm.authenticationContext()
|
||||||
|
userList, resp, err := jm.client.SearchApi.SearchSystemusersPost(authCtx, contentType, accept, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
if jm.appMetrics != nil {
|
||||||
|
jm.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to get all accounts, statusCode %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jm.appMetrics != nil {
|
||||||
|
jm.appMetrics.IDPMetrics().CountGetAllAccounts()
|
||||||
|
}
|
||||||
|
|
||||||
|
indexedUsers := make(map[string][]*UserData)
|
||||||
|
for _, user := range userList.Results {
|
||||||
|
userData := parseJumpCloudUser(user)
|
||||||
|
indexedUsers[UnsetAccountID] = append(indexedUsers[UnsetAccountID], userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexedUsers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser creates a new user in JumpCloud Idp and sends an invitation.
|
||||||
|
func (jm *JumpCloudManager) CreateUser(_, _, _, _ string) (*UserData, error) {
|
||||||
|
return nil, fmt.Errorf("method CreateUser not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByEmail searches users with a given email.
|
||||||
|
// If no users have been found, this function returns an empty list.
|
||||||
|
func (jm *JumpCloudManager) GetUserByEmail(email string) ([]*UserData, error) {
|
||||||
|
searchFilter := map[string]interface{}{
|
||||||
|
"searchFilter": map[string]interface{}{
|
||||||
|
"filter": []string{email},
|
||||||
|
"fields": []string{"email"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
authCtx := jm.authenticationContext()
|
||||||
|
userList, resp, err := jm.client.SearchApi.SearchSystemusersPost(authCtx, contentType, accept, searchFilter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
if jm.appMetrics != nil {
|
||||||
|
jm.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to get user %s, statusCode %d", email, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jm.appMetrics != nil {
|
||||||
|
jm.appMetrics.IDPMetrics().CountGetUserByEmail()
|
||||||
|
}
|
||||||
|
|
||||||
|
usersData := make([]*UserData, 0)
|
||||||
|
for _, user := range userList.Results {
|
||||||
|
usersData = append(usersData, parseJumpCloudUser(user))
|
||||||
|
}
|
||||||
|
|
||||||
|
return usersData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InviteUserByID resend invitations to users who haven't activated,
|
||||||
|
// their accounts prior to the expiration period.
|
||||||
|
func (jm *JumpCloudManager) InviteUserByID(_ string) error {
|
||||||
|
return fmt.Errorf("method InviteUserByID not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUser from jumpCloud directory
|
||||||
|
func (jm *JumpCloudManager) DeleteUser(userID string) error {
|
||||||
|
authCtx := jm.authenticationContext()
|
||||||
|
_, resp, err := jm.client.SystemusersApi.SystemusersDelete(authCtx, userID, contentType, accept, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
if jm.appMetrics != nil {
|
||||||
|
jm.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unable to delete user, statusCode %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jm.appMetrics != nil {
|
||||||
|
jm.appMetrics.IDPMetrics().CountDeleteUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseJumpCloudUser parse JumpCloud system user returned from API V1 to UserData.
|
||||||
|
func parseJumpCloudUser(user v1.Systemuserreturn) *UserData {
|
||||||
|
names := []string{user.Firstname, user.Middlename, user.Lastname}
|
||||||
|
return &UserData{
|
||||||
|
Email: user.Email,
|
||||||
|
Name: strings.Join(names, " "),
|
||||||
|
ID: user.Id,
|
||||||
|
}
|
||||||
|
}
|
||||||
46
management/server/idp/jumpcloud_test.go
Normal file
46
management/server/idp/jumpcloud_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewJumpCloudManager(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
name string
|
||||||
|
inputConfig JumpCloudClientConfig
|
||||||
|
assertErrFunc require.ErrorAssertionFunc
|
||||||
|
assertErrFuncMessage string
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultTestConfig := JumpCloudClientConfig{
|
||||||
|
APIToken: "test123",
|
||||||
|
}
|
||||||
|
|
||||||
|
testCase1 := test{
|
||||||
|
name: "Good Configuration",
|
||||||
|
inputConfig: defaultTestConfig,
|
||||||
|
assertErrFunc: require.NoError,
|
||||||
|
assertErrFuncMessage: "shouldn't return error",
|
||||||
|
}
|
||||||
|
|
||||||
|
testCase2Config := defaultTestConfig
|
||||||
|
testCase2Config.APIToken = ""
|
||||||
|
|
||||||
|
testCase2 := test{
|
||||||
|
name: "Missing APIToken Configuration",
|
||||||
|
inputConfig: testCase2Config,
|
||||||
|
assertErrFunc: require.Error,
|
||||||
|
assertErrFuncMessage: "should return error when field empty",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range []test{testCase1, testCase2} {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
_, err := NewJumpCloudManager(testCase.inputConfig, &telemetry.MockAppMetrics{})
|
||||||
|
testCase.assertErrFunc(t, err, testCase.assertErrFuncMessage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
package idp
|
package idp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -18,11 +16,6 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
wtAccountID = "wt_account_id"
|
|
||||||
wtPendingInvite = "wt_pending_invite"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KeycloakManager keycloak manager client instance.
|
// KeycloakManager keycloak manager client instance.
|
||||||
type KeycloakManager struct {
|
type KeycloakManager struct {
|
||||||
adminEndpoint string
|
adminEndpoint string
|
||||||
@@ -51,28 +44,10 @@ type KeycloakCredentials struct {
|
|||||||
appMetrics telemetry.AppMetrics
|
appMetrics telemetry.AppMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
// keycloakUserCredential describe the authentication method for,
|
|
||||||
// newly created user profile.
|
|
||||||
type keycloakUserCredential struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
Temporary bool `json:"temporary"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// keycloakUserAttributes holds additional user data fields.
|
// keycloakUserAttributes holds additional user data fields.
|
||||||
type keycloakUserAttributes map[string][]string
|
type keycloakUserAttributes map[string][]string
|
||||||
|
|
||||||
// createUserRequest is a user create request.
|
// keycloakProfile represents a keycloak user profile response.
|
||||||
type keycloakCreateUserRequest struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
EmailVerified bool `json:"emailVerified"`
|
|
||||||
Credentials []keycloakUserCredential `json:"credentials"`
|
|
||||||
Attributes keycloakUserAttributes `json:"attributes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// keycloakProfile represents an keycloak user profile response.
|
|
||||||
type keycloakProfile struct {
|
type keycloakProfile struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
CreatedTimestamp int64 `json:"createdTimestamp"`
|
CreatedTimestamp int64 `json:"createdTimestamp"`
|
||||||
@@ -230,62 +205,8 @@ func (kc *KeycloakCredentials) Authenticate() (JWTToken, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser creates a new user in keycloak Idp and sends an invite.
|
// CreateUser creates a new user in keycloak Idp and sends an invite.
|
||||||
func (km *KeycloakManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
|
func (km *KeycloakManager) CreateUser(_, _, _, _ string) (*UserData, error) {
|
||||||
jwtToken, err := km.credentials.Authenticate()
|
return nil, fmt.Errorf("method CreateUser not implemented")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
invite := true
|
|
||||||
appMetadata := AppMetadata{
|
|
||||||
WTAccountID: accountID,
|
|
||||||
WTPendingInvite: &invite,
|
|
||||||
}
|
|
||||||
|
|
||||||
payloadString, err := buildKeycloakCreateUserRequestPayload(email, name, appMetadata)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqURL := fmt.Sprintf("%s/users", km.adminEndpoint)
|
|
||||||
payload := strings.NewReader(payloadString)
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPost, reqURL, payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
|
||||||
req.Header.Add("content-type", "application/json")
|
|
||||||
|
|
||||||
if km.appMetrics != nil {
|
|
||||||
km.appMetrics.IDPMetrics().CountCreateUser()
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := km.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
if km.appMetrics != nil {
|
|
||||||
km.appMetrics.IDPMetrics().CountRequestError()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusCreated {
|
|
||||||
if km.appMetrics != nil {
|
|
||||||
km.appMetrics.IDPMetrics().CountRequestStatusError()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("unable to create user, statusCode %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
locationHeader := resp.Header.Get("location")
|
|
||||||
userID, err := extractUserIDFromLocationHeader(locationHeader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return km.GetUserDataByID(userID, appMetadata)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail searches users with a given email.
|
// GetUserByEmail searches users with a given email.
|
||||||
@@ -319,7 +240,7 @@ func (km *KeycloakManager) GetUserByEmail(email string) ([]*UserData, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetUserDataByID requests user data from keycloak via ID.
|
// GetUserDataByID requests user data from keycloak via ID.
|
||||||
func (km *KeycloakManager) GetUserDataByID(userID string, appMetadata AppMetadata) (*UserData, error) {
|
func (km *KeycloakManager) GetUserDataByID(userID string, _ AppMetadata) (*UserData, error) {
|
||||||
body, err := km.get("users/"+userID, nil)
|
body, err := km.get("users/"+userID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -338,12 +259,9 @@ func (km *KeycloakManager) GetUserDataByID(userID string, appMetadata AppMetadat
|
|||||||
return profile.userData(), nil
|
return profile.userData(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccount returns all the users for a given profile.
|
// GetAccount returns all the users for a given account profile.
|
||||||
func (km *KeycloakManager) GetAccount(accountID string) ([]*UserData, error) {
|
func (km *KeycloakManager) GetAccount(accountID string) ([]*UserData, error) {
|
||||||
q := url.Values{}
|
profiles, err := km.fetchAllUserProfiles()
|
||||||
q.Add("q", wtAccountID+":"+accountID)
|
|
||||||
|
|
||||||
body, err := km.get("users", q)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -352,15 +270,12 @@ func (km *KeycloakManager) GetAccount(accountID string) ([]*UserData, error) {
|
|||||||
km.appMetrics.IDPMetrics().CountGetAccount()
|
km.appMetrics.IDPMetrics().CountGetAccount()
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles := make([]keycloakProfile, 0)
|
|
||||||
err = km.helper.Unmarshal(body, &profiles)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
users := make([]*UserData, 0)
|
users := make([]*UserData, 0)
|
||||||
for _, profile := range profiles {
|
for _, profile := range profiles {
|
||||||
users = append(users, profile.userData())
|
userData := profile.userData()
|
||||||
|
userData.AppMetadata.WTAccountID = accountID
|
||||||
|
|
||||||
|
users = append(users, userData)
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, nil
|
return users, nil
|
||||||
@@ -369,15 +284,7 @@ func (km *KeycloakManager) GetAccount(accountID string) ([]*UserData, error) {
|
|||||||
// GetAllAccounts gets all registered accounts with corresponding user data.
|
// GetAllAccounts gets all registered accounts with corresponding user data.
|
||||||
// It returns a list of users indexed by accountID.
|
// It returns a list of users indexed by accountID.
|
||||||
func (km *KeycloakManager) GetAllAccounts() (map[string][]*UserData, error) {
|
func (km *KeycloakManager) GetAllAccounts() (map[string][]*UserData, error) {
|
||||||
totalUsers, err := km.totalUsersCount()
|
profiles, err := km.fetchAllUserProfiles()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
q := url.Values{}
|
|
||||||
q.Add("max", fmt.Sprint(*totalUsers))
|
|
||||||
|
|
||||||
body, err := km.get("users", q)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -386,78 +293,17 @@ func (km *KeycloakManager) GetAllAccounts() (map[string][]*UserData, error) {
|
|||||||
km.appMetrics.IDPMetrics().CountGetAllAccounts()
|
km.appMetrics.IDPMetrics().CountGetAllAccounts()
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles := make([]keycloakProfile, 0)
|
|
||||||
err = km.helper.Unmarshal(body, &profiles)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
indexedUsers := make(map[string][]*UserData)
|
indexedUsers := make(map[string][]*UserData)
|
||||||
for _, profile := range profiles {
|
for _, profile := range profiles {
|
||||||
userData := profile.userData()
|
userData := profile.userData()
|
||||||
|
indexedUsers[UnsetAccountID] = append(indexedUsers[UnsetAccountID], userData)
|
||||||
accountID := userData.AppMetadata.WTAccountID
|
|
||||||
if accountID != "" {
|
|
||||||
if _, ok := indexedUsers[accountID]; !ok {
|
|
||||||
indexedUsers[accountID] = make([]*UserData, 0)
|
|
||||||
}
|
|
||||||
indexedUsers[accountID] = append(indexedUsers[accountID], userData)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexedUsers, nil
|
return indexedUsers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
||||||
func (km *KeycloakManager) UpdateUserAppMetadata(userID string, appMetadata AppMetadata) error {
|
func (km *KeycloakManager) UpdateUserAppMetadata(_ string, _ AppMetadata) error {
|
||||||
jwtToken, err := km.credentials.Authenticate()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs := keycloakUserAttributes{}
|
|
||||||
attrs.Set(wtAccountID, appMetadata.WTAccountID)
|
|
||||||
if appMetadata.WTPendingInvite != nil {
|
|
||||||
attrs.Set(wtPendingInvite, strconv.FormatBool(*appMetadata.WTPendingInvite))
|
|
||||||
} else {
|
|
||||||
attrs.Set(wtPendingInvite, "false")
|
|
||||||
}
|
|
||||||
|
|
||||||
reqURL := fmt.Sprintf("%s/users/%s", km.adminEndpoint, userID)
|
|
||||||
data, err := km.helper.Marshal(map[string]any{
|
|
||||||
"attributes": attrs,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
payload := strings.NewReader(string(data))
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPut, reqURL, payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
|
||||||
req.Header.Add("content-type", "application/json")
|
|
||||||
|
|
||||||
log.Debugf("updating IdP metadata for user %s", userID)
|
|
||||||
|
|
||||||
resp, err := km.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
if km.appMetrics != nil {
|
|
||||||
km.appMetrics.IDPMetrics().CountRequestError()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if km.appMetrics != nil {
|
|
||||||
km.appMetrics.IDPMetrics().CountUpdateUserAppMetadata()
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusNoContent {
|
|
||||||
return fmt.Errorf("unable to update the appMetadata, statusCode %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,7 +313,7 @@ func (km *KeycloakManager) InviteUserByID(_ string) error {
|
|||||||
return fmt.Errorf("method InviteUserByID not implemented")
|
return fmt.Errorf("method InviteUserByID not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUser from Keycloack
|
// DeleteUser from Keycloak by user ID.
|
||||||
func (km *KeycloakManager) DeleteUser(userID string) error {
|
func (km *KeycloakManager) DeleteUser(userID string) error {
|
||||||
jwtToken, err := km.credentials.Authenticate()
|
jwtToken, err := km.credentials.Authenticate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -475,7 +321,6 @@ func (km *KeycloakManager) DeleteUser(userID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reqURL := fmt.Sprintf("%s/users/%s", km.adminEndpoint, url.QueryEscape(userID))
|
reqURL := fmt.Sprintf("%s/users/%s", km.adminEndpoint, url.QueryEscape(userID))
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodDelete, reqURL, nil)
|
req, err := http.NewRequest(http.MethodDelete, reqURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -508,32 +353,27 @@ func (km *KeycloakManager) DeleteUser(userID string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildKeycloakCreateUserRequestPayload(email string, name string, appMetadata AppMetadata) (string, error) {
|
func (km *KeycloakManager) fetchAllUserProfiles() ([]keycloakProfile, error) {
|
||||||
attrs := keycloakUserAttributes{}
|
totalUsers, err := km.totalUsersCount()
|
||||||
attrs.Set(wtAccountID, appMetadata.WTAccountID)
|
|
||||||
attrs.Set(wtPendingInvite, strconv.FormatBool(*appMetadata.WTPendingInvite))
|
|
||||||
|
|
||||||
req := &keycloakCreateUserRequest{
|
|
||||||
Email: email,
|
|
||||||
Username: name,
|
|
||||||
Enabled: true,
|
|
||||||
EmailVerified: true,
|
|
||||||
Credentials: []keycloakUserCredential{
|
|
||||||
{
|
|
||||||
Type: "password",
|
|
||||||
Value: GeneratePassword(8, 1, 1, 1),
|
|
||||||
Temporary: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Attributes: attrs,
|
|
||||||
}
|
|
||||||
|
|
||||||
str, err := json.Marshal(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(str), nil
|
q := url.Values{}
|
||||||
|
q.Add("max", fmt.Sprint(*totalUsers))
|
||||||
|
|
||||||
|
body, err := km.get("users", q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles := make([]keycloakProfile, 0)
|
||||||
|
err = km.helper.Unmarshal(body, &profiles)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return profiles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// get perform Get requests.
|
// get perform Get requests.
|
||||||
@@ -588,53 +428,11 @@ func (km *KeycloakManager) totalUsersCount() (*int, error) {
|
|||||||
return &count, nil
|
return &count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractUserIDFromLocationHeader extracts the user ID from the location,
|
|
||||||
// header once the user is created successfully
|
|
||||||
func extractUserIDFromLocationHeader(locationHeader string) (string, error) {
|
|
||||||
userURL, err := url.Parse(locationHeader)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.Base(userURL.Path), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// userData construct user data from keycloak profile.
|
// userData construct user data from keycloak profile.
|
||||||
func (kp keycloakProfile) userData() *UserData {
|
func (kp keycloakProfile) userData() *UserData {
|
||||||
accountID := kp.Attributes.Get(wtAccountID)
|
|
||||||
pendingInvite, err := strconv.ParseBool(kp.Attributes.Get(wtPendingInvite))
|
|
||||||
if err != nil {
|
|
||||||
pendingInvite = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return &UserData{
|
return &UserData{
|
||||||
Email: kp.Email,
|
Email: kp.Email,
|
||||||
Name: kp.Username,
|
Name: kp.Username,
|
||||||
ID: kp.ID,
|
ID: kp.ID,
|
||||||
AppMetadata: AppMetadata{
|
|
||||||
WTAccountID: accountID,
|
|
||||||
WTPendingInvite: &pendingInvite,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets the key to value. It replaces any existing
|
|
||||||
// values.
|
|
||||||
func (ka keycloakUserAttributes) Set(key, value string) {
|
|
||||||
ka[key] = []string{value}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the first value associated with the given key.
|
|
||||||
// If there are no values associated with the key, Get returns
|
|
||||||
// the empty string.
|
|
||||||
func (ka keycloakUserAttributes) Get(key string) string {
|
|
||||||
if ka == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
values := ka[key]
|
|
||||||
if len(values) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return values[0]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -84,15 +84,6 @@ func TestNewKeycloakManager(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockKeycloakCredentials struct {
|
|
||||||
jwtToken JWTToken
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *mockKeycloakCredentials) Authenticate() (JWTToken, error) {
|
|
||||||
return mc.jwtToken, mc.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKeycloakRequestJWTToken(t *testing.T) {
|
func TestKeycloakRequestJWTToken(t *testing.T) {
|
||||||
|
|
||||||
type requestJWTTokenTest struct {
|
type requestJWTTokenTest struct {
|
||||||
@@ -316,108 +307,3 @@ func TestKeycloakAuthenticate(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeycloakUpdateUserAppMetadata(t *testing.T) {
|
|
||||||
type updateUserAppMetadataTest struct {
|
|
||||||
name string
|
|
||||||
inputReqBody string
|
|
||||||
expectedReqBody string
|
|
||||||
appMetadata AppMetadata
|
|
||||||
statusCode int
|
|
||||||
helper ManagerHelper
|
|
||||||
managerCreds ManagerCredentials
|
|
||||||
assertErrFunc assert.ErrorAssertionFunc
|
|
||||||
assertErrFuncMessage string
|
|
||||||
}
|
|
||||||
|
|
||||||
appMetadata := AppMetadata{WTAccountID: "ok"}
|
|
||||||
|
|
||||||
updateUserAppMetadataTestCase1 := updateUserAppMetadataTest{
|
|
||||||
name: "Bad Authentication",
|
|
||||||
expectedReqBody: "",
|
|
||||||
appMetadata: appMetadata,
|
|
||||||
statusCode: 400,
|
|
||||||
helper: JsonParser{},
|
|
||||||
managerCreds: &mockKeycloakCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
err: fmt.Errorf("error"),
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.Error,
|
|
||||||
assertErrFuncMessage: "should return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUserAppMetadataTestCase2 := updateUserAppMetadataTest{
|
|
||||||
name: "Bad Status Code",
|
|
||||||
expectedReqBody: fmt.Sprintf("{\"attributes\":{\"wt_account_id\":[\"%s\"],\"wt_pending_invite\":[\"false\"]}}", appMetadata.WTAccountID),
|
|
||||||
appMetadata: appMetadata,
|
|
||||||
statusCode: 400,
|
|
||||||
helper: JsonParser{},
|
|
||||||
managerCreds: &mockKeycloakCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.Error,
|
|
||||||
assertErrFuncMessage: "should return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUserAppMetadataTestCase3 := updateUserAppMetadataTest{
|
|
||||||
name: "Bad Response Parsing",
|
|
||||||
statusCode: 400,
|
|
||||||
helper: &mockJsonParser{marshalErrorString: "error"},
|
|
||||||
managerCreds: &mockKeycloakCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.Error,
|
|
||||||
assertErrFuncMessage: "should return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUserAppMetadataTestCase4 := updateUserAppMetadataTest{
|
|
||||||
name: "Good request",
|
|
||||||
expectedReqBody: fmt.Sprintf("{\"attributes\":{\"wt_account_id\":[\"%s\"],\"wt_pending_invite\":[\"false\"]}}", appMetadata.WTAccountID),
|
|
||||||
appMetadata: appMetadata,
|
|
||||||
statusCode: 204,
|
|
||||||
helper: JsonParser{},
|
|
||||||
managerCreds: &mockKeycloakCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.NoError,
|
|
||||||
assertErrFuncMessage: "shouldn't return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
invite := true
|
|
||||||
updateUserAppMetadataTestCase5 := updateUserAppMetadataTest{
|
|
||||||
name: "Update Pending Invite",
|
|
||||||
expectedReqBody: fmt.Sprintf("{\"attributes\":{\"wt_account_id\":[\"%s\"],\"wt_pending_invite\":[\"true\"]}}", appMetadata.WTAccountID),
|
|
||||||
appMetadata: AppMetadata{
|
|
||||||
WTAccountID: "ok",
|
|
||||||
WTPendingInvite: &invite,
|
|
||||||
},
|
|
||||||
statusCode: 204,
|
|
||||||
helper: JsonParser{},
|
|
||||||
managerCreds: &mockKeycloakCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.NoError,
|
|
||||||
assertErrFuncMessage: "shouldn't return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range []updateUserAppMetadataTest{updateUserAppMetadataTestCase1, updateUserAppMetadataTestCase2,
|
|
||||||
updateUserAppMetadataTestCase3, updateUserAppMetadataTestCase4, updateUserAppMetadataTestCase5} {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
reqClient := mockHTTPClient{
|
|
||||||
resBody: testCase.inputReqBody,
|
|
||||||
code: testCase.statusCode,
|
|
||||||
}
|
|
||||||
|
|
||||||
manager := &KeycloakManager{
|
|
||||||
httpClient: &reqClient,
|
|
||||||
credentials: testCase.managerCreds,
|
|
||||||
helper: testCase.helper,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := manager.UpdateUserAppMetadata("1", testCase.appMetadata)
|
|
||||||
testCase.assertErrFunc(t, err, testCase.assertErrFuncMessage)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.expectedReqBody, reqClient.reqBody, "request body should match")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
|
||||||
"github.com/okta/okta-sdk-golang/v2/okta"
|
"github.com/okta/okta-sdk-golang/v2/okta"
|
||||||
"github.com/okta/okta-sdk-golang/v2/okta/query"
|
"github.com/okta/okta-sdk-golang/v2/okta/query"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OktaManager okta manager client instance.
|
// OktaManager okta manager client instance.
|
||||||
@@ -76,11 +77,6 @@ func NewOktaManager(config OktaClientConfig, appMetrics telemetry.AppMetrics) (*
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = updateUserProfileSchema(client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
credentials := &OktaCredentials{
|
credentials := &OktaCredentials{
|
||||||
clientConfig: config,
|
clientConfig: config,
|
||||||
httpClient: httpClient,
|
httpClient: httpClient,
|
||||||
@@ -103,49 +99,8 @@ func (oc *OktaCredentials) Authenticate() (JWTToken, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser creates a new user in okta Idp and sends an invitation.
|
// CreateUser creates a new user in okta Idp and sends an invitation.
|
||||||
func (om *OktaManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
|
func (om *OktaManager) CreateUser(_, _, _, _ string) (*UserData, error) {
|
||||||
var (
|
return nil, fmt.Errorf("method CreateUser not implemented")
|
||||||
sendEmail = true
|
|
||||||
activate = true
|
|
||||||
userProfile = okta.UserProfile{
|
|
||||||
"email": email,
|
|
||||||
"login": email,
|
|
||||||
wtAccountID: accountID,
|
|
||||||
wtPendingInvite: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
fields := strings.Fields(name)
|
|
||||||
if n := len(fields); n > 0 {
|
|
||||||
userProfile["firstName"] = strings.Join(fields[:n-1], " ")
|
|
||||||
userProfile["lastName"] = fields[n-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
user, resp, err := om.client.User.CreateUser(context.Background(),
|
|
||||||
okta.CreateUserRequest{
|
|
||||||
Profile: &userProfile,
|
|
||||||
},
|
|
||||||
&query.Params{
|
|
||||||
Activate: &activate,
|
|
||||||
SendEmail: &sendEmail,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if om.appMetrics != nil {
|
|
||||||
om.appMetrics.IDPMetrics().CountCreateUser()
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
if om.appMetrics != nil {
|
|
||||||
om.appMetrics.IDPMetrics().CountRequestStatusError()
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unable to create user, statusCode %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseOktaUser(user)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserDataByID requests user data from keycloak via ID.
|
// GetUserDataByID requests user data from keycloak via ID.
|
||||||
@@ -166,7 +121,13 @@ func (om *OktaManager) GetUserDataByID(userID string, appMetadata AppMetadata) (
|
|||||||
return nil, fmt.Errorf("unable to get user %s, statusCode %d", userID, resp.StatusCode)
|
return nil, fmt.Errorf("unable to get user %s, statusCode %d", userID, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseOktaUser(user)
|
userData, err := parseOktaUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
userData.AppMetadata = appMetadata
|
||||||
|
|
||||||
|
return userData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail searches users with a given email.
|
// GetUserByEmail searches users with a given email.
|
||||||
@@ -200,8 +161,7 @@ func (om *OktaManager) GetUserByEmail(email string) ([]*UserData, error) {
|
|||||||
|
|
||||||
// GetAccount returns all the users for a given profile.
|
// GetAccount returns all the users for a given profile.
|
||||||
func (om *OktaManager) GetAccount(accountID string) ([]*UserData, error) {
|
func (om *OktaManager) GetAccount(accountID string) ([]*UserData, error) {
|
||||||
search := fmt.Sprintf("profile.wt_account_id eq %q", accountID)
|
users, err := om.getAllUsers()
|
||||||
users, resp, err := om.client.User.ListUsers(context.Background(), &query.Params{Search: search})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -210,38 +170,40 @@ func (om *OktaManager) GetAccount(accountID string) ([]*UserData, error) {
|
|||||||
om.appMetrics.IDPMetrics().CountGetAccount()
|
om.appMetrics.IDPMetrics().CountGetAccount()
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
for index, user := range users {
|
||||||
if om.appMetrics != nil {
|
user.AppMetadata.WTAccountID = accountID
|
||||||
om.appMetrics.IDPMetrics().CountRequestStatusError()
|
users[index] = user
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unable to get account, statusCode %d", resp.StatusCode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
list := make([]*UserData, 0)
|
return users, nil
|
||||||
for _, user := range users {
|
|
||||||
userData, err := parseOktaUser(user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
list = append(list, userData)
|
|
||||||
}
|
|
||||||
|
|
||||||
return list, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllAccounts gets all registered accounts with corresponding user data.
|
// GetAllAccounts gets all registered accounts with corresponding user data.
|
||||||
// It returns a list of users indexed by accountID.
|
// It returns a list of users indexed by accountID.
|
||||||
func (om *OktaManager) GetAllAccounts() (map[string][]*UserData, error) {
|
func (om *OktaManager) GetAllAccounts() (map[string][]*UserData, error) {
|
||||||
users, resp, err := om.client.User.ListUsers(context.Background(), nil)
|
users, err := om.getAllUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
indexedUsers := make(map[string][]*UserData)
|
||||||
|
indexedUsers[UnsetAccountID] = append(indexedUsers[UnsetAccountID], users...)
|
||||||
|
|
||||||
if om.appMetrics != nil {
|
if om.appMetrics != nil {
|
||||||
om.appMetrics.IDPMetrics().CountGetAllAccounts()
|
om.appMetrics.IDPMetrics().CountGetAllAccounts()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return indexedUsers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAllUsers returns all users in an Okta account.
|
||||||
|
func (om *OktaManager) getAllUsers() ([]*UserData, error) {
|
||||||
|
qp := query.NewQueryParams(query.WithLimit(200))
|
||||||
|
userList, resp, err := om.client.User.ListUsers(context.Background(), qp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
if om.appMetrics != nil {
|
if om.appMetrics != nil {
|
||||||
om.appMetrics.IDPMetrics().CountRequestStatusError()
|
om.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||||
@@ -249,67 +211,38 @@ func (om *OktaManager) GetAllAccounts() (map[string][]*UserData, error) {
|
|||||||
return nil, fmt.Errorf("unable to get all accounts, statusCode %d", resp.StatusCode)
|
return nil, fmt.Errorf("unable to get all accounts, statusCode %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
indexedUsers := make(map[string][]*UserData)
|
for resp.HasNextPage() {
|
||||||
for _, user := range users {
|
paginatedUsers := make([]*okta.User, 0)
|
||||||
|
resp, err = resp.Next(context.Background(), &paginatedUsers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
if om.appMetrics != nil {
|
||||||
|
om.appMetrics.IDPMetrics().CountRequestStatusError()
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to get all accounts, statusCode %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
userList = append(userList, paginatedUsers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
users := make([]*UserData, 0, len(userList))
|
||||||
|
for _, user := range userList {
|
||||||
userData, err := parseOktaUser(user)
|
userData, err := parseOktaUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accountID := userData.AppMetadata.WTAccountID
|
users = append(users, userData)
|
||||||
if accountID != "" {
|
|
||||||
if _, ok := indexedUsers[accountID]; !ok {
|
|
||||||
indexedUsers[accountID] = make([]*UserData, 0)
|
|
||||||
}
|
|
||||||
indexedUsers[accountID] = append(indexedUsers[accountID], userData)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexedUsers, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
||||||
func (om *OktaManager) UpdateUserAppMetadata(userID string, appMetadata AppMetadata) error {
|
func (om *OktaManager) UpdateUserAppMetadata(userID string, appMetadata AppMetadata) error {
|
||||||
user, resp, err := om.client.User.GetUser(context.Background(), userID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
if om.appMetrics != nil {
|
|
||||||
om.appMetrics.IDPMetrics().CountRequestStatusError()
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unable to update user, statusCode %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
profile := *user.Profile
|
|
||||||
|
|
||||||
if appMetadata.WTPendingInvite != nil {
|
|
||||||
profile[wtPendingInvite] = *appMetadata.WTPendingInvite
|
|
||||||
}
|
|
||||||
|
|
||||||
if appMetadata.WTAccountID != "" {
|
|
||||||
profile[wtAccountID] = appMetadata.WTAccountID
|
|
||||||
}
|
|
||||||
|
|
||||||
user.Profile = &profile
|
|
||||||
_, resp, err = om.client.User.UpdateUser(context.Background(), userID, *user, nil)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if om.appMetrics != nil {
|
|
||||||
om.appMetrics.IDPMetrics().CountUpdateUserAppMetadata()
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
if om.appMetrics != nil {
|
|
||||||
om.appMetrics.IDPMetrics().CountRequestStatusError()
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unable to update user, statusCode %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,60 +274,12 @@ func (om *OktaManager) DeleteUser(userID string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateUserProfileSchema updates the Okta user schema to include custom fields,
|
|
||||||
// wt_account_id and wt_pending_invite.
|
|
||||||
func updateUserProfileSchema(client *okta.Client) error {
|
|
||||||
// Ensure Okta doesn't enforce user input for these fields, as they are solely used by Netbird
|
|
||||||
userPermissions := []*okta.UserSchemaAttributePermission{{Action: "HIDE", Principal: "SELF"}}
|
|
||||||
|
|
||||||
_, resp, err := client.UserSchema.UpdateUserProfile(
|
|
||||||
context.Background(),
|
|
||||||
"default",
|
|
||||||
okta.UserSchema{
|
|
||||||
Definitions: &okta.UserSchemaDefinitions{
|
|
||||||
Custom: &okta.UserSchemaPublic{
|
|
||||||
Id: "#custom",
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]*okta.UserSchemaAttribute{
|
|
||||||
wtAccountID: {
|
|
||||||
MaxLength: 100,
|
|
||||||
MinLength: 1,
|
|
||||||
Required: new(bool),
|
|
||||||
Scope: "NONE",
|
|
||||||
Title: "Wt Account Id",
|
|
||||||
Type: "string",
|
|
||||||
Permissions: userPermissions,
|
|
||||||
},
|
|
||||||
wtPendingInvite: {
|
|
||||||
Required: new(bool),
|
|
||||||
Scope: "NONE",
|
|
||||||
Title: "Wt Pending Invite",
|
|
||||||
Type: "boolean",
|
|
||||||
Permissions: userPermissions,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("unable to update user profile schema, statusCode %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseOktaUserToUserData parse okta user to UserData.
|
// parseOktaUserToUserData parse okta user to UserData.
|
||||||
func parseOktaUser(user *okta.User) (*UserData, error) {
|
func parseOktaUser(user *okta.User) (*UserData, error) {
|
||||||
var oktaUser struct {
|
var oktaUser struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
FirstName string `json:"firstName"`
|
FirstName string `json:"firstName"`
|
||||||
LastName string `json:"lastName"`
|
LastName string `json:"lastName"`
|
||||||
AccountID string `json:"wt_account_id"`
|
|
||||||
PendingInvite bool `json:"wt_pending_invite"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
@@ -418,9 +303,5 @@ func parseOktaUser(user *okta.User) (*UserData, error) {
|
|||||||
Email: oktaUser.Email,
|
Email: oktaUser.Email,
|
||||||
Name: strings.Join([]string{oktaUser.FirstName, oktaUser.LastName}, " "),
|
Name: strings.Join([]string{oktaUser.FirstName, oktaUser.LastName}, " "),
|
||||||
ID: user.Id,
|
ID: user.Id,
|
||||||
AppMetadata: AppMetadata{
|
|
||||||
WTAccountID: oktaUser.AccountID,
|
|
||||||
WTPendingInvite: &oktaUser.PendingInvite,
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
package idp
|
package idp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/okta/okta-sdk-golang/v2/okta"
|
"github.com/okta/okta-sdk-golang/v2/okta"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseOktaUser(t *testing.T) {
|
func TestParseOktaUser(t *testing.T) {
|
||||||
type parseOktaUserTest struct {
|
type parseOktaUserTest struct {
|
||||||
name string
|
name string
|
||||||
invite bool
|
|
||||||
inputProfile *okta.User
|
inputProfile *okta.User
|
||||||
expectedUserData *UserData
|
expectedUserData *UserData
|
||||||
assertErrFunc assert.ErrorAssertionFunc
|
assertErrFunc assert.ErrorAssertionFunc
|
||||||
@@ -17,15 +17,12 @@ func TestParseOktaUser(t *testing.T) {
|
|||||||
|
|
||||||
parseOktaTestCase1 := parseOktaUserTest{
|
parseOktaTestCase1 := parseOktaUserTest{
|
||||||
name: "Good Request",
|
name: "Good Request",
|
||||||
invite: true,
|
|
||||||
inputProfile: &okta.User{
|
inputProfile: &okta.User{
|
||||||
Id: "123",
|
Id: "123",
|
||||||
Profile: &okta.UserProfile{
|
Profile: &okta.UserProfile{
|
||||||
"email": "test@example.com",
|
"email": "test@example.com",
|
||||||
"firstName": "John",
|
"firstName": "John",
|
||||||
"lastName": "Doe",
|
"lastName": "Doe",
|
||||||
"wt_account_id": "456",
|
|
||||||
"wt_pending_invite": true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedUserData: &UserData{
|
expectedUserData: &UserData{
|
||||||
@@ -41,36 +38,17 @@ func TestParseOktaUser(t *testing.T) {
|
|||||||
|
|
||||||
parseOktaTestCase2 := parseOktaUserTest{
|
parseOktaTestCase2 := parseOktaUserTest{
|
||||||
name: "Invalid okta user",
|
name: "Invalid okta user",
|
||||||
invite: true,
|
|
||||||
inputProfile: nil,
|
inputProfile: nil,
|
||||||
expectedUserData: nil,
|
expectedUserData: nil,
|
||||||
assertErrFunc: assert.Error,
|
assertErrFunc: assert.Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
parseOktaTestCase3 := parseOktaUserTest{
|
for _, testCase := range []parseOktaUserTest{parseOktaTestCase1, parseOktaTestCase2} {
|
||||||
name: "Invalid pending invite type",
|
|
||||||
invite: false,
|
|
||||||
inputProfile: &okta.User{
|
|
||||||
Id: "123",
|
|
||||||
Profile: &okta.UserProfile{
|
|
||||||
"email": "test@example.com",
|
|
||||||
"firstName": "John",
|
|
||||||
"lastName": "Doe",
|
|
||||||
"wt_account_id": "456",
|
|
||||||
"wt_pending_invite": "true",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedUserData: nil,
|
|
||||||
assertErrFunc: assert.Error,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range []parseOktaUserTest{parseOktaTestCase1, parseOktaTestCase2, parseOktaTestCase3} {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
userData, err := parseOktaUser(testCase.inputProfile)
|
userData, err := parseOktaUser(testCase.inputProfile)
|
||||||
testCase.assertErrFunc(t, err, testCase.assertErrFunc)
|
testCase.assertErrFunc(t, err, testCase.assertErrFunc)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
testCase.expectedUserData.AppMetadata.WTPendingInvite = &testCase.invite
|
|
||||||
assert.True(t, userDataEqual(testCase.expectedUserData, userData), "user data should match")
|
assert.True(t, userDataEqual(testCase.expectedUserData, userData), "user data should match")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -83,13 +61,5 @@ func userDataEqual(a, b *UserData) bool {
|
|||||||
if a.Email != b.Email || a.Name != b.Name || a.ID != b.ID {
|
if a.Email != b.Email || a.Name != b.Name || a.ID != b.ID {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if a.AppMetadata.WTAccountID != b.AppMetadata.WTAccountID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.AppMetadata.WTPendingInvite != nil && b.AppMetadata.WTPendingInvite != nil &&
|
|
||||||
*a.AppMetadata.WTPendingInvite != *b.AppMetadata.WTPendingInvite {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
package idp
|
package idp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ZitadelManager zitadel manager client instance.
|
// ZitadelManager zitadel manager client instance.
|
||||||
@@ -67,12 +65,6 @@ type zitadelUser struct {
|
|||||||
|
|
||||||
type zitadelAttributes map[string][]map[string]any
|
type zitadelAttributes map[string][]map[string]any
|
||||||
|
|
||||||
// zitadelMetadata holds additional user data.
|
|
||||||
type zitadelMetadata struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// zitadelProfile represents an zitadel user profile response.
|
// zitadelProfile represents an zitadel user profile response.
|
||||||
type zitadelProfile struct {
|
type zitadelProfile struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
@@ -81,7 +73,6 @@ type zitadelProfile struct {
|
|||||||
PreferredLoginName string `json:"preferredLoginName"`
|
PreferredLoginName string `json:"preferredLoginName"`
|
||||||
LoginNames []string `json:"loginNames"`
|
LoginNames []string `json:"loginNames"`
|
||||||
Human *zitadelUser `json:"human"`
|
Human *zitadelUser `json:"human"`
|
||||||
Metadata []zitadelMetadata
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewZitadelManager creates a new instance of the ZitadelManager.
|
// NewZitadelManager creates a new instance of the ZitadelManager.
|
||||||
@@ -234,42 +225,8 @@ func (zc *ZitadelCredentials) Authenticate() (JWTToken, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser creates a new user in zitadel Idp and sends an invite.
|
// CreateUser creates a new user in zitadel Idp and sends an invite.
|
||||||
func (zm *ZitadelManager) CreateUser(email, name, accountID, invitedByEmail string) (*UserData, error) {
|
func (zm *ZitadelManager) CreateUser(_, _, _, _ string) (*UserData, error) {
|
||||||
payload, err := buildZitadelCreateUserRequestPayload(email, name)
|
return nil, fmt.Errorf("method CreateUser not implemented")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := zm.post("users/human/_import", payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if zm.appMetrics != nil {
|
|
||||||
zm.appMetrics.IDPMetrics().CountCreateUser()
|
|
||||||
}
|
|
||||||
|
|
||||||
var result struct {
|
|
||||||
UserID string `json:"userId"`
|
|
||||||
}
|
|
||||||
err = zm.helper.Unmarshal(body, &result)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
invite := true
|
|
||||||
appMetadata := AppMetadata{
|
|
||||||
WTAccountID: accountID,
|
|
||||||
WTPendingInvite: &invite,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add metadata to new user
|
|
||||||
err = zm.UpdateUserAppMetadata(result.UserID, appMetadata)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return zm.GetUserDataByID(result.UserID, appMetadata)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail searches users with a given email.
|
// GetUserByEmail searches users with a given email.
|
||||||
@@ -307,12 +264,6 @@ func (zm *ZitadelManager) GetUserByEmail(email string) ([]*UserData, error) {
|
|||||||
|
|
||||||
users := make([]*UserData, 0)
|
users := make([]*UserData, 0)
|
||||||
for _, profile := range profiles.Result {
|
for _, profile := range profiles.Result {
|
||||||
metadata, err := zm.getUserMetadata(profile.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
profile.Metadata = metadata
|
|
||||||
|
|
||||||
users = append(users, profile.userData())
|
users = append(users, profile.userData())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,18 +287,15 @@ func (zm *ZitadelManager) GetUserDataByID(userID string, appMetadata AppMetadata
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata, err := zm.getUserMetadata(userID)
|
userData := profile.User.userData()
|
||||||
if err != nil {
|
userData.AppMetadata = appMetadata
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
profile.User.Metadata = metadata
|
|
||||||
|
|
||||||
return profile.User.userData(), nil
|
return userData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccount returns all the users for a given profile.
|
// GetAccount returns all the users for a given profile.
|
||||||
func (zm *ZitadelManager) GetAccount(accountID string) ([]*UserData, error) {
|
func (zm *ZitadelManager) GetAccount(accountID string) ([]*UserData, error) {
|
||||||
accounts, err := zm.GetAllAccounts()
|
body, err := zm.post("users/_search", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -356,7 +304,21 @@ func (zm *ZitadelManager) GetAccount(accountID string) ([]*UserData, error) {
|
|||||||
zm.appMetrics.IDPMetrics().CountGetAccount()
|
zm.appMetrics.IDPMetrics().CountGetAccount()
|
||||||
}
|
}
|
||||||
|
|
||||||
return accounts[accountID], nil
|
var profiles struct{ Result []zitadelProfile }
|
||||||
|
err = zm.helper.Unmarshal(body, &profiles)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
users := make([]*UserData, 0)
|
||||||
|
for _, profile := range profiles.Result {
|
||||||
|
userData := profile.userData()
|
||||||
|
userData.AppMetadata.WTAccountID = accountID
|
||||||
|
|
||||||
|
users = append(users, userData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllAccounts gets all registered accounts with corresponding user data.
|
// GetAllAccounts gets all registered accounts with corresponding user data.
|
||||||
@@ -379,22 +341,8 @@ func (zm *ZitadelManager) GetAllAccounts() (map[string][]*UserData, error) {
|
|||||||
|
|
||||||
indexedUsers := make(map[string][]*UserData)
|
indexedUsers := make(map[string][]*UserData)
|
||||||
for _, profile := range profiles.Result {
|
for _, profile := range profiles.Result {
|
||||||
// fetch user metadata
|
|
||||||
metadata, err := zm.getUserMetadata(profile.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
profile.Metadata = metadata
|
|
||||||
|
|
||||||
userData := profile.userData()
|
userData := profile.userData()
|
||||||
accountID := userData.AppMetadata.WTAccountID
|
indexedUsers[UnsetAccountID] = append(indexedUsers[UnsetAccountID], userData)
|
||||||
|
|
||||||
if accountID != "" {
|
|
||||||
if _, ok := indexedUsers[accountID]; !ok {
|
|
||||||
indexedUsers[accountID] = make([]*UserData, 0)
|
|
||||||
}
|
|
||||||
indexedUsers[accountID] = append(indexedUsers[accountID], userData)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return indexedUsers, nil
|
return indexedUsers, nil
|
||||||
@@ -402,42 +350,7 @@ func (zm *ZitadelManager) GetAllAccounts() (map[string][]*UserData, error) {
|
|||||||
|
|
||||||
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
// UpdateUserAppMetadata updates user app metadata based on userID and metadata map.
|
||||||
// Metadata values are base64 encoded.
|
// Metadata values are base64 encoded.
|
||||||
func (zm *ZitadelManager) UpdateUserAppMetadata(userID string, appMetadata AppMetadata) error {
|
func (zm *ZitadelManager) UpdateUserAppMetadata(_ string, _ AppMetadata) error {
|
||||||
if appMetadata.WTPendingInvite == nil {
|
|
||||||
appMetadata.WTPendingInvite = new(bool)
|
|
||||||
}
|
|
||||||
pendingInviteBuf := strconv.AppendBool([]byte{}, *appMetadata.WTPendingInvite)
|
|
||||||
|
|
||||||
wtAccountIDValue := base64.StdEncoding.EncodeToString([]byte(appMetadata.WTAccountID))
|
|
||||||
wtPendingInviteValue := base64.StdEncoding.EncodeToString(pendingInviteBuf)
|
|
||||||
|
|
||||||
metadata := zitadelAttributes{
|
|
||||||
"metadata": {
|
|
||||||
{
|
|
||||||
"key": wtAccountID,
|
|
||||||
"value": wtAccountIDValue,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": wtPendingInvite,
|
|
||||||
"value": wtPendingInviteValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
payload, err := zm.helper.Marshal(metadata)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resource := fmt.Sprintf("users/%s", userID)
|
|
||||||
_, err = zm.post(resource, string(payload))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if zm.appMetrics != nil {
|
|
||||||
zm.appMetrics.IDPMetrics().CountUpdateUserAppMetadata()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,24 +372,6 @@ func (zm *ZitadelManager) DeleteUser(userID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUserMetadata requests user metadata from zitadel via ID.
|
|
||||||
func (zm *ZitadelManager) getUserMetadata(userID string) ([]zitadelMetadata, error) {
|
|
||||||
resource := fmt.Sprintf("users/%s/metadata/_search", userID)
|
|
||||||
body, err := zm.post(resource, "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadata struct{ Result []zitadelMetadata }
|
|
||||||
err = zm.helper.Unmarshal(body, &metadata)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadata.Result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// post perform Post requests.
|
// post perform Post requests.
|
||||||
@@ -516,38 +411,7 @@ func (zm *ZitadelManager) post(resource string, body string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// delete perform Delete requests.
|
// delete perform Delete requests.
|
||||||
func (zm *ZitadelManager) delete(resource string) error {
|
func (zm *ZitadelManager) delete(_ string) error {
|
||||||
jwtToken, err := zm.credentials.Authenticate()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqURL := fmt.Sprintf("%s/%s", zm.managementEndpoint, resource)
|
|
||||||
req, err := http.NewRequest(http.MethodDelete, reqURL, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Add("authorization", "Bearer "+jwtToken.AccessToken)
|
|
||||||
req.Header.Add("content-type", "application/json")
|
|
||||||
|
|
||||||
resp, err := zm.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
if zm.appMetrics != nil {
|
|
||||||
zm.appMetrics.IDPMetrics().CountRequestError()
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
if zm.appMetrics != nil {
|
|
||||||
zm.appMetrics.IDPMetrics().CountRequestStatusError()
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("unable to delete %s, statusCode %d", reqURL, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,38 +451,13 @@ func (zm *ZitadelManager) get(resource string, q url.Values) ([]byte, error) {
|
|||||||
return io.ReadAll(resp.Body)
|
return io.ReadAll(resp.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// value returns string represented by the base64 string value.
|
|
||||||
func (zm zitadelMetadata) value() string {
|
|
||||||
value, err := base64.StdEncoding.DecodeString(zm.Value)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// userData construct user data from zitadel profile.
|
// userData construct user data from zitadel profile.
|
||||||
func (zp zitadelProfile) userData() *UserData {
|
func (zp zitadelProfile) userData() *UserData {
|
||||||
var (
|
var (
|
||||||
email string
|
email string
|
||||||
name string
|
name string
|
||||||
wtAccountIDValue string
|
|
||||||
wtPendingInviteValue bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, metadata := range zp.Metadata {
|
|
||||||
if metadata.Key == wtAccountID {
|
|
||||||
wtAccountIDValue = metadata.value()
|
|
||||||
}
|
|
||||||
|
|
||||||
if metadata.Key == wtPendingInvite {
|
|
||||||
value, err := strconv.ParseBool(metadata.value())
|
|
||||||
if err == nil {
|
|
||||||
wtPendingInviteValue = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtain the email for the human account and the login name,
|
// Obtain the email for the human account and the login name,
|
||||||
// for the machine account.
|
// for the machine account.
|
||||||
if zp.Human != nil {
|
if zp.Human != nil {
|
||||||
@@ -635,39 +474,5 @@ func (zp zitadelProfile) userData() *UserData {
|
|||||||
Email: email,
|
Email: email,
|
||||||
Name: name,
|
Name: name,
|
||||||
ID: zp.ID,
|
ID: zp.ID,
|
||||||
AppMetadata: AppMetadata{
|
|
||||||
WTAccountID: wtAccountIDValue,
|
|
||||||
WTPendingInvite: &wtPendingInviteValue,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildZitadelCreateUserRequestPayload(email string, name string) (string, error) {
|
|
||||||
var firstName, lastName string
|
|
||||||
|
|
||||||
words := strings.Fields(name)
|
|
||||||
if n := len(words); n > 0 {
|
|
||||||
firstName = strings.Join(words[:n-1], " ")
|
|
||||||
lastName = words[n-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &zitadelUser{
|
|
||||||
UserName: name,
|
|
||||||
Profile: zitadelUserInfo{
|
|
||||||
FirstName: strings.TrimSpace(firstName),
|
|
||||||
LastName: strings.TrimSpace(lastName),
|
|
||||||
DisplayName: name,
|
|
||||||
},
|
|
||||||
Email: zitadelEmail{
|
|
||||||
Email: email,
|
|
||||||
IsEmailVerified: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
str, err := json.Marshal(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(str), nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewZitadelManager(t *testing.T) {
|
func TestNewZitadelManager(t *testing.T) {
|
||||||
@@ -63,15 +64,6 @@ func TestNewZitadelManager(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockZitadelCredentials struct {
|
|
||||||
jwtToken JWTToken
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *mockZitadelCredentials) Authenticate() (JWTToken, error) {
|
|
||||||
return mc.jwtToken, mc.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestZitadelRequestJWTToken(t *testing.T) {
|
func TestZitadelRequestJWTToken(t *testing.T) {
|
||||||
|
|
||||||
type requestJWTTokenTest struct {
|
type requestJWTTokenTest struct {
|
||||||
@@ -296,98 +288,6 @@ func TestZitadelAuthenticate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestZitadelUpdateUserAppMetadata(t *testing.T) {
|
|
||||||
type updateUserAppMetadataTest struct {
|
|
||||||
name string
|
|
||||||
inputReqBody string
|
|
||||||
expectedReqBody string
|
|
||||||
appMetadata AppMetadata
|
|
||||||
statusCode int
|
|
||||||
helper ManagerHelper
|
|
||||||
managerCreds ManagerCredentials
|
|
||||||
assertErrFunc assert.ErrorAssertionFunc
|
|
||||||
assertErrFuncMessage string
|
|
||||||
}
|
|
||||||
|
|
||||||
appMetadata := AppMetadata{WTAccountID: "ok"}
|
|
||||||
|
|
||||||
updateUserAppMetadataTestCase1 := updateUserAppMetadataTest{
|
|
||||||
name: "Bad Authentication",
|
|
||||||
expectedReqBody: "",
|
|
||||||
appMetadata: appMetadata,
|
|
||||||
statusCode: 400,
|
|
||||||
helper: JsonParser{},
|
|
||||||
managerCreds: &mockZitadelCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
err: fmt.Errorf("error"),
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.Error,
|
|
||||||
assertErrFuncMessage: "should return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUserAppMetadataTestCase2 := updateUserAppMetadataTest{
|
|
||||||
name: "Bad Response Parsing",
|
|
||||||
statusCode: 400,
|
|
||||||
helper: &mockJsonParser{marshalErrorString: "error"},
|
|
||||||
managerCreds: &mockZitadelCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.Error,
|
|
||||||
assertErrFuncMessage: "should return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUserAppMetadataTestCase3 := updateUserAppMetadataTest{
|
|
||||||
name: "Good request",
|
|
||||||
expectedReqBody: "{\"metadata\":[{\"key\":\"wt_account_id\",\"value\":\"b2s=\"},{\"key\":\"wt_pending_invite\",\"value\":\"ZmFsc2U=\"}]}",
|
|
||||||
appMetadata: appMetadata,
|
|
||||||
statusCode: 200,
|
|
||||||
helper: JsonParser{},
|
|
||||||
managerCreds: &mockZitadelCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.NoError,
|
|
||||||
assertErrFuncMessage: "shouldn't return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
invite := true
|
|
||||||
updateUserAppMetadataTestCase4 := updateUserAppMetadataTest{
|
|
||||||
name: "Update Pending Invite",
|
|
||||||
expectedReqBody: "{\"metadata\":[{\"key\":\"wt_account_id\",\"value\":\"b2s=\"},{\"key\":\"wt_pending_invite\",\"value\":\"dHJ1ZQ==\"}]}",
|
|
||||||
appMetadata: AppMetadata{
|
|
||||||
WTAccountID: "ok",
|
|
||||||
WTPendingInvite: &invite,
|
|
||||||
},
|
|
||||||
statusCode: 200,
|
|
||||||
helper: JsonParser{},
|
|
||||||
managerCreds: &mockZitadelCredentials{
|
|
||||||
jwtToken: JWTToken{},
|
|
||||||
},
|
|
||||||
assertErrFunc: assert.NoError,
|
|
||||||
assertErrFuncMessage: "shouldn't return error",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range []updateUserAppMetadataTest{updateUserAppMetadataTestCase1, updateUserAppMetadataTestCase2,
|
|
||||||
updateUserAppMetadataTestCase3, updateUserAppMetadataTestCase4} {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
reqClient := mockHTTPClient{
|
|
||||||
resBody: testCase.inputReqBody,
|
|
||||||
code: testCase.statusCode,
|
|
||||||
}
|
|
||||||
|
|
||||||
manager := &ZitadelManager{
|
|
||||||
httpClient: &reqClient,
|
|
||||||
credentials: testCase.managerCreds,
|
|
||||||
helper: testCase.helper,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := manager.UpdateUserAppMetadata("1", testCase.appMetadata)
|
|
||||||
testCase.assertErrFunc(t, err, testCase.assertErrFuncMessage)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.expectedReqBody, reqClient.reqBody, "request body should match")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestZitadelProfile(t *testing.T) {
|
func TestZitadelProfile(t *testing.T) {
|
||||||
type azureProfileTest struct {
|
type azureProfileTest struct {
|
||||||
name string
|
name string
|
||||||
@@ -418,16 +318,6 @@ func TestZitadelProfile(t *testing.T) {
|
|||||||
IsEmailVerified: true,
|
IsEmailVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Metadata: []zitadelMetadata{
|
|
||||||
{
|
|
||||||
Key: "wt_account_id",
|
|
||||||
Value: "MQ==",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "wt_pending_invite",
|
|
||||||
Value: "ZmFsc2U=",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
expectedUserData: UserData{
|
expectedUserData: UserData{
|
||||||
ID: "test1",
|
ID: "test1",
|
||||||
@@ -451,16 +341,6 @@ func TestZitadelProfile(t *testing.T) {
|
|||||||
"machine",
|
"machine",
|
||||||
},
|
},
|
||||||
Human: nil,
|
Human: nil,
|
||||||
Metadata: []zitadelMetadata{
|
|
||||||
{
|
|
||||||
Key: "wt_account_id",
|
|
||||||
Value: "MQ==",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "wt_pending_invite",
|
|
||||||
Value: "dHJ1ZQ==",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
expectedUserData: UserData{
|
expectedUserData: UserData{
|
||||||
ID: "test2",
|
ID: "test2",
|
||||||
@@ -480,8 +360,6 @@ func TestZitadelProfile(t *testing.T) {
|
|||||||
assert.Equal(t, testCase.expectedUserData.ID, userData.ID, "User id should match")
|
assert.Equal(t, testCase.expectedUserData.ID, userData.ID, "User id should match")
|
||||||
assert.Equal(t, testCase.expectedUserData.Email, userData.Email, "User email should match")
|
assert.Equal(t, testCase.expectedUserData.Email, userData.Email, "User email should match")
|
||||||
assert.Equal(t, testCase.expectedUserData.Name, userData.Name, "User name should match")
|
assert.Equal(t, testCase.expectedUserData.Name, userData.Name, "User name should match")
|
||||||
assert.Equal(t, testCase.expectedUserData.AppMetadata.WTAccountID, userData.AppMetadata.WTAccountID, "Account id should match")
|
|
||||||
assert.Equal(t, testCase.expectedUserData.AppMetadata.WTPendingInvite, userData.AppMetadata.WTPendingInvite, "Pending invite should match")
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ func startManagement(t *testing.T, config *Config) (*grpc.Server, string, error)
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||||
store, err := NewFileStore(config.Datadir, nil)
|
store, err := NewStoreFromJson(config.Datadir, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -393,6 +393,7 @@ var _ = Describe("Management service", func() {
|
|||||||
ipChannel := make(chan string, 20)
|
ipChannel := make(chan string, 20)
|
||||||
for i := 0; i < initialPeers; i++ {
|
for i := 0; i < initialPeers; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
|
defer GinkgoRecover()
|
||||||
key, _ := wgtypes.GenerateKey()
|
key, _ := wgtypes.GenerateKey()
|
||||||
loginPeerWithValidSetupKey(serverPubKey, key, client)
|
loginPeerWithValidSetupKey(serverPubKey, key, client)
|
||||||
encryptedBytes, err := encryption.EncryptMessage(serverPubKey, key, &mgmtProto.SyncRequest{})
|
encryptedBytes, err := encryption.EncryptMessage(serverPubKey, key, &mgmtProto.SyncRequest{})
|
||||||
@@ -496,7 +497,7 @@ func startServer(config *server.Config) (*grpc.Server, net.Listener) {
|
|||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
s := grpc.NewServer()
|
s := grpc.NewServer()
|
||||||
|
|
||||||
store, err := server.NewFileStore(config.Datadir, nil)
|
store, err := server.NewStoreFromJson(config.Datadir, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
log.Fatalf("failed creating a store: %s: %v", config.Datadir, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ type properties map[string]interface{}
|
|||||||
// DataSource metric data source
|
// DataSource metric data source
|
||||||
type DataSource interface {
|
type DataSource interface {
|
||||||
GetAllAccounts() []*server.Account
|
GetAllAccounts() []*server.Account
|
||||||
|
GetStoreEngine() server.StoreEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnManager peer connection manager that holds state for current active connections
|
// ConnManager peer connection manager that holds state for current active connections
|
||||||
@@ -176,6 +177,7 @@ func (w *Worker) generateProperties() properties {
|
|||||||
rulesDirection map[string]int
|
rulesDirection map[string]int
|
||||||
groups int
|
groups int
|
||||||
routes int
|
routes int
|
||||||
|
routesWithRGGroups int
|
||||||
nameservers int
|
nameservers int
|
||||||
uiClient int
|
uiClient int
|
||||||
version string
|
version string
|
||||||
@@ -201,6 +203,11 @@ func (w *Worker) generateProperties() properties {
|
|||||||
|
|
||||||
groups = groups + len(account.Groups)
|
groups = groups + len(account.Groups)
|
||||||
routes = routes + len(account.Routes)
|
routes = routes + len(account.Routes)
|
||||||
|
for _, route := range account.Routes {
|
||||||
|
if len(route.PeerGroups) > 0 {
|
||||||
|
routesWithRGGroups++
|
||||||
|
}
|
||||||
|
}
|
||||||
nameservers = nameservers + len(account.NameServerGroups)
|
nameservers = nameservers + len(account.NameServerGroups)
|
||||||
|
|
||||||
for _, policy := range account.Policies {
|
for _, policy := range account.Policies {
|
||||||
@@ -282,12 +289,14 @@ func (w *Worker) generateProperties() properties {
|
|||||||
metricsProperties["rules"] = rules
|
metricsProperties["rules"] = rules
|
||||||
metricsProperties["groups"] = groups
|
metricsProperties["groups"] = groups
|
||||||
metricsProperties["routes"] = routes
|
metricsProperties["routes"] = routes
|
||||||
|
metricsProperties["routes_with_routing_groups"] = routesWithRGGroups
|
||||||
metricsProperties["nameservers"] = nameservers
|
metricsProperties["nameservers"] = nameservers
|
||||||
metricsProperties["version"] = version
|
metricsProperties["version"] = version
|
||||||
metricsProperties["min_active_peer_version"] = minActivePeerVersion
|
metricsProperties["min_active_peer_version"] = minActivePeerVersion
|
||||||
metricsProperties["max_active_peer_version"] = maxActivePeerVersion
|
metricsProperties["max_active_peer_version"] = maxActivePeerVersion
|
||||||
metricsProperties["ui_clients"] = uiClient
|
metricsProperties["ui_clients"] = uiClient
|
||||||
metricsProperties["idp_manager"] = w.idpManager
|
metricsProperties["idp_manager"] = w.idpManager
|
||||||
|
metricsProperties["store_engine"] = w.dataSource.GetStoreEngine()
|
||||||
|
|
||||||
for protocol, count := range rulesProtocol {
|
for protocol, count := range rulesProtocol {
|
||||||
metricsProperties["rules_protocol_"+protocol] = count
|
metricsProperties["rules_protocol_"+protocol] = count
|
||||||
|
|||||||
248
management/server/metrics/selfhosted_test.go
Normal file
248
management/server/metrics/selfhosted_test.go
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/route"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockDatasource struct{}
|
||||||
|
|
||||||
|
// GetAllConnectedPeers returns a map of connected peer IDs for use in tests with predefined information
|
||||||
|
func (mockDatasource) GetAllConnectedPeers() map[string]struct{} {
|
||||||
|
return map[string]struct{}{
|
||||||
|
"1": {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllAccounts returns a list of *server.Account for use in tests with predefined information
|
||||||
|
func (mockDatasource) GetAllAccounts() []*server.Account {
|
||||||
|
return []*server.Account{
|
||||||
|
{
|
||||||
|
Id: "1",
|
||||||
|
Settings: &server.Settings{PeerLoginExpirationEnabled: true},
|
||||||
|
SetupKeys: map[string]*server.SetupKey{
|
||||||
|
"1": {
|
||||||
|
Id: "1",
|
||||||
|
Ephemeral: true,
|
||||||
|
UsedTimes: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Groups: map[string]*server.Group{
|
||||||
|
"1": {},
|
||||||
|
"2": {},
|
||||||
|
},
|
||||||
|
NameServerGroups: map[string]*nbdns.NameServerGroup{
|
||||||
|
"1": {},
|
||||||
|
},
|
||||||
|
Peers: map[string]*server.Peer{
|
||||||
|
"1": {
|
||||||
|
ID: "1",
|
||||||
|
UserID: "test",
|
||||||
|
SSHEnabled: true,
|
||||||
|
Meta: server.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Policies: []*server.Policy{
|
||||||
|
{
|
||||||
|
Rules: []*server.PolicyRule{
|
||||||
|
{
|
||||||
|
Bidirectional: true,
|
||||||
|
Protocol: server.PolicyRuleProtocolTCP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rules: []*server.PolicyRule{
|
||||||
|
{
|
||||||
|
Bidirectional: false,
|
||||||
|
Protocol: server.PolicyRuleProtocolTCP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Routes: map[string]*route.Route{
|
||||||
|
"1": {
|
||||||
|
ID: "1",
|
||||||
|
PeerGroups: make([]string, 1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Users: map[string]*server.User{
|
||||||
|
"1": {
|
||||||
|
IsServiceUser: true,
|
||||||
|
PATs: map[string]*server.PersonalAccessToken{
|
||||||
|
"1": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
IsServiceUser: false,
|
||||||
|
PATs: map[string]*server.PersonalAccessToken{
|
||||||
|
"1": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "2",
|
||||||
|
Settings: &server.Settings{PeerLoginExpirationEnabled: true},
|
||||||
|
SetupKeys: map[string]*server.SetupKey{
|
||||||
|
"1": {
|
||||||
|
Id: "1",
|
||||||
|
Ephemeral: true,
|
||||||
|
UsedTimes: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Groups: map[string]*server.Group{
|
||||||
|
"1": {},
|
||||||
|
"2": {},
|
||||||
|
},
|
||||||
|
NameServerGroups: map[string]*nbdns.NameServerGroup{
|
||||||
|
"1": {},
|
||||||
|
},
|
||||||
|
Peers: map[string]*server.Peer{
|
||||||
|
"1": {
|
||||||
|
ID: "1",
|
||||||
|
UserID: "test",
|
||||||
|
SSHEnabled: true,
|
||||||
|
Meta: server.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Policies: []*server.Policy{
|
||||||
|
{
|
||||||
|
Rules: []*server.PolicyRule{
|
||||||
|
{
|
||||||
|
Bidirectional: true,
|
||||||
|
Protocol: server.PolicyRuleProtocolTCP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rules: []*server.PolicyRule{
|
||||||
|
{
|
||||||
|
Bidirectional: false,
|
||||||
|
Protocol: server.PolicyRuleProtocolTCP,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Routes: map[string]*route.Route{
|
||||||
|
"1": {
|
||||||
|
ID: "1",
|
||||||
|
PeerGroups: make([]string, 1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Users: map[string]*server.User{
|
||||||
|
"1": {
|
||||||
|
IsServiceUser: true,
|
||||||
|
PATs: map[string]*server.PersonalAccessToken{
|
||||||
|
"1": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
IsServiceUser: false,
|
||||||
|
PATs: map[string]*server.PersonalAccessToken{
|
||||||
|
"1": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStoreEngine returns FileStoreEngine
|
||||||
|
func (mockDatasource) GetStoreEngine() server.StoreEngine {
|
||||||
|
return server.FileStoreEngine
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGenerateProperties tests and validate the properties generation by using the mockDatasource for the Worker.generateProperties
|
||||||
|
func TestGenerateProperties(t *testing.T) {
|
||||||
|
ds := mockDatasource{}
|
||||||
|
worker := Worker{
|
||||||
|
dataSource: ds,
|
||||||
|
connManager: ds,
|
||||||
|
}
|
||||||
|
|
||||||
|
properties := worker.generateProperties()
|
||||||
|
|
||||||
|
if properties["accounts"] != 2 {
|
||||||
|
t.Errorf("expected 2 accounts, got %d", properties["accounts"])
|
||||||
|
}
|
||||||
|
if properties["peers"] != 2 {
|
||||||
|
t.Errorf("expected 2 peers, got %d", properties["peers"])
|
||||||
|
}
|
||||||
|
if properties["routes"] != 2 {
|
||||||
|
t.Errorf("expected 2 routes, got %d", properties["routes"])
|
||||||
|
}
|
||||||
|
if properties["rules"] != 4 {
|
||||||
|
t.Errorf("expected 4 rules, got %d", properties["rules"])
|
||||||
|
}
|
||||||
|
if properties["users"] != 2 {
|
||||||
|
t.Errorf("expected 1 users, got %d", properties["users"])
|
||||||
|
}
|
||||||
|
if properties["setup_keys_usage"] != 2 {
|
||||||
|
t.Errorf("expected 1 setup_keys_usage, got %d", properties["setup_keys_usage"])
|
||||||
|
}
|
||||||
|
if properties["pats"] != 4 {
|
||||||
|
t.Errorf("expected 4 personal_access_tokens, got %d", properties["pats"])
|
||||||
|
}
|
||||||
|
if properties["peers_ssh_enabled"] != 2 {
|
||||||
|
t.Errorf("expected 2 peers_ssh_enabled, got %d", properties["peers_ssh_enabled"])
|
||||||
|
}
|
||||||
|
if properties["routes_with_routing_groups"] != 2 {
|
||||||
|
t.Errorf("expected 2 routes_with_routing_groups, got %d", properties["routes_with_routing_groups"])
|
||||||
|
}
|
||||||
|
if properties["rules_protocol_tcp"] != 4 {
|
||||||
|
t.Errorf("expected 4 rules_protocol_tcp, got %d", properties["rules_protocol_tcp"])
|
||||||
|
}
|
||||||
|
if properties["rules_direction_oneway"] != 2 {
|
||||||
|
t.Errorf("expected 2 rules_direction_oneway, got %d", properties["rules_direction_oneway"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["active_peers_last_day"] != 2 {
|
||||||
|
t.Errorf("expected 2 active_peers_last_day, got %d", properties["active_peers_last_day"])
|
||||||
|
}
|
||||||
|
if properties["min_active_peer_version"] != "0.0.1" {
|
||||||
|
t.Errorf("expected 0.0.1 min_active_peer_version, got %s", properties["min_active_peer_version"])
|
||||||
|
}
|
||||||
|
if properties["max_active_peer_version"] != "0.0.1" {
|
||||||
|
t.Errorf("expected 0.0.1 max_active_peer_version, got %s", properties["max_active_peer_version"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["peers_login_expiration_enabled"] != 2 {
|
||||||
|
t.Errorf("expected 2 peers_login_expiration_enabled, got %d", properties["peers_login_expiration_enabled"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["service_users"] != 2 {
|
||||||
|
t.Errorf("expected 2 service_users, got %d", properties["service_users"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["peer_os_linux"] != 2 {
|
||||||
|
t.Errorf("expected 2 peer_os_linux, got %d", properties["peer_os_linux"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["ephemeral_peers_setup_keys"] != 2 {
|
||||||
|
t.Errorf("expected 2 ephemeral_peers_setup_keys, got %d", properties["ephemeral_peers_setup_keys_usage"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["ephemeral_peers_setup_keys_usage"] != 2 {
|
||||||
|
t.Errorf("expected 2 ephemeral_peers_setup_keys_usage, got %d", properties["ephemeral_peers_setup_keys_usage"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["nameservers"] != 2 {
|
||||||
|
t.Errorf("expected 2 nameservers, got %d", properties["nameservers"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["groups"] != 4 {
|
||||||
|
t.Errorf("expected 4 groups, got %d", properties["groups"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["user_peers"] != 2 {
|
||||||
|
t.Errorf("expected 2 user_peers, got %d", properties["user_peers"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties["store_engine"] != server.FileStoreEngine {
|
||||||
|
t.Errorf("expected JsonFile, got %s", properties["store_engine"])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,12 +20,9 @@ type MockAccountManager struct {
|
|||||||
GetSetupKeyFunc func(accountID, userID, keyID string) (*server.SetupKey, error)
|
GetSetupKeyFunc func(accountID, userID, keyID string) (*server.SetupKey, error)
|
||||||
GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error)
|
GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error)
|
||||||
GetUserFunc func(claims jwtclaims.AuthorizationClaims) (*server.User, error)
|
GetUserFunc func(claims jwtclaims.AuthorizationClaims) (*server.User, error)
|
||||||
AccountExistsFunc func(accountId string) (*bool, error)
|
|
||||||
GetPeerByKeyFunc func(peerKey string) (*server.Peer, error)
|
|
||||||
GetPeersFunc func(accountID, userID string) ([]*server.Peer, error)
|
GetPeersFunc func(accountID, userID string) ([]*server.Peer, error)
|
||||||
MarkPeerConnectedFunc func(peerKey string, connected bool) error
|
MarkPeerConnectedFunc func(peerKey string, connected bool) error
|
||||||
DeletePeerFunc func(accountID, peerKey, userID string) (*server.Peer, error)
|
DeletePeerFunc func(accountID, peerKey, userID string) error
|
||||||
GetPeerByIPFunc func(accountId string, peerIP string) (*server.Peer, error)
|
|
||||||
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
|
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
|
||||||
GetPeerNetworkFunc func(peerKey string) (*server.Network, error)
|
GetPeerNetworkFunc func(peerKey string) (*server.Network, error)
|
||||||
AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, *server.NetworkMap, error)
|
AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, *server.NetworkMap, error)
|
||||||
@@ -33,9 +30,8 @@ type MockAccountManager struct {
|
|||||||
SaveGroupFunc func(accountID, userID string, group *server.Group) error
|
SaveGroupFunc func(accountID, userID string, group *server.Group) error
|
||||||
DeleteGroupFunc func(accountID, userId, groupID string) error
|
DeleteGroupFunc func(accountID, userId, groupID string) error
|
||||||
ListGroupsFunc func(accountID string) ([]*server.Group, error)
|
ListGroupsFunc func(accountID string) ([]*server.Group, error)
|
||||||
GroupAddPeerFunc func(accountID, groupID, peerKey string) error
|
GroupAddPeerFunc func(accountID, groupID, peerID string) error
|
||||||
GroupDeletePeerFunc func(accountID, groupID, peerKey string) error
|
GroupDeletePeerFunc func(accountID, groupID, peerID string) error
|
||||||
GroupListPeersFunc func(accountID, groupID string) ([]*server.Peer, error)
|
|
||||||
GetRuleFunc func(accountID, ruleID, userID string) (*server.Rule, error)
|
GetRuleFunc func(accountID, ruleID, userID string) (*server.Rule, error)
|
||||||
SaveRuleFunc func(accountID, userID string, rule *server.Rule) error
|
SaveRuleFunc func(accountID, userID string, rule *server.Rule) error
|
||||||
DeleteRuleFunc func(accountID, ruleID, userID string) error
|
DeleteRuleFunc func(accountID, ruleID, userID string) error
|
||||||
@@ -50,7 +46,7 @@ type MockAccountManager struct {
|
|||||||
UpdatePeerMetaFunc func(peerID string, meta server.PeerSystemMeta) error
|
UpdatePeerMetaFunc func(peerID string, meta server.PeerSystemMeta) error
|
||||||
UpdatePeerSSHKeyFunc func(peerID string, sshKey string) error
|
UpdatePeerSSHKeyFunc func(peerID string, sshKey string) error
|
||||||
UpdatePeerFunc func(accountID, userID string, peer *server.Peer) (*server.Peer, error)
|
UpdatePeerFunc func(accountID, userID string, peer *server.Peer) (*server.Peer, error)
|
||||||
CreateRouteFunc func(accountID string, prefix, peer, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error)
|
CreateRouteFunc func(accountID, prefix, peer string, peerGroups []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error)
|
||||||
GetRouteFunc func(accountID, routeID, userID string) (*route.Route, error)
|
GetRouteFunc func(accountID, routeID, userID string) (*route.Route, error)
|
||||||
SaveRouteFunc func(accountID, userID string, route *route.Route) error
|
SaveRouteFunc func(accountID, userID string, route *route.Route) error
|
||||||
DeleteRouteFunc func(accountID, routeID, userID string) error
|
DeleteRouteFunc func(accountID, routeID, userID string) error
|
||||||
@@ -64,7 +60,7 @@ type MockAccountManager struct {
|
|||||||
GetPATFunc func(accountID string, initiatorUserID string, targetUserId string, tokenID string) (*server.PersonalAccessToken, error)
|
GetPATFunc func(accountID string, initiatorUserID string, targetUserId string, tokenID string) (*server.PersonalAccessToken, error)
|
||||||
GetAllPATsFunc func(accountID string, initiatorUserID string, targetUserId string) ([]*server.PersonalAccessToken, error)
|
GetAllPATsFunc func(accountID string, initiatorUserID string, targetUserId string) ([]*server.PersonalAccessToken, error)
|
||||||
GetNameServerGroupFunc func(accountID, nsGroupID string) (*nbdns.NameServerGroup, error)
|
GetNameServerGroupFunc func(accountID, nsGroupID string) (*nbdns.NameServerGroup, error)
|
||||||
CreateNameServerGroupFunc func(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string) (*nbdns.NameServerGroup, error)
|
CreateNameServerGroupFunc func(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string, searchDomainsEnabled bool) (*nbdns.NameServerGroup, error)
|
||||||
SaveNameServerGroupFunc func(accountID, userID string, nsGroupToSave *nbdns.NameServerGroup) error
|
SaveNameServerGroupFunc func(accountID, userID string, nsGroupToSave *nbdns.NameServerGroup) error
|
||||||
DeleteNameServerGroupFunc func(accountID, nsGroupID, userID string) error
|
DeleteNameServerGroupFunc func(accountID, nsGroupID, userID string) error
|
||||||
ListNameServerGroupsFunc func(accountID string) ([]*nbdns.NameServerGroup, error)
|
ListNameServerGroupsFunc func(accountID string) ([]*nbdns.NameServerGroup, error)
|
||||||
@@ -79,6 +75,7 @@ type MockAccountManager struct {
|
|||||||
LoginPeerFunc func(login server.PeerLogin) (*server.Peer, *server.NetworkMap, error)
|
LoginPeerFunc func(login server.PeerLogin) (*server.Peer, *server.NetworkMap, error)
|
||||||
SyncPeerFunc func(sync server.PeerSync) (*server.Peer, *server.NetworkMap, error)
|
SyncPeerFunc func(sync server.PeerSync) (*server.Peer, *server.NetworkMap, error)
|
||||||
InviteUserFunc func(accountID string, initiatorUserID string, targetUserEmail string) error
|
InviteUserFunc func(accountID string, initiatorUserID string, targetUserEmail string) error
|
||||||
|
GetAllConnectedPeersFunc func() (map[string]struct{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
// GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface
|
||||||
@@ -90,11 +87,11 @@ func (am *MockAccountManager) GetUsersFromAccount(accountID string, userID strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeletePeer mock implementation of DeletePeer from server.AccountManager interface
|
// DeletePeer mock implementation of DeletePeer from server.AccountManager interface
|
||||||
func (am *MockAccountManager) DeletePeer(accountID, peerID, userID string) (*server.Peer, error) {
|
func (am *MockAccountManager) DeletePeer(accountID, peerID, userID string) error {
|
||||||
if am.DeletePeerFunc != nil {
|
if am.DeletePeerFunc != nil {
|
||||||
return am.DeletePeerFunc(accountID, peerID, userID)
|
return am.DeletePeerFunc(accountID, peerID, userID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DeletePeer is not implemented")
|
return status.Errorf(codes.Unimplemented, "method DeletePeer is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrCreateAccountByUser mock implementation of GetOrCreateAccountByUser from server.AccountManager interface
|
// GetOrCreateAccountByUser mock implementation of GetOrCreateAccountByUser from server.AccountManager interface
|
||||||
@@ -140,22 +137,6 @@ func (am *MockAccountManager) GetAccountByUserOrAccountID(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountExists mock implementation of AccountExists from server.AccountManager interface
|
|
||||||
func (am *MockAccountManager) AccountExists(accountId string) (*bool, error) {
|
|
||||||
if am.AccountExistsFunc != nil {
|
|
||||||
return am.AccountExistsFunc(accountId)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method AccountExists is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeerByKey mocks implementation of GetPeerByKey from server.AccountManager interface
|
|
||||||
func (am *MockAccountManager) GetPeerByKey(peerKey string) (*server.Peer, error) {
|
|
||||||
if am.GetPeerByKeyFunc != nil {
|
|
||||||
return am.GetPeerByKeyFunc(peerKey)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeerByKey is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkPeerConnected mock implementation of MarkPeerConnected from server.AccountManager interface
|
// MarkPeerConnected mock implementation of MarkPeerConnected from server.AccountManager interface
|
||||||
func (am *MockAccountManager) MarkPeerConnected(peerKey string, connected bool) error {
|
func (am *MockAccountManager) MarkPeerConnected(peerKey string, connected bool) error {
|
||||||
if am.MarkPeerConnectedFunc != nil {
|
if am.MarkPeerConnectedFunc != nil {
|
||||||
@@ -164,14 +145,6 @@ func (am *MockAccountManager) MarkPeerConnected(peerKey string, connected bool)
|
|||||||
return status.Errorf(codes.Unimplemented, "method MarkPeerConnected is not implemented")
|
return status.Errorf(codes.Unimplemented, "method MarkPeerConnected is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerByIP mock implementation of GetPeerByIP from server.AccountManager interface
|
|
||||||
func (am *MockAccountManager) GetPeerByIP(accountId string, peerIP string) (*server.Peer, error) {
|
|
||||||
if am.GetPeerByIPFunc != nil {
|
|
||||||
return am.GetPeerByIPFunc(accountId, peerIP)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeerByIP is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountFromPAT mock implementation of GetAccountFromPAT from server.AccountManager interface
|
// GetAccountFromPAT mock implementation of GetAccountFromPAT from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GetAccountFromPAT(pat string) (*server.Account, *server.User, *server.PersonalAccessToken, error) {
|
func (am *MockAccountManager) GetAccountFromPAT(pat string) (*server.Account, *server.User, *server.PersonalAccessToken, error) {
|
||||||
if am.GetAccountFromPATFunc != nil {
|
if am.GetAccountFromPATFunc != nil {
|
||||||
@@ -281,29 +254,21 @@ func (am *MockAccountManager) ListGroups(accountID string) ([]*server.Group, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GroupAddPeer mock implementation of GroupAddPeer from server.AccountManager interface
|
// GroupAddPeer mock implementation of GroupAddPeer from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GroupAddPeer(accountID, groupID, peerKey string) error {
|
func (am *MockAccountManager) GroupAddPeer(accountID, groupID, peerID string) error {
|
||||||
if am.GroupAddPeerFunc != nil {
|
if am.GroupAddPeerFunc != nil {
|
||||||
return am.GroupAddPeerFunc(accountID, groupID, peerKey)
|
return am.GroupAddPeerFunc(accountID, groupID, peerID)
|
||||||
}
|
}
|
||||||
return status.Errorf(codes.Unimplemented, "method GroupAddPeer is not implemented")
|
return status.Errorf(codes.Unimplemented, "method GroupAddPeer is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupDeletePeer mock implementation of GroupDeletePeer from server.AccountManager interface
|
// GroupDeletePeer mock implementation of GroupDeletePeer from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GroupDeletePeer(accountID, groupID, peerKey string) error {
|
func (am *MockAccountManager) GroupDeletePeer(accountID, groupID, peerID string) error {
|
||||||
if am.GroupDeletePeerFunc != nil {
|
if am.GroupDeletePeerFunc != nil {
|
||||||
return am.GroupDeletePeerFunc(accountID, groupID, peerKey)
|
return am.GroupDeletePeerFunc(accountID, groupID, peerID)
|
||||||
}
|
}
|
||||||
return status.Errorf(codes.Unimplemented, "method GroupDeletePeer is not implemented")
|
return status.Errorf(codes.Unimplemented, "method GroupDeletePeer is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupListPeers mock implementation of GroupListPeers from server.AccountManager interface
|
|
||||||
func (am *MockAccountManager) GroupListPeers(accountID, groupID string) ([]*server.Peer, error) {
|
|
||||||
if am.GroupListPeersFunc != nil {
|
|
||||||
return am.GroupListPeersFunc(accountID, groupID)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GroupListPeers is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRule mock implementation of GetRule from server.AccountManager interface
|
// GetRule mock implementation of GetRule from server.AccountManager interface
|
||||||
func (am *MockAccountManager) GetRule(accountID, ruleID, userID string) (*server.Rule, error) {
|
func (am *MockAccountManager) GetRule(accountID, ruleID, userID string) (*server.Rule, error) {
|
||||||
if am.GetRuleFunc != nil {
|
if am.GetRuleFunc != nil {
|
||||||
@@ -401,9 +366,9 @@ func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *server.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateRoute mock implementation of CreateRoute from server.AccountManager interface
|
// CreateRoute mock implementation of CreateRoute from server.AccountManager interface
|
||||||
func (am *MockAccountManager) CreateRoute(accountID string, network, peerID, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) {
|
func (am *MockAccountManager) CreateRoute(accountID, network, peerID string, peerGroups []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) {
|
||||||
if am.CreateRouteFunc != nil {
|
if am.CreateRouteFunc != nil {
|
||||||
return am.CreateRouteFunc(accountID, network, peerID, description, netID, masquerade, metric, groups, enabled, userID)
|
return am.CreateRouteFunc(accountID, network, peerID, peerGroups, description, netID, masquerade, metric, groups, enabled, userID)
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateRoute is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method CreateRoute is not implemented")
|
||||||
}
|
}
|
||||||
@@ -499,9 +464,9 @@ func (am *MockAccountManager) GetNameServerGroup(accountID, nsGroupID string) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateNameServerGroup mocks CreateNameServerGroup of the AccountManager interface
|
// CreateNameServerGroup mocks CreateNameServerGroup of the AccountManager interface
|
||||||
func (am *MockAccountManager) CreateNameServerGroup(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string) (*nbdns.NameServerGroup, error) {
|
func (am *MockAccountManager) CreateNameServerGroup(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string, searchDomainsEnabled bool) (*nbdns.NameServerGroup, error) {
|
||||||
if am.CreateNameServerGroupFunc != nil {
|
if am.CreateNameServerGroupFunc != nil {
|
||||||
return am.CreateNameServerGroupFunc(accountID, name, description, nameServerList, groups, primary, domains, enabled, userID)
|
return am.CreateNameServerGroupFunc(accountID, name, description, nameServerList, groups, primary, domains, enabled, userID, searchDomainsEnabled)
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -619,3 +584,11 @@ func (am *MockAccountManager) SyncPeer(sync server.PeerSync) (*server.Peer, *ser
|
|||||||
}
|
}
|
||||||
return nil, nil, status.Errorf(codes.Unimplemented, "method SyncPeer is not implemented")
|
return nil, nil, status.Errorf(codes.Unimplemented, "method SyncPeer is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllConnectedPeers mocks GetAllConnectedPeers of the AccountManager interface
|
||||||
|
func (am *MockAccountManager) GetAllConnectedPeers() (map[string]struct{}, error) {
|
||||||
|
if am.GetAllConnectedPeersFunc != nil {
|
||||||
|
return am.GetAllConnectedPeersFunc()
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetAllConnectedPeers is not implemented")
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
@@ -36,7 +35,7 @@ func (am *DefaultAccountManager) GetNameServerGroup(accountID, nsGroupID string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateNameServerGroup creates and saves a new nameserver group
|
// CreateNameServerGroup creates and saves a new nameserver group
|
||||||
func (am *DefaultAccountManager) CreateNameServerGroup(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string) (*nbdns.NameServerGroup, error) {
|
func (am *DefaultAccountManager) CreateNameServerGroup(accountID string, name, description string, nameServerList []nbdns.NameServer, groups []string, primary bool, domains []string, enabled bool, userID string, searchDomainEnabled bool) (*nbdns.NameServerGroup, error) {
|
||||||
|
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
@@ -55,6 +54,7 @@ func (am *DefaultAccountManager) CreateNameServerGroup(accountID string, name, d
|
|||||||
Enabled: enabled,
|
Enabled: enabled,
|
||||||
Primary: primary,
|
Primary: primary,
|
||||||
Domains: domains,
|
Domains: domains,
|
||||||
|
SearchDomainsEnabled: searchDomainEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateNameServerGroup(false, newNSGroup, account)
|
err = validateNameServerGroup(false, newNSGroup, account)
|
||||||
@@ -74,11 +74,7 @@ func (am *DefaultAccountManager) CreateNameServerGroup(accountID string, name, d
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return newNSGroup.Copy(), status.Errorf(status.Internal, "failed to update peers after create nameserver %s", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
am.storeEvent(userID, newNSGroup.ID, accountID, activity.NameserverGroupCreated, newNSGroup.EventMeta())
|
am.storeEvent(userID, newNSGroup.ID, accountID, activity.NameserverGroupCreated, newNSGroup.EventMeta())
|
||||||
|
|
||||||
@@ -113,11 +109,7 @@ func (am *DefaultAccountManager) SaveNameServerGroup(accountID, userID string, n
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
return status.Errorf(status.Internal, "failed to update peers after update nameserver %s", nsGroupToSave.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
am.storeEvent(userID, nsGroupToSave.ID, accountID, activity.NameserverGroupUpdated, nsGroupToSave.EventMeta())
|
am.storeEvent(userID, nsGroupToSave.ID, accountID, activity.NameserverGroupUpdated, nsGroupToSave.EventMeta())
|
||||||
|
|
||||||
@@ -147,10 +139,7 @@ func (am *DefaultAccountManager) DeleteNameServerGroup(accountID, nsGroupID, use
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
if err != nil {
|
|
||||||
return status.Errorf(status.Internal, "failed to update peers after deleting nameserver %s", nsGroupID)
|
|
||||||
}
|
|
||||||
|
|
||||||
am.storeEvent(userID, nsGroup.ID, accountID, activity.NameserverGroupDeleted, nsGroup.EventMeta())
|
am.storeEvent(userID, nsGroup.ID, accountID, activity.NameserverGroupDeleted, nsGroup.EventMeta())
|
||||||
|
|
||||||
@@ -186,7 +175,7 @@ func validateNameServerGroup(existingGroup bool, nameserverGroup *nbdns.NameServ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := validateDomainInput(nameserverGroup.Primary, nameserverGroup.Domains)
|
err := validateDomainInput(nameserverGroup.Primary, nameserverGroup.Domains, nameserverGroup.SearchDomainsEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -209,7 +198,7 @@ func validateNameServerGroup(existingGroup bool, nameserverGroup *nbdns.NameServ
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateDomainInput(primary bool, domains []string) error {
|
func validateDomainInput(primary bool, domains []string, searchDomainsEnabled bool) error {
|
||||||
if !primary && len(domains) == 0 {
|
if !primary && len(domains) == 0 {
|
||||||
return status.Errorf(status.InvalidArgument, "nameserver group primary status is false and domains are empty,"+
|
return status.Errorf(status.InvalidArgument, "nameserver group primary status is false and domains are empty,"+
|
||||||
" it should be primary or have at least one domain")
|
" it should be primary or have at least one domain")
|
||||||
@@ -218,6 +207,12 @@ func validateDomainInput(primary bool, domains []string) error {
|
|||||||
return status.Errorf(status.InvalidArgument, "nameserver group primary status is true and domains are not empty,"+
|
return status.Errorf(status.InvalidArgument, "nameserver group primary status is true and domains are not empty,"+
|
||||||
" you should set either primary or domain")
|
" you should set either primary or domain")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if primary && searchDomainsEnabled {
|
||||||
|
return status.Errorf(status.InvalidArgument, "nameserver group primary status is true and search domains is enabled,"+
|
||||||
|
" you should not set search domains for primary nameservers")
|
||||||
|
}
|
||||||
|
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
if err := validateDomain(domain); err != nil {
|
if err := validateDomain(domain); err != nil {
|
||||||
return status.Errorf(status.InvalidArgument, "nameserver group got an invalid domain: %s %q", domain, err)
|
return status.Errorf(status.InvalidArgument, "nameserver group got an invalid domain: %s %q", domain, err)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ func TestCreateNameServerGroup(t *testing.T) {
|
|||||||
nameServers []nbdns.NameServer
|
nameServers []nbdns.NameServer
|
||||||
primary bool
|
primary bool
|
||||||
domains []string
|
domains []string
|
||||||
|
searchDomains bool
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@@ -383,6 +384,7 @@ func TestCreateNameServerGroup(t *testing.T) {
|
|||||||
testCase.inputArgs.domains,
|
testCase.inputArgs.domains,
|
||||||
testCase.inputArgs.enabled,
|
testCase.inputArgs.enabled,
|
||||||
userID,
|
userID,
|
||||||
|
testCase.inputArgs.searchDomains,
|
||||||
)
|
)
|
||||||
|
|
||||||
testCase.errFunc(t, err)
|
testCase.errFunc(t, err)
|
||||||
@@ -749,7 +751,7 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
|
|||||||
|
|
||||||
func createNSStore(t *testing.T) (Store, error) {
|
func createNSStore(t *testing.T) (Store, error) {
|
||||||
dataDir := t.TempDir()
|
dataDir := t.TempDir()
|
||||||
store, err := NewFileStore(dataDir, nil)
|
store, err := NewStoreFromJson(dataDir, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,14 +34,14 @@ type NetworkMap struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Network struct {
|
type Network struct {
|
||||||
Id string
|
Identifier string `json:"id"`
|
||||||
Net net.IPNet
|
Net net.IPNet `gorm:"serializer:gob"`
|
||||||
Dns string
|
Dns string
|
||||||
// Serial is an ID that increments by 1 when any change to the network happened (e.g. new peer has been added).
|
// Serial is an ID that increments by 1 when any change to the network happened (e.g. new peer has been added).
|
||||||
// Used to synchronize state to the client apps.
|
// Used to synchronize state to the client apps.
|
||||||
Serial uint64
|
Serial uint64
|
||||||
|
|
||||||
mu sync.Mutex `json:"-"`
|
mu sync.Mutex `json:"-" gorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNetwork creates a new Network initializing it with a Serial=0
|
// NewNetwork creates a new Network initializing it with a Serial=0
|
||||||
@@ -56,7 +56,7 @@ func NewNetwork() *Network {
|
|||||||
intn := r.Intn(len(sub))
|
intn := r.Intn(len(sub))
|
||||||
|
|
||||||
return &Network{
|
return &Network{
|
||||||
Id: xid.New().String(),
|
Identifier: xid.New().String(),
|
||||||
Net: sub[intn].IPNet,
|
Net: sub[intn].IPNet,
|
||||||
Dns: "",
|
Dns: "",
|
||||||
Serial: 0}
|
Serial: 0}
|
||||||
@@ -78,7 +78,7 @@ func (n *Network) CurrentSerial() uint64 {
|
|||||||
|
|
||||||
func (n *Network) Copy() *Network {
|
func (n *Network) Copy() *Network {
|
||||||
return &Network{
|
return &Network{
|
||||||
Id: n.Id,
|
Identifier: n.Identifier,
|
||||||
Net: n.Net,
|
Net: n.Net,
|
||||||
Dns: n.Dns,
|
Dns: n.Dns,
|
||||||
Serial: n.Serial,
|
Serial: n.Serial,
|
||||||
|
|||||||
@@ -72,22 +72,24 @@ type PeerLogin struct {
|
|||||||
// The Peer is a WireGuard peer identified by a public key
|
// The Peer is a WireGuard peer identified by a public key
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
// ID is an internal ID of the peer
|
// ID is an internal ID of the peer
|
||||||
ID string
|
ID string `gorm:"primaryKey"`
|
||||||
|
// AccountID is a reference to Account that this object belongs
|
||||||
|
AccountID string `json:"-" gorm:"index;uniqueIndex:idx_peers_account_id_ip"`
|
||||||
// WireGuard public key
|
// WireGuard public key
|
||||||
Key string
|
Key string `gorm:"index"`
|
||||||
// A setup key this peer was registered with
|
// A setup key this peer was registered with
|
||||||
SetupKey string
|
SetupKey string
|
||||||
// IP address of the Peer
|
// IP address of the Peer
|
||||||
IP net.IP
|
IP net.IP `gorm:"uniqueIndex:idx_peers_account_id_ip"`
|
||||||
// Meta is a Peer system meta data
|
// Meta is a Peer system meta data
|
||||||
Meta PeerSystemMeta
|
Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"`
|
||||||
// Name is peer's name (machine name)
|
// Name is peer's name (machine name)
|
||||||
Name string
|
Name string
|
||||||
// DNSLabel is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's
|
// DNSLabel 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
|
// domain to the peer label. e.g. peer-dns-label.netbird.cloud
|
||||||
DNSLabel string
|
DNSLabel string
|
||||||
// Status peer's management connection status
|
// Status peer's management connection status
|
||||||
Status *PeerStatus
|
Status *PeerStatus `gorm:"embedded;embeddedPrefix:peer_status_"`
|
||||||
// The user ID that registered the peer
|
// The user ID that registered the peer
|
||||||
UserID string
|
UserID string
|
||||||
// SSHKey is a public SSH key of the peer
|
// SSHKey is a public SSH key of the peer
|
||||||
@@ -116,6 +118,7 @@ func (p *Peer) Copy() *Peer {
|
|||||||
}
|
}
|
||||||
return &Peer{
|
return &Peer{
|
||||||
ID: p.ID,
|
ID: p.ID,
|
||||||
|
AccountID: p.AccountID,
|
||||||
Key: p.Key,
|
Key: p.Key,
|
||||||
SetupKey: p.SetupKey,
|
SetupKey: p.SetupKey,
|
||||||
IP: p.IP,
|
IP: p.IP,
|
||||||
@@ -195,16 +198,6 @@ func (p *PeerStatus) Copy() *PeerStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerByKey looks up peer by its public WireGuard key
|
|
||||||
func (am *DefaultAccountManager) GetPeerByKey(peerPubKey string) (*Peer, error) {
|
|
||||||
account, err := am.Store.GetAccountByPeerPubKey(peerPubKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return account.FindPeerByPubKey(peerPubKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeers returns a list of peers under the given account filtering out peers that do not belong to a user if
|
// GetPeers returns a list of peers under the given account filtering out peers that do not belong to a user if
|
||||||
// the current user is not an admin.
|
// the current user is not an admin.
|
||||||
func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, error) {
|
func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, error) {
|
||||||
@@ -290,10 +283,7 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected
|
|||||||
if oldStatus.LoginExpired {
|
if oldStatus.LoginExpired {
|
||||||
// we need to update other peers because when peer login expires all other peers are notified to disconnect from
|
// we need to update other peers because when peer login expires all other peers are notified to disconnect from
|
||||||
// the expired one. Here we notify them that connection is now allowed again.
|
// the expired one. Here we notify them that connection is now allowed again.
|
||||||
err = am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -364,37 +354,30 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Pe
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return peer, nil
|
return peer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePeer removes peer from the account by its IP
|
// deletePeers will delete all specified peers and send updates to the remote peers. Don't call without acquiring account lock
|
||||||
func (am *DefaultAccountManager) DeletePeer(accountID, peerID, userID string) (*Peer, error) {
|
func (am *DefaultAccountManager) deletePeers(account *Account, peerIDs []string, userID string) error {
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
|
||||||
defer unlock()
|
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountID)
|
// the first loop is needed to ensure all peers present under the account before modifying, otherwise
|
||||||
if err != nil {
|
// we might have some inconsistencies
|
||||||
return nil, err
|
peers := make([]*Peer, 0, len(peerIDs))
|
||||||
}
|
for _, peerID := range peerIDs {
|
||||||
|
|
||||||
peer := account.GetPeer(peerID)
|
peer := account.GetPeer(peerID)
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
return nil, status.Errorf(status.NotFound, "peer %s not found", peerID)
|
return status.Errorf(status.NotFound, "peer %s not found", peerID)
|
||||||
|
}
|
||||||
|
peers = append(peers, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
account.DeletePeer(peerID)
|
// the 2nd loop performs the actual modification
|
||||||
|
for _, peer := range peers {
|
||||||
err = am.Store.SaveAccount(account)
|
account.DeletePeer(peer.ID)
|
||||||
if err != nil {
|
am.peersUpdateManager.SendUpdate(peer.ID,
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = am.peersUpdateManager.SendUpdate(peer.ID,
|
|
||||||
&UpdateMessage{
|
&UpdateMessage{
|
||||||
Update: &proto.SyncResponse{
|
Update: &proto.SyncResponse{
|
||||||
// fill those field for backward compatibility
|
// fill those field for backward compatibility
|
||||||
@@ -410,36 +393,36 @@ func (am *DefaultAccountManager) DeletePeer(accountID, peerID, userID string) (*
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
am.peersUpdateManager.CloseChannel(peer.ID)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := am.updateAccountPeers(account); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
am.peersUpdateManager.CloseChannel(peerID)
|
|
||||||
am.storeEvent(userID, peer.ID, account.Id, activity.PeerRemovedByUser, peer.EventMeta(am.GetDNSDomain()))
|
am.storeEvent(userID, peer.ID, account.Id, activity.PeerRemovedByUser, peer.EventMeta(am.GetDNSDomain()))
|
||||||
return peer, nil
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeerByIP returns peer by its IP
|
// DeletePeer removes peer from the account by its IP
|
||||||
func (am *DefaultAccountManager) GetPeerByIP(accountID string, peerIP string) (*Peer, error) {
|
func (am *DefaultAccountManager) DeletePeer(accountID, peerID, userID string) error {
|
||||||
unlock := am.Store.AcquireAccountLock(accountID)
|
unlock := am.Store.AcquireAccountLock(accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
account, err := am.Store.GetAccount(accountID)
|
account, err := am.Store.GetAccount(accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, peer := range account.Peers {
|
err = am.deletePeers(account, []string{peerID}, userID)
|
||||||
if peerIP == peer.IP.String() {
|
if err != nil {
|
||||||
return peer, nil
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, status.Errorf(status.NotFound, "peer with IP %s not found", peerIP)
|
err = am.Store.SaveAccount(account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
am.updateAccountPeers(account)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNetworkMap returns Network map for a given peer (omits original peer from the Peers result)
|
// GetNetworkMap returns Network map for a given peer (omits original peer from the Peers result)
|
||||||
@@ -609,10 +592,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*
|
|||||||
opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain())
|
opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain())
|
||||||
am.storeEvent(opEvent.InitiatorID, opEvent.TargetID, opEvent.AccountID, opEvent.Activity, opEvent.Meta)
|
am.storeEvent(opEvent.InitiatorID, opEvent.TargetID, opEvent.AccountID, opEvent.Activity, opEvent.Meta)
|
||||||
|
|
||||||
err = am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
networkMap := account.GetPeerNetworkMap(newPeer.ID, am.dnsDomain)
|
networkMap := account.GetPeerNetworkMap(newPeer.ID, am.dnsDomain)
|
||||||
return newPeer, networkMap, nil
|
return newPeer, networkMap, nil
|
||||||
@@ -727,10 +707,7 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if updateRemotePeers {
|
if updateRemotePeers {
|
||||||
err = am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil
|
return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil
|
||||||
}
|
}
|
||||||
@@ -804,10 +781,7 @@ func (am *DefaultAccountManager) checkAndUpdatePeerSSHKey(peer *Peer, account *A
|
|||||||
}
|
}
|
||||||
|
|
||||||
// trigger network map update
|
// trigger network map update
|
||||||
err = am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return peer, nil
|
return peer, nil
|
||||||
}
|
}
|
||||||
@@ -852,7 +826,9 @@ func (am *DefaultAccountManager) UpdatePeerSSHKey(peerID string, sshKey string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// trigger network map update
|
// trigger network map update
|
||||||
return am.updateAccountPeers(account)
|
am.updateAccountPeers(account)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPeer for a given accountID, peerID and userID error if not found.
|
// GetPeer for a given accountID, peerID and userID error if not found.
|
||||||
@@ -909,21 +885,12 @@ func updatePeerMeta(peer *Peer, meta PeerSystemMeta, account *Account) (*Peer, b
|
|||||||
|
|
||||||
// updateAccountPeers updates all peers that belong to an account.
|
// updateAccountPeers updates all peers that belong to an account.
|
||||||
// Should be called when changes have to be synced to peers.
|
// Should be called when changes have to be synced to peers.
|
||||||
func (am *DefaultAccountManager) updateAccountPeers(account *Account) error {
|
func (am *DefaultAccountManager) updateAccountPeers(account *Account) {
|
||||||
peers := account.GetPeers()
|
peers := account.GetPeers()
|
||||||
|
|
||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
remotePeerNetworkMap, err := am.GetNetworkMap(peer.ID)
|
remotePeerNetworkMap := account.GetPeerNetworkMap(peer.ID, am.dnsDomain)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
update := toSyncResponse(nil, peer, nil, remotePeerNetworkMap, am.GetDNSDomain())
|
update := toSyncResponse(nil, peer, nil, remotePeerNetworkMap, am.GetDNSDomain())
|
||||||
err = am.peersUpdateManager.SendUpdate(peer.ID, &UpdateMessage{Update: update})
|
am.peersUpdateManager.SendUpdate(peer.ID, &UpdateMessage{Update: update})
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,8 +369,8 @@ func TestAccountManager_GetPeerNetwork(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if account.Network.Id != network.Id {
|
if account.Network.Identifier != network.Identifier {
|
||||||
t.Errorf("expecting Account Networks ID to be equal, got %s expected %s", network.Id, account.Network.Id)
|
t.Errorf("expecting Account Networks ID to be equal, got %s expected %s", network.Identifier, account.Network.Identifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ const (
|
|||||||
|
|
||||||
// PersonalAccessToken holds all information about a PAT including a hashed version of it for verification
|
// PersonalAccessToken holds all information about a PAT including a hashed version of it for verification
|
||||||
type PersonalAccessToken struct {
|
type PersonalAccessToken struct {
|
||||||
ID string
|
ID string `gorm:"primaryKey"`
|
||||||
|
// User is a reference to Account that this object belongs
|
||||||
|
UserID string `gorm:"index"`
|
||||||
Name string
|
Name string
|
||||||
HashedToken string
|
HashedToken string
|
||||||
ExpirationDate time.Time
|
ExpirationDate time.Time
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user