241 lines
6.9 KiB
Batchfile
241 lines
6.9 KiB
Batchfile
@echo off
|
|
setlocal
|
|
set "TEST_BAT_PATH=%~f0"
|
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "$path=$env:TEST_BAT_PATH; $raw=Get-Content -Raw -LiteralPath $path; $marker='# POWERSHELL'; $start=$raw.LastIndexOf($marker); if($start -lt 0){ throw 'PowerShell marker not found' }; $ps=$raw.Substring($start + $marker.Length); Set-Location -LiteralPath (Split-Path -Parent $path); Invoke-Expression $ps"
|
|
exit /b %ERRORLEVEL%
|
|
|
|
# POWERSHELL
|
|
$ErrorActionPreference = 'Stop'
|
|
if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) {
|
|
$PSNativeCommandUseErrorActionPreference = $false
|
|
}
|
|
|
|
$forceBuild = if ($env:FORCE_BUILD) { $env:FORCE_BUILD } else { '0' }
|
|
$verbose = if ($env:VERBOSE) { $env:VERBOSE } else { '0' }
|
|
$composeBase = @('compose', '--env-file', '.env', '-f', 'compose.yml')
|
|
|
|
function Write-TaggedLine {
|
|
param([string] $Line)
|
|
|
|
if ($Line.StartsWith('[PASS]')) {
|
|
Write-Host $Line -ForegroundColor Green
|
|
} elseif ($Line.StartsWith('[NO]') -or $Line.StartsWith('reason:')) {
|
|
Write-Host $Line -ForegroundColor Red
|
|
} elseif ($Line.StartsWith('[RUN]')) {
|
|
Write-Host $Line -ForegroundColor Cyan
|
|
} elseif ($Line.StartsWith('[INFO]')) {
|
|
Write-Host $Line -ForegroundColor DarkCyan
|
|
}
|
|
}
|
|
|
|
function Invoke-Docker {
|
|
param([string[]] $Arguments)
|
|
|
|
$previousErrorActionPreference = $ErrorActionPreference
|
|
$ErrorActionPreference = 'Continue'
|
|
try {
|
|
$output = & docker @Arguments 2>&1
|
|
$code = $LASTEXITCODE
|
|
} finally {
|
|
$ErrorActionPreference = $previousErrorActionPreference
|
|
}
|
|
|
|
$lines = @(
|
|
$output | ForEach-Object {
|
|
if ($_ -is [System.Management.Automation.ErrorRecord]) {
|
|
$_.Exception.Message
|
|
} else {
|
|
$_.ToString()
|
|
}
|
|
} | Where-Object {
|
|
$_ -and $_ -ne 'System.Management.Automation.RemoteException'
|
|
}
|
|
)
|
|
|
|
[pscustomobject]@{
|
|
Code = $code
|
|
Lines = $lines
|
|
}
|
|
}
|
|
|
|
function Print-FilteredOutput {
|
|
param([string[]] $Lines)
|
|
|
|
foreach ($line in $Lines) {
|
|
if ($verbose -eq '1') {
|
|
Write-Host $line
|
|
} else {
|
|
Write-TaggedLine $line
|
|
}
|
|
}
|
|
}
|
|
|
|
function Print-FailureDiagnostics {
|
|
param([string[]] $Lines)
|
|
|
|
$hasNoLine = $Lines | Where-Object { $_.StartsWith('[NO]') } | Select-Object -First 1
|
|
if (-not $hasNoLine) {
|
|
Write-Host '--- raw docker output ---' -ForegroundColor Yellow
|
|
$Lines | Select-Object -Last 80 | ForEach-Object { Write-Host $_ }
|
|
}
|
|
}
|
|
|
|
function Build-Image {
|
|
Write-Host ''
|
|
Write-TaggedLine '[RUN] Build image'
|
|
|
|
$sourceFiles = Get-ChildItem -Recurse -File scripts, rootfs, Dockerfile, compose.yml, .env, .env.example
|
|
$env:SOURCE_VERSION = ($sourceFiles | Sort-Object LastWriteTimeUtc -Descending | Select-Object -First 1).LastWriteTimeUtc.Ticks
|
|
|
|
$dockerArgs = $composeBase + @('build', '--quiet')
|
|
if ($forceBuild -eq '1') {
|
|
$dockerArgs += '--no-cache'
|
|
}
|
|
|
|
$result = Invoke-Docker $dockerArgs
|
|
if ($result.Code -ne 0) {
|
|
Write-TaggedLine '[NO] Build image'
|
|
$result.Lines | ForEach-Object { Write-Host $_ }
|
|
exit $result.Code
|
|
}
|
|
|
|
Write-TaggedLine '[PASS] Build image'
|
|
if ($verbose -eq '1') {
|
|
$result.Lines | ForEach-Object { Write-Host $_ }
|
|
}
|
|
}
|
|
|
|
function Run-Case {
|
|
param(
|
|
[string] $Title,
|
|
[string] $Service,
|
|
[string] $Script
|
|
)
|
|
|
|
Write-Host ''
|
|
Write-TaggedLine "[RUN] $Title"
|
|
|
|
$casePath = Join-Path (Get-Location) ('.test-case-{0}.sh' -f ([guid]::NewGuid().ToString('N')))
|
|
Set-Content -LiteralPath $casePath -Value $Script -NoNewline -Encoding Ascii
|
|
|
|
try {
|
|
$volumeArg = "${casePath}:/tmp/test-case.sh:ro"
|
|
$dockerArgs = $composeBase + @('run', '--rm', '--no-deps', '-T', '--volume', $volumeArg, $Service, 'bash', '/tmp/test-case.sh')
|
|
$result = Invoke-Docker $dockerArgs
|
|
} finally {
|
|
Remove-Item -LiteralPath $casePath -Force -ErrorAction SilentlyContinue
|
|
}
|
|
Print-FilteredOutput $result.Lines
|
|
|
|
if ($result.Code -ne 0) {
|
|
Print-FailureDiagnostics $result.Lines
|
|
}
|
|
|
|
return $result.Code
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath '.env')) {
|
|
Write-TaggedLine '[NO] .env exists'
|
|
Write-TaggedLine 'reason: create Ubuntu\.env from Ubuntu\.env.example and set XRAY_URL.'
|
|
exit 1
|
|
}
|
|
|
|
$tests = @(
|
|
@{
|
|
Title = 'IPv6 default off'
|
|
Service = 'ipv6-default-off'
|
|
Script = @'
|
|
set -euo pipefail
|
|
if curl --noproxy '*' -6 -fsSIL --connect-timeout 5 --max-time 10 http://google.com >/tmp/curl.out 2>/tmp/curl.err; then
|
|
echo '[NO] IPv6 request is blocked by default'
|
|
echo 'reason: IPv6 request unexpectedly succeeded'
|
|
exit 1
|
|
fi
|
|
echo '[PASS] IPv6 request is blocked by default'
|
|
'@
|
|
}
|
|
@{
|
|
Title = 'IPv6 enabled'
|
|
Service = 'ipv6-enabled'
|
|
Script = @'
|
|
set -euo pipefail
|
|
if [ -r /proc/sys/net/ipv6/conf/all/disable_ipv6 ]; then
|
|
value="$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6)"
|
|
if [ "${value}" != "0" ]; then
|
|
echo '[NO] IPv6 is allowed by container policy'
|
|
echo "reason: disable_ipv6=${value}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
echo '[PASS] IPv6 is allowed by container policy'
|
|
'@
|
|
}
|
|
@{
|
|
Title = 'Proxy on'
|
|
Service = 'proxy-on'
|
|
Script = @'
|
|
set -euo pipefail
|
|
sleep 3
|
|
|
|
if ! iptables -t nat -S XRAY_OUTPUT >/tmp/iptables.out 2>/tmp/iptables.err; then
|
|
echo '[NO] transparent proxy iptables chain exists'
|
|
echo "reason: $(sed -n '1p' /tmp/iptables.err)"
|
|
exit 1
|
|
fi
|
|
|
|
if ! grep -q -- "--to-ports ${XRAY_REDIRECT_PORT:-12345}" /tmp/iptables.out; then
|
|
echo '[NO] transparent proxy redirect rule exists'
|
|
echo "reason: XRAY_OUTPUT has no REDIRECT to ${XRAY_REDIRECT_PORT:-12345}"
|
|
exit 1
|
|
fi
|
|
echo '[PASS] transparent proxy redirect rule exists'
|
|
|
|
if curl --noproxy '*' -fsSIL --connect-timeout 10 --max-time 20 http://google.com >/tmp/curl.out 2>/tmp/curl.err; then
|
|
echo '[PASS] HTTP request through transparent proxy'
|
|
else
|
|
echo '[NO] HTTP request through transparent proxy'
|
|
echo "reason: $(sed -n '1p' /tmp/curl.err)"
|
|
exit 1
|
|
fi
|
|
'@
|
|
}
|
|
@{
|
|
Title = 'Proxy off'
|
|
Service = 'proxy-off'
|
|
Script = @'
|
|
set -euo pipefail
|
|
if pgrep -x xray >/dev/null 2>&1; then
|
|
echo '[NO] xray is not running'
|
|
echo 'reason: xray process exists'
|
|
exit 1
|
|
fi
|
|
echo '[PASS] xray is not running'
|
|
|
|
if curl --noproxy '*' -fsSIL --connect-timeout 3 --max-time 3 http://google.com >/tmp/curl.out 2>/tmp/curl.err; then
|
|
echo '[INFO] HTTP request without xray succeeded directly'
|
|
exit 0
|
|
fi
|
|
echo '[PASS] HTTP request without xray is blocked'
|
|
'@
|
|
}
|
|
)
|
|
|
|
Build-Image
|
|
|
|
$failed = $false
|
|
foreach ($test in $tests) {
|
|
$code = Run-Case -Title $test.Title -Service $test.Service -Script $test.Script
|
|
if ($code -ne 0) {
|
|
$failed = $true
|
|
}
|
|
}
|
|
|
|
Write-Host ''
|
|
if ($failed) {
|
|
Write-TaggedLine '[NO] one or more tests failed'
|
|
exit 1
|
|
}
|
|
|
|
Write-TaggedLine '[PASS] all tests passed'
|
|
exit 0
|