{"record":{"id":"d78a9a6239e5ed1c","repo":"tailscale/tailscale","slug":"w-cannot-logon-as-domain-user-without-being-join","errorCode":null,"errorMessage":"%w: cannot logon as domain user without being joined to a domain","messagePattern":"%w: cannot logon as domain user without being joined to a domain","errorType":"validation","errorClass":"os.ErrInvalid","httpStatus":null,"severity":"error","filePath":"util/winutil/s4u/lsa_windows.go","lineNumber":271,"sourceCode":"// access token for the user if successful. srcName must be non-empty, ASCII,\n// and no more than 8 characters long. If srcName does not meet this criteria,\n// LogonAs will return ErrBadSrcName wrapped with additional information; use\n// errors.Is to check for it. When capLevel == CapCreateProcess, the logon\n// enforces the user's logon hours policy (when present).\nfunc (ls *lsaSession) logonAs(srcName string, u *user.User, capLevel CapabilityLevel) (token windows.Token, err error) {\n\tif ln := len(srcName); ln == 0 || ln > _TOKEN_SOURCE_LENGTH {\n\t\treturn 0, fmt.Errorf(\"%w, actual length is %d\", ErrBadSrcName, ln)\n\t}\n\tif err := checkASCII(srcName); err != nil {\n\t\treturn 0, fmt.Errorf(\"%w: %v\", ErrBadSrcName, err)\n\t}\n\n\tsanitizedUserName, isDomainUser, err := checkDomainAccount(u.Username)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif isDomainUser && !winenv.IsDomainJoined() {\n\t\treturn 0, fmt.Errorf(\"%w: cannot logon as domain user without being joined to a domain\", os.ErrInvalid)\n\t}\n\n\tvar pkgID uint32\n\tvar authInfo unsafe.Pointer\n\tvar authInfoLen uint32\n\tenforceLogonHours := capLevel == CapCreateProcess\n\tif isDomainUser {\n\t\tpkgID, err = authPkgIDKerberos.GetErr(func() (uint32, error) {\n\t\t\treturn ls.getAuthPkgID(_MICROSOFT_KERBEROS_NAME)\n\t\t})\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tupn16, err := samToUPN16(sanitizedUserName)\n\t\tif err != nil {\n\t\t\treturn 0, fmt.Errorf(\"samToUPN16: %w\", err)\n\t\t}","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/util/winutil/s4u/lsa_windows.go#L253-L289","documentation":"logonAs refuses S4U logon for domain-qualified accounts (DOMAIN\\user where the prefix is neither '.' nor the local computer name) when winenv.IsDomainJoined() reports the machine is not domain-joined. Kerberos S4U requires domain membership; the error wraps os.ErrInvalid so callers can errors.Is it.","triggerScenarios":"u.Username like \"CORP\\alice\" on a workgroup machine; the computer was un-joined from the domain after configuration; a local user whose name happens to carry a foreign domain-style prefix.","commonSituations":"Environment drift: test VMs cloned without domain join; machines removed from the domain but still holding domain-format usernames; usernames captured on one host reused on another.","solutions":["Check domain-join state (dsregedit /status, nltest /dsgetdc:) when this error appears","Rejoin the domain, or target the local account by stripping the domain prefix","Branch on errors.Is(err, os.ErrInvalid) and route the user to a configuration fix rather than retrying"],"exampleFix":"// before\nu := &user.User{Username: \"CORP\\\\alice\"}\nsess, err := s4u.Login(logf, srcName, u, s4u.CapCreateProcess)\n\n// after\nif strings.Contains(u.Username, \"\\\\\") && !winenv.IsDomainJoined() {\n    return errors.New(\"domain account on non-domain machine; rejoin domain or use a local account\")\n}\nsess, err := s4u.Login(logf, srcName, u, s4u.CapCreateProcess)","handlingStrategy":"validation","validationCode":"// gate before calling s4u.Login with domain-style names\nif _, _, ok := strings.Cut(u.Username, \"\\\\\"); ok && !winenv.IsDomainJoined() {\n    return errors.New(\"domain account on non-domain machine\")\n}","typeGuard":null,"tryCatchPattern":"sess, err := s4u.Login(logf, srcName, u, capLevel)\nif err != nil {\n    if errors.Is(err, os.ErrInvalid) && strings.Contains(err.Error(), \"joined to a domain\") {\n        return errors.New(\"machine is not domain-joined; rejoin or use a local account\")\n    }\n    return err\n}","preventionTips":["Check domain-join state during deployment validation, not only at runtime","Normalize usernames: strip domain prefixes when targeting local accounts","Treat os.ErrInvalid from this path as configuration, never transient"],"tags":["go","windows","domain-join","kerberos","s4u","account-name"],"backgroundTag":"machine-not-domain-joined","analyzedSha":"6e0912f97994f927632b34ae9e63b53d6516a6ac","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}