104 lines
3.5 KiB
Go
104 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Invented SIDs: the domain part is made up and names no real domain.
|
|
const (
|
|
testSIDAlice = "S-1-5-21-1000000001-1000000002-1000000003-1101"
|
|
testSIDBob = "S-1-5-21-1000000001-1000000002-1000000003-1102"
|
|
)
|
|
|
|
// One scan asks each SID once, and the account label (winTokenUser) and the
|
|
// owner's domain (ownerFactsReader) share the answer.
|
|
func TestSIDNamesAsksEachSIDOncePerScan(t *testing.T) {
|
|
var calls int32
|
|
lookup := func(sid string) (string, string, error) {
|
|
atomic.AddInt32(&calls, 1)
|
|
switch sid {
|
|
case testSIDAlice:
|
|
return "alice", "EXAMPLECORP", nil
|
|
case testSIDBob:
|
|
return "bob", "EXAMPLECORP", nil
|
|
}
|
|
return "", "", errors.New("none mapped")
|
|
}
|
|
names := newSIDNames(lookup, time.Second, make(chan struct{}, 1))
|
|
for i := 0; i < 40; i++ {
|
|
if got := winAccountLabel(testSIDAlice, names); got != `EXAMPLECORP\alice` {
|
|
t.Fatalf("label = %q", got)
|
|
}
|
|
if got := winAccountDomain(testSIDAlice, names); got != "EXAMPLECORP" {
|
|
t.Fatalf("domain = %q", got)
|
|
}
|
|
winAccountLabel(testSIDBob, names)
|
|
}
|
|
if got := winAccountLabel("S-1-5-21-9-9-9-9", names); got != "S-1-5-21-9-9-9-9" {
|
|
t.Fatalf("an unmapped SID must be shown as itself, got %q", got)
|
|
}
|
|
winAccountDomain("S-1-5-21-9-9-9-9", names)
|
|
if n := atomic.LoadInt32(&calls); n != 3 {
|
|
t.Fatalf("%d lookups for 3 distinct SIDs in one scan", n)
|
|
}
|
|
}
|
|
|
|
// A domain controller that does not answer: the first lookup hangs. The scan
|
|
// must stop waiting at its budget, every later SID must come back unresolved
|
|
// at once (no second lookup started), the owner's class must read unknown,
|
|
// and the next scan must not stack a second blocked lookup behind the first.
|
|
func TestSIDNamesUnreachableDirectoryDegradesWithinBudget(t *testing.T) {
|
|
release := make(chan struct{})
|
|
defer close(release)
|
|
var calls int32
|
|
lookup := func(sid string) (string, string, error) {
|
|
atomic.AddInt32(&calls, 1)
|
|
<-release
|
|
return "", "", errors.New("released")
|
|
}
|
|
slots := make(chan struct{}, 1)
|
|
const budget = 150 * time.Millisecond
|
|
|
|
for scan := 0; scan < 3; scan++ {
|
|
names := newSIDNames(lookup, budget, slots)
|
|
start := time.Now()
|
|
for i := 0; i < 30; i++ {
|
|
sid := fmt.Sprintf("S-1-5-21-1000000001-1000000002-1000000003-%d", 2000+i)
|
|
if got := winAccountLabel(sid, names); got != sid {
|
|
t.Fatalf("scan %d: unresolved SID shown as %q", scan, got)
|
|
}
|
|
o := classifyWinOwner(winOwner{SID: sid, Domain: winAccountDomain(sid, names),
|
|
Computer: "DESKTOP-TEST", Groups: map[string]bool{}, GroupsKnown: true})
|
|
if o.Class != ownerUnknown {
|
|
t.Fatalf("scan %d: class %q for an account whose domain could not be read", scan, o.Class)
|
|
}
|
|
}
|
|
if took := time.Since(start); took > budget+time.Second {
|
|
t.Fatalf("scan %d spent %v on lookups, budget %v", scan, took, budget)
|
|
}
|
|
}
|
|
if n := atomic.LoadInt32(&calls); n != 1 {
|
|
t.Fatalf("%d lookups started while the first was still blocked", n)
|
|
}
|
|
}
|
|
|
|
// A slow but answering lookup is used, and only what it took counts against
|
|
// the budget.
|
|
func TestSIDNamesSlowLookupWithinBudgetIsUsed(t *testing.T) {
|
|
lookup := func(sid string) (string, string, error) {
|
|
time.Sleep(20 * time.Millisecond)
|
|
return "carol", "EXAMPLECORP", nil
|
|
}
|
|
names := newSIDNames(lookup, 2*time.Second, make(chan struct{}, 1))
|
|
for i := 0; i < 5; i++ {
|
|
sid := fmt.Sprintf("S-1-5-21-1000000001-1000000002-1000000003-%d", 3000+i)
|
|
if d := winAccountDomain(sid, names); d != "EXAMPLECORP" {
|
|
t.Fatalf("lookup %d within budget came back %q", i, d)
|
|
}
|
|
}
|
|
}
|