diff --git a/.github/workflows/golang-test-linux.yml b/.github/workflows/golang-test-linux.yml index da1db5c03e2..5f7d7b4a3cf 100644 --- a/.github/workflows/golang-test-linux.yml +++ b/.github/workflows/golang-test-linux.yml @@ -13,7 +13,7 @@ concurrency: jobs: build-cache: runs-on: ubuntu-22.04 - steps: + steps: - name: Checkout code uses: actions/checkout@v4 @@ -183,7 +183,7 @@ jobs: run: git --no-pager diff --exit-code - name: Login to Docker hub - if: matrix.store == 'mysql' + if: matrix.store == 'mysql' && (github.repository == github.head.repo.full_name || !github.head_ref) uses: docker/login-action@v1 with: username: ${{ secrets.DOCKER_USER }} @@ -243,7 +243,7 @@ jobs: run: git --no-pager diff --exit-code - name: Login to Docker hub - if: matrix.store == 'mysql' + if: matrix.store == 'mysql' && (github.repository == github.head.repo.full_name || !github.head_ref) uses: docker/login-action@v1 with: username: ${{ secrets.DOCKER_USER }} @@ -303,7 +303,7 @@ jobs: run: git --no-pager diff --exit-code - name: Login to Docker hub - if: matrix.store == 'mysql' + if: matrix.store == 'mysql' && (github.repository == github.head.repo.full_name || !github.head_ref) uses: docker/login-action@v1 with: username: ${{ secrets.DOCKER_USER }} diff --git a/client/configs/configs.go b/client/configs/configs.go new file mode 100644 index 00000000000..8f9c3ba2870 --- /dev/null +++ b/client/configs/configs.go @@ -0,0 +1,24 @@ +package configs + +import ( + "os" + "path/filepath" + "runtime" +) + +var StateDir string + +func init() { + StateDir = os.Getenv("NB_STATE_DIR") + if StateDir != "" { + return + } + switch runtime.GOOS { + case "windows": + StateDir = filepath.Join(os.Getenv("PROGRAMDATA"), "Netbird") + case "darwin", "linux": + StateDir = "/var/lib/netbird" + case "freebsd", "openbsd", "netbsd", "dragonfly": + StateDir = "/var/db/netbird" + } +} diff --git a/client/internal/dns/consts.go b/client/internal/dns/consts.go new file mode 100644 index 00000000000..b333d080807 --- /dev/null +++ b/client/internal/dns/consts.go @@ -0,0 +1,18 @@ +//go:build !android + +package dns + +import ( + "github.com/netbirdio/netbird/client/configs" + "os" + "path/filepath" +) + +var fileUncleanShutdownResolvConfLocation string + +func init() { + fileUncleanShutdownResolvConfLocation = os.Getenv("NB_UNCLEAN_SHUTDOWN_RESOLV_FILE") + if fileUncleanShutdownResolvConfLocation == "" { + fileUncleanShutdownResolvConfLocation = filepath.Join(configs.StateDir, "resolv.conf") + } +} diff --git a/client/internal/dns/consts_freebsd.go b/client/internal/dns/consts_freebsd.go deleted file mode 100644 index 64c8fe5ebed..00000000000 --- a/client/internal/dns/consts_freebsd.go +++ /dev/null @@ -1,5 +0,0 @@ -package dns - -const ( - fileUncleanShutdownResolvConfLocation = "/var/db/netbird/resolv.conf" -) diff --git a/client/internal/dns/consts_linux.go b/client/internal/dns/consts_linux.go deleted file mode 100644 index 15614b0c599..00000000000 --- a/client/internal/dns/consts_linux.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !android - -package dns - -const ( - fileUncleanShutdownResolvConfLocation = "/var/lib/netbird/resolv.conf" -) diff --git a/client/internal/dns/resolvconf_unix.go b/client/internal/dns/resolvconf_unix.go index a5d1cc8a225..6b5fdaf8698 100644 --- a/client/internal/dns/resolvconf_unix.go +++ b/client/internal/dns/resolvconf_unix.go @@ -7,6 +7,7 @@ import ( "fmt" "net/netip" "os/exec" + "strings" log "github.com/sirupsen/logrus" @@ -15,23 +16,64 @@ import ( const resolvconfCommand = "resolvconf" +// resolvconfType represents the type of resolvconf implementation +type resolvconfType int + +func (r resolvconfType) String() string { + switch r { + case typeOpenresolv: + return "openresolv" + case typeResolvconf: + return "resolvconf" + default: + return "unknown" + } +} + +const ( + typeOpenresolv resolvconfType = iota + typeResolvconf +) + type resolvconf struct { ifaceName string + implType resolvconfType originalSearchDomains []string originalNameServers []string othersConfigs []string } -// supported "openresolv" only +func detectResolvconfType() (resolvconfType, error) { + cmd := exec.Command(resolvconfCommand, "--version") + out, err := cmd.Output() + if err != nil { + return typeOpenresolv, fmt.Errorf("failed to determine resolvconf type: %w", err) + } + + if strings.Contains(string(out), "openresolv") { + return typeOpenresolv, nil + } + return typeResolvconf, nil +} + func newResolvConfConfigurator(wgInterface string) (*resolvconf, error) { resolvConfEntries, err := parseDefaultResolvConf() if err != nil { log.Errorf("could not read original search domains from %s: %s", defaultResolvConfPath, err) } + implType, err := detectResolvconfType() + if err != nil { + log.Warnf("failed to detect resolvconf type, defaulting to openresolv: %v", err) + implType = typeOpenresolv + } else { + log.Infof("detected resolvconf type: %v", implType) + } + return &resolvconf{ ifaceName: wgInterface, + implType: implType, originalSearchDomains: resolvConfEntries.searchDomains, originalNameServers: resolvConfEntries.nameServers, othersConfigs: resolvConfEntries.others, @@ -80,8 +122,15 @@ func (r *resolvconf) applyDNSConfig(config HostDNSConfig, stateManager *stateman } func (r *resolvconf) restoreHostDNS() error { - // openresolv only, debian resolvconf doesn't support "-f" - cmd := exec.Command(resolvconfCommand, "-f", "-d", r.ifaceName) + var cmd *exec.Cmd + + switch r.implType { + case typeOpenresolv: + cmd = exec.Command(resolvconfCommand, "-f", "-d", r.ifaceName) + case typeResolvconf: + cmd = exec.Command(resolvconfCommand, "-d", r.ifaceName) + } + _, err := cmd.Output() if err != nil { return fmt.Errorf("removing resolvconf configuration for %s interface: %w", r.ifaceName, err) @@ -91,10 +140,21 @@ func (r *resolvconf) restoreHostDNS() error { } func (r *resolvconf) applyConfig(content bytes.Buffer) error { - // openresolv only, debian resolvconf doesn't support "-x" - cmd := exec.Command(resolvconfCommand, "-x", "-a", r.ifaceName) + var cmd *exec.Cmd + + switch r.implType { + case typeOpenresolv: + // OpenResolv supports exclusive mode with -x + cmd = exec.Command(resolvconfCommand, "-x", "-a", r.ifaceName) + case typeResolvconf: + cmd = exec.Command(resolvconfCommand, "-a", r.ifaceName) + default: + return fmt.Errorf("unsupported resolvconf type: %v", r.implType) + } + cmd.Stdin = &content - _, err := cmd.Output() + out, err := cmd.Output() + log.Tracef("resolvconf output: %s", out) if err != nil { return fmt.Errorf("applying resolvconf configuration for %s interface: %w", r.ifaceName, err) } diff --git a/client/internal/statemanager/path.go b/client/internal/statemanager/path.go index 6cfd79a1212..d232e5f0cb0 100644 --- a/client/internal/statemanager/path.go +++ b/client/internal/statemanager/path.go @@ -1,23 +1,16 @@ package statemanager import ( + "github.com/netbirdio/netbird/client/configs" "os" "path/filepath" - "runtime" ) // GetDefaultStatePath returns the path to the state file based on the operating system // It returns an empty string if the path cannot be determined. func GetDefaultStatePath() string { - switch runtime.GOOS { - case "windows": - return filepath.Join(os.Getenv("PROGRAMDATA"), "Netbird", "state.json") - case "darwin", "linux": - return "/var/lib/netbird/state.json" - case "freebsd", "openbsd", "netbsd", "dragonfly": - return "/var/db/netbird/state.json" + if path := os.Getenv("NB_DNS_STATE_FILE"); path != "" { + return path } - - return "" - + return filepath.Join(configs.StateDir, "state.json") } diff --git a/go.mod b/go.mod index ee286df1c44..9e7ced1c38b 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( ) require ( - fyne.io/fyne/v2 v2.5.0 + fyne.io/fyne/v2 v2.5.3 fyne.io/systray v1.11.0 github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible github.com/c-robinson/iplib v1.0.3 @@ -147,7 +147,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fredbi/uri v1.1.0 // indirect github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect - github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a // indirect + github.com/fyne-io/glfw-js v0.0.0-20241126112943-313d8a0fe1d0 // indirect github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect @@ -156,8 +156,8 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect - github.com/go-text/render v0.1.0 // indirect - github.com/go-text/typesetting v0.1.0 // indirect + github.com/go-text/render v0.2.0 // indirect + github.com/go-text/typesetting v0.2.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/btree v1.1.2 // indirect @@ -207,7 +207,7 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.53.0 // indirect github.com/prometheus/procfs v0.15.0 // indirect - github.com/rymdport/portal v0.2.2 // indirect + github.com/rymdport/portal v0.3.0 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect diff --git a/go.sum b/go.sum index a571417dfdf..2ea30d0c20b 100644 --- a/go.sum +++ b/go.sum @@ -50,8 +50,8 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -fyne.io/fyne/v2 v2.5.0 h1:lEjEIso0Vi4sJXYngIMoXOM6aUjqnPjK7pBpxRxG9aI= -fyne.io/fyne/v2 v2.5.0/go.mod h1:9D4oT3NWeG+MLi/lP7ItZZyujHC/qqMJpoGTAYX5Uqc= +fyne.io/fyne/v2 v2.5.3 h1:k6LjZx6EzRZhClsuzy6vucLZBstdH2USDGHSGWq8ly8= +fyne.io/fyne/v2 v2.5.3/go.mod h1:0GOXKqyvNwk3DLmsFu9v0oYM0ZcD1ysGnlHCerKoAmo= fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg= fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= @@ -204,8 +204,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4= github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg= -github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a h1:ybgRdYvAHTn93HW79bLiBiJwVL4jVeyGQRZMgImoeWs= -github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a/go.mod h1:gsGA2dotD4v0SR6PmPCYvS9JuOeMwAtmfvDE7mbYXMY= +github.com/fyne-io/glfw-js v0.0.0-20241126112943-313d8a0fe1d0 h1:/1YRWFv9bAWkoo3SuxpFfzpXH0D/bQnTjNXyF4ih7Os= +github.com/fyne-io/glfw-js v0.0.0-20241126112943-313d8a0fe1d0/go.mod h1:gsGA2dotD4v0SR6PmPCYvS9JuOeMwAtmfvDE7mbYXMY= github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk= github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -246,12 +246,12 @@ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqw github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/go-text/render v0.1.0 h1:osrmVDZNHuP1RSu3pNG7Z77Sd2xSbcb/xWytAj9kyVs= -github.com/go-text/render v0.1.0/go.mod h1:jqEuNMenrmj6QRnkdpeaP0oKGFLDNhDkVKwGjsWWYU4= -github.com/go-text/typesetting v0.1.0 h1:vioSaLPYcHwPEPLT7gsjCGDCoYSbljxoHJzMnKwVvHw= -github.com/go-text/typesetting v0.1.0/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI= -github.com/go-text/typesetting-utils v0.0.0-20240329101916-eee87fb235a3 h1:levTnuLLUmpavLGbJYLJA7fQnKeS7P1eCdAlM+vReXk= -github.com/go-text/typesetting-utils v0.0.0-20240329101916-eee87fb235a3/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc= +github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU= +github.com/go-text/typesetting v0.2.0 h1:fbzsgbmk04KiWtE+c3ZD4W2nmCRzBqrqQOvYlwAOdho= +github.com/go-text/typesetting v0.2.0/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I= +github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY= +github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -621,8 +621,8 @@ github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/rymdport/portal v0.2.2 h1:P2Q/4k673zxdFAsbD8EESZ7psfuO6/4jNu6EDrDICkM= -github.com/rymdport/portal v0.2.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= +github.com/rymdport/portal v0.3.0 h1:QRHcwKwx3kY5JTQcsVhmhC3TGqGQb9LFghVNUy8AdB8= +github.com/rymdport/portal v0.3.0/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8= diff --git a/management/server/account_test.go b/management/server/account_test.go index d8ceef0e7e0..e4f079507d2 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -16,6 +16,7 @@ import ( "time" "github.com/golang-jwt/jwt" + "github.com/netbirdio/netbird/management/server/util" resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types" @@ -3021,12 +3022,12 @@ func BenchmarkSyncAndMarkPeer(b *testing.B) { minMsPerOpCICD float64 maxMsPerOpCICD float64 }{ - {"Small", 50, 5, 1, 3, 3, 14}, - {"Medium", 500, 100, 7, 13, 10, 80}, - {"Large", 5000, 200, 65, 80, 60, 220}, - {"Small single", 50, 10, 1, 3, 3, 70}, - {"Medium single", 500, 10, 7, 13, 10, 32}, - {"Large 5", 5000, 15, 65, 80, 60, 200}, + {"Small", 50, 5, 1, 3, 3, 19}, + {"Medium", 500, 100, 7, 13, 10, 90}, + {"Large", 5000, 200, 65, 80, 60, 240}, + {"Small single", 50, 10, 1, 3, 3, 80}, + {"Medium single", 500, 10, 7, 13, 10, 37}, + {"Large 5", 5000, 15, 65, 80, 60, 220}, } log.SetOutput(io.Discard) @@ -3088,12 +3089,12 @@ func BenchmarkLoginPeer_ExistingPeer(b *testing.B) { minMsPerOpCICD float64 maxMsPerOpCICD float64 }{ - {"Small", 50, 5, 102, 110, 102, 120}, - {"Medium", 500, 100, 105, 140, 105, 170}, - {"Large", 5000, 200, 160, 200, 160, 300}, - {"Small single", 50, 10, 102, 110, 102, 120}, - {"Medium single", 500, 10, 105, 140, 105, 170}, - {"Large 5", 5000, 15, 160, 200, 160, 270}, + {"Small", 50, 5, 102, 110, 102, 130}, + {"Medium", 500, 100, 105, 140, 105, 190}, + {"Large", 5000, 200, 160, 200, 160, 320}, + {"Small single", 50, 10, 102, 110, 102, 130}, + {"Medium single", 500, 10, 105, 140, 105, 190}, + {"Large 5", 5000, 15, 160, 200, 160, 290}, } log.SetOutput(io.Discard) diff --git a/management/server/http/handlers/networks/resources_handler.go b/management/server/http/handlers/networks/resources_handler.go index a0dc9a10def..6499bd6521d 100644 --- a/management/server/http/handlers/networks/resources_handler.go +++ b/management/server/http/handlers/networks/resources_handler.go @@ -123,6 +123,7 @@ func (h *resourceHandler) createResource(w http.ResponseWriter, r *http.Request) resource.NetworkID = mux.Vars(r)["networkId"] resource.AccountID = accountID + resource.Enabled = true resource, err = h.resourceManager.CreateResource(r.Context(), userID, resource) if err != nil { util.WriteError(r.Context(), err, w) diff --git a/management/server/http/handlers/networks/routers_handler.go b/management/server/http/handlers/networks/routers_handler.go index 2cf39a1329a..7ca95d902f9 100644 --- a/management/server/http/handlers/networks/routers_handler.go +++ b/management/server/http/handlers/networks/routers_handler.go @@ -85,7 +85,7 @@ func (h *routersHandler) createRouter(w http.ResponseWriter, r *http.Request) { router.NetworkID = networkID router.AccountID = accountID - + router.Enabled = true router, err = h.routersManager.CreateRouter(r.Context(), userID, router) if err != nil { util.WriteError(r.Context(), err, w) diff --git a/management/server/idp/idp.go b/management/server/idp/idp.go index 419220942d4..0f1ff0f1fdd 100644 --- a/management/server/idp/idp.go +++ b/management/server/idp/idp.go @@ -149,6 +149,7 @@ func NewManager(ctx context.Context, config Config, appMetrics telemetry.AppMetr GrantType: config.ClientConfig.GrantType, TokenEndpoint: config.ClientConfig.TokenEndpoint, ManagementEndpoint: config.ExtraConfig["ManagementEndpoint"], + PAT: config.ExtraConfig["PAT"], } } diff --git a/management/server/idp/zitadel.go b/management/server/idp/zitadel.go index 9d7626844a5..3433579271b 100644 --- a/management/server/idp/zitadel.go +++ b/management/server/idp/zitadel.go @@ -34,6 +34,7 @@ type ZitadelClientConfig struct { GrantType string TokenEndpoint string ManagementEndpoint string + PAT string } // ZitadelCredentials zitadel authentication information. @@ -135,6 +136,28 @@ func readZitadelError(body io.ReadCloser) error { return errors.New(strings.Join(errsOut, " ")) } +// verifyJWTConfig ensures necessary values are set in the ZitadelClientConfig for JWTs to be generated. +func verifyJWTConfig(config ZitadelClientConfig) error { + + if config.ClientID == "" { + return fmt.Errorf("zitadel IdP configuration is incomplete, clientID is missing") + } + + if config.ClientSecret == "" { + return fmt.Errorf("zitadel IdP configuration is incomplete, ClientSecret is missing") + } + + if config.TokenEndpoint == "" { + return fmt.Errorf("zitadel IdP configuration is incomplete, TokenEndpoint is missing") + } + + if config.GrantType == "" { + return fmt.Errorf("zitadel IdP configuration is incomplete, GrantType is missing") + } + + return nil +} + // NewZitadelManager creates a new instance of the ZitadelManager. func NewZitadelManager(config ZitadelClientConfig, appMetrics telemetry.AppMetrics) (*ZitadelManager, error) { httpTransport := http.DefaultTransport.(*http.Transport).Clone() @@ -146,26 +169,18 @@ func NewZitadelManager(config ZitadelClientConfig, appMetrics telemetry.AppMetri } helper := JsonParser{} - if config.ClientID == "" { - return nil, fmt.Errorf("zitadel IdP configuration is incomplete, clientID is missing") - } - - if config.ClientSecret == "" { - return nil, fmt.Errorf("zitadel IdP configuration is incomplete, ClientSecret is missing") - } - - if config.TokenEndpoint == "" { - return nil, fmt.Errorf("zitadel IdP configuration is incomplete, TokenEndpoint is missing") + hasPAT := config.PAT != "" + if !hasPAT { + jwtErr := verifyJWTConfig(config) + if jwtErr != nil { + return nil, jwtErr + } } if config.ManagementEndpoint == "" { return nil, fmt.Errorf("zitadel IdP configuration is incomplete, ManagementEndpoint is missing") } - if config.GrantType == "" { - return nil, fmt.Errorf("zitadel IdP configuration is incomplete, GrantType is missing") - } - credentials := &ZitadelCredentials{ clientConfig: config, httpClient: httpClient, @@ -254,6 +269,20 @@ func (zc *ZitadelCredentials) parseRequestJWTResponse(rawBody io.ReadCloser) (JW return jwtToken, nil } +// generatePATToken creates a functional JWTToken instance which will pass the +// PAT to the API directly and skip requesting a token. +func (zc *ZitadelCredentials) generatePATToken() (JWTToken, error) { + tok := JWTToken{ + AccessToken: zc.clientConfig.PAT, + Scope: "openid", + ExpiresIn: 9999, + TokenType: "PAT", + } + tok.expiresInTime = time.Now().Add(time.Duration(tok.ExpiresIn) * time.Second) + zc.jwtToken = tok + return tok, nil +} + // Authenticate retrieves access token to use the Zitadel Management API. func (zc *ZitadelCredentials) Authenticate(ctx context.Context) (JWTToken, error) { zc.mux.Lock() @@ -269,6 +298,10 @@ func (zc *ZitadelCredentials) Authenticate(ctx context.Context) (JWTToken, error return zc.jwtToken, nil } + if zc.clientConfig.PAT != "" { + return zc.generatePATToken() + } + resp, err := zc.requestJWTToken(ctx) if err != nil { return zc.jwtToken, err diff --git a/management/server/peer_test.go b/management/server/peer_test.go index 0c751e6c409..2f5d0e04701 100644 --- a/management/server/peer_test.go +++ b/management/server/peer_test.go @@ -938,7 +938,7 @@ func BenchmarkUpdateAccountPeers(b *testing.B) { {"Small single", 50, 10, 90, 120, 90, 120}, {"Medium single", 500, 10, 110, 170, 120, 200}, {"Large 5", 5000, 15, 1300, 2100, 4900, 7000}, - {"Extra Large", 2000, 2000, 1300, 2400, 3900, 6400}, + {"Extra Large", 2000, 2000, 1300, 2400, 3800, 6400}, } log.SetOutput(io.Discard)