commit e5c8bf19341eac732173f0bcbd1a0ff9915b7166 Author: root Date: Mon Apr 21 09:31:22 2025 +0000 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27c9241 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +./traefik/acme.json +.temp \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a55abbc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,99 @@ +services: + traefik: + image: traefik:v2.11 + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro + - ./traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro + - ./traefik/acme.json:/acme.json + labels: + - "traefik.enable=true" + + signal: + image: netbirdio/signal:latest + restart: unless-stopped + environment: + - SIGNAL_ADDR=signal.dev.d3h.space:51820 + ports: + - "51820:51820/udp" + labels: + - "traefik.enable=true" + - "traefik.http.routers.signal.rule=Host(`signal.dev.d3h.space`)" + - "traefik.http.routers.signal.entrypoints=websecure" + - "traefik.http.routers.signal.tls.certresolver=letsencrypt" + + management: + image: netbirdio/management:latest + restart: unless-stopped + volumes: + - ./management:/etc/netbird + - ./management:/var/lib/netbird + labels: + - "traefik.enable=true" + - "traefik.http.routers.management.rule=Host(`management.dev.d3h.space`)" + - "traefik.http.routers.management.entrypoints=websecure" + - "traefik.http.routers.management.tls.certresolver=letsencrypt" + + dashboard: + image: netbirdio/dashboard:latest + restart: unless-stopped + environment: + - NB_MANAGEMENT_URL=http://management:80 + labels: + - "traefik.enable=true" + - "traefik.http.routers.dashboard.rule=Host(`dashboard.dev.d3h.space`)" + - "traefik.http.routers.dashboard.entrypoints=websecure" + - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt" + + postgres: + image: postgres:16 + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - ./pgdata:/var/lib/postgresql/data + + keycloak: + image: quay.io/keycloak/keycloak:latest + restart: unless-stopped + ports: + - "8080" # для доступа из других контейнеров и дебага + environment: + KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN} + KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} + KC_DB: postgres + KC_DB_URL_HOST: postgres + KC_DB_URL_DATABASE: ${POSTGRES_DB} + KC_DB_USERNAME: ${POSTGRES_USER} + KC_DB_PASSWORD: ${POSTGRES_PASSWORD} + KC_PROXY: edge + KC_HTTP_ENABLED: true + KC_HOSTNAME: keycloak.dev.d3h.space + KC_HOSTNAME_STRICT: true + KC_HOSTNAME_STRICT_HTTPS: true + KC_PROXY_HEADERS: xforwarded + KC_FRONTEND_URL: https://keycloak.dev.d3h.space + KC_HOSTNAME_URL: https://keycloak.dev.d3h.space + depends_on: + - postgres + labels: + - "traefik.enable=true" + - "traefik.http.routers.keycloak.rule=Host(`keycloak.dev.d3h.space`)" + - "traefik.http.routers.keycloak.entrypoints=websecure" + - "traefik.http.routers.keycloak.tls.certresolver=letsencrypt" + - "traefik.http.routers.keycloak.middlewares=keycloak-https-headers" + - "traefik.http.services.keycloak.loadbalancer.server.port=8080" + - "traefik.http.middlewares.keycloak-https-headers.headers.customrequestheaders.X-Forwarded-Proto=https" + - "traefik.http.middlewares.keycloak-https-headers.headers.customrequestheaders.X-Forwarded-Scheme=https" + - "traefik.http.middlewares.keycloak-https-headers.headers.customrequestheaders.X-Forwarded-Host=keycloak.dev.d3h.space" + + - "traefik.http.routers.keycloak-http.rule=Host(`keycloak.dev.d3h.space`)" + - "traefik.http.routers.keycloak-http.entrypoints=web" + - "traefik.http.routers.keycloak-http.middlewares=redirect-to-https" + entrypoint: ["/opt/keycloak/bin/kc.sh", "start"] diff --git a/management/GeoLite2-City_20241115.mmdb b/management/GeoLite2-City_20241115.mmdb new file mode 100644 index 0000000..9158c0e Binary files /dev/null and b/management/GeoLite2-City_20241115.mmdb differ diff --git a/management/events.db b/management/events.db new file mode 100644 index 0000000..efe2905 Binary files /dev/null and b/management/events.db differ diff --git a/management/geonames_20241115.db b/management/geonames_20241115.db new file mode 100644 index 0000000..675d713 Binary files /dev/null and b/management/geonames_20241115.db differ diff --git a/management/management.json b/management/management.json new file mode 100644 index 0000000..ef08b3b --- /dev/null +++ b/management/management.json @@ -0,0 +1,31 @@ +{ + "Stuns": null, + "TURNConfig": null, + "Relay": null, + "Signal": null, + "Datadir": "/var/lib/netbird/", + "DataStoreEncryptionKey": "0vOXAJFtvmg1k4jAPQtfUnB94inSVLsCjO8gNX+O5/0=", + "HttpConfig": { + "LetsEncryptDomain": "", + "CertFile": "", + "CertKey": "", + "AuthAudience": "", + "AuthIssuer": "", + "AuthUserIDClaim": "", + "AuthKeysLocation": "", + "OIDCConfigEndpoint": "", + "IdpSignKeyRefreshEnabled": false, + "ExtraAuthAudience": "" + }, + "IdpManagerConfig": null, + "DeviceAuthorizationFlow": null, + "PKCEAuthorizationFlow": null, + "StoreConfig": { + "Engine": "" + }, + "ReverseProxy": { + "TrustedHTTPProxies": null, + "TrustedHTTPProxiesCount": 0, + "TrustedPeers": null + } +} \ No newline at end of file diff --git a/management/store.db b/management/store.db new file mode 100644 index 0000000..99f974e Binary files /dev/null and b/management/store.db differ diff --git a/netbird/.devcontainer/Dockerfile b/netbird/.devcontainer/Dockerfile new file mode 100644 index 0000000..4697acf --- /dev/null +++ b/netbird/.devcontainer/Dockerfile @@ -0,0 +1,15 @@ +FROM golang:1.23-bullseye + +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends\ + gettext-base=0.21-4 \ + iptables=1.8.7-1 \ + libgl1-mesa-dev=20.3.5-1 \ + xorg-dev=1:7.7+22 \ + libayatana-appindicator3-dev=0.5.5-2+deb11u2 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && go install -v golang.org/x/tools/gopls@latest + + +WORKDIR /app diff --git a/netbird/.devcontainer/devcontainer.json b/netbird/.devcontainer/devcontainer.json new file mode 100644 index 0000000..97aad75 --- /dev/null +++ b/netbird/.devcontainer/devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "NetBird", + "build": { + "context": "..", + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/devcontainers/features/go:1": { + "version": "1.23" + } + }, + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "capAdd": [ + "NET_ADMIN", + "SYS_ADMIN", + "SYS_RESOURCE" + ], + "privileged": true +} \ No newline at end of file diff --git a/netbird/.editorconfig b/netbird/.editorconfig new file mode 100644 index 0000000..3dcb869 --- /dev/null +++ b/netbird/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.go] +indent_style = tab diff --git a/netbird/.git-branches.toml b/netbird/.git-branches.toml new file mode 100644 index 0000000..d181809 --- /dev/null +++ b/netbird/.git-branches.toml @@ -0,0 +1,27 @@ +# More info around this file at https://www.git-town.com/configuration-file + +[branches] +main = "main" +perennials = [] +perennial-regex = "" + +[create] +new-branch-type = "feature" +push-new-branches = false + +[hosting] +dev-remote = "origin" +# platform = "" +# origin-hostname = "" + +[ship] +delete-tracking-branch = false +strategy = "squash-merge" + +[sync] +feature-strategy = "merge" +perennial-strategy = "rebase" +prototype-strategy = "merge" +push-hook = true +tags = true +upstream = false diff --git a/netbird/.gitattributes b/netbird/.gitattributes new file mode 100644 index 0000000..d207b18 --- /dev/null +++ b/netbird/.gitattributes @@ -0,0 +1 @@ +*.go text eol=lf diff --git a/netbird/.github/FUNDING.yml b/netbird/.github/FUNDING.yml new file mode 100644 index 0000000..c3d3221 --- /dev/null +++ b/netbird/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [netbirdio] diff --git a/netbird/.github/ISSUE_TEMPLATE/bug-issue-report.md b/netbird/.github/ISSUE_TEMPLATE/bug-issue-report.md new file mode 100644 index 0000000..3633cca --- /dev/null +++ b/netbird/.github/ISSUE_TEMPLATE/bug-issue-report.md @@ -0,0 +1,64 @@ +--- +name: Bug/Issue report +about: Create a report to help us improve +title: '' +labels: ['triage-needed'] +assignees: '' + +--- + +**Describe the problem** + +A clear and concise description of what the problem is. + +**To Reproduce** + +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** + +A clear and concise description of what you expected to happen. + +**Are you using NetBird Cloud?** + +Please specify whether you use NetBird Cloud or self-host NetBird's control plane. + +**NetBird version** + +`netbird version` + +**Is any other VPN software installed?** + +If yes, which one? + +**Debug output** + +To help us resolve the problem, please attach the following debug output + + netbird status -dA + +As well as the file created by + + netbird debug for 1m -AS + + +We advise reviewing the anonymized output for any remaining personal information. + +**Screenshots** + +If applicable, add screenshots to help explain your problem. + +**Additional context** + +Add any other context about the problem here. + +**Have you tried these troubleshooting steps?** +- [ ] Checked for newer NetBird versions +- [ ] Searched for similar issues on GitHub (including closed ones) +- [ ] Restarted the NetBird client +- [ ] Disabled other VPN software +- [ ] Checked firewall settings diff --git a/netbird/.github/ISSUE_TEMPLATE/feature_request.md b/netbird/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..4a3e578 --- /dev/null +++ b/netbird/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: ['feature-request'] +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/netbird/.github/pull_request_template.md b/netbird/.github/pull_request_template.md new file mode 100644 index 0000000..c4bd314 --- /dev/null +++ b/netbird/.github/pull_request_template.md @@ -0,0 +1,15 @@ +## Describe your changes + +## Issue ticket number and link + +## Stack + + + +### Checklist +- [ ] Is it a bug fix +- [ ] Is a typo/documentation fix +- [ ] Is a feature enhancement +- [ ] It is a refactor +- [ ] Created tests that fail without the change (if possible) +- [ ] Extended the README / documentation, if necessary diff --git a/netbird/.github/workflows/git-town.yml b/netbird/.github/workflows/git-town.yml new file mode 100644 index 0000000..c54fcb4 --- /dev/null +++ b/netbird/.github/workflows/git-town.yml @@ -0,0 +1,21 @@ +name: Git Town + +on: + pull_request: + branches: + - '**' + +jobs: + git-town: + name: Display the branch stack + runs-on: ubuntu-latest + + permissions: + contents: read + pull-requests: write + + steps: + - uses: actions/checkout@v4 + - uses: git-town/action@v1 + with: + skip-single-stacks: true \ No newline at end of file diff --git a/netbird/.github/workflows/golang-test-darwin.yml b/netbird/.github/workflows/golang-test-darwin.yml new file mode 100644 index 0000000..4571ce7 --- /dev/null +++ b/netbird/.github/workflows/golang-test-darwin.yml @@ -0,0 +1,46 @@ +name: "Darwin" + +on: + push: + branches: + - main + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} + cancel-in-progress: true + +jobs: + test: + name: "Client / Unit" + runs-on: macos-latest + steps: + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + cache: false + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: macos-gotest-${{ hashFiles('**/go.sum') }} + restore-keys: | + macos-gotest- + macos-go- + + - name: Install libpcap + run: brew install libpcap + + - name: Install modules + run: go mod tidy + + - name: check git status + run: git --no-pager diff --exit-code + + - name: Test + run: NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true go test -tags=devcert -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' -timeout 5m -p 1 $(go list ./... | grep -v /management) + diff --git a/netbird/.github/workflows/golang-test-freebsd.yml b/netbird/.github/workflows/golang-test-freebsd.yml new file mode 100644 index 0000000..32ceb36 --- /dev/null +++ b/netbird/.github/workflows/golang-test-freebsd.yml @@ -0,0 +1,52 @@ +name: "FreeBSD" + +on: + push: + branches: + - main + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} + cancel-in-progress: true + +jobs: + test: + name: "Client / Unit" + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Test in FreeBSD + id: test + uses: vmactions/freebsd-vm@v1 + with: + usesh: true + copyback: false + release: "14.2" + prepare: | + pkg install -y curl pkgconf xorg + LATEST_VERSION=$(curl -s https://go.dev/VERSION?m=text|head -n 1) + GO_TARBALL="$LATEST_VERSION.freebsd-amd64.tar.gz" + GO_URL="https://go.dev/dl/$GO_TARBALL" + curl -vLO "$GO_URL" + tar -C /usr/local -vxzf "$GO_TARBALL" + + # -x - to print all executed commands + # -e - to faile on first error + run: | + set -e -x + export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin + time go build -o netbird client/main.go + # check all component except management, since we do not support management server on freebsd + time go test -timeout 1m -failfast ./base62/... + # NOTE: without -p1 `client/internal/dns` will fail because of `listen udp4 :33100: bind: address already in use` + time go test -timeout 8m -failfast -p 1 ./client/... + time go test -timeout 1m -failfast ./dns/... + time go test -timeout 1m -failfast ./encryption/... + time go test -timeout 1m -failfast ./formatter/... + time go test -timeout 1m -failfast ./client/iface/... + time go test -timeout 1m -failfast ./route/... + time go test -timeout 1m -failfast ./sharedsock/... + time go test -timeout 1m -failfast ./signal/... + time go test -timeout 1m -failfast ./util/... + time go test -timeout 1m -failfast ./version/... diff --git a/netbird/.github/workflows/golang-test-linux.yml b/netbird/.github/workflows/golang-test-linux.yml new file mode 100644 index 0000000..cf061f8 --- /dev/null +++ b/netbird/.github/workflows/golang-test-linux.yml @@ -0,0 +1,593 @@ +name: Linux + +on: + push: + branches: + - main + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} + cancel-in-progress: true + +jobs: + build-cache: + name: "Build Cache" + runs-on: ubuntu-22.04 + outputs: + management: ${{ steps.filter.outputs.management }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + management: + - 'management/**' + + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + cache: false + + - name: Get Go environment + run: | + echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV + echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV + + - name: Cache Go modules + uses: actions/cache@v4 + id: cache + with: + path: | + ${{ env.cache }} + ${{ env.modcache }} + key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }} + + - name: Install dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev + + - name: Install 32-bit libpcap + if: steps.cache.outputs.cache-hit != 'true' + run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386 + + - name: Build client + if: steps.cache.outputs.cache-hit != 'true' + working-directory: client + run: CGO_ENABLED=1 go build . + + - name: Build client 386 + if: steps.cache.outputs.cache-hit != 'true' + working-directory: client + run: CGO_ENABLED=1 GOARCH=386 go build -o client-386 . + + - name: Build management + if: steps.cache.outputs.cache-hit != 'true' + working-directory: management + run: CGO_ENABLED=1 go build . + + - name: Build management 386 + if: steps.cache.outputs.cache-hit != 'true' + working-directory: management + run: CGO_ENABLED=1 GOARCH=386 go build -o management-386 . + + - name: Build signal + if: steps.cache.outputs.cache-hit != 'true' + working-directory: signal + run: CGO_ENABLED=1 go build . + + - name: Build signal 386 + if: steps.cache.outputs.cache-hit != 'true' + working-directory: signal + run: CGO_ENABLED=1 GOARCH=386 go build -o signal-386 . + + - name: Build relay + if: steps.cache.outputs.cache-hit != 'true' + working-directory: relay + run: CGO_ENABLED=1 go build . + + - name: Build relay 386 + if: steps.cache.outputs.cache-hit != 'true' + working-directory: relay + run: CGO_ENABLED=1 GOARCH=386 go build -o relay-386 . + + test: + name: "Client / Unit" + needs: [build-cache] + strategy: + fail-fast: false + matrix: + arch: [ '386','amd64' ] + runs-on: ubuntu-22.04 + steps: + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + cache: false + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get Go environment + run: | + echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV + echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV + + - name: Cache Go modules + uses: actions/cache/restore@v4 + with: + path: | + ${{ env.cache }} + ${{ env.modcache }} + key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-gotest-cache- + + - name: Install dependencies + run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev + + - name: Install 32-bit libpcap + if: matrix.arch == '386' + run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386 + + - name: Install modules + run: go mod tidy + + - name: check git status + run: git --no-pager diff --exit-code + + - name: Test + run: CGO_ENABLED=1 GOARCH=${{ matrix.arch }} CI=true go test -tags devcert -exec 'sudo' -timeout 10m -p 1 $(go list ./... | grep -v -e /management -e /signal -e /relay) + + test_relay: + name: "Relay / Unit" + needs: [build-cache] + strategy: + fail-fast: false + matrix: + arch: [ '386','amd64' ] + runs-on: ubuntu-22.04 + steps: + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + cache: false + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get Go environment + run: | + echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV + echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV + + - name: Cache Go modules + uses: actions/cache/restore@v4 + with: + path: | + ${{ env.cache }} + ${{ env.modcache }} + key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-gotest-cache- + + - name: Install dependencies + run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev + + - name: Install 32-bit libpcap + if: matrix.arch == '386' + run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386 + + - name: Install modules + run: go mod tidy + + - name: check git status + run: git --no-pager diff --exit-code + + - name: Test + run: | + CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \ + go test \ + -exec 'sudo' \ + -timeout 10m ./signal/... + + test_signal: + name: "Signal / Unit" + needs: [build-cache] + strategy: + fail-fast: false + matrix: + arch: [ '386','amd64' ] + runs-on: ubuntu-22.04 + steps: + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + cache: false + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get Go environment + run: | + echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV + echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV + + - name: Cache Go modules + uses: actions/cache/restore@v4 + with: + path: | + ${{ env.cache }} + ${{ env.modcache }} + key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-gotest-cache- + + - name: Install dependencies + run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev + + - name: Install 32-bit libpcap + if: matrix.arch == '386' + run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386 + + - name: Install modules + run: go mod tidy + + - name: check git status + run: git --no-pager diff --exit-code + + - name: Test + run: | + CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \ + go test \ + -exec 'sudo' \ + -timeout 10m ./signal/... + + test_management: + name: "Management / Unit" + needs: [ build-cache ] + strategy: + fail-fast: false + matrix: + arch: [ 'amd64' ] + store: [ 'sqlite', 'postgres', 'mysql' ] + runs-on: ubuntu-22.04 + steps: + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + cache: false + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get Go environment + run: | + echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV + echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV + + - name: Cache Go modules + uses: actions/cache/restore@v4 + with: + path: | + ${{ env.cache }} + ${{ env.modcache }} + key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-gotest-cache- + + - name: Install dependencies + run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev + + - name: Install 32-bit libpcap + if: matrix.arch == '386' + run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386 + + - name: Install modules + run: go mod tidy + + - name: check git status + run: git --no-pager diff --exit-code + + - name: Login to Docker hub + if: matrix.store == 'mysql' && (github.repository == github.head.repo.full_name || !github.head_ref) + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: download mysql image + if: matrix.store == 'mysql' + run: docker pull mlsmaycon/warmed-mysql:8 + + - name: Test + run: | + CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \ + NETBIRD_STORE_ENGINE=${{ matrix.store }} \ + go test -tags=devcert \ + -exec "sudo --preserve-env=CI,NETBIRD_STORE_ENGINE" \ + -timeout 20m ./management/... + + benchmark: + name: "Management / Benchmark" + needs: [ build-cache ] + if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }} + strategy: + fail-fast: false + matrix: + arch: [ 'amd64' ] + store: [ 'sqlite', 'postgres' ] + runs-on: ubuntu-22.04 + steps: + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + cache: false + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get Go environment + run: | + echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV + echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV + + - name: Cache Go modules + uses: actions/cache/restore@v4 + with: + path: | + ${{ env.cache }} + ${{ env.modcache }} + key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-gotest-cache- + + - name: Install dependencies + run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev + + - name: Install 32-bit libpcap + if: matrix.arch == '386' + run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386 + + - name: Install modules + run: go mod tidy + + - name: check git status + run: git --no-pager diff --exit-code + + - name: Login to Docker hub + if: matrix.store == 'mysql' && (github.repository == github.head.repo.full_name || !github.head_ref) + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: download mysql image + if: matrix.store == 'mysql' + run: docker pull mlsmaycon/warmed-mysql:8 + + - name: Test + run: | + CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \ + NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true \ + go test -tags devcert -run=^$ -bench=. \ + -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' \ + -timeout 20m ./... + + api_benchmark: + name: "Management / Benchmark (API)" + needs: [ build-cache ] + if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }} + strategy: + fail-fast: false + matrix: + arch: [ 'amd64' ] + store: [ 'sqlite', 'postgres' ] + runs-on: ubuntu-22.04 + steps: + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + cache: false + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get Go environment + run: | + echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV + echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV + + - name: Cache Go modules + uses: actions/cache/restore@v4 + with: + path: | + ${{ env.cache }} + ${{ env.modcache }} + key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-gotest-cache- + + - name: Install dependencies + run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev + + - name: Install 32-bit libpcap + if: matrix.arch == '386' + run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386 + + - name: Install modules + run: go mod tidy + + - name: check git status + run: git --no-pager diff --exit-code + + - name: Login to Docker hub + if: matrix.store == 'mysql' && (github.repository == github.head.repo.full_name || !github.head_ref) + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: download mysql image + if: matrix.store == 'mysql' + run: docker pull mlsmaycon/warmed-mysql:8 + + - name: Test + run: | + CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \ + NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true \ + go test -tags=benchmark \ + -run=^$ \ + -bench=. \ + -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' \ + -timeout 20m ./management/... + + api_integration_test: + name: "Management / Integration" + needs: [ build-cache ] + if: ${{ needs.build-cache.outputs.management == 'true' || github.event_name != 'pull_request' }} + strategy: + fail-fast: false + matrix: + arch: [ 'amd64' ] + store: [ 'sqlite', 'postgres'] + runs-on: ubuntu-22.04 + steps: + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + cache: false + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get Go environment + run: | + echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV + echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV + + - name: Cache Go modules + uses: actions/cache/restore@v4 + with: + path: | + ${{ env.cache }} + ${{ env.modcache }} + key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-gotest-cache- + + - name: Install dependencies + run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev + + - name: Install 32-bit libpcap + if: matrix.arch == '386' + run: sudo dpkg --add-architecture i386 && sudo apt update && sudo apt-get install -y libpcap0.8-dev:i386 + + - name: Install modules + run: go mod tidy + + - name: check git status + run: git --no-pager diff --exit-code + + - name: Test + run: | + CGO_ENABLED=1 GOARCH=${{ matrix.arch }} \ + NETBIRD_STORE_ENGINE=${{ matrix.store }} CI=true \ + go test -tags=integration \ + -exec 'sudo --preserve-env=CI,NETBIRD_STORE_ENGINE' \ + -timeout 20m ./management/... + + test_client_on_docker: + name: "Client (Docker) / Unit" + needs: [ build-cache ] + runs-on: ubuntu-20.04 + steps: + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + cache: false + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get Go environment + run: | + echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV + echo "modcache=$(go env GOMODCACHE)" >> $GITHUB_ENV + + - name: Cache Go modules + uses: actions/cache/restore@v4 + with: + path: | + ${{ env.cache }} + ${{ env.modcache }} + key: ${{ runner.os }}-gotest-cache-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-gotest-cache- + + - name: Install dependencies + run: sudo apt update && sudo apt install -y -q libgtk-3-dev libayatana-appindicator3-dev libgl1-mesa-dev xorg-dev gcc-multilib libpcap-dev + + - name: Install modules + run: go mod tidy + + - name: check git status + run: git --no-pager diff --exit-code + + - name: Generate Shared Sock Test bin + run: CGO_ENABLED=0 go test -c -o sharedsock-testing.bin ./sharedsock + + - name: Generate RouteManager Test bin + run: CGO_ENABLED=0 go test -c -o routemanager-testing.bin ./client/internal/routemanager + + - name: Generate SystemOps Test bin + run: CGO_ENABLED=1 go test -c -o systemops-testing.bin -tags netgo -ldflags '-w -extldflags "-static -ldbus-1 -lpcap"' ./client/internal/routemanager/systemops + + - name: Generate nftables Manager Test bin + run: CGO_ENABLED=0 go test -c -o nftablesmanager-testing.bin ./client/firewall/nftables/... + + - name: Generate Engine Test bin + run: CGO_ENABLED=1 go test -c -o engine-testing.bin ./client/internal + + - name: Generate Peer Test bin + run: CGO_ENABLED=0 go test -c -o peer-testing.bin ./client/internal/peer/ + + - run: chmod +x *testing.bin + + - name: Run Shared Sock tests in docker + run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/sharedsock --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/sharedsock-testing.bin -test.timeout 5m -test.parallel 1 + + - name: Run Iface tests in docker + run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/netbird -v /tmp/cache:/tmp/cache -v /tmp/modcache:/tmp/modcache -w /netbird -e GOCACHE=/tmp/cache -e GOMODCACHE=/tmp/modcache -e CGO_ENABLED=0 golang:1.23-alpine go test -test.timeout 5m -test.parallel 1 ./client/iface/... + + - 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 + + - name: Run SystemOps tests in docker + run: docker run -t --cap-add=NET_ADMIN --privileged --rm -v $PWD:/ci -w /ci/client/internal/routemanager/systemops --entrypoint /busybox/sh gcr.io/distroless/base:debug -c /ci/systemops-testing.bin -test.timeout 5m -test.parallel 1 + + - 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 + + - 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 -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 + 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 diff --git a/netbird/.github/workflows/golang-test-windows.yml b/netbird/.github/workflows/golang-test-windows.yml new file mode 100644 index 0000000..d9ff0a8 --- /dev/null +++ b/netbird/.github/workflows/golang-test-windows.yml @@ -0,0 +1,72 @@ +name: "Windows" + +on: + push: + branches: + - main + pull_request: + +env: + downloadPath: '${{ github.workspace }}\temp' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} + cancel-in-progress: true + +jobs: + test: + name: "Client / Unit" + runs-on: windows-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Go + uses: actions/setup-go@v5 + id: go + with: + go-version: "1.23.x" + cache: false + + - name: Get Go environment + run: | + echo "cache=$(go env GOCACHE)" >> $env:GITHUB_ENV + echo "modcache=$(go env GOMODCACHE)" >> $env:GITHUB_ENV + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: | + ${{ env.cache }} + ${{ env.modcache }} + key: ${{ runner.os }}-gotest-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-gotest- + ${{ runner.os }}-go- + + - name: Download wintun + uses: carlosperate/download-file-action@v2 + id: download-wintun + with: + file-url: https://pkgs.netbird.io/wintun/wintun-0.14.1.zip + file-name: wintun.zip + location: ${{ env.downloadPath }} + sha256: '07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51' + + - name: Decompressing wintun files + run: tar -zvxf "${{ steps.download-wintun.outputs.file-path }}" -C ${{ env.downloadPath }} + + - run: mv ${{ env.downloadPath }}/wintun/bin/amd64/wintun.dll 'C:\Windows\System32\' + + - 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=${{ env.cache }} + - run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe env -w GOCACHE=${{ env.modcache }} + - run: PsExec64 -s -w ${{ github.workspace }} C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe mod tidy + - run: echo "files=$(go list ./... | ForEach-Object { $_ } | Where-Object { $_ -notmatch '/management' })" >> $env:GITHUB_ENV + + - name: test + run: PsExec64 -s -w ${{ github.workspace }} cmd.exe /c "C:\hostedtoolcache\windows\go\${{ steps.go.outputs.go-version }}\x64\bin\go.exe test -tags=devcert -timeout 10m -p 1 ${{ env.files }} > test-out.txt 2>&1" + - name: test output + if: ${{ always() }} + run: Get-Content test-out.txt diff --git a/netbird/.github/workflows/golangci-lint.yml b/netbird/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..bdd508e --- /dev/null +++ b/netbird/.github/workflows/golangci-lint.yml @@ -0,0 +1,59 @@ +name: Lint +on: [pull_request] + +permissions: + contents: read + pull-requests: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} + cancel-in-progress: true + +jobs: + codespell: + name: codespell + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: codespell + uses: codespell-project/actions-codespell@v2 + with: + ignore_words_list: erro,clienta,hastable,iif,groupd,testin,groupe + skip: go.mod,go.sum + only_warn: 1 + golangci: + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + include: + - os: macos-latest + display_name: Darwin + - os: windows-latest + display_name: Windows + - os: ubuntu-latest + display_name: Linux + name: ${{ matrix.display_name }} + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Check for duplicate constants + if: matrix.os == 'ubuntu-latest' + run: | + ! awk '/const \(/,/)/{print $0}' management/server/activity/codes.go | grep -o '= [0-9]*' | sort | uniq -d | grep . + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + cache: false + - 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 libpcap-dev + - name: golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + version: latest + args: --timeout=12m --out-format colored-line-number diff --git a/netbird/.github/workflows/install-script-test.yml b/netbird/.github/workflows/install-script-test.yml new file mode 100644 index 0000000..22d002a --- /dev/null +++ b/netbird/.github/workflows/install-script-test.yml @@ -0,0 +1,37 @@ +name: Test installation + +on: + push: + branches: + - main + pull_request: + paths: + - "release_files/install.sh" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} + cancel-in-progress: true +jobs: + test-install-script: + strategy: + fail-fast: false + max-parallel: 2 + matrix: + os: [ubuntu-latest, macos-latest] + skip_ui_mode: [true, false] + install_binary: [true, false] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: run install script + env: + SKIP_UI_APP: ${{ matrix.skip_ui_mode }} + USE_BIN_INSTALL: ${{ matrix.install_binary }} + GITHUB_TOKEN: ${{ secrets.RO_API_CALLER_TOKEN }} + run: | + [ "$SKIP_UI_APP" == "false" ] && export XDG_CURRENT_DESKTOP="none" + cat release_files/install.sh | sh -x + + - name: check cli binary + run: command -v netbird diff --git a/netbird/.github/workflows/mobile-build-validation.yml b/netbird/.github/workflows/mobile-build-validation.yml new file mode 100644 index 0000000..569956a --- /dev/null +++ b/netbird/.github/workflows/mobile-build-validation.yml @@ -0,0 +1,67 @@ +name: Mobile + +on: + push: + branches: + - main + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} + cancel-in-progress: true + +jobs: + android_build: + name: "Android / Build" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + with: + cmdline-tools-version: 8512546 + - name: Setup Java + uses: actions/setup-java@v4 + with: + java-version: "11" + distribution: "adopt" + - name: NDK Cache + id: ndk-cache + uses: actions/cache@v4 + with: + path: /usr/local/lib/android/sdk/ndk + key: ndk-cache-23.1.7779620 + - name: Setup NDK + run: /usr/local/lib/android/sdk/cmdline-tools/7.0/bin/sdkmanager --install "ndk;23.1.7779620" + - name: install gomobile + run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240404231514-09dbf07665ed + - name: gomobile init + run: gomobile init + - name: build android netbird lib + run: PATH=$PATH:$(go env GOPATH) gomobile bind -o $GITHUB_WORKSPACE/netbird.aar -javapkg=io.netbird.gomobile -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/io.netbird.client/cache/wireguard -X github.com/netbirdio/netbird/version.version=buildtest" $GITHUB_WORKSPACE/client/android + env: + CGO_ENABLED: 0 + ANDROID_NDK_HOME: /usr/local/lib/android/sdk/ndk/23.1.7779620 + ios_build: + name: "iOS / Build" + runs-on: macos-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + - name: install gomobile + run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20240404231514-09dbf07665ed + - name: gomobile init + run: gomobile init + - name: build iOS netbird lib + run: PATH=$PATH:$(go env GOPATH) gomobile bind -target=ios -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=buildtest" -o ./NetBirdSDK.xcframework ./client/ios/NetBirdSDK + env: + CGO_ENABLED: 0 diff --git a/netbird/.github/workflows/release.yml b/netbird/.github/workflows/release.yml new file mode 100644 index 0000000..4806b56 --- /dev/null +++ b/netbird/.github/workflows/release.yml @@ -0,0 +1,226 @@ +name: Release + +on: + push: + tags: + - "v*" + branches: + - main + pull_request: + +env: + SIGN_PIPE_VER: "v0.0.18" + GORELEASER_VER: "v2.3.2" + PRODUCT_NAME: "NetBird" + COPYRIGHT: "NetBird GmbH" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} + cancel-in-progress: true + +jobs: + release: + runs-on: ubuntu-22.04 + env: + flags: "" + steps: + - name: Parse semver string + id: semver_parser + uses: booxmedialtd/ws-action-parse-semver@v1 + with: + input_string: ${{ (startsWith(github.ref, 'refs/tags/v') && github.ref) || 'refs/tags/v0.0.0' }} + version_extractor_regex: '\/v(.*)$' + + - if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + run: echo "flags=--snapshot" >> $GITHUB_ENV + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # It is required for GoReleaser to work properly + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.23" + cache: false + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-go-releaser-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-releaser- + - name: Install modules + run: go mod tidy + - name: check git status + run: git --no-pager diff --exit-code + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to Docker hub + if: github.event_name != 'pull_request' + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_TOKEN }} + - name: Install OS build dependencies + run: sudo apt update && sudo apt install -y -q gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu + + - name: Install goversioninfo + run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e + - name: Generate windows syso amd64 + run: goversioninfo -icon client/ui/assets/netbird.ico -manifest client/manifest.xml -product-name ${{ env.PRODUCT_NAME }} -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/resources_windows_amd64.syso + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v4 + with: + version: ${{ env.GORELEASER_VER }} + args: release --clean ${{ env.flags }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + UPLOAD_DEBIAN_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }} + UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }} + - name: upload non tags for debug purposes + uses: actions/upload-artifact@v4 + with: + name: release + path: dist/ + retention-days: 7 + - name: upload linux packages + uses: actions/upload-artifact@v4 + with: + name: linux-packages + path: dist/netbird_linux** + retention-days: 7 + - name: upload windows packages + uses: actions/upload-artifact@v4 + with: + name: windows-packages + path: dist/netbird_windows** + retention-days: 7 + - name: upload macos packages + uses: actions/upload-artifact@v4 + with: + name: macos-packages + path: dist/netbird_darwin** + retention-days: 7 + + release_ui: + runs-on: ubuntu-latest + steps: + - name: Parse semver string + id: semver_parser + uses: booxmedialtd/ws-action-parse-semver@v1 + with: + input_string: ${{ (startsWith(github.ref, 'refs/tags/v') && github.ref) || 'refs/tags/v0.0.0' }} + version_extractor_regex: '\/v(.*)$' + + - if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + run: echo "flags=--snapshot" >> $GITHUB_ENV + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # It is required for GoReleaser to work properly + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.23" + cache: false + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-ui-go-releaser-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-ui-go-releaser- + + - name: Install modules + run: go mod tidy + + - name: check git status + run: git --no-pager diff --exit-code + + - name: Install dependencies + run: sudo apt update && sudo apt install -y -q libappindicator3-dev gir1.2-appindicator3-0.1 libxxf86vm-dev gcc-mingw-w64-x86-64 + - name: Install goversioninfo + run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e + - name: Generate windows syso amd64 + run: goversioninfo -64 -icon client/ui/assets/netbird.ico -manifest client/ui/manifest.xml -product-name ${{ env.PRODUCT_NAME }}-"UI" -copyright "${{ env.COPYRIGHT }}" -ver-major ${{ steps.semver_parser.outputs.major }} -ver-minor ${{ steps.semver_parser.outputs.minor }} -ver-patch ${{ steps.semver_parser.outputs.patch }} -ver-build 0 -file-version ${{ steps.semver_parser.outputs.fullversion }}.0 -product-version ${{ steps.semver_parser.outputs.fullversion }}.0 -o client/ui/resources_windows_amd64.syso + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v4 + with: + version: ${{ env.GORELEASER_VER }} + args: release --config .goreleaser_ui.yaml --clean ${{ env.flags }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + UPLOAD_DEBIAN_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }} + UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }} + - name: upload non tags for debug purposes + uses: actions/upload-artifact@v4 + with: + name: release-ui + path: dist/ + retention-days: 3 + + release_ui_darwin: + runs-on: macos-latest + steps: + - if: ${{ !startsWith(github.ref, 'refs/tags/v') }} + run: echo "flags=--snapshot" >> $GITHUB_ENV + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # It is required for GoReleaser to work properly + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.23" + cache: false + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-ui-go-releaser-darwin-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-ui-go-releaser-darwin- + - name: Install modules + run: go mod tidy + - name: check git status + run: git --no-pager diff --exit-code + - name: Run GoReleaser + id: goreleaser + uses: goreleaser/goreleaser-action@v4 + with: + version: ${{ env.GORELEASER_VER }} + args: release --config .goreleaser_ui_darwin.yaml --clean ${{ env.flags }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: upload non tags for debug purposes + uses: actions/upload-artifact@v4 + with: + name: release-ui-darwin + path: dist/ + retention-days: 3 + + trigger_signer: + runs-on: ubuntu-latest + needs: [release, release_ui, release_ui_darwin] + if: startsWith(github.ref, 'refs/tags/') + steps: + - name: Trigger binaries sign pipelines + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: Sign bin and installer + repo: netbirdio/sign-pipelines + ref: ${{ env.SIGN_PIPE_VER }} + token: ${{ secrets.SIGN_GITHUB_TOKEN }} + inputs: '{ "tag": "${{ github.ref }}", "skipRelease": false }' diff --git a/netbird/.github/workflows/sync-main.yml b/netbird/.github/workflows/sync-main.yml new file mode 100644 index 0000000..e36e35a --- /dev/null +++ b/netbird/.github/workflows/sync-main.yml @@ -0,0 +1,22 @@ +name: sync main + +on: + push: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} + cancel-in-progress: true + +jobs: + trigger_sync_main: + runs-on: ubuntu-latest + steps: + - name: Trigger main branch sync + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: sync-main.yml + repo: ${{ secrets.UPSTREAM_REPO }} + token: ${{ secrets.NC_GITHUB_TOKEN }} + inputs: '{ "sha": "${{ github.sha }}" }' \ No newline at end of file diff --git a/netbird/.github/workflows/sync-tag.yml b/netbird/.github/workflows/sync-tag.yml new file mode 100644 index 0000000..1cc553b --- /dev/null +++ b/netbird/.github/workflows/sync-tag.yml @@ -0,0 +1,23 @@ +name: sync tag + +on: + push: + tags: + - 'v*' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} + cancel-in-progress: true + +jobs: + trigger_sync_tag: + runs-on: ubuntu-latest + steps: + - name: Trigger release tag sync + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: sync-tag.yml + ref: main + repo: ${{ secrets.UPSTREAM_REPO }} + token: ${{ secrets.NC_GITHUB_TOKEN }} + inputs: '{ "tag": "${{ github.ref_name }}" }' \ No newline at end of file diff --git a/netbird/.github/workflows/test-infrastructure-files.yml b/netbird/.github/workflows/test-infrastructure-files.yml new file mode 100644 index 0000000..174b7d2 --- /dev/null +++ b/netbird/.github/workflows/test-infrastructure-files.yml @@ -0,0 +1,309 @@ +name: Test Infrastructure files + +on: + push: + branches: + - main + pull_request: + paths: + - 'infrastructure_files/**' + - '.github/workflows/test-infrastructure-files.yml' + - 'management/cmd/**' + - 'signal/cmd/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.actor_id }} + cancel-in-progress: true + +jobs: + test-docker-compose: + runs-on: ubuntu-latest + strategy: + matrix: + store: [ 'sqlite', 'postgres', 'mysql' ] + services: + postgres: + image: ${{ (matrix.store == 'postgres') && 'postgres' || '' }} + env: + POSTGRES_USER: netbird + POSTGRES_PASSWORD: postgres + POSTGRES_DB: netbird + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + ports: + - 5432:5432 + mysql: + image: ${{ (matrix.store == 'mysql') && 'mysql' || '' }} + env: + MYSQL_USER: netbird + MYSQL_PASSWORD: mysql + MYSQL_ROOT_PASSWORD: mysqlroot + MYSQL_DATABASE: netbird + options: >- + --health-cmd "mysqladmin ping --silent" + --health-interval 10s + --health-timeout 5s + ports: + - 3306:3306 + steps: + - name: Set Database Connection String + run: | + if [ "${{ matrix.store }}" == "postgres" ]; then + echo "NETBIRD_STORE_ENGINE_POSTGRES_DSN=host=$(hostname -I | awk '{print $1}') user=netbird password=postgres dbname=netbird port=5432" >> $GITHUB_ENV + else + echo "NETBIRD_STORE_ENGINE_POSTGRES_DSN==" >> $GITHUB_ENV + fi + if [ "${{ matrix.store }}" == "mysql" ]; then + echo "NETBIRD_STORE_ENGINE_MYSQL_DSN=netbird:mysql@tcp($(hostname -I | awk '{print $1}'):3306)/netbird" >> $GITHUB_ENV + else + echo "NETBIRD_STORE_ENGINE_MYSQL_DSN==" >> $GITHUB_ENV + fi + + - name: Install jq + run: sudo apt-get install -y jq + + - name: Install curl + run: sudo apt-get install -y curl + + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: "1.23.x" + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Checkout code + uses: actions/checkout@v4 + + - name: cp setup.env + run: cp infrastructure_files/tests/setup.env infrastructure_files/ + + - name: run configure + working-directory: infrastructure_files + run: bash -x configure.sh + env: + CI_NETBIRD_DOMAIN: localhost + CI_NETBIRD_AUTH_CLIENT_ID: testing.client.id + CI_NETBIRD_AUTH_CLIENT_SECRET: testing.client.secret + CI_NETBIRD_AUTH_AUDIENCE: testing.ci + CI_NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT: https://example.eu.auth0.com/.well-known/openid-configuration + CI_NETBIRD_USE_AUTH0: true + CI_NETBIRD_MGMT_IDP: "none" + CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id + CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret + CI_NETBIRD_AUTH_SUPPORTED_SCOPES: "openid profile email offline_access api email_verified" + CI_NETBIRD_STORE_CONFIG_ENGINE: ${{ matrix.store }} + NETBIRD_STORE_ENGINE_POSTGRES_DSN: ${{ env.NETBIRD_STORE_ENGINE_POSTGRES_DSN }} + NETBIRD_STORE_ENGINE_MYSQL_DSN: ${{ env.NETBIRD_STORE_ENGINE_MYSQL_DSN }} + CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH: false + + - name: check values + working-directory: infrastructure_files/artifacts + env: + CI_NETBIRD_DOMAIN: localhost + CI_NETBIRD_AUTH_CLIENT_ID: testing.client.id + CI_NETBIRD_AUTH_CLIENT_SECRET: testing.client.secret + CI_NETBIRD_AUTH_AUDIENCE: testing.ci + CI_NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT: https://example.eu.auth0.com/.well-known/openid-configuration + CI_NETBIRD_USE_AUTH0: true + CI_NETBIRD_AUTH_SUPPORTED_SCOPES: "openid profile email offline_access api email_verified" + CI_NETBIRD_AUTH_AUTHORITY: https://example.eu.auth0.com/ + CI_NETBIRD_AUTH_JWT_CERTS: https://example.eu.auth0.com/.well-known/jwks.json + CI_NETBIRD_AUTH_TOKEN_ENDPOINT: https://example.eu.auth0.com/oauth/token + CI_NETBIRD_AUTH_DEVICE_AUTH_ENDPOINT: https://example.eu.auth0.com/oauth/device/code + CI_NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT: https://example.eu.auth0.com/authorize + CI_NETBIRD_AUTH_REDIRECT_URI: "/peers" + CI_NETBIRD_TOKEN_SOURCE: "idToken" + CI_NETBIRD_AUTH_USER_ID_CLAIM: "email" + CI_NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE: "super" + CI_NETBIRD_AUTH_DEVICE_AUTH_SCOPE: "openid email" + CI_NETBIRD_MGMT_IDP: "none" + CI_NETBIRD_IDP_MGMT_CLIENT_ID: testing.client.id + CI_NETBIRD_IDP_MGMT_CLIENT_SECRET: testing.client.secret + CI_NETBIRD_SIGNAL_PORT: 12345 + CI_NETBIRD_STORE_CONFIG_ENGINE: ${{ matrix.store }} + NETBIRD_STORE_ENGINE_POSTGRES_DSN: '${{ env.NETBIRD_STORE_ENGINE_POSTGRES_DSN }}$' + NETBIRD_STORE_ENGINE_MYSQL_DSN: '${{ env.NETBIRD_STORE_ENGINE_MYSQL_DSN }}$' + CI_NETBIRD_MGMT_IDP_SIGNKEY_REFRESH: false + CI_NETBIRD_TURN_EXTERNAL_IP: "1.2.3.4" + + run: | + set -x + grep AUTH_CLIENT_ID docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_ID + grep AUTH_CLIENT_SECRET docker-compose.yml | grep $CI_NETBIRD_AUTH_CLIENT_SECRET + grep AUTH_AUTHORITY docker-compose.yml | grep $CI_NETBIRD_AUTH_AUTHORITY + grep AUTH_AUDIENCE docker-compose.yml | grep $CI_NETBIRD_AUTH_AUDIENCE + grep AUTH_SUPPORTED_SCOPES docker-compose.yml | grep "$CI_NETBIRD_AUTH_SUPPORTED_SCOPES" + grep USE_AUTH0 docker-compose.yml | grep $CI_NETBIRD_USE_AUTH0 + grep NETBIRD_MGMT_API_ENDPOINT docker-compose.yml | grep "$CI_NETBIRD_DOMAIN:33073" + grep AUTH_REDIRECT_URI docker-compose.yml | grep $CI_NETBIRD_AUTH_REDIRECT_URI + grep AUTH_SILENT_REDIRECT_URI docker-compose.yml | egrep 'AUTH_SILENT_REDIRECT_URI=$' + grep $CI_NETBIRD_SIGNAL_PORT docker-compose.yml | grep ':80' + grep LETSENCRYPT_DOMAIN docker-compose.yml | egrep 'LETSENCRYPT_DOMAIN=$' + grep NETBIRD_TOKEN_SOURCE docker-compose.yml | grep $CI_NETBIRD_TOKEN_SOURCE + grep AuthUserIDClaim management.json | grep $CI_NETBIRD_AUTH_USER_ID_CLAIM + 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 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 -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 4 IdpManagerConfig management.json | grep -A 2 ClientConfig | grep TokenEndpoint | grep $CI_NETBIRD_AUTH_TOKEN_ENDPOINT + 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 7 IdpManagerConfig management.json | grep -A 5 ClientConfig | grep GrantType | grep client_credentials + grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep Audience | grep $CI_NETBIRD_AUTH_AUDIENCE + grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep ClientID | grep $CI_NETBIRD_AUTH_CLIENT_ID + grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep ClientSecret | grep $CI_NETBIRD_AUTH_CLIENT_SECRET + grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep AuthorizationEndpoint | grep $CI_NETBIRD_AUTH_PKCE_AUTHORIZATION_ENDPOINT + grep -A 10 PKCEAuthorizationFlow management.json | grep -A 10 ProviderConfig | grep TokenEndpoint | grep $CI_NETBIRD_AUTH_TOKEN_ENDPOINT + 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" + grep "external-ip" turnserver.conf | grep $CI_NETBIRD_TURN_EXTERNAL_IP + grep "NETBIRD_STORE_ENGINE_MYSQL_DSN=$NETBIRD_STORE_ENGINE_MYSQL_DSN" docker-compose.yml + grep NETBIRD_STORE_ENGINE_POSTGRES_DSN docker-compose.yml | egrep "$NETBIRD_STORE_ENGINE_POSTGRES_DSN" + # check relay values + grep "NB_EXPOSED_ADDRESS=$CI_NETBIRD_DOMAIN:33445" docker-compose.yml + grep "NB_LISTEN_ADDRESS=:33445" docker-compose.yml + grep '33445:33445' docker-compose.yml + grep -A 10 'relay:' docker-compose.yml | egrep 'NB_AUTH_SECRET=.+$' + grep -A 7 Relay management.json | grep "rel://$CI_NETBIRD_DOMAIN:33445" + grep -A 7 Relay management.json | egrep '"Secret": ".+"' + grep DisablePromptLogin management.json | grep 'true' + + - name: Install modules + run: go mod tidy + + - name: check git status + run: git --no-pager diff --exit-code + + - 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: Build relay binary + working-directory: relay + run: CGO_ENABLED=0 go build -o netbird-relay main.go + + - name: Build relay docker image + working-directory: relay + run: | + docker build -t netbirdio/relay:latest . + + - name: run docker compose up + working-directory: infrastructure_files/artifacts + run: | + docker compose up -d + sleep 5 + docker compose ps + docker compose logs --tail=20 + + - name: test running containers + run: | + count=$(docker compose ps --format json | jq '. | select(.Name | contains("artifacts")) | .State' | grep -c running) + test $count -eq 5 || docker compose logs + working-directory: infrastructure_files/artifacts + + - name: test geolocation databases + working-directory: infrastructure_files/artifacts + run: | + sleep 30 + docker compose exec management ls -l /var/lib/netbird/ | grep -i GeoLite2-City_[0-9]*.mmdb + docker compose exec management ls -l /var/lib/netbird/ | grep -i geonames_[0-9]*.db + + test-getting-started-script: + runs-on: ubuntu-latest + steps: + - name: Install jq + run: sudo apt-get install -y jq + + - name: Checkout code + uses: actions/checkout@v4 + + - name: run script with Zitadel PostgreSQL + run: NETBIRD_DOMAIN=use-ip bash -x infrastructure_files/getting-started-with-zitadel.sh + + - name: test Caddy file gen postgres + run: test -f Caddyfile + + - name: test docker-compose file gen postgres + run: test -f docker-compose.yml + + - name: test management.json file gen postgres + run: test -f management.json + + - name: test turnserver.conf file gen postgres + run: | + set -x + test -f turnserver.conf + grep external-ip turnserver.conf + + - name: test zitadel.env file gen postgres + run: test -f zitadel.env + + - name: test dashboard.env file gen postgres + run: test -f dashboard.env + + - name: test relay.env file gen postgres + run: test -f relay.env + + - name: test zdb.env file gen postgres + run: test -f zdb.env + + - name: Postgres run cleanup + run: | + docker compose down --volumes --rmi all + rm -rf docker-compose.yml Caddyfile zitadel.env dashboard.env machinekey/zitadel-admin-sa.token turnserver.conf management.json zdb.env + + - name: run script with Zitadel CockroachDB + run: bash -x infrastructure_files/getting-started-with-zitadel.sh + env: + NETBIRD_DOMAIN: use-ip + ZITADEL_DATABASE: cockroach + + - name: test Caddy file gen CockroachDB + run: test -f Caddyfile + + - name: test docker-compose file gen CockroachDB + run: test -f docker-compose.yml + + - name: test management.json file gen CockroachDB + run: test -f management.json + + - name: test turnserver.conf file gen CockroachDB + run: | + set -x + test -f turnserver.conf + grep external-ip turnserver.conf + + - name: test zitadel.env file gen CockroachDB + run: test -f zitadel.env + + - name: test dashboard.env file gen CockroachDB + run: test -f dashboard.env + + - name: test relay.env file gen CockroachDB + run: test -f relay.env diff --git a/netbird/.github/workflows/update-docs.yml b/netbird/.github/workflows/update-docs.yml new file mode 100644 index 0000000..7709679 --- /dev/null +++ b/netbird/.github/workflows/update-docs.yml @@ -0,0 +1,22 @@ +name: update docs + +on: + push: + tags: + - 'v*' + paths: + - 'management/server/http/api/openapi.yml' + +jobs: + trigger_docs_api_update: + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + steps: + - name: Trigger API pages generation + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: generate api pages + repo: netbirdio/docs + ref: "refs/heads/main" + token: ${{ secrets.SIGN_GITHUB_TOKEN }} + inputs: '{ "tag": "${{ github.ref }}" }' \ No newline at end of file diff --git a/netbird/.gitignore b/netbird/.gitignore new file mode 100644 index 0000000..abb728b --- /dev/null +++ b/netbird/.gitignore @@ -0,0 +1,32 @@ +.idea +.run +*.iml +dist/ +bin/ +.env +conf.json +http-cmds.sh +setup.env +infrastructure_files/**/Caddyfile +infrastructure_files/**/dashboard.env +infrastructure_files/**/zitadel.env +infrastructure_files/**/management.json +infrastructure_files/**/management-*.json +infrastructure_files/**/docker-compose.yml +infrastructure_files/**/openid-configuration.json +infrastructure_files/**/turnserver.conf +infrastructure_files/**/management.json.bkp.** +infrastructure_files/**/management-*.json.bkp.** +infrastructure_files/**/docker-compose.yml.bkp.** +infrastructure_files/**/openid-configuration.json.bkp.** +infrastructure_files/**/turnserver.conf.bkp.** +management/management +client/client +client/client.exe +*.syso +client/.distfiles/ +infrastructure_files/setup.env +infrastructure_files/setup-*.env +.vscode +.DS_Store +vendor/ diff --git a/netbird/.golangci.yaml b/netbird/.golangci.yaml new file mode 100644 index 0000000..461677c --- /dev/null +++ b/netbird/.golangci.yaml @@ -0,0 +1,139 @@ +run: + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 6m + +# This file contains only configs which differ from defaults. +# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml +linters-settings: + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: false + + gosec: + includes: + - G101 # Look for hard coded credentials + #- G102 # Bind to all interfaces + - G103 # Audit the use of unsafe block + - G104 # Audit errors not checked + - G106 # Audit the use of ssh.InsecureIgnoreHostKey + #- G107 # Url provided to HTTP request as taint input + - G108 # Profiling endpoint automatically exposed on /debug/pprof + - G109 # Potential Integer overflow made by strconv.Atoi result conversion to int16/32 + - G110 # Potential DoS vulnerability via decompression bomb + - G111 # Potential directory traversal + #- G112 # Potential slowloris attack + - G113 # Usage of Rat.SetString in math/big with an overflow (CVE-2022-23772) + #- G114 # Use of net/http serve function that has no support for setting timeouts + - G201 # SQL query construction using format string + - G202 # SQL query construction using string concatenation + - G203 # Use of unescaped data in HTML templates + #- G204 # Audit use of command execution + - G301 # Poor file permissions used when creating a directory + - G302 # Poor file permissions used with chmod + - G303 # Creating tempfile using a predictable path + - G304 # File path provided as taint input + - G305 # File traversal when extracting zip/tar archive + - G306 # Poor file permissions used when writing to a new file + - G307 # Poor file permissions used when creating a file with os.Create + #- G401 # Detect the usage of DES, RC4, MD5 or SHA1 + #- G402 # Look for bad TLS connection settings + - G403 # Ensure minimum RSA key length of 2048 bits + #- G404 # Insecure random number source (rand) + #- G501 # Import blocklist: crypto/md5 + - G502 # Import blocklist: crypto/des + - G503 # Import blocklist: crypto/rc4 + - G504 # Import blocklist: net/http/cgi + #- G505 # Import blocklist: crypto/sha1 + - G601 # Implicit memory aliasing of items from a range statement + - G602 # Slice access out of bounds + + gocritic: + disabled-checks: + - commentFormatting + - captLocal + - deprecatedComment + + govet: + # Enable all analyzers. + # Default: false + enable-all: false + enable: + - nilness + + revive: + rules: + - name: exported + severity: warning + disabled: false + arguments: + - "checkPrivateReceivers" + - "sayRepetitiveInsteadOfStutters" + tenv: + # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. + # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. + # Default: false + all: true + +linters: + disable-all: true + enable: + ## enabled by default + - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases + - gosimple # specializes in simplifying a code + - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # detects when assignments to existing variables are not used + - staticcheck # is a go vet on steroids, applying a ton of static analysis checks + - tenv # Tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17. + - typecheck # like the front-end of a Go compiler, parses and type-checks Go code + - unused # checks for unused constants, variables, functions and types + ## disable by default but the have interesting results so lets add them + - bodyclose # checks whether HTTP response body is closed successfully + - dupword # dupword checks for duplicate words in the source code + - durationcheck # durationcheck checks for two durations multiplied together + - forbidigo # forbidigo forbids identifiers + - gocritic # provides diagnostics that check for bugs, performance and style issues + - gosec # inspects source code for security problems + - mirror # mirror reports wrong mirror patterns of bytes/strings usage + - misspell # misspess finds commonly misspelled English words in comments + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - nilnil # checks that there is no simultaneous return of nil error and an invalid value + - predeclared # predeclared finds code that shadows one of Go's predeclared identifiers + - revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. + - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + # - thelper # thelper detects Go test helpers without t.Helper() call and checks the consistency of test helpers. + - wastedassign # wastedassign finds wasted assignment statements +issues: + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 5 + + exclude-rules: + # allow fmt + - path: management/cmd/root\.go + linters: forbidigo + - path: signal/cmd/root\.go + linters: forbidigo + - path: sharedsock/filter\.go + linters: + - unused + - path: client/firewall/iptables/rule\.go + linters: + - unused + - path: test\.go + linters: + - mirror + - gosec + - path: mock\.go + linters: + - nilnil + # Exclude specific deprecation warnings for grpc methods + - linters: + - staticcheck + text: "grpc.DialContext is deprecated" + - linters: + - staticcheck + text: "grpc.WithBlock is deprecated" diff --git a/netbird/.goreleaser.yaml b/netbird/.goreleaser.yaml new file mode 100644 index 0000000..d647976 --- /dev/null +++ b/netbird/.goreleaser.yaml @@ -0,0 +1,521 @@ +version: 2 + +project_name: netbird +builds: + - id: netbird + dir: client + binary: netbird + env: [CGO_ENABLED=0] + goos: + - linux + - darwin + - windows + goarch: + - arm + - amd64 + - arm64 + - 386 + ignore: + - goos: windows + goarch: arm64 + - goos: windows + goarch: arm + - goos: windows + goarch: 386 + ldflags: + - -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser + mod_timestamp: "{{ .CommitTimestamp }}" + tags: + - load_wgnt_from_rsrc + + - id: netbird-static + dir: client + binary: netbird + env: [CGO_ENABLED=0] + goos: + - linux + goarch: + - mips + - mipsle + - mips64 + - mips64le + gomips: + - hardfloat + - softfloat + ldflags: + - -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser + mod_timestamp: "{{ .CommitTimestamp }}" + tags: + - load_wgnt_from_rsrc + + - id: netbird-mgmt + dir: management + env: + - CGO_ENABLED=1 + - >- + {{- if eq .Runtime.Goos "linux" }} + {{- if eq .Arch "arm64"}}CC=aarch64-linux-gnu-gcc{{- end }} + {{- if eq .Arch "arm"}}CC=arm-linux-gnueabihf-gcc{{- end }} + {{- end }} + binary: netbird-mgmt + goos: + - linux + goarch: + - amd64 + - arm64 + - arm + ldflags: + - -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser + mod_timestamp: "{{ .CommitTimestamp }}" + + - id: netbird-signal + dir: signal + env: [CGO_ENABLED=0] + binary: netbird-signal + goos: + - linux + goarch: + - amd64 + - arm64 + - arm + ldflags: + - -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser + mod_timestamp: "{{ .CommitTimestamp }}" + + - id: netbird-relay + dir: relay + env: [CGO_ENABLED=0] + binary: netbird-relay + goos: + - linux + goarch: + - amd64 + - arm64 + - arm + ldflags: + - -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser + mod_timestamp: "{{ .CommitTimestamp }}" + +universal_binaries: + - id: netbird + +archives: + - builds: + - netbird + - netbird-static + +nfpms: + - maintainer: Netbird + description: Netbird client. + homepage: https://netbird.io/ + id: netbird-deb + bindir: /usr/bin + builds: + - netbird + formats: + - deb + + scripts: + postinstall: "release_files/post_install.sh" + preremove: "release_files/pre_remove.sh" + + - maintainer: Netbird + description: Netbird client. + homepage: https://netbird.io/ + id: netbird-rpm + bindir: /usr/bin + builds: + - netbird + formats: + - rpm + + scripts: + postinstall: "release_files/post_install.sh" + preremove: "release_files/pre_remove.sh" +dockers: + - image_templates: + - netbirdio/netbird:{{ .Version }}-amd64 + ids: + - netbird + goarch: amd64 + use: buildx + dockerfile: client/Dockerfile + build_flag_templates: + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/netbird:{{ .Version }}-arm64v8 + ids: + - netbird + goarch: arm64 + use: buildx + dockerfile: client/Dockerfile + build_flag_templates: + - "--platform=linux/arm64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/netbird:{{ .Version }}-arm + ids: + - netbird + goarch: arm + goarm: 6 + use: buildx + dockerfile: client/Dockerfile + build_flag_templates: + - "--platform=linux/arm" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + + - image_templates: + - netbirdio/netbird:{{ .Version }}-rootless-amd64 + ids: + - netbird + goarch: amd64 + use: buildx + dockerfile: client/Dockerfile-rootless + build_flag_templates: + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/netbird:{{ .Version }}-rootless-arm64v8 + ids: + - netbird + goarch: arm64 + use: buildx + dockerfile: client/Dockerfile-rootless + build_flag_templates: + - "--platform=linux/arm64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/netbird:{{ .Version }}-rootless-arm + ids: + - netbird + goarch: arm + goarm: 6 + use: buildx + dockerfile: client/Dockerfile-rootless + build_flag_templates: + - "--platform=linux/arm" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=maintainer=dev@netbird.io" + + - image_templates: + - netbirdio/relay:{{ .Version }}-amd64 + ids: + - netbird-relay + goarch: amd64 + use: buildx + dockerfile: relay/Dockerfile + build_flag_templates: + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/relay:{{ .Version }}-arm64v8 + ids: + - netbird-relay + goarch: arm64 + use: buildx + dockerfile: relay/Dockerfile + build_flag_templates: + - "--platform=linux/arm64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/relay:{{ .Version }}-arm + ids: + - netbird-relay + goarch: arm + goarm: 6 + use: buildx + dockerfile: relay/Dockerfile + build_flag_templates: + - "--platform=linux/arm" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/signal:{{ .Version }}-amd64 + ids: + - netbird-signal + goarch: amd64 + use: buildx + dockerfile: signal/Dockerfile + build_flag_templates: + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/signal:{{ .Version }}-arm64v8 + ids: + - netbird-signal + goarch: arm64 + use: buildx + dockerfile: signal/Dockerfile + build_flag_templates: + - "--platform=linux/arm64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/signal:{{ .Version }}-arm + ids: + - netbird-signal + goarch: arm + goarm: 6 + use: buildx + dockerfile: signal/Dockerfile + build_flag_templates: + - "--platform=linux/arm" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/management:{{ .Version }}-amd64 + ids: + - netbird-mgmt + goarch: amd64 + use: buildx + dockerfile: management/Dockerfile + build_flag_templates: + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/management:{{ .Version }}-arm64v8 + ids: + - netbird-mgmt + goarch: arm64 + use: buildx + dockerfile: management/Dockerfile + build_flag_templates: + - "--platform=linux/arm64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/management:{{ .Version }}-arm + ids: + - netbird-mgmt + goarch: arm + goarm: 6 + use: buildx + dockerfile: management/Dockerfile + build_flag_templates: + - "--platform=linux/arm" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/management:{{ .Version }}-debug-amd64 + ids: + - netbird-mgmt + goarch: amd64 + use: buildx + dockerfile: management/Dockerfile.debug + build_flag_templates: + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + - image_templates: + - netbirdio/management:{{ .Version }}-debug-arm64v8 + ids: + - netbird-mgmt + goarch: arm64 + use: buildx + dockerfile: management/Dockerfile.debug + build_flag_templates: + - "--platform=linux/arm64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" + + - image_templates: + - netbirdio/management:{{ .Version }}-debug-arm + ids: + - netbird-mgmt + goarch: arm + goarm: 6 + use: buildx + dockerfile: management/Dockerfile.debug + build_flag_templates: + - "--platform=linux/arm" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title={{.ProjectName}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=maintainer=dev@netbird.io" +docker_manifests: + - name_template: netbirdio/netbird:{{ .Version }} + image_templates: + - netbirdio/netbird:{{ .Version }}-arm64v8 + - netbirdio/netbird:{{ .Version }}-arm + - netbirdio/netbird:{{ .Version }}-amd64 + + - name_template: netbirdio/netbird:latest + image_templates: + - netbirdio/netbird:{{ .Version }}-arm64v8 + - netbirdio/netbird:{{ .Version }}-arm + - netbirdio/netbird:{{ .Version }}-amd64 + + - name_template: netbirdio/netbird:{{ .Version }}-rootless + image_templates: + - netbirdio/netbird:{{ .Version }}-rootless-arm64v8 + - netbirdio/netbird:{{ .Version }}-rootless-arm + - netbirdio/netbird:{{ .Version }}-rootless-amd64 + + - name_template: netbirdio/netbird:rootless-latest + image_templates: + - netbirdio/netbird:{{ .Version }}-rootless-arm64v8 + - netbirdio/netbird:{{ .Version }}-rootless-arm + - netbirdio/netbird:{{ .Version }}-rootless-amd64 + + - name_template: netbirdio/relay:{{ .Version }} + image_templates: + - netbirdio/relay:{{ .Version }}-arm64v8 + - netbirdio/relay:{{ .Version }}-arm + - netbirdio/relay:{{ .Version }}-amd64 + + - name_template: netbirdio/relay:latest + image_templates: + - netbirdio/relay:{{ .Version }}-arm64v8 + - netbirdio/relay:{{ .Version }}-arm + - netbirdio/relay:{{ .Version }}-amd64 + + - name_template: netbirdio/signal:{{ .Version }} + image_templates: + - netbirdio/signal:{{ .Version }}-arm64v8 + - netbirdio/signal:{{ .Version }}-arm + - netbirdio/signal:{{ .Version }}-amd64 + + - name_template: netbirdio/signal:latest + image_templates: + - netbirdio/signal:{{ .Version }}-arm64v8 + - netbirdio/signal:{{ .Version }}-arm + - netbirdio/signal:{{ .Version }}-amd64 + + - name_template: netbirdio/management:{{ .Version }} + image_templates: + - netbirdio/management:{{ .Version }}-arm64v8 + - netbirdio/management:{{ .Version }}-arm + - netbirdio/management:{{ .Version }}-amd64 + + - name_template: netbirdio/management:latest + image_templates: + - netbirdio/management:{{ .Version }}-arm64v8 + - netbirdio/management:{{ .Version }}-arm + - netbirdio/management:{{ .Version }}-amd64 + + - name_template: netbirdio/management:debug-latest + image_templates: + - netbirdio/management:{{ .Version }}-debug-arm64v8 + - netbirdio/management:{{ .Version }}-debug-arm + - netbirdio/management:{{ .Version }}-debug-amd64 + +brews: + - ids: + - default + repository: + owner: netbirdio + name: homebrew-tap + token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" + commit_author: + name: Netbird + email: dev@netbird.io + description: Netbird project. + download_strategy: CurlDownloadStrategy + homepage: https://netbird.io/ + license: "BSD3" + test: | + system "#{bin}/{{ .ProjectName }} version" + +uploads: + - name: debian + ids: + - netbird-deb + mode: archive + target: https://pkgs.wiretrustee.com/debian/pool/{{ .ArtifactName }};deb.distribution=stable;deb.component=main;deb.architecture={{ if .Arm }}armhf{{ else }}{{ .Arch }}{{ end }};deb.package= + username: dev@wiretrustee.com + method: PUT + + - name: yum + ids: + - netbird-rpm + mode: archive + target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }} + username: dev@wiretrustee.com + method: PUT + +checksum: + extra_files: + - glob: ./infrastructure_files/getting-started-with-zitadel.sh + - glob: ./release_files/install.sh + +release: + extra_files: + - glob: ./infrastructure_files/getting-started-with-zitadel.sh + - glob: ./release_files/install.sh diff --git a/netbird/.goreleaser_ui.yaml b/netbird/.goreleaser_ui.yaml new file mode 100644 index 0000000..459f204 --- /dev/null +++ b/netbird/.goreleaser_ui.yaml @@ -0,0 +1,97 @@ +version: 2 + +project_name: netbird-ui +builds: + - id: netbird-ui + dir: client/ui + binary: netbird-ui + env: + - CGO_ENABLED=1 + goos: + - linux + goarch: + - amd64 + ldflags: + - -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser + mod_timestamp: "{{ .CommitTimestamp }}" + + - id: netbird-ui-windows + dir: client/ui + binary: netbird-ui + env: + - CGO_ENABLED=1 + - CC=x86_64-w64-mingw32-gcc + goos: + - windows + goarch: + - amd64 + ldflags: + - -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser + - -H windowsgui + mod_timestamp: "{{ .CommitTimestamp }}" + +archives: + - id: linux-arch + name_template: "{{ .ProjectName }}-linux_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + builds: + - netbird-ui + - id: windows-arch + name_template: "{{ .ProjectName }}-windows_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + builds: + - netbird-ui-windows + +nfpms: + - maintainer: Netbird + description: Netbird client UI. + homepage: https://netbird.io/ + id: netbird-ui-deb + package_name: netbird-ui + builds: + - netbird-ui + formats: + - deb + scripts: + postinstall: "release_files/ui-post-install.sh" + contents: + - src: client/ui/build/netbird.desktop + dst: /usr/share/applications/netbird.desktop + - src: client/ui/assets/netbird.png + dst: /usr/share/pixmaps/netbird.png + dependencies: + - netbird + + - maintainer: Netbird + description: Netbird client UI. + homepage: https://netbird.io/ + id: netbird-ui-rpm + package_name: netbird-ui + builds: + - netbird-ui + formats: + - rpm + scripts: + postinstall: "release_files/ui-post-install.sh" + contents: + - src: client/ui/build/netbird.desktop + dst: /usr/share/applications/netbird.desktop + - src: client/ui/assets/netbird.png + dst: /usr/share/pixmaps/netbird.png + dependencies: + - netbird + +uploads: + - name: debian + ids: + - netbird-ui-deb + mode: archive + target: https://pkgs.wiretrustee.com/debian/pool/{{ .ArtifactName }};deb.distribution=stable;deb.component=main;deb.architecture={{ if .Arm }}armhf{{ else }}{{ .Arch }}{{ end }};deb.package= + username: dev@wiretrustee.com + method: PUT + + - name: yum + ids: + - netbird-ui-rpm + mode: archive + target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }} + username: dev@wiretrustee.com + method: PUT diff --git a/netbird/.goreleaser_ui_darwin.yaml b/netbird/.goreleaser_ui_darwin.yaml new file mode 100644 index 0000000..0a00820 --- /dev/null +++ b/netbird/.goreleaser_ui_darwin.yaml @@ -0,0 +1,36 @@ +version: 2 + +project_name: netbird-ui +builds: + - id: netbird-ui-darwin + dir: client/ui + binary: netbird-ui + env: + - CGO_ENABLED=1 + - MACOSX_DEPLOYMENT_TARGET=11.0 + - MACOS_DEPLOYMENT_TARGET=11.0 + goos: + - darwin + goarch: + - amd64 + - arm64 + gomips: + - hardfloat + - softfloat + ldflags: + - -s -w -X github.com/netbirdio/netbird/version.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser + mod_timestamp: "{{ .CommitTimestamp }}" + tags: + - load_wgnt_from_rsrc + +universal_binaries: + - id: netbird-ui-darwin + +archives: + - builds: + - netbird-ui-darwin + +checksum: + name_template: "{{ .ProjectName }}_darwin_checksums.txt" +changelog: + disable: true diff --git a/netbird/AUTHORS b/netbird/AUTHORS new file mode 100644 index 0000000..f39620a --- /dev/null +++ b/netbird/AUTHORS @@ -0,0 +1,3 @@ +Mikhail Bragin (https://github.com/braginini) +Maycon Santos (https://github.com/mlsmaycon) +NetBird GmbH diff --git a/netbird/CODE_OF_CONDUCT.md b/netbird/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7eb8e4e --- /dev/null +++ b/netbird/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socioeconomic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +community@netbird.io. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/netbird/CONTRIBUTING.md b/netbird/CONTRIBUTING.md new file mode 100644 index 0000000..c82cfc7 --- /dev/null +++ b/netbird/CONTRIBUTING.md @@ -0,0 +1,299 @@ +# Contributing to NetBird + +Thanks for your interest in contributing to NetBird. + +There are many ways that you can contribute: +- Reporting issues +- Updating documentation +- Sharing use cases in slack or Reddit +- Bug fix or feature enhancement + +If you haven't already, join our slack workspace [here](https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A), we would love to discuss topics that need community contribution and enhancements to existing features. + +## Contents + +- [Contributing to NetBird](#contributing-to-netbird) + - [Contents](#contents) + - [Code of conduct](#code-of-conduct) + - [Directory structure](#directory-structure) + - [Development setup](#development-setup) + - [Requirements](#requirements) + - [Local NetBird setup](#local-netbird-setup) + - [Dev Container Support](#dev-container-support) + - [Build and start](#build-and-start) + - [Test suite](#test-suite) + - [Checklist before submitting a PR](#checklist-before-submitting-a-pr) + - [Other project repositories](#other-project-repositories) + - [Contributor License Agreement](#contributor-license-agreement) + +## Code of conduct + +This project and everyone participating in it are governed by the Code of +Conduct which can be found in the file [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. Please report +unacceptable behavior to community@netbird.io. + +## Directory structure + +The NetBird project monorepo is organized to maintain most of its individual dependencies code within their directories, except for a few auxiliary or shared packages. + +The most important directories are: + +- [/.github](/.github) - Github actions workflow files and issue templates +- [/client](/client) - NetBird agent code +- [/client/cmd](/client/cmd) - NetBird agent cli code +- [/client/internal](/client/internal) - NetBird agent business logic code +- [/client/proto](/client/proto) - NetBird agent daemon GRPC proto files +- [/client/server](/client/server) - NetBird agent daemon code for background execution +- [/client/ui](/client/ui) - NetBird agent UI code +- [/encryption](/encryption) - Contain main encryption code for agent communication +- [/iface](/iface) - Wireguard® interface code +- [/infrastructure_files](/infrastructure_files) - Getting started files containing docker and template scripts +- [/management](/management) - Management service code +- [/management/client](/management/client) - Management service client code which is imported by the agent code +- [/management/proto](/management/proto) - Management service GRPC proto files +- [/management/server](/management/server) - Management service server code +- [/management/server/http](/management/server/http) - Management service REST API code +- [/management/server/idp](/management/server/idp) - Management service IDP management code +- [/release_files](/release_files) - Files that goes into release packages +- [/signal](/signal) - Signal service code +- [/signal/client](/signal/client) - Signal service client code which is imported by the agent code +- [/signal/peer](/signal/peer) - Signal service peer message logic +- [/signal/proto](/signal/proto) - Signal service GRPC proto files +- [/signal/server](/signal/server) - Signal service server code + + +## Development setup + +If you want to contribute to bug fixes or improve existing features, you have to ensure that all needed +dependencies are installed. Here is a short guide on how that can be done. + +### Requirements + +#### Go 1.21 + +Follow the installation guide from https://go.dev/ + +#### UI client - Fyne toolkit + +We use the fyne toolkit in our UI client. You can follow its requirement guide to have all its dependencies installed: https://developer.fyne.io/started/#prerequisites + +#### gRPC +You can follow the instructions from the quickstarter guide https://grpc.io/docs/languages/go/quickstart/#prerequisites and then run the `generate.sh` files located in each `proto` directory to generate changes. +> **IMPORTANT**: We are very open to contributions that can improve the client daemon protocol. For Signal and Management protocols, please reach out on slack or via github issues with your proposals. + +#### Docker + +Follow the installation guide from https://docs.docker.com/get-docker/ + +#### Goreleaser and golangci-lint + +We utilize two tools in our Github actions workflows: +- Goreleaser: Used for release packaging. You can follow the installation steps [here](https://goreleaser.com/install/); keep in mind to match the version defined in [release.yml](/.github/workflows/release.yml) +- golangci-lint: Used for linting checks. You can follow the installation steps [here](https://golangci-lint.run/usage/install/); keep in mind to match the version defined in [golangci-lint.yml](/.github/workflows/golangci-lint.yml) + +They can be executed from the repository root before every push or PR: + +**Goreleaser** +```shell +goreleaser build --snapshot --clean +``` +**golangci-lint** +```shell +golangci-lint run +``` + +### Local NetBird setup + +> **IMPORTANT**: All the steps below have to get executed at least once to get the development setup up and running! + +Now that everything NetBird requires to run is installed, the actual NetBird code can be +checked out and set up: + +1. [Fork](https://guides.github.com/activities/forking/#fork) the NetBird repository + +2. Clone your forked repository + + ``` + git clone https://github.com//netbird.git + ``` + +3. Go into the repository folder + + ``` + cd netbird + ``` + +4. Add the original NetBird repository as `upstream` to your forked repository + + ``` + git remote add upstream https://github.com/netbirdio/netbird.git + ``` + +5. Install all Go dependencies: + + ``` + go mod tidy + ``` + +### Dev Container Support + +If you prefer using a dev container for development, NetBird now includes support for dev containers. +Dev containers provide a consistent and isolated development environment, making it easier for contributors to get started quickly. Follow the steps below to set up NetBird in a dev container. + +#### 1. Prerequisites: + +* Install Docker on your machine: [Docker Installation Guide](https://docs.docker.com/get-docker/) +* Install Visual Studio Code: [VS Code Installation Guide](https://code.visualstudio.com/download) +* If you prefer JetBrains Goland please follow this [manual](https://www.jetbrains.com/help/go/connect-to-devcontainer.html) + +#### 2. Clone the Repository: + +Clone the repository following previous [Local NetBird setup](#local-netbird-setup). + +#### 3. Open in project in IDE of your choice: + +**VScode**: + +Open the project folder in Visual Studio Code: + +```bash +code . +``` + +When you open the project in VS Code, it will detect the presence of a dev container configuration. +Click on the green "Reopen in Container" button in the bottom-right corner of VS Code. + +**Goland**: + +Open GoLand and select `"File" > "Open"` to open the NetBird project folder. +GoLand will detect the dev container configuration and prompt you to open the project in the container. Accept the prompt. + +#### 4. Wait for the Container to Build: + +VsCode or GoLand will use the specified Docker image to build the dev container. This might take some time, depending on your internet connection. + +#### 6. Development: + +Once the container is built, you can start developing within the dev container. All the necessary dependencies and configurations are set up within the container. + + +### Build and start +#### Client + +To start NetBird, execute: +``` +cd client +CGO_ENABLED=0 go build . +``` + +> Windows clients have a Wireguard driver requirement. You can download 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 test the client GUI application on Windows machines with RDP or vituralized environments (e.g. virtualbox or cloud), you need to download and extract the opengl32.dll from https://fdossena.com/?p=mesa/index.frag next to the built application. + +To start NetBird the client in the foreground: + +``` +sudo ./client up --log-level debug --log-file console +``` +> On Windows use a powershell with administrator privileges +#### Signal service + +To start NetBird's signal, execute: + +``` +cd signal +go build . +``` + +To start NetBird the signal service: + +``` +./signal run --log-level debug --log-file console +``` + +#### Management service +> You may need to generate a configuration file for management. Follow steps 2 to 5 from our [self-hosting guide](https://netbird.io/docs/getting-started/self-hosting). + +To start NetBird's management, execute: + +``` +cd management +go build . +``` + +To start NetBird the management service: + +``` +./management management --log-level debug --log-file console --config ./management.json +``` + +#### Windows Netbird Installer +Create dist directory +```shell +mkdir -p dist/netbird_windows_amd64 +``` + +UI client +```shell +CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -o netbird-ui.exe -ldflags "-s -w -H windowsgui" ./client/ui +mv netbird-ui.exe ./dist/netbird_windows_amd64/ +``` + +Client +```shell +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o netbird.exe ./client/ +mv netbird.exe ./dist/netbird_windows_amd64/ +``` +> Windows clients have a Wireguard driver requirement. You can download 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 `./dist/netbird_windows_amd64/`. + +NSIS compiler +- [Windows-nsis]( https://nsis.sourceforge.io/Download) +- [MacOS-makensis](https://formulae.brew.sh/formula/makensis#default) +- [Linux-makensis](https://manpages.ubuntu.com/manpages/trusty/man1/makensis.1.html) + +NSIS Plugins. Download and move them to the NSIS plugins folder. +- [EnVar](https://nsis.sourceforge.io/mediawiki/images/7/7f/EnVar_plugin.zip) +- [ShellExecAsUser](https://nsis.sourceforge.io/mediawiki/images/6/68/ShellExecAsUser_amd64-Unicode.7z) + +Windows Installer +```shell +export APPVER=0.0.0.1 +makensis -V4 client/installer.nsis +``` + +The installer `netbird-installer.exe` will be created in root directory. + +### Test suite + +The tests can be started via: + +``` +cd netbird +go test -exec sudo ./... +``` +> On Windows use a powershell with administrator privileges + +> Non-GTK environments will need the `libayatana-appindicator3-dev` (debian/ubuntu) package installed + +## Checklist before submitting a PR +As a critical network service and open-source project, we must enforce a few things before submitting the pull-requests: +- Keep functions as simple as possible, with a single purpose +- Use private functions and constants where possible +- Comment on any new public functions +- Add unit tests for any new public function + +> When pushing fixes to the PR comments, please push as separate commits; we will squash the PR before merging, so there is no need to squash it before pushing it, and we are more than okay with 10-100 commits in a single PR. This helps review the fixes to the requested changes. + +## Other project repositories + +NetBird project is composed of 3 main repositories: +- NetBird: This repository, which contains the code for the agents and control plane services. +- Dashboard: https://github.com/netbirdio/dashboard, contains the Administration UI for the management service +- Documentations: https://github.com/netbirdio/docs, contains the documentation from https://netbird.io/docs + +## Contributor License Agreement + +That we do not have any potential problems later it is sadly necessary to sign a [Contributor License Agreement](CONTRIBUTOR_LICENSE_AGREEMENT.md). That can be done literally with the push of a button. + +A bot will automatically comment on the pull request once it got opened asking for the agreement to be signed. Before it did not get signed it is sadly not possible to merge it in. diff --git a/netbird/CONTRIBUTOR_LICENSE_AGREEMENT.md b/netbird/CONTRIBUTOR_LICENSE_AGREEMENT.md new file mode 100644 index 0000000..1fdd072 --- /dev/null +++ b/netbird/CONTRIBUTOR_LICENSE_AGREEMENT.md @@ -0,0 +1,64 @@ +## Contributor License Agreement + +This Contributor License Agreement (referred to as the "Agreement") is entered into by the individual +submitting this Agreement and NetBird GmbH, c/o Max-Beer-Straße 2-4 Münzstraße 12 10178 Berlin, Germany, +referred to as "NetBird" (collectively, the "Parties"). The Agreement outlines the terms and conditions +under which NetBird may utilize software contributions provided by the Contributor for inclusion in +its software development projects. By submitting this Agreement, the Contributor confirms their acceptance +of the terms and conditions outlined below. The Contributor further represents that they are authorized to +complete this process as described herein. + + +## 1 Preamble +In order to clarify the IP Rights situation with regard to Contributions from any person or entity, NetBird +must have a contributor license agreement on file to be signed by each Contributor, containing the license +terms below. This license serves as protection for both the Contributor as well as NetBird and its software users; +it does not change Contributor’s rights to use his/her own Contributions for any other purpose. + +## 2 Definitions +2.1 “IP Rights” shall mean all industrial and intellectual property rights, whether registered or not registered, whether created by Contributor or acquired by Contributor from third parties, and similar rights, including (but not limited to) semiconductor property rights, design rights, copyrights (including in the form of database rights and rights to software), all neighbouring rights (Leistungsschutzrechte), trademarks, service marks, titles, internet domain names, trade names and other labelling rights, rights deriving from corresponding applications and registrations of such rights as well as any licenses (Nutzungsrechte) under and entitlements to any such intellectual and industrial property rights. + +2.2 "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is or previously has been intentionally Submitted by Contributor to NetBird for inclusion in, or documentation of any Work. + +2.3 "Contributor" shall mean the copyright owner or legal entity authorized by the copyright owner that is concluding this Agreement with NetBird. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +2.4 "Submitted" shall mean any form of electronic, verbal, or written communication sent to NetBird or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, NetBird for the purpose of discussing and improving the Work, but excluding communication that is marked or otherwise designated in writing by Contributor as "Not a Contribution". + +2.5 "Work" means any of the products owned or managed by NetBird, in particular, but not exclusively, software. + +## 3 Licenses +3.1 Subject to the terms and conditions of this agreement, Contributor hereby grants to NetBird and to recipients of software distributed by NetBird a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license to reproduce by any means and in any form, in whole or in part, permanently or temporarily, the Contributions (including loading, displaying, executing, transmitting or storing works for the purpose of executing and processing data or transferring them to video, audio and other data carriers), including the right to distribute, display and present such Contributions and make them available to the public (e.g. via the internet) and to transmit and display such Contributions by any means. The license also includes the right to modify, translate, adapt, edit and otherwise alter the Contributions and to use these results in the same manner as the original Contributions and derivative works. Except for licenses in patents acc. to Sec. 3, such license refers to any IP Rights in the Contributions and derivative works. The Contributor acknowledges that NetBird is not required to credit them by name for their Contribution and agrees to waive any moral rights associated with their Contribution in relation to NetBird or its sublicensees. + +3.2 Subject to the terms and conditions of this agreement, Contributor hereby grants to NetBird and to recipients of software distributed by NetBird a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license in the Contributions to make, have made, use, sell, offer to sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by the Contributor which are necessarily infringed by Contributor‘s Contribution(s) alone or by combination of Contributor’s Contribution(s) with the Work to which such Contribution(s) was Submitted. + +3.3 NetBird hereby accepts such licenses. + +## 4 Contributor’s Representations +4.1 Contributor represents that Contributor is legally entitled to grant the above license. If Contributor’s employer has IP Rights to Contributor’s Contributions, Contributor represent that he/she has received permission to make Contributions on behalf of such employer, that such employer has waived such IP Rights to the Contributions of Contributor to NetBird, or that such employer has executed a separate contributor license agreement with NetBird. + +4.2 Contributor represents that any Contribution is his/her original creation. + +4.3 Contributor represents to his/her best knowledge that any Contribution does not violate any third party IP Rights. + +4.4 Contributor represents that any Contribution submission includes complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which Contributor is personally aware and which are associated with any part of the Contribution. + +4.5 The Contributor represents that their Contribution does not include any work distributed under a copyleft license. + +## 5 Information obligation +Contributor agrees to notify NetBird of any facts or circumstances of which Contributor become aware that would make these representations inaccurate in any respect. + +## 6 Submission of Third-Party works +Should Contributor wish to submit work that is not Contributor’s original creation, Contributor may submit it to NetBird separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which Contributor are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". + +## 7 No Consideration +Unless compensation is mandatory under statutory law, no compensation for any license under this agreement shall be payable. + +## 8 Final Provisions +8.1 Laws. This Agreement is governed by the laws of the Federal Republic of Germany. + +8.2 Venue. Place of jurisdiction shall, to the extent legally permissible, be Berlin, Germany. + +8.3 Severability. If any provision in this agreement is unlawful, invalid or ineffective, it shall not affect the enforceability or effectiveness of the remainder of this agreement. The parties agree to replace any unlawful, invalid or ineffective provision with a provision that comes as close as possible to the commercial intent and purpose of the original provision. This section also applies accordingly to any gaps in the contract. + +8.4 Variations. Any variations, amendments or supplements to this Agreement must be in writing. This also applies to any variation of this Section 8.4. + diff --git a/netbird/LICENSE b/netbird/LICENSE new file mode 100644 index 0000000..7cba76d --- /dev/null +++ b/netbird/LICENSE @@ -0,0 +1,13 @@ +BSD 3-Clause License + +Copyright (c) 2022 NetBird GmbH & AUTHORS + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/netbird/README.md b/netbird/README.md new file mode 100644 index 0000000..4ab9db0 --- /dev/null +++ b/netbird/README.md @@ -0,0 +1,136 @@ +
+
+
+

+ +

+

+ + + + + + +
+ + + +
+ + + +

+
+ + +

+ + Start using NetBird at netbird.io +
+ See Documentation +
+ Join our Slack channel +
+ +
+
+ + New: NetBird Kubernetes Operator + +

+ +
+ +**NetBird combines a configuration-free peer-to-peer private network and a centralized access control system in a single platform, making it easy to create secure private networks for your organization or home.** + +**Connect.** NetBird creates a WireGuard-based overlay network that automatically connects your machines over an encrypted tunnel, leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth. + +**Secure.** NetBird enables secure remote access by applying granular access policies while allowing you to manage them intuitively from a single place. Works universally on any infrastructure. + +### Open-Source Network Security in a Single Platform + + +![netbird_2](https://github.com/netbirdio/netbird/assets/700848/46bc3b73-508d-4a0e-bb9a-f465d68646ab) + +### NetBird on Lawrence Systems (Video) +[![Watch the video](https://img.youtube.com/vi/Kwrff6h0rEw/0.jpg)](https://www.youtube.com/watch?v=Kwrff6h0rEw) + +### Key features + +| Connectivity | Management | Security | Automation| Platforms | +|----|----|----|----|----| +|
  • - \[x] Kernel WireGuard
|
  • - \[x] [Admin Web UI](https://github.com/netbirdio/dashboard)
|
  • - \[x] [SSO & MFA support](https://docs.netbird.io/how-to/installation#running-net-bird-with-sso-login)
|
  • - \[x] [Public API](https://docs.netbird.io/api)
|
  • - \[x] Linux
| +|
  • - \[x] Peer-to-peer connections
|
  • - \[x] Auto peer discovery and configuration
  • |
    • - \[x] [Access control - groups & rules](https://docs.netbird.io/how-to/manage-network-access)
    • |
      • - \[x] [Setup keys for bulk network provisioning](https://docs.netbird.io/how-to/register-machines-using-setup-keys)
      • |
        • - \[x] Mac
        • | +|
          • - \[x] Connection relay fallback
          • |
            • - \[x] [IdP integrations](https://docs.netbird.io/selfhosted/identity-providers)
            • |
              • - \[x] [Activity logging](https://docs.netbird.io/how-to/monitor-system-and-network-activity)
              • |
                • - \[x] [Self-hosting quickstart script](https://docs.netbird.io/selfhosted/selfhosted-quickstart)
                • |
                  • - \[x] Windows
                  • | +|
                    • - \[x] [Routes to external networks](https://docs.netbird.io/how-to/routing-traffic-to-private-networks)
                    • |
                      • - \[x] [Private DNS](https://docs.netbird.io/how-to/manage-dns-in-your-network)
                      • |
                        • - \[x] [Device posture checks](https://docs.netbird.io/how-to/manage-posture-checks)
                        • |
                          • - \[x] IdP groups sync with JWT
                          • |
                            • - \[x] Android
                            • | +|
                              • - \[x] NAT traversal with BPF
                              • |
                                • - \[x] [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network)
                                • |
                                  • - \[x] Peer-to-peer encryption
                                  • ||
                                    • - \[x] iOS
                                    • | +|||
                                      • - \[x] [Quantum-resistance with Rosenpass](https://netbird.io/knowledge-hub/the-first-quantum-resistant-mesh-vpn)
                                      • ||
                                        • - \[x] OpenWRT
                                        • | +|||
                                          • - \[x] [Periodic re-authentication](https://docs.netbird.io/how-to/enforce-periodic-user-authentication)
                                          • ||
                                            • - \[x] [Serverless](https://docs.netbird.io/how-to/netbird-on-faas)
                                            • | +|||||
                                              • - \[x] Docker
                                              • | + +### Quickstart with NetBird Cloud + +- Download and install NetBird at [https://app.netbird.io/install](https://app.netbird.io/install) +- Follow the steps to sign-up with Google, Microsoft, GitHub or your email address. +- Check NetBird [admin UI](https://app.netbird.io/). +- Add more machines. + +### Quickstart with self-hosted NetBird + +> This is the quickest way to try self-hosted NetBird. It should take around 5 minutes to get started if you already have a public domain and a VM. +Follow the [Advanced guide with a custom identity provider](https://docs.netbird.io/selfhosted/selfhosted-guide#advanced-guide-with-a-custom-identity-provider) for installations with different IDPs. + +**Infrastructure requirements:** +- A Linux VM with at least **1CPU** and **2GB** of memory. +- The VM should be publicly accessible on TCP ports **80** and **443** and UDP ports: **3478**, **49152-65535**. +- **Public domain** name pointing to the VM. + +**Software requirements:** +- Docker installed on the VM with the docker-compose plugin ([Docker installation guide](https://docs.docker.com/engine/install/)) or docker with docker-compose in version 2 or higher. +- [jq](https://jqlang.github.io/jq/) installed. In most distributions + Usually available in the official repositories and can be installed with `sudo apt install jq` or `sudo yum install jq` +- [curl](https://curl.se/) installed. + Usually available in the official repositories and can be installed with `sudo apt install curl` or `sudo yum install curl` + +**Steps** +- Download and run the installation script: +```bash +export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started-with-zitadel.sh | bash +``` +- Once finished, you can manage the resources via `docker-compose` + +### A bit on NetBird internals +- Every machine in the network runs [NetBird Agent (or Client)](client/) that manages WireGuard. +- Every agent connects to [Management Service](management/) that holds network state, manages peer IPs, and distributes network updates to agents (peers). +- NetBird agent uses WebRTC ICE implemented in [pion/ice library](https://github.com/pion/ice) to discover connection candidates when establishing a peer-to-peer connection between machines. +- Connection candidates are discovered with the help of [STUN](https://en.wikipedia.org/wiki/STUN) servers. +- Agents negotiate a connection through [Signal Service](signal/) passing p2p encrypted messages with candidates. +- Sometimes the NAT traversal is unsuccessful due to strict NATs (e.g. mobile carrier-grade NAT) and a p2p connection isn't possible. When this occurs the system falls back to a relay server called [TURN](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT), and a secure WireGuard tunnel is established via the TURN server. + +[Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in NetBird setups. + +

                                                + +

                                                + +See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details. + +### Community projects +- [NetBird installer script](https://github.com/physk/netbird-installer) +- [NetBird ansible collection by Dominion Solutions](https://galaxy.ansible.com/ui/repo/published/dominion_solutions/netbird/) + +**Note**: The `main` branch may be in an *unstable or even broken state* during development. +For stable versions, see [releases](https://github.com/netbirdio/netbird/releases). + +### Support acknowledgement + +In November 2022, NetBird joined the [StartUpSecure program](https://www.forschung-it-sicherheit-kommunikationssysteme.de/foerderung/bekanntmachungen/startup-secure) sponsored by The Federal Ministry of Education and Research of The Federal Republic of Germany. Together with [CISPA Helmholtz Center for Information Security](https://cispa.de/en) NetBird brings the security best practices and simplicity to private networking. + +![CISPA_Logo_BLACK_EN_RZ_RGB (1)](https://user-images.githubusercontent.com/700848/203091324-c6d311a0-22b5-4b05-a288-91cbc6cdcc46.png) + +### Testimonials +We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [Coturn](https://github.com/coturn/coturn), and [Rosenpass](https://rosenpass.eu). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g., by giving a star or a contribution). + +### Legal + _WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld. + diff --git a/netbird/SECURITY.md b/netbird/SECURITY.md new file mode 100644 index 0000000..745c66e --- /dev/null +++ b/netbird/SECURITY.md @@ -0,0 +1,12 @@ +# Security Policy + +NetBird's goal is to provide a secure network. If you find a vulnerability or bug, please report it by opening an issue [here](https://github.com/netbirdio/netbird/issues/new?assignees=&labels=&template=bug-issue-report.md&title=) or by contacting us by email. + +There has yet to be an official bug bounty program for the NetBird project. + +## Supported Versions +- We currently support only the latest version + +## Reporting a Vulnerability + +Please report security issues to `security@netbird.io` diff --git a/netbird/base62/base62.go b/netbird/base62/base62.go new file mode 100644 index 0000000..efafbc7 --- /dev/null +++ b/netbird/base62/base62.go @@ -0,0 +1,58 @@ +package base62 + +import ( + "fmt" + "math" + "strings" +) + +const ( + alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + base = uint32(len(alphabet)) +) + +// Encode encodes a uint32 value to a base62 string. +func Encode(num uint32) string { + if num == 0 { + return string(alphabet[0]) + } + + var encoded strings.Builder + + for num > 0 { + remainder := num % base + encoded.WriteByte(alphabet[remainder]) + num /= base + } + + // Reverse the encoded string + encodedString := encoded.String() + reversed := reverse(encodedString) + return reversed +} + +// Decode decodes a base62 string to a uint32 value. +func Decode(encoded string) (uint32, error) { + var decoded uint32 + strLen := len(encoded) + + for i, char := range encoded { + index := strings.IndexRune(alphabet, char) + if index < 0 { + return 0, fmt.Errorf("invalid character: %c", char) + } + + decoded += uint32(index) * uint32(math.Pow(float64(base), float64(strLen-i-1))) + } + + return decoded, nil +} + +// Reverse a string. +func reverse(s string) string { + runes := []rune(s) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} diff --git a/netbird/base62/base62_test.go b/netbird/base62/base62_test.go new file mode 100644 index 0000000..00da212 --- /dev/null +++ b/netbird/base62/base62_test.go @@ -0,0 +1,31 @@ +package base62 + +import ( + "testing" +) + +func TestEncodeDecode(t *testing.T) { + tests := []struct { + num uint32 + }{ + {0}, + {1}, + {42}, + {12345}, + {99999}, + {123456789}, + } + + for _, tt := range tests { + encoded := Encode(tt.num) + decoded, err := Decode(encoded) + + if err != nil { + t.Errorf("Decode error: %v", err) + } + + if decoded != tt.num { + t.Errorf("Decode(%v) = %v, want %v", encoded, decoded, tt.num) + } + } +} diff --git a/netbird/client/Dockerfile b/netbird/client/Dockerfile new file mode 100644 index 0000000..35c1d04 --- /dev/null +++ b/netbird/client/Dockerfile @@ -0,0 +1,5 @@ +FROM alpine:3.21.3 +RUN apk add --no-cache ca-certificates iptables ip6tables +ENV NB_FOREGROUND_MODE=true +ENTRYPOINT [ "/usr/local/bin/netbird","up"] +COPY netbird /usr/local/bin/netbird \ No newline at end of file diff --git a/netbird/client/Dockerfile-rootless b/netbird/client/Dockerfile-rootless new file mode 100644 index 0000000..78314ba --- /dev/null +++ b/netbird/client/Dockerfile-rootless @@ -0,0 +1,17 @@ +FROM alpine:3.21.0 + +COPY netbird /usr/local/bin/netbird + +RUN apk add --no-cache ca-certificates \ + && adduser -D -h /var/lib/netbird netbird +WORKDIR /var/lib/netbird +USER netbird:netbird + +ENV NB_FOREGROUND_MODE=true +ENV NB_USE_NETSTACK_MODE=true +ENV NB_ENABLE_NETSTACK_LOCAL_FORWARDING=true +ENV NB_CONFIG=config.json +ENV NB_DAEMON_ADDR=unix://netbird.sock +ENV NB_DISABLE_DNS=true + +ENTRYPOINT [ "/usr/local/bin/netbird", "up" ] diff --git a/netbird/client/android/client.go b/netbird/client/android/client.go new file mode 100644 index 0000000..229bcd9 --- /dev/null +++ b/netbird/client/android/client.go @@ -0,0 +1,196 @@ +//go:build android + +package android + +import ( + "context" + "sync" + + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/client/iface/device" + "github.com/netbirdio/netbird/client/internal" + "github.com/netbirdio/netbird/client/internal/dns" + "github.com/netbirdio/netbird/client/internal/listener" + "github.com/netbirdio/netbird/client/internal/peer" + "github.com/netbirdio/netbird/client/internal/stdnet" + "github.com/netbirdio/netbird/client/system" + "github.com/netbirdio/netbird/formatter" + "github.com/netbirdio/netbird/util/net" +) + +// ConnectionListener export internal Listener for mobile +type ConnectionListener interface { + peer.Listener +} + +// TunAdapter export internal TunAdapter for mobile +type TunAdapter interface { + device.TunAdapter +} + +// IFaceDiscover export internal IFaceDiscover for mobile +type IFaceDiscover interface { + stdnet.ExternalIFaceDiscover +} + +// NetworkChangeListener export internal NetworkChangeListener for mobile +type NetworkChangeListener interface { + listener.NetworkChangeListener +} + +// DnsReadyListener export internal dns ReadyListener for mobile +type DnsReadyListener interface { + dns.ReadyListener +} + +func init() { + formatter.SetLogcatFormatter(log.StandardLogger()) +} + +// Client struct manage the life circle of background service +type Client struct { + cfgFile string + tunAdapter device.TunAdapter + iFaceDiscover IFaceDiscover + recorder *peer.Status + ctxCancel context.CancelFunc + ctxCancelLock *sync.Mutex + deviceName string + uiVersion string + networkChangeListener listener.NetworkChangeListener +} + +// NewClient instantiate a new Client +func NewClient(cfgFile, deviceName string, uiVersion string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener) *Client { + net.SetAndroidProtectSocketFn(tunAdapter.ProtectSocket) + return &Client{ + cfgFile: cfgFile, + deviceName: deviceName, + uiVersion: uiVersion, + tunAdapter: tunAdapter, + iFaceDiscover: iFaceDiscover, + recorder: peer.NewRecorder(""), + ctxCancelLock: &sync.Mutex{}, + networkChangeListener: networkChangeListener, + } +} + +// Run start the internal client. It is a blocker function +func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsReadyListener) error { + cfg, err := internal.UpdateOrCreateConfig(internal.ConfigInput{ + ConfigPath: c.cfgFile, + }) + if err != nil { + return err + } + c.recorder.UpdateManagementAddress(cfg.ManagementURL.String()) + c.recorder.UpdateRosenpass(cfg.RosenpassEnabled, cfg.RosenpassPermissive) + + var ctx context.Context + //nolint + ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName) + //nolint + ctxWithValues = context.WithValue(ctxWithValues, system.UiVersionCtxKey, c.uiVersion) + + c.ctxCancelLock.Lock() + ctx, c.ctxCancel = context.WithCancel(ctxWithValues) + defer c.ctxCancel() + c.ctxCancelLock.Unlock() + + auth := NewAuthWithConfig(ctx, cfg) + err = auth.login(urlOpener) + if err != nil { + return err + } + + // todo do not throw error in case of cancelled context + ctx = internal.CtxInitState(ctx) + connectClient := internal.NewConnectClient(ctx, cfg, c.recorder) + return connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, dns.items, dnsReadyListener) +} + +// RunWithoutLogin we apply this type of run function when the backed has been started without UI (i.e. after reboot). +// In this case make no sense handle registration steps. +func (c *Client) RunWithoutLogin(dns *DNSList, dnsReadyListener DnsReadyListener) error { + cfg, err := internal.UpdateOrCreateConfig(internal.ConfigInput{ + ConfigPath: c.cfgFile, + }) + if err != nil { + return err + } + c.recorder.UpdateManagementAddress(cfg.ManagementURL.String()) + c.recorder.UpdateRosenpass(cfg.RosenpassEnabled, cfg.RosenpassPermissive) + + var ctx context.Context + //nolint + ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName) + c.ctxCancelLock.Lock() + ctx, c.ctxCancel = context.WithCancel(ctxWithValues) + defer c.ctxCancel() + c.ctxCancelLock.Unlock() + + // todo do not throw error in case of cancelled context + ctx = internal.CtxInitState(ctx) + connectClient := internal.NewConnectClient(ctx, cfg, c.recorder) + return connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, dns.items, dnsReadyListener) +} + +// Stop the internal client and free the resources +func (c *Client) Stop() { + c.ctxCancelLock.Lock() + defer c.ctxCancelLock.Unlock() + if c.ctxCancel == nil { + return + } + + c.ctxCancel() +} + +// SetTraceLogLevel configure the logger to trace level +func (c *Client) SetTraceLogLevel() { + log.SetLevel(log.TraceLevel) +} + +// SetInfoLogLevel configure the logger to info level +func (c *Client) SetInfoLogLevel() { + log.SetLevel(log.InfoLevel) +} + +// PeersList return with the list of the PeerInfos +func (c *Client) PeersList() *PeerInfoArray { + + fullStatus := c.recorder.GetFullStatus() + + peerInfos := make([]PeerInfo, len(fullStatus.Peers)) + for n, p := range fullStatus.Peers { + pi := PeerInfo{ + p.IP, + p.FQDN, + p.ConnStatus.String(), + } + peerInfos[n] = pi + } + return &PeerInfoArray{items: peerInfos} +} + +// OnUpdatedHostDNS update the DNS servers addresses for root zones +func (c *Client) OnUpdatedHostDNS(list *DNSList) error { + dnsServer, err := dns.GetServerDns() + if err != nil { + return err + } + + dnsServer.OnUpdatedHostDNSServer(list.items) + return nil +} + +// SetConnectionListener set the network connection listener +func (c *Client) SetConnectionListener(listener ConnectionListener) { + c.recorder.SetConnectionListener(listener) +} + +// RemoveConnectionListener remove connection listener +func (c *Client) RemoveConnectionListener() { + c.recorder.RemoveConnectionListener() +} diff --git a/netbird/client/android/dns_list.go b/netbird/client/android/dns_list.go new file mode 100644 index 0000000..76b9222 --- /dev/null +++ b/netbird/client/android/dns_list.go @@ -0,0 +1,26 @@ +package android + +import "fmt" + +// DNSList is a wrapper of []string +type DNSList struct { + items []string +} + +// Add new DNS address to the collection +func (array *DNSList) Add(s string) { + array.items = append(array.items, s) +} + +// Get return an element of the collection +func (array *DNSList) Get(i int) (string, error) { + if i >= len(array.items) || i < 0 { + return "", fmt.Errorf("out of range") + } + return array.items[i], nil +} + +// Size return with the size of the collection +func (array *DNSList) Size() int { + return len(array.items) +} diff --git a/netbird/client/android/dns_list_test.go b/netbird/client/android/dns_list_test.go new file mode 100644 index 0000000..93aea78 --- /dev/null +++ b/netbird/client/android/dns_list_test.go @@ -0,0 +1,24 @@ +package android + +import "testing" + +func TestDNSList_Get(t *testing.T) { + l := DNSList{ + items: make([]string, 1), + } + + _, err := l.Get(0) + if err != nil { + t.Errorf("invalid error: %s", err) + } + + _, err = l.Get(-1) + if err == nil { + t.Errorf("expected error but got nil") + } + + _, err = l.Get(1) + if err == nil { + t.Errorf("expected error but got nil") + } +} diff --git a/netbird/client/android/gomobile.go b/netbird/client/android/gomobile.go new file mode 100644 index 0000000..533f0c7 --- /dev/null +++ b/netbird/client/android/gomobile.go @@ -0,0 +1,5 @@ +package android + +import _ "golang.org/x/mobile/bind" + +// to keep our CI/CD that checks go.mod and go.sum files happy, we need to import the package above diff --git a/netbird/client/android/login.go b/netbird/client/android/login.go new file mode 100644 index 0000000..3d674c5 --- /dev/null +++ b/netbird/client/android/login.go @@ -0,0 +1,226 @@ +package android + +import ( + "context" + "fmt" + "time" + + "github.com/cenkalti/backoff/v4" + log "github.com/sirupsen/logrus" + "google.golang.org/grpc/codes" + gstatus "google.golang.org/grpc/status" + + "github.com/netbirdio/netbird/client/cmd" + "github.com/netbirdio/netbird/client/internal" + "github.com/netbirdio/netbird/client/internal/auth" + "github.com/netbirdio/netbird/client/system" +) + +// SSOListener is async listener for mobile framework +type SSOListener interface { + OnSuccess(bool) + OnError(error) +} + +// ErrListener is async listener for mobile framework +type ErrListener interface { + OnSuccess() + OnError(error) +} + +// URLOpener it is a callback interface. The Open function will be triggered if +// the backend want to show an url for the user +type URLOpener interface { + Open(string) +} + +// Auth can register or login new client +type Auth struct { + ctx context.Context + config *internal.Config + cfgPath string +} + +// NewAuth instantiate Auth struct and validate the management URL +func NewAuth(cfgPath string, mgmURL string) (*Auth, error) { + inputCfg := internal.ConfigInput{ + ManagementURL: mgmURL, + } + + cfg, err := internal.CreateInMemoryConfig(inputCfg) + if err != nil { + return nil, err + } + + return &Auth{ + ctx: context.Background(), + config: cfg, + cfgPath: cfgPath, + }, nil +} + +// NewAuthWithConfig instantiate Auth based on existing config +func NewAuthWithConfig(ctx context.Context, config *internal.Config) *Auth { + return &Auth{ + ctx: ctx, + config: config, + } +} + +// SaveConfigIfSSOSupported test the connectivity with the management server by retrieving the server device flow info. +// If it returns a flow info than save the configuration and return true. If it gets a codes.NotFound, it means that SSO +// is not supported and returns false without saving the configuration. For other errors return false. +func (a *Auth) SaveConfigIfSSOSupported(listener SSOListener) { + go func() { + sso, err := a.saveConfigIfSSOSupported() + if err != nil { + listener.OnError(err) + } else { + listener.OnSuccess(sso) + } + }() +} + +func (a *Auth) saveConfigIfSSOSupported() (bool, error) { + supportsSSO := true + err := a.withBackOff(a.ctx, func() (err error) { + _, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL, nil) + if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.NotFound || s.Code() == codes.Unimplemented) { + _, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) + s, ok := gstatus.FromError(err) + if !ok { + return err + } + if s.Code() == codes.NotFound || s.Code() == codes.Unimplemented { + supportsSSO = false + err = nil + } + + return err + } + + return err + }) + + if !supportsSSO { + return false, nil + } + + if err != nil { + return false, fmt.Errorf("backoff cycle failed: %v", err) + } + + err = internal.WriteOutConfig(a.cfgPath, a.config) + return true, err +} + +// LoginWithSetupKeyAndSaveConfig test the connectivity with the management server with the setup key. +func (a *Auth) LoginWithSetupKeyAndSaveConfig(resultListener ErrListener, setupKey string, deviceName string) { + go func() { + err := a.loginWithSetupKeyAndSaveConfig(setupKey, deviceName) + if err != nil { + resultListener.OnError(err) + } else { + resultListener.OnSuccess() + } + }() +} + +func (a *Auth) loginWithSetupKeyAndSaveConfig(setupKey string, deviceName string) error { + //nolint + ctxWithValues := context.WithValue(a.ctx, system.DeviceNameCtxKey, deviceName) + + err := a.withBackOff(a.ctx, func() error { + backoffErr := internal.Login(ctxWithValues, a.config, setupKey, "") + if s, ok := gstatus.FromError(backoffErr); ok && (s.Code() == codes.PermissionDenied) { + // we got an answer from management, exit backoff earlier + return backoff.Permanent(backoffErr) + } + return backoffErr + }) + if err != nil { + return fmt.Errorf("backoff cycle failed: %v", err) + } + + return internal.WriteOutConfig(a.cfgPath, a.config) +} + +// Login try register the client on the server +func (a *Auth) Login(resultListener ErrListener, urlOpener URLOpener) { + go func() { + err := a.login(urlOpener) + if err != nil { + resultListener.OnError(err) + } else { + resultListener.OnSuccess() + } + }() +} + +func (a *Auth) login(urlOpener URLOpener) error { + var needsLogin bool + + // check if we need to generate JWT token + err := a.withBackOff(a.ctx, func() (err error) { + needsLogin, err = internal.IsLoginRequired(a.ctx, a.config) + return + }) + if err != nil { + return fmt.Errorf("backoff cycle failed: %v", err) + } + + jwtToken := "" + if needsLogin { + tokenInfo, err := a.foregroundGetTokenInfo(urlOpener) + if err != nil { + return fmt.Errorf("interactive sso login failed: %v", err) + } + jwtToken = tokenInfo.GetTokenToUse() + } + + err = a.withBackOff(a.ctx, func() error { + err := internal.Login(a.ctx, a.config, "", jwtToken) + if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.InvalidArgument || s.Code() == codes.PermissionDenied) { + return nil + } + return err + }) + if err != nil { + return fmt.Errorf("backoff cycle failed: %v", err) + } + + return nil +} + +func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*auth.TokenInfo, error) { + oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config, false) + if err != nil { + return nil, err + } + + flowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO()) + if err != nil { + return nil, fmt.Errorf("getting a request OAuth flow info failed: %v", err) + } + + go urlOpener.Open(flowInfo.VerificationURIComplete) + + waitTimeout := time.Duration(flowInfo.ExpiresIn) * time.Second + waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout) + defer cancel() + tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo) + if err != nil { + return nil, fmt.Errorf("waiting for browser login failed: %v", err) + } + + return &tokenInfo, nil +} + +func (a *Auth) withBackOff(ctx context.Context, bf func() error) error { + return backoff.RetryNotify( + bf, + backoff.WithContext(cmd.CLIBackOffSettings, ctx), + func(err error, duration time.Duration) { + log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err) + }) +} diff --git a/netbird/client/android/peer_notifier.go b/netbird/client/android/peer_notifier.go new file mode 100644 index 0000000..9f6fcdd --- /dev/null +++ b/netbird/client/android/peer_notifier.go @@ -0,0 +1,36 @@ +package android + +// PeerInfo describe information about the peers. It designed for the UI usage +type PeerInfo struct { + IP string + FQDN string + ConnStatus string // Todo replace to enum +} + +// PeerInfoCollection made for Java layer to get non default types as collection +type PeerInfoCollection interface { + Add(s string) PeerInfoCollection + Get(i int) string + Size() int +} + +// PeerInfoArray is the implementation of the PeerInfoCollection +type PeerInfoArray struct { + items []PeerInfo +} + +// Add new PeerInfo to the collection +func (array PeerInfoArray) Add(s PeerInfo) PeerInfoArray { + array.items = append(array.items, s) + return array +} + +// Get return an element of the collection +func (array PeerInfoArray) Get(i int) *PeerInfo { + return &array.items[i] +} + +// Size return with the size of the collection +func (array PeerInfoArray) Size() int { + return len(array.items) +} diff --git a/netbird/client/android/preferences.go b/netbird/client/android/preferences.go new file mode 100644 index 0000000..08485ea --- /dev/null +++ b/netbird/client/android/preferences.go @@ -0,0 +1,78 @@ +package android + +import ( + "github.com/netbirdio/netbird/client/internal" +) + +// Preferences export a subset of the internal config for gomobile +type Preferences struct { + configInput internal.ConfigInput +} + +// NewPreferences create new Preferences instance +func NewPreferences(configPath string) *Preferences { + ci := internal.ConfigInput{ + ConfigPath: configPath, + } + return &Preferences{ci} +} + +// GetManagementURL read url from config file +func (p *Preferences) GetManagementURL() (string, error) { + if p.configInput.ManagementURL != "" { + return p.configInput.ManagementURL, nil + } + + cfg, err := internal.ReadConfig(p.configInput.ConfigPath) + if err != nil { + return "", err + } + return cfg.ManagementURL.String(), err +} + +// SetManagementURL store the given url and wait for commit +func (p *Preferences) SetManagementURL(url string) { + p.configInput.ManagementURL = url +} + +// GetAdminURL read url from config file +func (p *Preferences) GetAdminURL() (string, error) { + if p.configInput.AdminURL != "" { + return p.configInput.AdminURL, nil + } + + cfg, err := internal.ReadConfig(p.configInput.ConfigPath) + if err != nil { + return "", err + } + return cfg.AdminURL.String(), err +} + +// SetAdminURL store the given url and wait for commit +func (p *Preferences) SetAdminURL(url string) { + p.configInput.AdminURL = url +} + +// GetPreSharedKey read preshared key from config file +func (p *Preferences) GetPreSharedKey() (string, error) { + if p.configInput.PreSharedKey != nil { + return *p.configInput.PreSharedKey, nil + } + + cfg, err := internal.ReadConfig(p.configInput.ConfigPath) + if err != nil { + return "", err + } + return cfg.PreSharedKey, err +} + +// SetPreSharedKey store the given key and wait for commit +func (p *Preferences) SetPreSharedKey(key string) { + p.configInput.PreSharedKey = &key +} + +// Commit write out the changes into config file +func (p *Preferences) Commit() error { + _, err := internal.UpdateOrCreateConfig(p.configInput) + return err +} diff --git a/netbird/client/android/preferences_test.go b/netbird/client/android/preferences_test.go new file mode 100644 index 0000000..9851759 --- /dev/null +++ b/netbird/client/android/preferences_test.go @@ -0,0 +1,120 @@ +package android + +import ( + "path/filepath" + "testing" + + "github.com/netbirdio/netbird/client/internal" +) + +func TestPreferences_DefaultValues(t *testing.T) { + cfgFile := filepath.Join(t.TempDir(), "netbird.json") + p := NewPreferences(cfgFile) + defaultVar, err := p.GetAdminURL() + if err != nil { + t.Fatalf("failed to read default value: %s", err) + } + + if defaultVar != internal.DefaultAdminURL { + t.Errorf("invalid default admin url: %s", defaultVar) + } + + defaultVar, err = p.GetManagementURL() + if err != nil { + t.Fatalf("failed to read default management URL: %s", err) + } + + if defaultVar != internal.DefaultManagementURL { + t.Errorf("invalid default management url: %s", defaultVar) + } + + var preSharedKey string + preSharedKey, err = p.GetPreSharedKey() + if err != nil { + t.Fatalf("failed to read default preshared key: %s", err) + } + + if preSharedKey != "" { + t.Errorf("invalid preshared key: %s", preSharedKey) + } +} + +func TestPreferences_ReadUncommitedValues(t *testing.T) { + exampleString := "exampleString" + cfgFile := filepath.Join(t.TempDir(), "netbird.json") + p := NewPreferences(cfgFile) + + p.SetAdminURL(exampleString) + resp, err := p.GetAdminURL() + if err != nil { + t.Fatalf("failed to read admin url: %s", err) + } + + if resp != exampleString { + t.Errorf("unexpected admin url: %s", resp) + } + + p.SetManagementURL(exampleString) + resp, err = p.GetManagementURL() + if err != nil { + t.Fatalf("failed to read management url: %s", err) + } + + if resp != exampleString { + t.Errorf("unexpected management url: %s", resp) + } + + p.SetPreSharedKey(exampleString) + resp, err = p.GetPreSharedKey() + if err != nil { + t.Fatalf("failed to read preshared key: %s", err) + } + + if resp != exampleString { + t.Errorf("unexpected preshared key: %s", resp) + } +} + +func TestPreferences_Commit(t *testing.T) { + exampleURL := "https://myurl.com:443" + examplePresharedKey := "topsecret" + cfgFile := filepath.Join(t.TempDir(), "netbird.json") + p := NewPreferences(cfgFile) + + p.SetAdminURL(exampleURL) + p.SetManagementURL(exampleURL) + p.SetPreSharedKey(examplePresharedKey) + + err := p.Commit() + if err != nil { + t.Fatalf("failed to save changes: %s", err) + } + + p = NewPreferences(cfgFile) + resp, err := p.GetAdminURL() + if err != nil { + t.Fatalf("failed to read admin url: %s", err) + } + + if resp != exampleURL { + t.Errorf("unexpected admin url: %s", resp) + } + + resp, err = p.GetManagementURL() + if err != nil { + t.Fatalf("failed to read management url: %s", err) + } + + if resp != exampleURL { + t.Errorf("unexpected management url: %s", resp) + } + + resp, err = p.GetPreSharedKey() + if err != nil { + t.Fatalf("failed to read preshared key: %s", err) + } + + if resp != examplePresharedKey { + t.Errorf("unexpected preshared key: %s", resp) + } +} diff --git a/netbird/client/anonymize/anonymize.go b/netbird/client/anonymize/anonymize.go new file mode 100644 index 0000000..2fc9d49 --- /dev/null +++ b/netbird/client/anonymize/anonymize.go @@ -0,0 +1,240 @@ +package anonymize + +import ( + "crypto/rand" + "fmt" + "math/big" + "net" + "net/netip" + "net/url" + "regexp" + "slices" + "strings" +) + +const anonTLD = ".domain" + +type Anonymizer struct { + ipAnonymizer map[netip.Addr]netip.Addr + domainAnonymizer map[string]string + currentAnonIPv4 netip.Addr + currentAnonIPv6 netip.Addr + startAnonIPv4 netip.Addr + startAnonIPv6 netip.Addr + + domainKeyRegex *regexp.Regexp +} + +func DefaultAddresses() (netip.Addr, netip.Addr) { + // 198.51.100.0, 100:: + return netip.AddrFrom4([4]byte{198, 51, 100, 0}), netip.AddrFrom16([16]byte{0x01}) +} + +func NewAnonymizer(startIPv4, startIPv6 netip.Addr) *Anonymizer { + return &Anonymizer{ + ipAnonymizer: map[netip.Addr]netip.Addr{}, + domainAnonymizer: map[string]string{}, + currentAnonIPv4: startIPv4, + currentAnonIPv6: startIPv6, + startAnonIPv4: startIPv4, + startAnonIPv6: startIPv6, + + domainKeyRegex: regexp.MustCompile(`\bdomain=([^\s,:"]+)`), + } +} + +func (a *Anonymizer) AnonymizeIP(ip netip.Addr) netip.Addr { + if ip.IsLoopback() || + ip.IsLinkLocalUnicast() || + ip.IsLinkLocalMulticast() || + ip.IsInterfaceLocalMulticast() || + ip.IsPrivate() || + ip.IsUnspecified() || + ip.IsMulticast() || + isWellKnown(ip) || + a.isInAnonymizedRange(ip) { + + return ip + } + + if _, ok := a.ipAnonymizer[ip]; !ok { + if ip.Is4() { + a.ipAnonymizer[ip] = a.currentAnonIPv4 + a.currentAnonIPv4 = a.currentAnonIPv4.Next() + } else { + a.ipAnonymizer[ip] = a.currentAnonIPv6 + a.currentAnonIPv6 = a.currentAnonIPv6.Next() + } + } + return a.ipAnonymizer[ip] +} + +// isInAnonymizedRange checks if an IP is within the range of already assigned anonymized IPs +func (a *Anonymizer) isInAnonymizedRange(ip netip.Addr) bool { + if ip.Is4() && ip.Compare(a.startAnonIPv4) >= 0 && ip.Compare(a.currentAnonIPv4) <= 0 { + return true + } else if !ip.Is4() && ip.Compare(a.startAnonIPv6) >= 0 && ip.Compare(a.currentAnonIPv6) <= 0 { + return true + } + return false +} + +func (a *Anonymizer) AnonymizeIPString(ip string) string { + addr, err := netip.ParseAddr(ip) + if err != nil { + return ip + } + + return a.AnonymizeIP(addr).String() +} + +func (a *Anonymizer) AnonymizeDomain(domain string) string { + baseDomain := domain + hasDot := strings.HasSuffix(domain, ".") + if hasDot { + baseDomain = domain[:len(domain)-1] + } + + if strings.HasSuffix(baseDomain, "netbird.io") || + strings.HasSuffix(baseDomain, "netbird.selfhosted") || + strings.HasSuffix(baseDomain, "netbird.cloud") || + strings.HasSuffix(baseDomain, "netbird.stage") || + strings.HasSuffix(baseDomain, anonTLD) { + return domain + } + + parts := strings.Split(baseDomain, ".") + if len(parts) < 2 { + return domain + } + + baseForLookup := parts[len(parts)-2] + "." + parts[len(parts)-1] + + anonymized, ok := a.domainAnonymizer[baseForLookup] + if !ok { + anonymizedBase := "anon-" + generateRandomString(5) + anonTLD + a.domainAnonymizer[baseForLookup] = anonymizedBase + anonymized = anonymizedBase + } + + result := strings.Replace(baseDomain, baseForLookup, anonymized, 1) + if hasDot { + result += "." + } + return result +} + +func (a *Anonymizer) AnonymizeURI(uri string) string { + u, err := url.Parse(uri) + if err != nil { + return uri + } + + var anonymizedHost string + if u.Opaque != "" { + host, port, err := net.SplitHostPort(u.Opaque) + if err == nil { + anonymizedHost = fmt.Sprintf("%s:%s", a.AnonymizeDomain(host), port) + } else { + anonymizedHost = a.AnonymizeDomain(u.Opaque) + } + u.Opaque = anonymizedHost + } else if u.Host != "" { + host, port, err := net.SplitHostPort(u.Host) + if err == nil { + anonymizedHost = fmt.Sprintf("%s:%s", a.AnonymizeDomain(host), port) + } else { + anonymizedHost = a.AnonymizeDomain(u.Host) + } + u.Host = anonymizedHost + } + return u.String() +} + +func (a *Anonymizer) AnonymizeString(str string) string { + ipv4Regex := regexp.MustCompile(`\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b`) + ipv6Regex := regexp.MustCompile(`\b([0-9a-fA-F:]+:+[0-9a-fA-F]{0,4})(?:%[0-9a-zA-Z]+)?(?:\/[0-9]{1,3})?(?::[0-9]{1,5})?\b`) + + str = ipv4Regex.ReplaceAllStringFunc(str, a.AnonymizeIPString) + str = ipv6Regex.ReplaceAllStringFunc(str, a.AnonymizeIPString) + + for domain, anonDomain := range a.domainAnonymizer { + str = strings.ReplaceAll(str, domain, anonDomain) + } + + str = a.AnonymizeSchemeURI(str) + str = a.AnonymizeDNSLogLine(str) + + return str +} + +// AnonymizeSchemeURI finds and anonymizes URIs with ws, wss, rel, rels, stun, stuns, turn, and turns schemes. +func (a *Anonymizer) AnonymizeSchemeURI(text string) string { + re := regexp.MustCompile(`(?i)\b(wss?://|rels?://|stuns?:|turns?:|https?://)\S+\b`) + + return re.ReplaceAllStringFunc(text, a.AnonymizeURI) +} + +func (a *Anonymizer) AnonymizeDNSLogLine(logEntry string) string { + return a.domainKeyRegex.ReplaceAllStringFunc(logEntry, func(match string) string { + parts := strings.SplitN(match, "=", 2) + if len(parts) >= 2 { + domain := parts[1] + if strings.HasSuffix(domain, anonTLD) { + return match + } + return "domain=" + a.AnonymizeDomain(domain) + } + return match + }) +} + +// AnonymizeRoute anonymizes a route string by replacing IP addresses with anonymized versions and +// domain names with random strings. +func (a *Anonymizer) AnonymizeRoute(route string) string { + prefix, err := netip.ParsePrefix(route) + if err == nil { + ip := a.AnonymizeIPString(prefix.Addr().String()) + return fmt.Sprintf("%s/%d", ip, prefix.Bits()) + } + domains := strings.Split(route, ", ") + for i, domain := range domains { + domains[i] = a.AnonymizeDomain(domain) + } + return strings.Join(domains, ", ") +} + +func isWellKnown(addr netip.Addr) bool { + wellKnown := []string{ + "8.8.8.8", "8.8.4.4", // Google DNS IPv4 + "2001:4860:4860::8888", "2001:4860:4860::8844", // Google DNS IPv6 + "1.1.1.1", "1.0.0.1", // Cloudflare DNS IPv4 + "2606:4700:4700::1111", "2606:4700:4700::1001", // Cloudflare DNS IPv6 + "9.9.9.9", "149.112.112.112", // Quad9 DNS IPv4 + "2620:fe::fe", "2620:fe::9", // Quad9 DNS IPv6 + + "128.0.0.0", "8000::", // 2nd split subnet for default routes + } + + if slices.Contains(wellKnown, addr.String()) { + return true + } + + cgnatRangeStart := netip.AddrFrom4([4]byte{100, 64, 0, 0}) + cgnatRange := netip.PrefixFrom(cgnatRangeStart, 10) + + return cgnatRange.Contains(addr) +} + +func generateRandomString(length int) string { + const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + result := make([]byte, length) + for i := range result { + num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) + if err != nil { + continue + } + result[i] = letters[num.Int64()] + } + return string(result) +} diff --git a/netbird/client/anonymize/anonymize_test.go b/netbird/client/anonymize/anonymize_test.go new file mode 100644 index 0000000..ff2e488 --- /dev/null +++ b/netbird/client/anonymize/anonymize_test.go @@ -0,0 +1,297 @@ +package anonymize_test + +import ( + "net/netip" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/netbirdio/netbird/client/anonymize" +) + +func TestAnonymizeIP(t *testing.T) { + startIPv4 := netip.MustParseAddr("198.51.100.0") + startIPv6 := netip.MustParseAddr("100::") + anonymizer := anonymize.NewAnonymizer(startIPv4, startIPv6) + + tests := []struct { + name string + ip string + expect string + }{ + {"Well known", "8.8.8.8", "8.8.8.8"}, + {"First Public IPv4", "1.2.3.4", "198.51.100.0"}, + {"Second Public IPv4", "4.3.2.1", "198.51.100.1"}, + {"Repeated IPv4", "1.2.3.4", "198.51.100.0"}, + {"Private IPv4", "192.168.1.1", "192.168.1.1"}, + {"First Public IPv6", "2607:f8b0:4005:805::200e", "100::"}, + {"Second Public IPv6", "a::b", "100::1"}, + {"Repeated IPv6", "2607:f8b0:4005:805::200e", "100::"}, + {"Private IPv6", "fe80::1", "fe80::1"}, + {"In Range IPv4", "198.51.100.2", "198.51.100.2"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ip := netip.MustParseAddr(tc.ip) + anonymizedIP := anonymizer.AnonymizeIP(ip) + if anonymizedIP.String() != tc.expect { + t.Errorf("%s: expected %s, got %s", tc.name, tc.expect, anonymizedIP) + } + }) + } +} + +func TestAnonymizeDNSLogLine(t *testing.T) { + anonymizer := anonymize.NewAnonymizer(netip.Addr{}, netip.Addr{}) + tests := []struct { + name string + input string + original string + expect string + }{ + { + name: "Basic domain with trailing content", + input: "received DNS request for DNS forwarder: domain=example.com: something happened with code=123", + original: "example.com", + expect: `received DNS request for DNS forwarder: domain=anon-[a-zA-Z0-9]+\.domain: something happened with code=123`, + }, + { + name: "Domain with trailing dot", + input: "domain=example.com. processing request with status=pending", + original: "example.com", + expect: `domain=anon-[a-zA-Z0-9]+\.domain\. processing request with status=pending`, + }, + { + name: "Multiple domains in log", + input: "forward domain=first.com status=ok, redirect to domain=second.com port=443", + original: "first.com", // testing just one is sufficient as AnonymizeDomain is tested separately + expect: `forward domain=anon-[a-zA-Z0-9]+\.domain status=ok, redirect to domain=anon-[a-zA-Z0-9]+\.domain port=443`, + }, + { + name: "Already anonymized domain", + input: "got request domain=anon-xyz123.domain from=client1 to=server2", + original: "", // nothing should be anonymized + expect: `got request domain=anon-xyz123\.domain from=client1 to=server2`, + }, + { + name: "Subdomain with trailing dot", + input: "domain=sub.example.com. next_hop=10.0.0.1 proto=udp", + original: "example.com", + expect: `domain=sub\.anon-[a-zA-Z0-9]+\.domain\. next_hop=10\.0\.0\.1 proto=udp`, + }, + { + name: "Handler chain pattern log", + input: "pattern: domain=example.com. original: domain=*.example.com. wildcard=true priority=100", + original: "example.com", + expect: `pattern: domain=anon-[a-zA-Z0-9]+\.domain\. original: domain=\*\.anon-[a-zA-Z0-9]+\.domain\. wildcard=true priority=100`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := anonymizer.AnonymizeDNSLogLine(tc.input) + if tc.original != "" { + assert.NotContains(t, result, tc.original) + } + assert.Regexp(t, tc.expect, result) + }) + } +} + +func TestAnonymizeDomain(t *testing.T) { + anonymizer := anonymize.NewAnonymizer(netip.Addr{}, netip.Addr{}) + tests := []struct { + name string + domain string + expectPattern string + shouldAnonymize bool + }{ + { + "General Domain", + "example.com", + `^anon-[a-zA-Z0-9]+\.domain$`, + true, + }, + { + "Domain with Trailing Dot", + "example.com.", + `^anon-[a-zA-Z0-9]+\.domain.$`, + true, + }, + { + "Subdomain", + "sub.example.com", + `^sub\.anon-[a-zA-Z0-9]+\.domain$`, + true, + }, + { + "Subdomain with Trailing Dot", + "sub.example.com.", + `^sub\.anon-[a-zA-Z0-9]+\.domain.$`, + true, + }, + { + "Protected Domain", + "netbird.io", + `^netbird\.io$`, + false, + }, + { + "Protected Domain with Trailing Dot", + "netbird.io.", + `^netbird\.io.$`, + false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := anonymizer.AnonymizeDomain(tc.domain) + if tc.shouldAnonymize { + assert.Regexp(t, tc.expectPattern, result, "The anonymized domain should match the expected pattern") + assert.NotContains(t, result, tc.domain, "The original domain should not be present in the result") + } else { + assert.Equal(t, tc.domain, result, "Protected domains should not be anonymized") + } + }) + } +} + +func TestAnonymizeURI(t *testing.T) { + anonymizer := anonymize.NewAnonymizer(netip.Addr{}, netip.Addr{}) + tests := []struct { + name string + uri string + regex string + }{ + { + "HTTP URI with Port", + "http://example.com:80/path", + `^http://anon-[a-zA-Z0-9]+\.domain:80/path$`, + }, + { + "HTTP URI without Port", + "http://example.com/path", + `^http://anon-[a-zA-Z0-9]+\.domain/path$`, + }, + { + "Opaque URI with Port", + "stun:example.com:80?transport=udp", + `^stun:anon-[a-zA-Z0-9]+\.domain:80\?transport=udp$`, + }, + { + "Opaque URI without Port", + "stun:example.com?transport=udp", + `^stun:anon-[a-zA-Z0-9]+\.domain\?transport=udp$`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := anonymizer.AnonymizeURI(tc.uri) + assert.Regexp(t, regexp.MustCompile(tc.regex), result, "URI should match expected pattern") + require.NotContains(t, result, "example.com", "Original domain should not be present") + }) + } +} + +func TestAnonymizeSchemeURI(t *testing.T) { + anonymizer := anonymize.NewAnonymizer(netip.Addr{}, netip.Addr{}) + tests := []struct { + name string + input string + expect string + }{ + {"STUN URI in text", "Connection made via stun:example.com", `Connection made via stun:anon-[a-zA-Z0-9]+\.domain`}, + {"STUNS URI in message", "Secure connection to stuns:example.com:443", `Secure connection to stuns:anon-[a-zA-Z0-9]+\.domain:443`}, + {"TURN URI in log", "Failed attempt turn:some.example.com:3478?transport=tcp: retrying", `Failed attempt turn:some.anon-[a-zA-Z0-9]+\.domain:3478\?transport=tcp: retrying`}, + {"TURNS URI in message", "Secure connection to turns:example.com:5349", `Secure connection to turns:anon-[a-zA-Z0-9]+\.domain:5349`}, + {"HTTP URI in text", "Visit http://example.com for more", `Visit http://anon-[a-zA-Z0-9]+\.domain for more`}, + {"HTTPS URI in CAPS", "Visit HTTPS://example.com for more", `Visit https://anon-[a-zA-Z0-9]+\.domain for more`}, + {"HTTPS URI in message", "Visit https://example.com for more", `Visit https://anon-[a-zA-Z0-9]+\.domain for more`}, + {"WS URI in log", "Connection established to ws://example.com:8080", `Connection established to ws://anon-[a-zA-Z0-9]+\.domain:8080`}, + {"WSS URI in message", "Secure connection to wss://example.com", `Secure connection to wss://anon-[a-zA-Z0-9]+\.domain`}, + {"Rel URI in text", "Relaying to rel://example.com", `Relaying to rel://anon-[a-zA-Z0-9]+\.domain`}, + {"Rels URI in message", "Relaying to rels://example.com", `Relaying to rels://anon-[a-zA-Z0-9]+\.domain`}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := anonymizer.AnonymizeSchemeURI(tc.input) + assert.Regexp(t, tc.expect, result, "The anonymized output should match expected pattern") + require.NotContains(t, result, "example.com", "Original domain should not be present") + }) + } +} + +func TestAnonymizString_MemorizedDomain(t *testing.T) { + anonymizer := anonymize.NewAnonymizer(netip.Addr{}, netip.Addr{}) + domain := "example.com" + anonymizedDomain := anonymizer.AnonymizeDomain(domain) + + sampleString := "This is a test string including the domain example.com which should be anonymized." + + firstPassResult := anonymizer.AnonymizeString(sampleString) + secondPassResult := anonymizer.AnonymizeString(firstPassResult) + + assert.Contains(t, firstPassResult, anonymizedDomain, "The domain should be anonymized in the first pass") + assert.NotContains(t, firstPassResult, domain, "The original domain should not appear in the first pass output") + + assert.Equal(t, firstPassResult, secondPassResult, "The second pass should not further anonymize the string") +} + +func TestAnonymizeString_DoubleURI(t *testing.T) { + anonymizer := anonymize.NewAnonymizer(netip.Addr{}, netip.Addr{}) + domain := "example.com" + anonymizedDomain := anonymizer.AnonymizeDomain(domain) + + sampleString := "Check out our site at https://example.com for more info." + + firstPassResult := anonymizer.AnonymizeString(sampleString) + secondPassResult := anonymizer.AnonymizeString(firstPassResult) + + assert.Contains(t, firstPassResult, "https://"+anonymizedDomain, "The URI should be anonymized in the first pass") + assert.NotContains(t, firstPassResult, "https://example.com", "The original URI should not appear in the first pass output") + + assert.Equal(t, firstPassResult, secondPassResult, "The second pass should not further anonymize the URI") +} + +func TestAnonymizeString_IPAddresses(t *testing.T) { + anonymizer := anonymize.NewAnonymizer(anonymize.DefaultAddresses()) + tests := []struct { + name string + input string + expect string + }{ + { + name: "IPv4 Address", + input: "Error occurred at IP 122.138.1.1", + expect: "Error occurred at IP 198.51.100.0", + }, + { + name: "IPv6 Address", + input: "Access attempted from 2001:db8::ff00:42", + expect: "Access attempted from 100::", + }, + { + name: "IPv6 Address with Port", + input: "Access attempted from [2001:db8::ff00:42]:8080", + expect: "Access attempted from [100::]:8080", + }, + { + name: "Both IPv4 and IPv6", + input: "IPv4: 142.108.0.1 and IPv6: 2001:db8::ff00:43", + expect: "IPv4: 198.51.100.1 and IPv6: 100::1", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := anonymizer.AnonymizeString(tc.input) + assert.Equal(t, tc.expect, result, "IP addresses should be anonymized correctly") + }) + } +} diff --git a/netbird/client/cmd/debug.go b/netbird/client/cmd/debug.go new file mode 100644 index 0000000..d2e5bdd --- /dev/null +++ b/netbird/client/cmd/debug.go @@ -0,0 +1,362 @@ +package cmd + +import ( + "context" + "fmt" + "strings" + "time" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "google.golang.org/grpc/status" + + "github.com/netbirdio/netbird/client/internal" + "github.com/netbirdio/netbird/client/internal/debug" + "github.com/netbirdio/netbird/client/internal/peer" + "github.com/netbirdio/netbird/client/proto" + "github.com/netbirdio/netbird/client/server" + nbstatus "github.com/netbirdio/netbird/client/status" + mgmProto "github.com/netbirdio/netbird/management/proto" +) + +const errCloseConnection = "Failed to close connection: %v" + +var debugCmd = &cobra.Command{ + Use: "debug", + Short: "Debugging commands", + Long: "Provides commands for debugging and logging control within the Netbird daemon.", +} + +var debugBundleCmd = &cobra.Command{ + Use: "bundle", + Example: " netbird debug bundle", + Short: "Create a debug bundle", + Long: "Generates a compressed archive of the daemon's logs and status for debugging purposes.", + RunE: debugBundle, +} + +var logCmd = &cobra.Command{ + Use: "log", + Short: "Manage logging for the Netbird daemon", + Long: `Commands to manage logging settings for the Netbird daemon, including ICE, gRPC, and general log levels.`, +} + +var logLevelCmd = &cobra.Command{ + Use: "level ", + Short: "Set the logging level for this session", + Long: `Sets the logging level for the current session. This setting is temporary and will revert to the default on daemon restart. +Available log levels are: + panic: for panic level, highest level of severity + fatal: for fatal level errors that cause the program to exit + error: for error conditions + warn: for warning conditions + info: for informational messages + debug: for debug-level messages + trace: for trace-level messages, which include more fine-grained information than debug`, + Args: cobra.ExactArgs(1), + RunE: setLogLevel, +} + +var forCmd = &cobra.Command{ + Use: "for