Self-hosted: /api/setup with create_pat: true creates an account with an empty domain, breaking single-account mode #7202
Unanswered
alex-allani
asked this question in
Issue Triage
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
On a brand new setup, I've created a main account and then setup google idp. However, when I connect with any of the google account (with custom domain), it creates a new tenant instead of having a single tenant. I tried with two google accounts from my workspace, and each of them creates a tenant.
I've seen that if I set the owner email and password inside the config.yaml, and then setup manually the idp, it doesn't create one account / user. But this removes the possibility to setup the first user programatically and the IdP as shown here: https://docs.netbird.io/selfhosted/automated-setup
Do you know what can cause that?
Also I've tried to check with claude if there was some issues, here are the outputs:
Version: v0.76.3, combined
netbird-serverbinary, embedded Dex IdP, Postgres storeWhat happens
Bootstrap a fresh instance via
POST /api/setup(create_pat: true,NB_SETUP_PAT_ENABLED=true) rather than theauth.owner:config key, add an external IdP, then log in with two Google users — each gets its own account/tenant instead of joining the existing one.select domain, domain_category, is_domain_primary_account from accountsshows the setup-created account as('', '', false). Bootstrapping withauth.owner:instead (so the first account is created by the first login) gives('netbird.selfhosted', 'private', true)and everyone shares one account.Why
Dex JWTs carry no
wt_account_domain*claims (shared/auth/jwt/extractor.go:104-112), so tenant grouping depends entirely on single-account mode, auto-enabled byResolveDomains(management/internals/server/server.go:411).SetupService.SetupOwnerprovisions the account by callingGetAccountIDByUserIDdirectly with a bareauth.UserAuth{UserId, Email, Name}(management/server/instance/setup_service.go:114), bypassingGetAccountIDFromUserAuthand thus single-account mode → the account is stored withdomain=''.updateUserAuthWithSingleMode(management/server/account.go:1573) then overwrites the configured domain with that empty one at line 1595.isDomainValid("")fails, andgetAccountIDWithAuthorizationClaimsfalls through to "create a new account" for every unknownsub.It is self-latching:
singleAccountModeis computed once at boot asaccountsCounter <= 1(management/server/account.go:242), so once a second account exists and the server restarts, single-account mode is off permanently.Note the account is only created at setup time because a PAT was requested — without
create_pat,SetupOwnercreates just the Dex user and the account is created correctly on first login.Suggested fix
Apply the single-account-mode domain when
SetupOwnerprovisions the account, and don't letupdateUserAuthWithSingleModereplace a validsingleAccountModeDomainwith an empty stored domain.Workaround
Before the first external-IdP login:
netbird.selfhostedspecifically —ResolveDomainsalso derivesdnsDomainfrom this value, so a real domain would change every peer's DNS suffix.Repro test (drop into
management/server/, uses the existingcreateManagerhelper)Output on v0.76.3:
The second test asserts the current (buggy) behaviour so it passes today — flipping the
NotEquals toEqualturns it into a failing regression test.All reactions