Add local Windows Server inventory collection and validation; include core package
This commit is contained in:
213
tests/fixtures/windows/server-2016.json
vendored
Normal file
213
tests/fixtures/windows/server-2016.json
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"synthetic": true,
|
||||
"collected_at": "2026-10-01T12:00:00Z",
|
||||
"server": {
|
||||
"hostname": "fixture-win2016.example.test",
|
||||
"operating_system": "Microsoft Windows Server 2016 Standard (synthetic)",
|
||||
"os_build": "14393",
|
||||
"last_boot": "2026-09-25T06:30:00Z",
|
||||
"ip_addresses": [
|
||||
"192.0.2.20"
|
||||
],
|
||||
"network_adapters": [
|
||||
"Synthetic Ethernet adapter"
|
||||
],
|
||||
"domain": "example.test",
|
||||
"platform": "windows",
|
||||
"os_version": "2016",
|
||||
"kernel_version": "10.0.14393",
|
||||
"product_type": 3,
|
||||
"domain_joined": true,
|
||||
"collection_source": "Synthetic Windows fixture"
|
||||
},
|
||||
"checks": {
|
||||
"system": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"roles": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"services": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"tasks": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"software": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"storage": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"shares": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"certificates": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
}
|
||||
},
|
||||
"inventory": {
|
||||
"roles": [
|
||||
{
|
||||
"name": "Web-Server",
|
||||
"state": "Installed"
|
||||
},
|
||||
{
|
||||
"name": "File-Services",
|
||||
"state": "Installed"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"name": "W3SVC",
|
||||
"display_name": "World Wide Web Publishing Service",
|
||||
"state": "Running",
|
||||
"startup": "Automatic",
|
||||
"account": "LocalSystem",
|
||||
"pid": 123
|
||||
}
|
||||
],
|
||||
"network": [
|
||||
{
|
||||
"state": "Listen",
|
||||
"local_address": "0.0.0.0",
|
||||
"local_port": 443,
|
||||
"remote_address": "",
|
||||
"remote_port": 0,
|
||||
"pid": 4,
|
||||
"process": "System",
|
||||
"direction": "Unknown"
|
||||
},
|
||||
{
|
||||
"state": "Established",
|
||||
"local_address": "192.0.2.20",
|
||||
"local_port": 443,
|
||||
"remote_address": "192.0.2.45",
|
||||
"remote_port": 52104,
|
||||
"pid": 4,
|
||||
"process": "System",
|
||||
"direction": "Unknown"
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"name": "Nightly export",
|
||||
"path": "\\Operations\\",
|
||||
"enabled": true,
|
||||
"account": "EXAMPLE\\svc_export",
|
||||
"triggers": "MSFT_TaskDailyTrigger; start=2026-10-01T02:00:00; daysInterval=1",
|
||||
"executables": "export.exe",
|
||||
"state": "Ready"
|
||||
}
|
||||
],
|
||||
"software": [
|
||||
{
|
||||
"name": "Example Reporting Service",
|
||||
"publisher": "Example Software",
|
||||
"version": "4.2",
|
||||
"installation_date": "2024-06-12"
|
||||
}
|
||||
],
|
||||
"storage": [
|
||||
{
|
||||
"type": "Volume",
|
||||
"name": "C:",
|
||||
"path": "C:\\",
|
||||
"capacity_gib": 160,
|
||||
"free_gib": 72
|
||||
},
|
||||
{
|
||||
"type": "SMB Share",
|
||||
"name": "Reports",
|
||||
"path": "D:\\Reports",
|
||||
"capacity_gib": null,
|
||||
"free_gib": null
|
||||
}
|
||||
],
|
||||
"certificates": [
|
||||
{
|
||||
"subject": "CN=demo-web-01.example.test",
|
||||
"issuer": "CN=Example Test CA",
|
||||
"expires": "2027-06-01T00:00:00Z",
|
||||
"thumbprint": "0123456789ABCDEF0123456789ABCDEF01234567",
|
||||
"store": "My"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
213
tests/fixtures/windows/server-2019.json
vendored
Normal file
213
tests/fixtures/windows/server-2019.json
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"synthetic": true,
|
||||
"collected_at": "2026-10-01T12:00:00Z",
|
||||
"server": {
|
||||
"hostname": "fixture-win2019.example.test",
|
||||
"operating_system": "Microsoft Windows Server 2019 Standard (synthetic)",
|
||||
"os_build": "17763",
|
||||
"last_boot": "2026-09-25T06:30:00Z",
|
||||
"ip_addresses": [
|
||||
"192.0.2.20"
|
||||
],
|
||||
"network_adapters": [
|
||||
"Synthetic Ethernet adapter"
|
||||
],
|
||||
"domain": "example.test",
|
||||
"platform": "windows",
|
||||
"os_version": "2019",
|
||||
"kernel_version": "10.0.17763",
|
||||
"product_type": 3,
|
||||
"domain_joined": true,
|
||||
"collection_source": "Synthetic Windows fixture"
|
||||
},
|
||||
"checks": {
|
||||
"system": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"roles": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"services": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"tasks": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"software": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"storage": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"shares": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"certificates": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
}
|
||||
},
|
||||
"inventory": {
|
||||
"roles": [
|
||||
{
|
||||
"name": "Web-Server",
|
||||
"state": "Installed"
|
||||
},
|
||||
{
|
||||
"name": "File-Services",
|
||||
"state": "Installed"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"name": "W3SVC",
|
||||
"display_name": "World Wide Web Publishing Service",
|
||||
"state": "Running",
|
||||
"startup": "Automatic",
|
||||
"account": "LocalSystem",
|
||||
"pid": 123
|
||||
}
|
||||
],
|
||||
"network": [
|
||||
{
|
||||
"state": "Listen",
|
||||
"local_address": "0.0.0.0",
|
||||
"local_port": 443,
|
||||
"remote_address": "",
|
||||
"remote_port": 0,
|
||||
"pid": 4,
|
||||
"process": "System",
|
||||
"direction": "Unknown"
|
||||
},
|
||||
{
|
||||
"state": "Established",
|
||||
"local_address": "192.0.2.20",
|
||||
"local_port": 443,
|
||||
"remote_address": "192.0.2.45",
|
||||
"remote_port": 52104,
|
||||
"pid": 4,
|
||||
"process": "System",
|
||||
"direction": "Unknown"
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"name": "Nightly export",
|
||||
"path": "\\Operations\\",
|
||||
"enabled": true,
|
||||
"account": "EXAMPLE\\svc_export",
|
||||
"triggers": "MSFT_TaskDailyTrigger; start=2026-10-01T02:00:00; daysInterval=1",
|
||||
"executables": "export.exe",
|
||||
"state": "Ready"
|
||||
}
|
||||
],
|
||||
"software": [
|
||||
{
|
||||
"name": "Example Reporting Service",
|
||||
"publisher": "Example Software",
|
||||
"version": "4.2",
|
||||
"installation_date": "2024-06-12"
|
||||
}
|
||||
],
|
||||
"storage": [
|
||||
{
|
||||
"type": "Volume",
|
||||
"name": "C:",
|
||||
"path": "C:\\",
|
||||
"capacity_gib": 160,
|
||||
"free_gib": 72
|
||||
},
|
||||
{
|
||||
"type": "SMB Share",
|
||||
"name": "Reports",
|
||||
"path": "D:\\Reports",
|
||||
"capacity_gib": null,
|
||||
"free_gib": null
|
||||
}
|
||||
],
|
||||
"certificates": [
|
||||
{
|
||||
"subject": "CN=demo-web-01.example.test",
|
||||
"issuer": "CN=Example Test CA",
|
||||
"expires": "2027-06-01T00:00:00Z",
|
||||
"thumbprint": "0123456789ABCDEF0123456789ABCDEF01234567",
|
||||
"store": "My"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
213
tests/fixtures/windows/server-2022.json
vendored
Normal file
213
tests/fixtures/windows/server-2022.json
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"synthetic": true,
|
||||
"collected_at": "2026-10-01T12:00:00Z",
|
||||
"server": {
|
||||
"hostname": "fixture-win2022.example.test",
|
||||
"operating_system": "Microsoft Windows Server 2022 Standard (synthetic)",
|
||||
"os_build": "20348",
|
||||
"last_boot": "2026-09-25T06:30:00Z",
|
||||
"ip_addresses": [
|
||||
"192.0.2.20"
|
||||
],
|
||||
"network_adapters": [
|
||||
"Synthetic Ethernet adapter"
|
||||
],
|
||||
"domain": "example.test",
|
||||
"platform": "windows",
|
||||
"os_version": "2022",
|
||||
"kernel_version": "10.0.20348",
|
||||
"product_type": 3,
|
||||
"domain_joined": true,
|
||||
"collection_source": "Synthetic Windows fixture"
|
||||
},
|
||||
"checks": {
|
||||
"system": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"roles": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"services": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"tasks": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"software": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"storage": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"shares": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"certificates": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
}
|
||||
},
|
||||
"inventory": {
|
||||
"roles": [
|
||||
{
|
||||
"name": "Web-Server",
|
||||
"state": "Installed"
|
||||
},
|
||||
{
|
||||
"name": "File-Services",
|
||||
"state": "Installed"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"name": "W3SVC",
|
||||
"display_name": "World Wide Web Publishing Service",
|
||||
"state": "Running",
|
||||
"startup": "Automatic",
|
||||
"account": "LocalSystem",
|
||||
"pid": 123
|
||||
}
|
||||
],
|
||||
"network": [
|
||||
{
|
||||
"state": "Listen",
|
||||
"local_address": "0.0.0.0",
|
||||
"local_port": 443,
|
||||
"remote_address": "",
|
||||
"remote_port": 0,
|
||||
"pid": 4,
|
||||
"process": "System",
|
||||
"direction": "Unknown"
|
||||
},
|
||||
{
|
||||
"state": "Established",
|
||||
"local_address": "192.0.2.20",
|
||||
"local_port": 443,
|
||||
"remote_address": "192.0.2.45",
|
||||
"remote_port": 52104,
|
||||
"pid": 4,
|
||||
"process": "System",
|
||||
"direction": "Unknown"
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"name": "Nightly export",
|
||||
"path": "\\Operations\\",
|
||||
"enabled": true,
|
||||
"account": "EXAMPLE\\svc_export",
|
||||
"triggers": "MSFT_TaskDailyTrigger; start=2026-10-01T02:00:00; daysInterval=1",
|
||||
"executables": "export.exe",
|
||||
"state": "Ready"
|
||||
}
|
||||
],
|
||||
"software": [
|
||||
{
|
||||
"name": "Example Reporting Service",
|
||||
"publisher": "Example Software",
|
||||
"version": "4.2",
|
||||
"installation_date": "2024-06-12"
|
||||
}
|
||||
],
|
||||
"storage": [
|
||||
{
|
||||
"type": "Volume",
|
||||
"name": "C:",
|
||||
"path": "C:\\",
|
||||
"capacity_gib": 160,
|
||||
"free_gib": 72
|
||||
},
|
||||
{
|
||||
"type": "SMB Share",
|
||||
"name": "Reports",
|
||||
"path": "D:\\Reports",
|
||||
"capacity_gib": null,
|
||||
"free_gib": null
|
||||
}
|
||||
],
|
||||
"certificates": [
|
||||
{
|
||||
"subject": "CN=demo-web-01.example.test",
|
||||
"issuer": "CN=Example Test CA",
|
||||
"expires": "2027-06-01T00:00:00Z",
|
||||
"thumbprint": "0123456789ABCDEF0123456789ABCDEF01234567",
|
||||
"store": "My"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
213
tests/fixtures/windows/server-2025.json
vendored
Normal file
213
tests/fixtures/windows/server-2025.json
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"synthetic": true,
|
||||
"collected_at": "2026-10-01T12:00:00Z",
|
||||
"server": {
|
||||
"hostname": "fixture-win2025.example.test",
|
||||
"operating_system": "Microsoft Windows Server 2025 Standard (synthetic)",
|
||||
"os_build": "26100",
|
||||
"last_boot": "2026-09-25T06:30:00Z",
|
||||
"ip_addresses": [
|
||||
"192.0.2.20"
|
||||
],
|
||||
"network_adapters": [
|
||||
"Synthetic Ethernet adapter"
|
||||
],
|
||||
"domain": "example.test",
|
||||
"platform": "windows",
|
||||
"os_version": "2025",
|
||||
"kernel_version": "10.0.26100",
|
||||
"product_type": 3,
|
||||
"domain_joined": true,
|
||||
"collection_source": "Synthetic Windows fixture"
|
||||
},
|
||||
"checks": {
|
||||
"system": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"roles": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"services": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"tasks": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"software": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"storage": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"shares": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
},
|
||||
"certificates": {
|
||||
"status": "complete",
|
||||
"message": "Synthetic contract fixture; no Windows collection performed.",
|
||||
"started_at": "2026-10-01T12:00:00Z",
|
||||
"completed_at": "2026-10-01T12:00:01Z",
|
||||
"errors": [],
|
||||
"permission_limitations": [],
|
||||
"limitations": [
|
||||
"Synthetic fixture only."
|
||||
]
|
||||
}
|
||||
},
|
||||
"inventory": {
|
||||
"roles": [
|
||||
{
|
||||
"name": "Web-Server",
|
||||
"state": "Installed"
|
||||
},
|
||||
{
|
||||
"name": "File-Services",
|
||||
"state": "Installed"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"name": "W3SVC",
|
||||
"display_name": "World Wide Web Publishing Service",
|
||||
"state": "Running",
|
||||
"startup": "Automatic",
|
||||
"account": "LocalSystem",
|
||||
"pid": 123
|
||||
}
|
||||
],
|
||||
"network": [
|
||||
{
|
||||
"state": "Listen",
|
||||
"local_address": "0.0.0.0",
|
||||
"local_port": 443,
|
||||
"remote_address": "",
|
||||
"remote_port": 0,
|
||||
"pid": 4,
|
||||
"process": "System",
|
||||
"direction": "Unknown"
|
||||
},
|
||||
{
|
||||
"state": "Established",
|
||||
"local_address": "192.0.2.20",
|
||||
"local_port": 443,
|
||||
"remote_address": "192.0.2.45",
|
||||
"remote_port": 52104,
|
||||
"pid": 4,
|
||||
"process": "System",
|
||||
"direction": "Unknown"
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"name": "Nightly export",
|
||||
"path": "\\Operations\\",
|
||||
"enabled": true,
|
||||
"account": "EXAMPLE\\svc_export",
|
||||
"triggers": "MSFT_TaskDailyTrigger; start=2026-10-01T02:00:00; daysInterval=1",
|
||||
"executables": "export.exe",
|
||||
"state": "Ready"
|
||||
}
|
||||
],
|
||||
"software": [
|
||||
{
|
||||
"name": "Example Reporting Service",
|
||||
"publisher": "Example Software",
|
||||
"version": "4.2",
|
||||
"installation_date": "2024-06-12"
|
||||
}
|
||||
],
|
||||
"storage": [
|
||||
{
|
||||
"type": "Volume",
|
||||
"name": "C:",
|
||||
"path": "C:\\",
|
||||
"capacity_gib": 160,
|
||||
"free_gib": 72
|
||||
},
|
||||
{
|
||||
"type": "SMB Share",
|
||||
"name": "Reports",
|
||||
"path": "D:\\Reports",
|
||||
"capacity_gib": null,
|
||||
"free_gib": null
|
||||
}
|
||||
],
|
||||
"certificates": [
|
||||
{
|
||||
"subject": "CN=demo-web-01.example.test",
|
||||
"issuer": "CN=Example Test CA",
|
||||
"expires": "2027-06-01T00:00:00Z",
|
||||
"thumbprint": "0123456789ABCDEF0123456789ABCDEF01234567",
|
||||
"store": "My"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
78
tests/powershell/Test-Collectors.ps1
Normal file
78
tests/powershell/Test-Collectors.ps1
Normal file
@@ -0,0 +1,78 @@
|
||||
#requires -Version 5.1
|
||||
# AST and synthetic collector contract tests only. Does not query any real server.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
$files = @(Get-ChildItem -LiteralPath (Join-Path $root 'powershell') -Filter '*.ps1')
|
||||
foreach ($file in $files) {
|
||||
$tokens = $null; $parseErrors = $null
|
||||
$null = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$tokens, [ref]$parseErrors)
|
||||
if ($parseErrors.Count) { throw ('PowerShell syntax invalid: ' + $file.Name) }
|
||||
}
|
||||
foreach ($file in @($files | Where-Object { $_.Name -ne 'Get-ServerInventory.ps1' })) { . $file.FullName }
|
||||
$script:SamsaraIsElevated = $true
|
||||
$script:SyntheticBuild = '20348'
|
||||
function Get-CimInstance {
|
||||
[CmdletBinding()] param($ClassName, $Property, $Filter)
|
||||
switch ($ClassName) {
|
||||
'Win32_OperatingSystem' { [pscustomobject]@{ Caption='Synthetic Windows Server'; Version=('10.0.' + $script:SyntheticBuild); BuildNumber=$script:SyntheticBuild; ProductType=3; LastBootUpTime=[DateTime]::UtcNow.AddDays(-2) } }
|
||||
'Win32_ComputerSystem' { [pscustomobject]@{ Domain='example.test'; PartOfDomain=$true } }
|
||||
'Win32_NetworkAdapterConfiguration' { [pscustomobject]@{ Description='Synthetic NIC'; IPAddress=@('192.0.2.20') } }
|
||||
'Win32_Service' { [pscustomobject]@{ Name='W3SVC'; DisplayName='Synthetic Web'; State='Running'; StartMode='Auto'; StartName='EXAMPLE\svc_web'; ProcessId=123; PathName='SECRET command arguments' } }
|
||||
default { throw 'Unexpected synthetic CIM query' }
|
||||
}
|
||||
}
|
||||
function Get-WindowsFeature { [CmdletBinding()] param() [pscustomobject]@{ Name='Web-Server'; DisplayName='Synthetic Web Server'; Installed=$true; InstallState='Installed'; FeatureType='Role' } }
|
||||
function Get-Process { [CmdletBinding()] param() [pscustomobject]@{ Id=123; ProcessName='SyntheticWorker'; CommandLine='SECRET' } }
|
||||
function Get-NetTCPConnection { [CmdletBinding()] param() [pscustomobject]@{ State='Established'; LocalAddress='192.0.2.20'; LocalPort=443; RemoteAddress='192.0.2.21'; RemotePort=50000; OwningProcess=123 } }
|
||||
function Get-ScheduledTask {
|
||||
[CmdletBinding()] param()
|
||||
[pscustomobject]@{ TaskName='Synthetic report'; TaskPath='\Example\'; State='Ready'
|
||||
Settings=[pscustomobject]@{ Enabled=$true }; Principal=[pscustomobject]@{ UserId='EXAMPLE\svc_report' }
|
||||
Actions=@([pscustomobject]@{ Execute='C:\Tools\report.exe'; Arguments='--password=SECRET' })
|
||||
Triggers=@([pscustomobject]@{ CimClass=[pscustomobject]@{ CimClassName='MSFT_TaskDailyTrigger' }; StartBoundary='2026-10-01T02:00:00'; Enabled=$true; DaysInterval=1; Repetition=$null }) }
|
||||
}
|
||||
function Get-Volume { [CmdletBinding()] param() [pscustomobject]@{ FileSystemLabel='Synthetic OS'; DriveLetter='C'; Path='C:\'; FileSystem='NTFS'; Size=100GB; SizeRemaining=50GB } }
|
||||
function Get-SmbShare { [CmdletBinding()] param() [pscustomobject]@{ Name='SyntheticReports'; Path='C:\Reports'; Special=$false } }
|
||||
function Get-ChildItem {
|
||||
[CmdletBinding()] param($LiteralPath)
|
||||
if ($LiteralPath -eq 'Cert:\LocalMachine') { [pscustomobject]@{ PSPath='Cert:\LocalMachine\My'; PSChildName='My' } }
|
||||
else { [pscustomobject]@{ Subject='CN=synthetic.example.test'; Issuer='CN=Synthetic CA'; NotAfter=[DateTime]::UtcNow.AddDays(30); Thumbprint='0000000000000000000000000000000000000000'; PrivateKey='SECRET' } }
|
||||
}
|
||||
function Assert-True { param($Value, [string]$Message) if (-not $Value) { throw $Message } }
|
||||
$collectors = [ordered]@{
|
||||
system={param($r) Get-SamsaraSystem $r}; roles={param($r) Get-SamsaraRoles $r}
|
||||
services={param($r) Get-SamsaraServices $r}; network={param($r) Get-SamsaraNetwork $r}
|
||||
tasks={param($r) Get-SamsaraTasks $r}; storage={param($r) Get-SamsaraStorage $r}
|
||||
shares={param($r) Get-SamsaraShares $r}; certificates={param($r) Get-SamsaraCertificates $r}
|
||||
}
|
||||
foreach ($name in $collectors.Keys) {
|
||||
$section = Invoke-SamsaraSection $name $collectors[$name]
|
||||
Assert-True ($section.status -eq 'complete') ('Synthetic section failed: ' + $name)
|
||||
Assert-True ($section.started_at -and $section.completed_at) 'Missing timestamps'
|
||||
Assert-True (($section | ConvertTo-Json -Depth 12) -notmatch 'SECRET') 'Secret field serialized'
|
||||
if ($name -eq 'network') { Assert-True ($section.data[0].process -eq 'SyntheticWorker') 'Process correlation failed' }
|
||||
if ($name -eq 'tasks') { Assert-True ($section.data[0].executables -eq 'report.exe') 'Executable basename missing' }
|
||||
}
|
||||
foreach ($build in @('14393','17763','20348','26100')) {
|
||||
$script:SyntheticBuild=$build
|
||||
$section=Invoke-SamsaraSection 'system' {param($r) Get-SamsaraSystem $r}
|
||||
Assert-True ($section.status -eq 'complete') 'Supported build mapping failed'
|
||||
}
|
||||
# Test registry projection with in-memory objects; never open a real registry hive.
|
||||
$fakeEntry = New-Object PSObject
|
||||
$fakeEntry | Add-Member ScriptMethod GetValue { param($key) switch ($key) { 'DisplayName' {'Synthetic application'}; 'DisplayVersion' {'1.0'}; 'Publisher' {'Example'}; 'InstallDate' {'20260101'}; default {throw 'Unexpected registry value requested'} } }
|
||||
$fakeEntry | Add-Member ScriptMethod Dispose { }
|
||||
$fakeUninstall = New-Object PSObject
|
||||
$fakeUninstall | Add-Member ScriptMethod GetSubKeyNames { @('Example') }
|
||||
$fakeUninstall | Add-Member ScriptMethod OpenSubKey { param($name,$writable) if ($writable) {throw 'Writable registry access'}; return $fakeEntry }
|
||||
$section=Invoke-SamsaraSection 'software' {param($r) Invoke-SamsaraRead $r 'Synthetic registry view' { Read-SamsaraSoftwareEntries $r $fakeUninstall 'Registry64' } }
|
||||
Assert-True ($section.status -eq 'complete' -and $section.data[0].name -eq 'Synthetic application') 'Registry metadata projection failed'
|
||||
function Get-SmbShare { [CmdletBinding()] param() throw [UnauthorizedAccessException]::new('SECRET protected details') }
|
||||
$section=Invoke-SamsaraSection 'shares' {param($r) Get-SamsaraShares $r}
|
||||
Assert-True ($section.status -eq 'failed') 'Denied collection was not failed'
|
||||
Assert-True ($section.errors[0].code -eq 'access_denied') 'Permission error not categorized'
|
||||
Assert-True (($section | ConvertTo-Json -Depth 12) -notmatch 'SECRET') 'Raw error exposed'
|
||||
$script:SamsaraIsElevated=$false
|
||||
$section=Invoke-SamsaraSection 'services' {param($r) Get-SamsaraServices $r}
|
||||
Assert-True ($section.status -eq 'partial' -and $section.permission_limitations.Count -gt 0) 'Non-elevated scope not marked partial'
|
||||
Write-Output 'PowerShell AST and synthetic collector tests passed. No real Windows inventory was collected.'
|
||||
16
tests/test_powershell.py
Normal file
16
tests/test_powershell.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
|
||||
class PowerShellTests(unittest.TestCase):
|
||||
def test_native_ast_and_synthetic_collectors(self):
|
||||
executable = shutil.which('powershell.exe') if os.name == 'nt' else shutil.which('pwsh')
|
||||
if not executable:
|
||||
self.skipTest('No PowerShell runtime available; native AST/collector tests require Windows PowerShell 5.1 or pwsh.')
|
||||
script=Path('tests/powershell/Test-Collectors.ps1').resolve()
|
||||
result=subprocess.run([executable,'-NoLogo','-NoProfile','-NonInteractive','-File',str(script)],capture_output=True,text=True,timeout=60,shell=False)
|
||||
self.assertEqual(result.returncode,0,result.stdout+'\n'+result.stderr)
|
||||
self.assertIn('synthetic collector tests passed',result.stdout)
|
||||
@@ -83,7 +83,7 @@ class UITests(unittest.TestCase):
|
||||
self.assertIn('LOCAL LINUX INVENTORY', report.read_text())
|
||||
window.close()
|
||||
|
||||
def test_mismatched_profile_and_windows_unavailable_keep_prior_results(self):
|
||||
def test_mismatched_profile_and_windows_failure_keep_prior_results(self):
|
||||
window = MainWindow()
|
||||
window.collection_done(collect_demo(LINUX_DEMO_HOST))
|
||||
previous = window.data
|
||||
@@ -95,10 +95,11 @@ class UITests(unittest.TestCase):
|
||||
collector.assert_not_called()
|
||||
self.assertIn('does not match', window.status.text())
|
||||
self.assertIs(window.data, previous)
|
||||
with patch('samsara.ui.window.detect_local_os', return_value={'platform': 'windows', 'family': 'windows', 'version': '', 'name': 'Windows'}):
|
||||
with patch('samsara.ui.window.detect_local_os', return_value={'platform': 'windows', 'family': 'windows', 'version': '', 'name': 'Windows'}), patch('samsara.ui.window.collect_local_windows', side_effect=ValueError('Windows identity collection failed.')):
|
||||
window.start_collection()
|
||||
self.finish(window)
|
||||
self.assertIn('not available yet', window.status.text())
|
||||
self.assertIn('identity collection failed', window.status.text())
|
||||
self.assertIs(window.data, previous)
|
||||
window.close()
|
||||
|
||||
def test_import_requires_file_and_reads_recorded_os(self):
|
||||
@@ -114,3 +115,29 @@ class UITests(unittest.TestCase):
|
||||
loader.assert_called_once_with('/tmp/saved-inventory.json')
|
||||
detect.assert_not_called()
|
||||
window.close()
|
||||
|
||||
def test_windows_local_route_preserves_summary_history_and_export(self):
|
||||
import json
|
||||
data = json.loads(Path('tests/fixtures/windows/server-2025.json').read_text())
|
||||
data['synthetic'] = False
|
||||
data['server']['collection_source'] = 'Local Windows read-only collection'
|
||||
with patch('samsara.ui.window.detect_local_os', return_value={'platform': 'windows', 'family': 'windows', 'version': '', 'name': 'Windows'}), patch('samsara.ui.window.collect_local_windows', return_value=data) as collector, patch('samsara.ui.window.collect_local_linux') as linux:
|
||||
window = MainWindow()
|
||||
window.target.setCurrentIndex(window.target.findData('windows:2025'))
|
||||
window.start_collection()
|
||||
self.assertFalse(window.analyze_button.isEnabled())
|
||||
self.finish(window)
|
||||
collector.assert_called_once()
|
||||
self.assertEqual(collector.call_args.kwargs['profile'], 'windows:2025')
|
||||
linux.assert_not_called()
|
||||
self.assertIn('Local Windows', window.banner.text())
|
||||
self.assertIn('2025', window.os_version.text())
|
||||
self.assertEqual(len(window.records), 1)
|
||||
self.assertEqual(window.nav.currentRow(), 0)
|
||||
self.assertTrue(window.report_buttons[0].isEnabled())
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
report = Path(folder)/'windows.html'
|
||||
with patch('samsara.ui.window.QFileDialog.getSaveFileName', return_value=(str(report), '')), patch('samsara.ui.window.QMessageBox.information'):
|
||||
window.quick_save.click()
|
||||
self.assertIn('LOCAL WINDOWS INVENTORY', report.read_text())
|
||||
window.close()
|
||||
|
||||
224
tests/test_windows.py
Normal file
224
tests/test_windows.py
Normal file
@@ -0,0 +1,224 @@
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from samsara.collectors.windows import (collect_local_windows, run_section, verify_server,
|
||||
validate_section, WindowsCollectionError, SECTIONS)
|
||||
from samsara.collectors.linux import CollectionCancelled
|
||||
from samsara.models.inventory import validate_inventory
|
||||
from samsara.collectors.importer import load_inventory
|
||||
from samsara.analyzers.assessment import assess
|
||||
from samsara.reporting.export import export_html, export_json
|
||||
|
||||
|
||||
def fixture(year='2022'):
|
||||
return json.loads(Path(f'tests/fixtures/windows/server-{year}.json').read_text())
|
||||
|
||||
|
||||
def record_for(data, section):
|
||||
record = {'section': section, **copy.deepcopy(data['checks'][section])}
|
||||
if section == 'system':
|
||||
record['data'] = copy.deepcopy(data['server'])
|
||||
elif section == 'shares':
|
||||
record['data'] = [r for r in data['inventory']['storage'] if r['type'] == 'SMB Share']
|
||||
elif section == 'storage':
|
||||
record['data'] = [r for r in data['inventory']['storage'] if r['type'] != 'SMB Share']
|
||||
else:
|
||||
record['data'] = copy.deepcopy(data['inventory'][section])
|
||||
return record
|
||||
|
||||
|
||||
class WindowsTests(unittest.TestCase):
|
||||
def collect(self, data=None, runner=None, **kwargs):
|
||||
data = data or fixture()
|
||||
if runner is None:
|
||||
runner = lambda section, executable, **options: record_for(data, section)
|
||||
with patch('samsara.collectors.windows.platform.system', return_value='Windows'), patch('samsara.collectors.windows.powershell_executable', return_value=Path('C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe')):
|
||||
return collect_local_windows(runner=runner, **kwargs)
|
||||
|
||||
def test_all_server_contracts_profiles_and_arrays(self):
|
||||
for year in ['2016','2019','2022','2025']:
|
||||
with self.subTest(year=year):
|
||||
data=fixture(year)
|
||||
validate_inventory(data)
|
||||
result=self.collect(data, profile='windows:'+year)
|
||||
self.assertEqual(result['server']['os_version'],year)
|
||||
self.assertFalse(result['synthetic'])
|
||||
self.assertEqual(result['server']['collection_source'],'Local Windows read-only collection')
|
||||
self.assertEqual(len(result['inventory']['storage']),2)
|
||||
self.assertEqual(set(result['checks']),set(SECTIONS))
|
||||
self.assertTrue(all(isinstance(rows,list) for rows in result['inventory'].values()))
|
||||
self.assertTrue(all('started_at' in c and 'completed_at' in c for c in result['checks'].values()))
|
||||
|
||||
def test_profiles_and_workstation_cannot_masquerade_as_server(self):
|
||||
for mutation in ['client','build','mismatch']:
|
||||
data=fixture()
|
||||
if mutation=='client':
|
||||
data['server']['product_type']=1
|
||||
elif mutation=='build':
|
||||
data['server']['os_build']='99999'
|
||||
with self.subTest(mutation=mutation), self.assertRaises(ValueError):
|
||||
self.collect(data,profile='windows:2016' if mutation=='mismatch' else 'auto')
|
||||
|
||||
def test_platform_guard_never_runs_powershell_on_linux(self):
|
||||
with patch('samsara.collectors.windows.platform.system', return_value='Linux'), patch('samsara.collectors.windows.run_section') as runner:
|
||||
with self.assertRaises(ValueError):
|
||||
collect_local_windows()
|
||||
runner.assert_not_called()
|
||||
|
||||
def test_timeout_and_partial_permission_results_preserve_other_sections(self):
|
||||
def runner(section, executable, **kwargs):
|
||||
if section=='roles':
|
||||
raise WindowsCollectionError('timeout','The local section exceeded its time limit.')
|
||||
record=record_for(fixture(),section)
|
||||
if section=='certificates':
|
||||
record['status']='partial'
|
||||
record['errors']=[{'code':'access_denied','scope':'Local machine certificate metadata','message':'Current account cannot read this scope.'}]
|
||||
record['permission_limitations']=['One store denied.']
|
||||
return record
|
||||
data=self.collect(runner=runner)
|
||||
self.assertEqual(data['checks']['roles']['status'],'failed')
|
||||
self.assertEqual(data['checks']['roles']['errors'][0]['code'],'timeout')
|
||||
self.assertTrue(data['inventory']['services'])
|
||||
self.assertTrue(data['inventory']['certificates'])
|
||||
self.assertEqual(data['checks']['certificates']['status'],'partial')
|
||||
self.assertTrue(any('roles:' in f['evidence'] for f in assess(data)['findings']))
|
||||
|
||||
def test_identity_failures_are_fatal_and_bad_sections_are_explicit(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.collect(runner=lambda *args,**kwargs: {})
|
||||
def runner(section, executable, **kwargs):
|
||||
record=record_for(fixture(),section)
|
||||
if section=='network':
|
||||
record['data']=[{'state':'Listen'}]
|
||||
return record
|
||||
data=self.collect(runner=runner)
|
||||
self.assertEqual(data['checks']['network']['errors'][0]['code'],'invalid_output')
|
||||
self.assertEqual(data['inventory']['network'],[])
|
||||
|
||||
def test_global_timeout_marks_unattempted_sections(self):
|
||||
# First iteration/system fits; remaining iterations exceed the deadline.
|
||||
times=iter([0,0,*([10]*8)])
|
||||
with patch('samsara.collectors.windows.time.monotonic',side_effect=lambda:next(times)):
|
||||
data=self.collect(total_timeout=1)
|
||||
self.assertEqual(data['checks']['services']['status'],'not_collected')
|
||||
self.assertEqual(data['checks']['services']['errors'][0]['code'],'overall_timeout')
|
||||
|
||||
def test_cancellation_and_output_limits(self):
|
||||
with self.assertRaises(CollectionCancelled):
|
||||
self.collect(cancelled=lambda:True)
|
||||
with patch('samsara.collectors.windows.MAX_OUTPUT',1), self.assertRaises(ValueError):
|
||||
self.collect()
|
||||
|
||||
def test_secret_fields_are_excluded_and_check_metadata_survives_import(self):
|
||||
def runner(section,executable,**kwargs):
|
||||
record=record_for(fixture(),section)
|
||||
if section=='services':
|
||||
record['data'][0]['arguments']='SECRET'
|
||||
record['data'][0]['PathName']='SECRET'
|
||||
return record
|
||||
data=self.collect(runner=runner)
|
||||
self.assertNotIn('SECRET',json.dumps(data))
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
out=Path(folder)/'inventory.json'
|
||||
export_json(out,data,assess(data))
|
||||
imported=load_inventory(out)
|
||||
self.assertNotIn('collection_source',imported['server'])
|
||||
self.assertEqual(imported['checks']['system']['started_at'],data['checks']['system']['started_at'])
|
||||
self.assertIn('permission_limitations',imported['checks']['services'])
|
||||
html=Path(folder)/'report.html'
|
||||
export_html(html,data,assess(data))
|
||||
text=html.read_text()
|
||||
self.assertIn('LOCAL WINDOWS INVENTORY',text)
|
||||
self.assertIn('started_at',text)
|
||||
self.assertIn('permission_limitations',text)
|
||||
self.assertNotIn('SECRET',text)
|
||||
|
||||
def test_invalid_section_cannot_be_interpolated(self):
|
||||
with self.assertRaises(ValueError):
|
||||
run_section('services;whoami','powershell.exe')
|
||||
|
||||
|
||||
class FakeProcess:
|
||||
def __init__(self,args,**kwargs):
|
||||
self.returncode=None
|
||||
self.args=args
|
||||
self.kwargs=kwargs
|
||||
self.killed=False
|
||||
self.waited=False
|
||||
def poll(self): return self.returncode
|
||||
def kill(self): self.killed=True; self.returncode=-1
|
||||
def wait(self): self.waited=True; return self.returncode
|
||||
|
||||
|
||||
class ProcessTests(unittest.TestCase):
|
||||
def test_timeout_and_cancel_kill_and_reap_without_shell(self):
|
||||
for cancel in [False,True]:
|
||||
processes=[]
|
||||
def create(args,**kwargs):
|
||||
process=FakeProcess(args,**kwargs);processes.append(process);return process
|
||||
calls=iter([False,True])
|
||||
with self.subTest(cancel=cancel), patch('samsara.collectors.windows.subprocess.Popen',side_effect=create):
|
||||
with self.assertRaises(CollectionCancelled if cancel else WindowsCollectionError):
|
||||
run_section('services',Path('C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe'), cancelled=(lambda:next(calls)) if cancel else (lambda:False),timeout=0)
|
||||
process=processes[0]
|
||||
self.assertTrue(process.killed and process.waited)
|
||||
self.assertFalse(process.kwargs['shell'])
|
||||
self.assertIn('-NoProfile',process.args)
|
||||
self.assertIn('-File',process.args)
|
||||
self.assertNotIn('-Command',process.args)
|
||||
self.assertNotIn('-ExecutionPolicy',process.args)
|
||||
self.assertEqual(process.kwargs['env']['PSModulePath'],str(Path('C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe').parent / 'Modules'))
|
||||
|
||||
def test_invalid_stdout_and_start_failure_are_sanitized(self):
|
||||
def create(args,**kwargs):
|
||||
process=FakeProcess(args,**kwargs);process.returncode=0
|
||||
kwargs['stdout'].write(b'not JSON SECRET')
|
||||
return process
|
||||
with patch('samsara.collectors.windows.subprocess.Popen',side_effect=create):
|
||||
with self.assertRaises(WindowsCollectionError) as error:
|
||||
run_section('services','powershell.exe')
|
||||
self.assertEqual(error.exception.code,'invalid_output')
|
||||
self.assertNotIn('SECRET',str(error.exception))
|
||||
with patch('samsara.collectors.windows.subprocess.Popen',side_effect=OSError('SECRET')):
|
||||
with self.assertRaises(WindowsCollectionError) as error:
|
||||
run_section('services','powershell.exe')
|
||||
self.assertNotIn('SECRET',str(error.exception))
|
||||
|
||||
def test_successful_utf8_bom_zero_rows_and_nonzero_exit(self):
|
||||
processes=[]
|
||||
def create(args,**kwargs):
|
||||
process=FakeProcess(args,**kwargs)
|
||||
process.returncode=0
|
||||
record=record_for(fixture(),'services')
|
||||
record['data']=[]
|
||||
kwargs['stdout'].write(b'\xef\xbb\xbf'+json.dumps(record).encode('utf-8'))
|
||||
processes.append(process)
|
||||
return process
|
||||
with patch('samsara.collectors.windows.subprocess.Popen',side_effect=create):
|
||||
record=run_section('services','powershell.exe')
|
||||
validate_section(record,'services')
|
||||
self.assertEqual(record['data'],[])
|
||||
self.assertTrue(processes[0].waited)
|
||||
def failed(args,**kwargs):
|
||||
process=FakeProcess(args,**kwargs);process.returncode=1
|
||||
kwargs['stdout'].write(b'SECRET')
|
||||
return process
|
||||
with patch('samsara.collectors.windows.subprocess.Popen',side_effect=failed):
|
||||
with self.assertRaises(WindowsCollectionError) as error:
|
||||
run_section('services','powershell.exe')
|
||||
self.assertEqual(error.exception.code,'powershell_failed')
|
||||
self.assertNotIn('SECRET',str(error.exception))
|
||||
|
||||
def test_completed_process_output_limit(self):
|
||||
def create(args,**kwargs):
|
||||
process=FakeProcess(args,**kwargs);process.returncode=0
|
||||
kwargs['stdout'].write(b'x'*20)
|
||||
return process
|
||||
with patch('samsara.collectors.windows.subprocess.Popen',side_effect=create), patch('samsara.collectors.windows.MAX_OUTPUT',10):
|
||||
with self.assertRaises(WindowsCollectionError) as error:
|
||||
run_section('services','powershell.exe')
|
||||
self.assertEqual(error.exception.code,'output_limit')
|
||||
Reference in New Issue
Block a user