//go:build windows package authconfig import ( "errors" "fmt" "path/filepath" "runtime" "sync" "testing" "github.com/aritmolab/thothii/tools/tht/internal/safeio" "golang.org/x/sys/windows" ) func TestLoadRejectsPermissiveWindowsDirectoryAuthAndUsersDACLs(t *testing.T) { for name, makePermissive := range map[string]func(t *testing.T, directory string){ "directory": func(t *testing.T, directory string) { setPermissiveAuthDACL(t, directory) }, "auth file": func(t *testing.T, directory string) { setPermissiveAuthDACL(t, filepath.Join(directory, "auth.yaml")) }, "users file": func(t *testing.T, directory string) { setPermissiveAuthDACL(t, filepath.Join(directory, "users.yaml")) }, } { t.Run(name, func(t *testing.T) { directory := writePrivateWindowsAuthFiles(t) makePermissive(t, directory) if _, _, err := Load(directory); !errors.Is(err, safeio.ErrUnsafeFile) { t.Fatalf("Load() error = %v, want ErrUnsafeFile", err) } }) } } func TestMutateUsersCreatesAndRejectsPermissiveWindowsLockDACL(t *testing.T) { directory := writePrivateWindowsAuthFiles(t) if err := MutateUsers(directory, func(*Registry) error { return nil }); err != nil { t.Fatal(err) } lockPath := filepath.Join(directory, lockFileName) if err := safeio.ValidatePrivateRegular(lockPath); err != nil { t.Fatalf("lock privacy error = %v", err) } setPermissiveAuthDACL(t, lockPath) if err := MutateUsers(directory, func(*Registry) error { return nil }); !errors.Is(err, safeio.ErrUnsafeFile) { t.Fatalf("MutateUsers() error = %v, want ErrUnsafeFile", err) } } func TestMutateUsersConcurrentWindowsLockCreationNeverObservesDefaultDACL(t *testing.T) { users := adminUserYAML("admin", "Admin", true, "admin") for index := 0; index < 32; index++ { users += userYAML(index) } directory := writePrivateWindowsAuthFiles(t, registryYAML(users)) var group sync.WaitGroup var ready sync.WaitGroup start := make(chan struct{}) errors := make(chan error, 32) for index := 0; index < 32; index++ { index := index group.Add(1) ready.Add(1) go func() { defer group.Done() ready.Done() <-start errors <- MutateUsers(directory, func(registry *Registry) error { registry.Users[index+1].DisplayName = fmt.Sprintf("Windows Updated %d", index) return nil }) }() } ready.Wait() close(start) group.Wait() close(errors) for err := range errors { if err != nil { t.Fatalf("MutateUsers() concurrent lock creation error = %v", err) } } if err := safeio.ValidatePrivateRegular(filepath.Join(directory, lockFileName)); err != nil { t.Fatalf("concurrently created lock DACL error = %v", err) } } func writePrivateWindowsAuthFiles(t *testing.T, users ...string) string { t.Helper() registry := registryYAML(adminUserYAML("admin", "Admin", true, "admin")) if len(users) > 0 { registry = users[0] } directory := writeAuthFiles(t, defaultAuthYAML, registry) if err := safeio.ProtectPrivateDirectory(directory); err != nil { t.Fatal(err) } for _, name := range []string{"auth.yaml", "users.yaml"} { if err := safeio.ProtectPrivateRegular(filepath.Join(directory, name)); err != nil { t.Fatal(err) } } return directory } func setPermissiveAuthDACL(t *testing.T, path string) { t.Helper() world, err := windows.StringToSid("S-1-1-0") if err != nil { t.Fatal(err) } var pinner runtime.Pinner pinner.Pin(world) defer pinner.Unpin() acl, err := windows.ACLFromEntries([]windows.EXPLICIT_ACCESS{{ AccessPermissions: windows.GENERIC_READ | windows.GENERIC_WRITE, AccessMode: windows.GRANT_ACCESS, Trustee: windows.TRUSTEE{ TrusteeForm: windows.TRUSTEE_IS_SID, TrusteeType: windows.TRUSTEE_IS_GROUP, TrusteeValue: windows.TrusteeValueFromSID(world), }, }}, nil) if err != nil { t.Fatal(err) } if err := windows.SetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, nil, nil, acl, nil); err != nil { t.Fatal(err) } }