tailscale/tailscale · error · os.ErrInvalid
%w: cannot logon as domain user without being joined to a do
Error message
%w: cannot logon as domain user without being joined to a domain
What it means
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.
Source
Thrown at util/winutil/s4u/lsa_windows.go:271
// access token for the user if successful. srcName must be non-empty, ASCII,
// and no more than 8 characters long. If srcName does not meet this criteria,
// LogonAs will return ErrBadSrcName wrapped with additional information; use
// errors.Is to check for it. When capLevel == CapCreateProcess, the logon
// enforces the user's logon hours policy (when present).
func (ls *lsaSession) logonAs(srcName string, u *user.User, capLevel CapabilityLevel) (token windows.Token, err error) {
if ln := len(srcName); ln == 0 || ln > _TOKEN_SOURCE_LENGTH {
return 0, fmt.Errorf("%w, actual length is %d", ErrBadSrcName, ln)
}
if err := checkASCII(srcName); err != nil {
return 0, fmt.Errorf("%w: %v", ErrBadSrcName, err)
}
sanitizedUserName, isDomainUser, err := checkDomainAccount(u.Username)
if err != nil {
return 0, err
}
if isDomainUser && !winenv.IsDomainJoined() {
return 0, fmt.Errorf("%w: cannot logon as domain user without being joined to a domain", os.ErrInvalid)
}
var pkgID uint32
var authInfo unsafe.Pointer
var authInfoLen uint32
enforceLogonHours := capLevel == CapCreateProcess
if isDomainUser {
pkgID, err = authPkgIDKerberos.GetErr(func() (uint32, error) {
return ls.getAuthPkgID(_MICROSOFT_KERBEROS_NAME)
})
if err != nil {
return 0, err
}
upn16, err := samToUPN16(sanitizedUserName)
if err != nil {
return 0, fmt.Errorf("samToUPN16: %w", err)
}View on GitHub (pinned to 6e0912f979)
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
Example fix
// before
u := &user.User{Username: "CORP\\alice"}
sess, err := s4u.Login(logf, srcName, u, s4u.CapCreateProcess)
// after
if strings.Contains(u.Username, "\\") && !winenv.IsDomainJoined() {
return errors.New("domain account on non-domain machine; rejoin domain or use a local account")
}
sess, err := s4u.Login(logf, srcName, u, s4u.CapCreateProcess) Defensive patterns
Strategy: validation
Validate before calling
// gate before calling s4u.Login with domain-style names
if _, _, ok := strings.Cut(u.Username, "\\"); ok && !winenv.IsDomainJoined() {
return errors.New("domain account on non-domain machine")
} Try / catch
sess, err := s4u.Login(logf, srcName, u, capLevel)
if err != nil {
if errors.Is(err, os.ErrInvalid) && strings.Contains(err.Error(), "joined to a domain") {
return errors.New("machine is not domain-joined; rejoin or use a local account")
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- LsaLogonUser(%q): %w, SubStatus: %v
- %q must be ASCII but contains value 0x%02X
- %w, actual length is %d
- %w: %v
- samToUPN16: %w
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/d78a9a6239e5ed1c.
Report an issue: GitHub.