up
Some checks failed
LNM Migration CI / build-runner (push) Has been cancelled
Ledger OpenAPI CI / deprecation-check (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Airgap Sealed CI Smoke / sealed-smoke (push) Has been cancelled
Ledger Packs CI / build-pack (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Ledger OpenAPI CI / validate-oas (push) Has been cancelled
Ledger OpenAPI CI / check-wellknown (push) Has been cancelled
Ledger Packs CI / verify-pack (push) Has been cancelled
LNM Migration CI / validate-metrics (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-14 18:33:02 +02:00
parent d233fa3529
commit 2e70c9fdb6
51 changed files with 5958 additions and 75 deletions

View File

@@ -0,0 +1,329 @@
#!/usr/bin/env bash
#
# validate-paths.sh - Validates offline kit path structure
#
# Usage: ./validate-paths.sh [--combined] [kit_directory]
#
# Options:
# --combined Expect combined runtime format (combined.runtime.ndjson)
# kit_directory Path to kit directory (default: parent of this script)
#
# Exit codes:
# 0 - All validations passed
# 1 - Missing required files or directories
# 2 - Invalid file format
# 3 - Usage error
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
COMBINED_FORMAT=false
KIT_DIR=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--combined)
COMBINED_FORMAT=true
shift
;;
--help|-h)
echo "Usage: $0 [--combined] [kit_directory]"
echo ""
echo "Validates offline kit path structure and file formats."
echo ""
echo "Options:"
echo " --combined Expect combined runtime format"
echo " kit_directory Path to kit directory (default: parent of this script)"
exit 0
;;
-*)
echo "Unknown option: $1" >&2
exit 3
;;
*)
KIT_DIR="$1"
shift
;;
esac
done
# Default to parent directory if not specified
if [[ -z "$KIT_DIR" ]]; then
KIT_DIR="${SCRIPT_DIR}/.."
fi
# Resolve to absolute path
KIT_DIR="$(cd "$KIT_DIR" && pwd)"
echo "Validating kit at: $KIT_DIR"
ERRORS=0
# Helper functions
check_file() {
local file="$1"
local required="${2:-true}"
local path="$KIT_DIR/$file"
if [[ -f "$path" ]]; then
echo " [OK] $file"
return 0
elif [[ "$required" == "true" ]]; then
echo " [MISSING] $file (required)" >&2
ERRORS=$((ERRORS + 1))
return 1
else
echo " [SKIP] $file (optional)"
return 0
fi
}
check_dir() {
local dir="$1"
local required="${2:-true}"
local path="$KIT_DIR/$dir"
if [[ -d "$path" ]]; then
echo " [OK] $dir/"
return 0
elif [[ "$required" == "true" ]]; then
echo " [MISSING] $dir/ (required)" >&2
ERRORS=$((ERRORS + 1))
return 1
else
echo " [SKIP] $dir/ (optional)"
return 0
fi
}
validate_json() {
local file="$1"
local path="$KIT_DIR/$file"
if [[ ! -f "$path" ]]; then
return 0 # Skip if file doesn't exist (handled by check_file)
fi
if command -v python3 >/dev/null 2>&1; then
if python3 -c "import json; json.load(open('$path'))" 2>/dev/null; then
echo " [VALID JSON] $file"
return 0
else
echo " [INVALID JSON] $file" >&2
ERRORS=$((ERRORS + 1))
return 1
fi
elif command -v jq >/dev/null 2>&1; then
if jq empty "$path" 2>/dev/null; then
echo " [VALID JSON] $file"
return 0
else
echo " [INVALID JSON] $file" >&2
ERRORS=$((ERRORS + 1))
return 1
fi
else
echo " [SKIP] $file (no JSON validator available)"
return 0
fi
}
validate_ndjson() {
local file="$1"
local path="$KIT_DIR/$file"
if [[ ! -f "$path" ]]; then
return 0 # Skip if file doesn't exist
fi
if command -v python3 >/dev/null 2>&1; then
local result
result=$(python3 -c "
import json, sys
path = '$path'
errors = 0
with open(path, 'r') as f:
for i, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
try:
json.loads(line)
except json.JSONDecodeError as e:
print(f'Line {i}: {e}', file=sys.stderr)
errors += 1
if errors >= 5:
print('(truncated after 5 errors)', file=sys.stderr)
break
sys.exit(0 if errors == 0 else 1)
" 2>&1)
if [[ $? -eq 0 ]]; then
echo " [VALID NDJSON] $file"
return 0
else
echo " [INVALID NDJSON] $file" >&2
echo "$result" >&2
ERRORS=$((ERRORS + 1))
return 1
fi
else
echo " [SKIP] $file (python3 required for NDJSON validation)"
return 0
fi
}
# =============================================================================
# Directory Structure Validation
# =============================================================================
echo ""
echo "=== Checking directory structure ==="
check_dir "schemas"
check_dir "exports"
check_dir "kit"
# =============================================================================
# Core Files Validation
# =============================================================================
echo ""
echo "=== Checking core files ==="
check_file "thresholds.yaml"
check_file "thresholds.yaml.dsse"
check_file "SHA256SUMS"
# =============================================================================
# Schema Files Validation
# =============================================================================
echo ""
echo "=== Checking schema files ==="
check_file "schemas/observer_event.schema.json"
check_file "schemas/observer_event.schema.json.dsse"
check_file "schemas/webhook_admission.schema.json"
check_file "schemas/webhook_admission.schema.json.dsse"
# =============================================================================
# Kit Files Validation
# =============================================================================
echo ""
echo "=== Checking kit files ==="
check_file "kit/ed25519.pub"
check_file "kit/verify.sh"
check_file "kit/zastava-kit.tzst" false # Optional - may not be in source tree
check_file "kit/zastava-kit.tzst.dsse" false
# =============================================================================
# Export Files Validation
# =============================================================================
echo ""
echo "=== Checking export files ==="
if [[ "$COMBINED_FORMAT" == "true" ]]; then
# Combined format
echo "(Combined format mode)"
check_file "exports/combined.runtime.ndjson"
check_file "exports/combined.runtime.ndjson.dsse"
# Legacy files are optional in combined mode
check_file "exports/observer_events.ndjson" false
check_file "exports/webhook_admissions.ndjson" false
else
# Legacy format
echo "(Legacy format mode)"
check_file "exports/observer_events.ndjson"
check_file "exports/observer_events.ndjson.dsse"
check_file "exports/webhook_admissions.ndjson"
check_file "exports/webhook_admissions.ndjson.dsse"
# Combined is optional in legacy mode
check_file "exports/combined.runtime.ndjson" false
fi
# =============================================================================
# JSON/NDJSON Format Validation
# =============================================================================
echo ""
echo "=== Validating file formats ==="
validate_json "schemas/observer_event.schema.json"
validate_json "schemas/webhook_admission.schema.json"
if [[ "$COMBINED_FORMAT" == "true" ]] && [[ -f "$KIT_DIR/exports/combined.runtime.ndjson" ]]; then
validate_ndjson "exports/combined.runtime.ndjson"
else
if [[ -f "$KIT_DIR/exports/observer_events.ndjson" ]]; then
validate_ndjson "exports/observer_events.ndjson"
fi
if [[ -f "$KIT_DIR/exports/webhook_admissions.ndjson" ]]; then
validate_ndjson "exports/webhook_admissions.ndjson"
fi
fi
# =============================================================================
# Combined Format Structure Validation
# =============================================================================
if [[ "$COMBINED_FORMAT" == "true" ]] && [[ -f "$KIT_DIR/exports/combined.runtime.ndjson" ]]; then
echo ""
echo "=== Validating combined format structure ==="
if command -v python3 >/dev/null 2>&1; then
python3 - "$KIT_DIR/exports/combined.runtime.ndjson" <<'PYTHON'
import json
import sys
path = sys.argv[1]
errors = []
has_header = False
has_footer = False
record_types = set()
with open(path, 'r') as f:
for i, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
try:
record = json.loads(line)
rtype = record.get("type", "unknown")
record_types.add(rtype)
if rtype == "combined.header":
if has_header:
errors.append(f"Line {i}: duplicate header")
has_header = True
if i != 1:
errors.append(f"Line {i}: header should be first record")
elif rtype == "combined.footer":
has_footer = True
except json.JSONDecodeError as e:
errors.append(f"Line {i}: {e}")
if not has_header:
errors.append("Missing combined.header record")
if not has_footer:
errors.append("Missing combined.footer record")
if errors:
for e in errors:
print(f" [ERROR] {e}", file=sys.stderr)
sys.exit(1)
print(f" [OK] Header and footer present")
print(f" [OK] Record types: {', '.join(sorted(record_types))}")
PYTHON
if [[ $? -ne 0 ]]; then
ERRORS=$((ERRORS + 1))
fi
fi
fi
# =============================================================================
# Summary
# =============================================================================
echo ""
echo "=== Validation Summary ==="
if [[ $ERRORS -eq 0 ]]; then
echo "All validations passed!"
exit 0
else
echo "$ERRORS validation error(s) found" >&2
exit 1
fi

View File

@@ -52,8 +52,22 @@ targets = [
("webhook exports", root / "exports" / "webhook_admissions.ndjson", root / "exports" / "webhook_admissions.ndjson.dsse", "application/vnd.stellaops.zastava.webhook-admissions+ndjson;version=1"),
]
# Combined runtime format (optional - may not exist in all kits)
combined_targets = [
("combined runtime", root / "exports" / "combined.runtime.ndjson", root / "exports" / "combined.runtime.ndjson.dsse", "application/vnd.stellaops.combined.runtime+ndjson;version=1"),
]
for name, payload_path, envelope_path, ptype in targets:
verify(name, payload_path, envelope_path, ptype)
# Verify combined format if present
for name, payload_path, envelope_path, ptype in combined_targets:
if payload_path.exists() and envelope_path.exists():
verify(name, payload_path, envelope_path, ptype)
elif payload_path.exists() or envelope_path.exists():
print(f"WARNING: {name} - incomplete (payload and envelope must both exist)")
else:
print(f"SKIP: {name} (not present)")
PY
echo "OK: SHA256 + DSSE signatures verified"

View File

@@ -0,0 +1,318 @@
# Docker Socket Permissions and Security
This document covers the security considerations and configuration options for Docker socket access in Zastava Agent deployments.
## Overview
The Zastava Agent requires read access to the Docker socket (`/var/run/docker.sock`) to:
1. **Monitor container lifecycle events** - Start, stop, pause, die, etc.
2. **Inspect running containers** - Image digest, labels, environment variables
3. **Collect runtime evidence** - Loaded libraries, process information
## Default Configuration
By default, the agent runs as:
- **User:** `zastava-agent` (system user)
- **Group:** `docker` (grants socket access)
- **Socket:** `/var/run/docker.sock`
```yaml
# systemd service configuration
User=zastava-agent
Group=docker
ReadWritePaths=/var/run/docker.sock
```
## Security Considerations
### Docker Socket Exposure Risks
The Docker socket provides significant privileges:
| Capability | Risk Level | Mitigation |
|------------|-----------|------------|
| List containers | Low | Required for operation |
| Inspect containers | Low | Required for operation |
| Read container logs | Medium | Agent does not use this |
| Create containers | High | Agent does not use this |
| Execute in containers | Critical | Agent does not use this |
| Pull images | High | Agent does not use this |
| Remove containers | High | Agent does not use this |
### Agent Behavior
The Zastava Agent performs **read-only operations**:
```go
// Operations used by agent
docker.ContainerList(...) // List running containers
docker.ContainerInspect(...) // Get container details
docker.Events(...) // Subscribe to lifecycle events
```
The agent **does not** perform write operations such as creating, starting, stopping, or removing containers.
## Alternative Configurations
### Option 1: Docker API Proxy (Recommended for High-Security)
Deploy a Docker API proxy that restricts available operations:
```yaml
# docker-proxy configuration example
allowed_endpoints:
- "GET /containers/json" # List containers
- "GET /containers/*/json" # Inspect container
- "GET /events" # Subscribe to events
- "GET /_ping" # Health check
```
Example proxy: [Tecnativa/docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy)
```bash
# Deploy proxy
docker run -d \
--name docker-proxy \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-e CONTAINERS=1 \
-e EVENTS=1 \
-p 2375:2375 \
tecnativa/docker-socket-proxy
```
Configure agent to use proxy:
```env
ZASTAVA_AGENT__DockerEndpoint=tcp://localhost:2375
```
### Option 2: Unix Socket with ACLs
Use filesystem ACLs for fine-grained access:
```bash
# Install ACL support
sudo apt-get install acl
# Set ACL for zastava-agent user
sudo setfacl -m u:zastava-agent:rw /var/run/docker.sock
# Verify ACL
getfacl /var/run/docker.sock
```
This allows removing the user from the `docker` group while maintaining socket access.
### Option 3: SELinux/AppArmor Policies
#### SELinux Policy
```te
# zastava-agent.te
module zastava_agent 1.0;
require {
type docker_var_run_t;
type zastava_agent_t;
class sock_file { read write };
}
# Allow read/write to Docker socket
allow zastava_agent_t docker_var_run_t:sock_file { read write getattr };
```
#### AppArmor Profile
```apparmor
# /etc/apparmor.d/zastava-agent
profile zastava-agent /opt/stellaops/zastava-agent/StellaOps.Zastava.Agent {
# Docker socket access
/var/run/docker.sock rw,
# Deny network access except to scanner backend
network inet stream,
network inet6 stream,
# Read-only system access
/etc/stellaops/* r,
/opt/stellaops/zastava-agent/** mr,
# Data directory
/var/lib/zastava-agent/** rw,
}
```
### Option 4: Rootless Docker
For maximum isolation, use rootless Docker:
```bash
# Install rootless Docker
dockerd-rootless-setuptool.sh install
# Configure agent to use rootless socket
export ZASTAVA_AGENT__DockerEndpoint=unix:///run/user/1000/docker.sock
```
Note: Rootless Docker has some limitations with networking and storage drivers.
## Log Paths
### Agent Logs
| Component | Log Location |
|-----------|--------------|
| Agent stdout/stderr | `journalctl -u zastava-agent` |
| Runtime events | `/var/lib/zastava-agent/runtime-events/*.ndjson` |
| Health check | Agent stdout (structured JSON) |
### Log Configuration
```env
# Set log level
Serilog__MinimumLevel__Default=Information
# Available levels: Verbose, Debug, Information, Warning, Error, Fatal
```
### Log Rotation
Event buffer files are automatically rotated:
```yaml
# Default settings
event_buffer:
max_file_size_mb: 10
max_total_size_mb: 100
retention_hours: 24
```
## Health Check Configuration
The agent exposes HTTP health endpoints:
| Endpoint | Port | Description |
|----------|------|-------------|
| `/healthz` | 8080 | Liveness probe |
| `/readyz` | 8080 | Readiness probe |
| `/livez` | 8080 | Alias for liveness |
### Health Check Port
Configure via environment variable:
```env
ZASTAVA_AGENT__HealthCheck__Port=8080
```
### Health Check Behavior
**Liveness (`/healthz`):**
- Returns 200 if agent process is running
- Returns 503 if critical subsystems failed
**Readiness (`/readyz`):**
- Returns 200 if agent can process events
- Returns 503 if:
- Docker socket is unreachable
- Event buffer is not writable
- Backend connection failed
### Prometheus Metrics
Health metrics are exposed at `/metrics`:
```
# HELP zastava_agent_docker_connected Docker connectivity status
# TYPE zastava_agent_docker_connected gauge
zastava_agent_docker_connected 1
# HELP zastava_agent_buffer_writable Event buffer writability
# TYPE zastava_agent_buffer_writable gauge
zastava_agent_buffer_writable 1
# HELP zastava_agent_events_buffered Number of events in buffer
# TYPE zastava_agent_events_buffered gauge
zastava_agent_events_buffered 42
```
## Monitoring Recommendations
### Alerting Rules
```yaml
groups:
- name: zastava-agent
rules:
- alert: ZastavaAgentDown
expr: up{job="zastava-agent"} == 0
for: 5m
annotations:
summary: "Zastava Agent is down on {{ $labels.instance }}"
- alert: ZastavaDockerDisconnected
expr: zastava_agent_docker_connected == 0
for: 1m
annotations:
summary: "Zastava Agent lost Docker connectivity"
- alert: ZastavaBufferNotWritable
expr: zastava_agent_buffer_writable == 0
for: 1m
severity: critical
annotations:
summary: "Zastava event buffer is not writable"
```
### Grafana Dashboard
Import the Zastava monitoring dashboard from:
`docs/modules/zastava/operations/dashboards/zastava-observability.json`
## Troubleshooting
### Cannot Access Docker Socket
```bash
# Check socket exists
ls -la /var/run/docker.sock
# Check agent user groups
id zastava-agent
# Check Docker daemon is running
systemctl status docker
# Test socket access manually
sudo -u zastava-agent docker ps
```
### Permission Denied Errors
```bash
# Add user to docker group (if not using ACLs)
sudo usermod -aG docker zastava-agent
# Restart agent
sudo systemctl restart zastava-agent
```
### Events Not Being Received
```bash
# Check Docker events stream
docker events --since 1m
# Verify agent can see events
journalctl -u zastava-agent | grep -i "event"
# Check event buffer
ls -la /var/lib/zastava-agent/runtime-events/
```
## References
- [Docker Engine Security](https://docs.docker.com/engine/security/)
- [Docker Socket Security](https://docs.docker.com/engine/security/protect-access/)
- [Rootless Docker](https://docs.docker.com/engine/security/rootless/)
- [docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy)

View File

@@ -0,0 +1,367 @@
# Windows Container Deployment Guide
This guide covers deploying and operating the Zastava Agent for Windows container monitoring.
## Overview
The Zastava Agent supports Windows container runtime monitoring via:
1. **Docker Desktop for Windows** - Docker API over named pipe
2. **Docker Engine on Windows Server** - Native Windows containers
3. **Windows Server Core containers** - Server-class workloads
## System Requirements
### Minimum Requirements
| Component | Requirement |
|-----------|-------------|
| Operating System | Windows Server 2019 or later |
| Container Runtime | Docker Engine 20.10+ or Docker Desktop 4.x |
| .NET Runtime | .NET 10.0 or later |
| Memory | 512 MB minimum, 1 GB recommended |
| Disk Space | 100 MB for agent + event buffer space |
### Supported Windows Versions
| Windows Version | Container Types | Status |
|-----------------|-----------------|--------|
| Windows Server 2022 | Windows Server Core, Nano Server | Full Support |
| Windows Server 2019 | Windows Server Core, Nano Server | Full Support |
| Windows 11 | Windows/Linux containers (via WSL2) | Supported |
| Windows 10 | Windows/Linux containers (via WSL2) | Supported |
## Installation
### Option 1: PowerShell Installation Script
```powershell
# Download and run installer
Invoke-WebRequest -Uri "https://releases.stellaops.org/zastava-agent/latest/Install-ZastavaAgent.ps1" -OutFile "$env:TEMP\Install-ZastavaAgent.ps1"
# Install with required parameters
& "$env:TEMP\Install-ZastavaAgent.ps1" `
-Tenant "your-tenant" `
-ScannerBackendUrl "https://scanner.internal" `
-InstallPath "C:\Program Files\StellaOps\Zastava"
```
### Option 2: Manual Installation
1. **Download the agent:**
```powershell
$version = "latest"
$arch = if ([System.Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" }
$url = "https://releases.stellaops.org/zastava-agent/$version/zastava-agent-win-$arch.zip"
Invoke-WebRequest -Uri $url -OutFile "C:\temp\zastava-agent.zip"
```
2. **Extract and install:**
```powershell
$installPath = "C:\Program Files\StellaOps\Zastava"
New-Item -ItemType Directory -Path $installPath -Force
Expand-Archive -Path "C:\temp\zastava-agent.zip" -DestinationPath $installPath
```
3. **Create configuration file:**
```powershell
@"
# Zastava Agent Configuration
ZASTAVA_TENANT=your-tenant
ZASTAVA_AGENT__Backend__BaseAddress=https://scanner.internal
ZASTAVA_AGENT__DockerEndpoint=npipe:////./pipe/docker_engine
ZASTAVA_AGENT__EventBufferPath=C:\ProgramData\StellaOps\Zastava\runtime-events
ZASTAVA_AGENT__HealthCheck__Port=8080
"@ | Out-File -FilePath "$installPath\zastava-agent.env" -Encoding UTF8
```
4. **Install as Windows Service:**
```powershell
# Using NSSM (Non-Sucking Service Manager)
nssm install ZastavaAgent "$installPath\StellaOps.Zastava.Agent.exe"
nssm set ZastavaAgent AppDirectory "$installPath"
nssm set ZastavaAgent AppEnvironmentExtra "+DOTNET_ENVIRONMENT=Production"
nssm set ZastavaAgent DisplayName "StellaOps Zastava Agent"
nssm set ZastavaAgent Description "Container Runtime Monitor for StellaOps"
nssm set ZastavaAgent Start SERVICE_AUTO_START
```
Alternatively, use the native `sc.exe`:
```powershell
sc.exe create ZastavaAgent binPath= "$installPath\StellaOps.Zastava.Agent.exe" start= auto
```
5. **Start the service:**
```powershell
Start-Service ZastavaAgent
```
## Configuration
### Docker Named Pipe Access
The Windows agent connects to Docker via named pipe:
```
npipe:////./pipe/docker_engine
```
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `ZASTAVA_TENANT` | (required) | Tenant identifier |
| `ZASTAVA_AGENT__Backend__BaseAddress` | (required) | Scanner backend URL |
| `ZASTAVA_AGENT__DockerEndpoint` | `npipe:////./pipe/docker_engine` | Docker API endpoint |
| `ZASTAVA_AGENT__EventBufferPath` | `%ProgramData%\StellaOps\Zastava\runtime-events` | Event buffer directory |
| `ZASTAVA_AGENT__HealthCheck__Port` | `8080` | Health check HTTP port |
### Configuration File Location
```
C:\Program Files\StellaOps\Zastava\zastava-agent.env
```
## Docker Desktop Configuration
### Enable TCP/Named Pipe Access
1. Open Docker Desktop Settings
2. Go to **Settings → General**
3. Enable **Expose daemon on tcp://localhost:2375 without TLS** (for development only)
4. Or use the named pipe (default): `npipe:////./pipe/docker_engine`
### Windows Containers Mode
Ensure Docker is in Windows containers mode:
```powershell
# Check current mode
docker info --format '{{.OSType}}'
# Should output: windows
```
To switch to Windows containers:
- Right-click Docker Desktop tray icon
- Select "Switch to Windows containers..."
## Security Considerations
### Named Pipe Permissions
The Docker named pipe requires membership in:
- `docker-users` group (Docker Desktop)
- `Administrators` group (Docker Engine)
```powershell
# Add service account to docker-users group
Add-LocalGroupMember -Group "docker-users" -Member "NT SERVICE\ZastavaAgent"
```
### Windows Firewall
If health checks are accessed remotely:
```powershell
New-NetFirewallRule `
-DisplayName "Zastava Agent Health Check" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 8080 `
-Action Allow
```
### PE Library Hashing
The agent collects SHA-256 hashes of loaded DLLs from Windows containers:
- Portable Executable (PE) format parsing
- Version information extraction
- Digital signature verification (if signed)
## Health Monitoring
### Health Endpoints
| Endpoint | URL | Description |
|----------|-----|-------------|
| Liveness | `http://localhost:8080/healthz` | Agent is running |
| Readiness | `http://localhost:8080/readyz` | Agent can process events |
### PowerShell Health Check
```powershell
# Check agent health
Invoke-RestMethod -Uri "http://localhost:8080/healthz"
# Check readiness
Invoke-RestMethod -Uri "http://localhost:8080/readyz"
```
### Windows Service Status
```powershell
# Check service status
Get-Service ZastavaAgent
# View service events
Get-EventLog -LogName Application -Source ZastavaAgent -Newest 20
```
## Logging
### Event Log
Agent logs are written to Windows Event Log:
- **Log:** Application
- **Source:** ZastavaAgent
```powershell
# View recent events
Get-EventLog -LogName Application -Source ZastavaAgent -Newest 50
# Filter by level
Get-EventLog -LogName Application -Source ZastavaAgent -EntryType Error,Warning
```
### File Logging (Optional)
Enable file logging via configuration:
```
Serilog__WriteTo__0__Name=File
Serilog__WriteTo__0__Args__path=C:\ProgramData\StellaOps\Zastava\logs\agent-.log
Serilog__WriteTo__0__Args__rollingInterval=Day
```
## Troubleshooting
### Agent Won't Start
1. **Check Docker is running:**
```powershell
docker info
```
2. **Verify named pipe exists:**
```powershell
Test-Path "\\.\pipe\docker_engine"
```
3. **Check service account permissions:**
```powershell
whoami /groups
```
4. **Review Event Log:**
```powershell
Get-EventLog -LogName Application -Source ZastavaAgent -Newest 10
```
### Cannot Connect to Docker
1. **Test Docker API:**
```powershell
Invoke-RestMethod -Uri "http://localhost:2375/info" -Method Get
# or for named pipe
docker version
```
2. **Verify Docker mode:**
```powershell
docker info --format '{{.OSType}}'
# Should be "windows" for Windows containers
```
3. **Check pipe permissions:**
```powershell
# List pipe ACL
Get-Acl "\\.\pipe\docker_engine" | Format-List
```
### Events Not Being Sent
1. **Check event buffer:**
```powershell
Get-ChildItem "C:\ProgramData\StellaOps\Zastava\runtime-events"
```
2. **Verify backend connectivity:**
```powershell
Test-NetConnection -ComputerName scanner.internal -Port 443
```
3. **Check readiness:**
```powershell
Invoke-RestMethod -Uri "http://localhost:8080/readyz"
```
## Upgrade Procedure
1. **Stop the service:**
```powershell
Stop-Service ZastavaAgent
```
2. **Backup configuration:**
```powershell
Copy-Item "C:\Program Files\StellaOps\Zastava\zastava-agent.env" "C:\temp\zastava-agent.env.bak"
```
3. **Download and extract new version:**
```powershell
$version = "1.2.0"
$url = "https://releases.stellaops.org/zastava-agent/$version/zastava-agent-win-x64.zip"
Invoke-WebRequest -Uri $url -OutFile "C:\temp\zastava-agent.zip"
Expand-Archive -Path "C:\temp\zastava-agent.zip" -DestinationPath "C:\Program Files\StellaOps\Zastava" -Force
```
4. **Restore configuration:**
```powershell
Copy-Item "C:\temp\zastava-agent.env.bak" "C:\Program Files\StellaOps\Zastava\zastava-agent.env"
```
5. **Start the service:**
```powershell
Start-Service ZastavaAgent
```
6. **Verify health:**
```powershell
Invoke-RestMethod -Uri "http://localhost:8080/healthz"
```
## Uninstallation
```powershell
# Stop and remove service
Stop-Service ZastavaAgent
sc.exe delete ZastavaAgent
# Remove installation directory
Remove-Item -Path "C:\Program Files\StellaOps\Zastava" -Recurse -Force
# Remove data directory
Remove-Item -Path "C:\ProgramData\StellaOps\Zastava" -Recurse -Force
```
## Known Limitations
1. **Hyper-V isolation only** - Process isolation containers have limited observability
2. **Windows container logs** - Container stdout/stderr capture not yet implemented
3. **WSL2 containers** - Linux containers on Windows require WSL2 mode, not directly supported
## References
- [Docker Desktop for Windows](https://docs.docker.com/desktop/windows/)
- [Windows Server Containers](https://docs.microsoft.com/en-us/virtualization/windowscontainers/)
- [Docker Engine on Windows Server](https://docs.docker.com/engine/install/windows/)