mirror of
https://github.com/golang/go.git
synced 2025-10-19 11:03:18 +00:00
[release-branch.go1.25] os/user: user random name for the test user account
TestImpersonated and TestGroupIdsTestUser are flaky due to sporadic
failures when creating the test user account when running the tests
from different processes at the same time.
This flakiness can be fixed by using a random name for the test user
account.
Fixes #73523
Fixes #74727
Fixes #74728
Fixes #74729
Fixes #74745
Fixes #74751
Cq-Include-Trybots: luci.golang.try:go1.25-windows-amd64-longtest
Change-Id: Ib2283a888437420502b1c11d876c975f5af4bc03
Reviewed-on: https://go-review.googlesource.com/c/go/+/690175
Auto-Submit: Quim Muntal <quimmuntal@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Bypass: Dmitri Shuralyov <dmitshur@golang.org>
(cherry picked from commit 374e3be2eb
)
Reviewed-on: https://go-review.googlesource.com/c/go/+/690555
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
Reviewed-by: Mark Freeman <mark@golang.org>
This commit is contained in:
parent
c95d3093ca
commit
84fb1b8253
1 changed files with 87 additions and 37 deletions
|
@ -7,6 +7,7 @@ package user
|
|||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/syscall/windows"
|
||||
|
@ -16,11 +17,92 @@ import (
|
|||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// addUserAccount creates a local user account.
|
||||
// It returns the name and password of the new account.
|
||||
// Multiple programs or goroutines calling addUserAccount simultaneously will not choose the same directory.
|
||||
func addUserAccount(t *testing.T) (name, password string) {
|
||||
t.TempDir()
|
||||
pattern := t.Name()
|
||||
// Windows limits the user name to 20 characters,
|
||||
// leave space for a 4 digits random suffix.
|
||||
const maxNameLen, suffixLen = 20, 4
|
||||
pattern = pattern[:min(len(pattern), maxNameLen-suffixLen)]
|
||||
// Drop unusual characters from the account name.
|
||||
mapper := func(r rune) rune {
|
||||
if r < utf8.RuneSelf {
|
||||
if '0' <= r && r <= '9' ||
|
||||
'a' <= r && r <= 'z' ||
|
||||
'A' <= r && r <= 'Z' {
|
||||
return r
|
||||
}
|
||||
} else if unicode.IsLetter(r) || unicode.IsNumber(r) {
|
||||
return r
|
||||
}
|
||||
return -1
|
||||
}
|
||||
pattern = strings.Map(mapper, pattern)
|
||||
|
||||
// Generate a long random password.
|
||||
var pwd [33]byte
|
||||
rand.Read(pwd[:])
|
||||
// Add special chars to ensure it satisfies password requirements.
|
||||
password = base64.StdEncoding.EncodeToString(pwd[:]) + "_-As@!%*(1)4#2"
|
||||
password16, err := syscall.UTF16PtrFromString(password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
try := 0
|
||||
for {
|
||||
// Calculate a random suffix to append to the user name.
|
||||
var suffix [2]byte
|
||||
rand.Read(suffix[:])
|
||||
suffixStr := strconv.FormatUint(uint64(binary.LittleEndian.Uint16(suffix[:])), 10)
|
||||
name := pattern + suffixStr[:min(len(suffixStr), suffixLen)]
|
||||
name16, err := syscall.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Create user.
|
||||
userInfo := windows.UserInfo1{
|
||||
Name: name16,
|
||||
Password: password16,
|
||||
Priv: windows.USER_PRIV_USER,
|
||||
}
|
||||
err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
|
||||
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
|
||||
t.Skip("skipping test; don't have permission to create user")
|
||||
}
|
||||
// If the user already exists, try again with a different name.
|
||||
if errors.Is(err, windows.NERR_UserExists) {
|
||||
if try++; try < 1000 {
|
||||
t.Log("user already exists, trying again with a different name")
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("NetUserAdd failed: %v", err)
|
||||
}
|
||||
// Delete the user when the test is done.
|
||||
t.Cleanup(func() {
|
||||
if err := windows.NetUserDel(nil, name16); err != nil {
|
||||
if !errors.Is(err, windows.NERR_UserNotFound) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
return name, password
|
||||
}
|
||||
}
|
||||
|
||||
// windowsTestAccount creates a test user and returns a token for that user.
|
||||
// If the user already exists, it will be deleted and recreated.
|
||||
// The caller is responsible for closing the token.
|
||||
|
@ -32,47 +114,15 @@ func windowsTestAccount(t *testing.T) (syscall.Token, *User) {
|
|||
// See https://dev.go/issue/70396.
|
||||
t.Skip("skipping non-hermetic test outside of Go builders")
|
||||
}
|
||||
const testUserName = "GoStdTestUser01"
|
||||
var password [33]byte
|
||||
rand.Read(password[:])
|
||||
// Add special chars to ensure it satisfies password requirements.
|
||||
pwd := base64.StdEncoding.EncodeToString(password[:]) + "_-As@!%*(1)4#2"
|
||||
name, err := syscall.UTF16PtrFromString(testUserName)
|
||||
name, password := addUserAccount(t)
|
||||
name16, err := syscall.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pwd16, err := syscall.UTF16PtrFromString(pwd)
|
||||
pwd16, err := syscall.UTF16PtrFromString(password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
userInfo := windows.UserInfo1{
|
||||
Name: name,
|
||||
Password: pwd16,
|
||||
Priv: windows.USER_PRIV_USER,
|
||||
}
|
||||
// Create user.
|
||||
err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
|
||||
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
|
||||
t.Skip("skipping test; don't have permission to create user")
|
||||
}
|
||||
if errors.Is(err, windows.NERR_UserExists) {
|
||||
// User already exists, delete and recreate.
|
||||
if err = windows.NetUserDel(nil, name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err = windows.NetUserDel(nil, name); err != nil {
|
||||
if !errors.Is(err, windows.NERR_UserNotFound) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
domain, err := syscall.UTF16PtrFromString(".")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -80,13 +130,13 @@ func windowsTestAccount(t *testing.T) (syscall.Token, *User) {
|
|||
const LOGON32_PROVIDER_DEFAULT = 0
|
||||
const LOGON32_LOGON_INTERACTIVE = 2
|
||||
var token syscall.Token
|
||||
if err = windows.LogonUser(name, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
|
||||
if err = windows.LogonUser(name16, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
token.Close()
|
||||
})
|
||||
usr, err := Lookup(testUserName)
|
||||
usr, err := Lookup(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue