diff --git a/.github/actions/agent-implant-smoke/action.yml b/.github/actions/agent-implant-smoke/action.yml new file mode 100644 index 00000000..0f677726 --- /dev/null +++ b/.github/actions/agent-implant-smoke/action.yml @@ -0,0 +1,155 @@ +name: "Agent Smoke Test" +description: > + Downloads the openaev-agent binary from JFrog, launches it with a fake + config pointing at localhost:1, and asserts it fails with a connection + error (proving the binary starts and reaches the network layer). + +inputs: + os: + description: "Target OS: linux, macos, windows" + required: true + jfrog-arch: + description: "JFrog architecture: x86_64, arm64" + required: true + jfrog-base: + description: "JFrog Artifactory base URL" + required: false + default: "https://filigran.jfrog.io/artifactory" + binary-version: + description: "Binary version to download (tag or 'latest')" + required: false + default: "latest" + +runs: + using: composite + steps: + # ── Resolve extension ──────────────────────────────────────────── + - name: Set binary extension + id: ext + shell: bash + run: | + if [[ "${{ inputs.os }}" == "windows" ]]; then + echo "ext=.exe" >> "$GITHUB_OUTPUT" + else + echo "ext=" >> "$GITHUB_OUTPUT" + fi + + # ── Download binaries ──────────────────────────────────────────── + - name: Download openaev-agent + shell: bash + run: | + URL="${{ inputs.jfrog-base }}/openaev-agent/${{ inputs.os }}/${{ inputs.jfrog-arch }}/openaev-agent-${{ inputs.binary-version }}${{ steps.ext.outputs.ext }}" + echo "⬇️ Downloading openaev-agent from $URL" + curl -fSL --retry 3 --retry-delay 5 -o "openaev-agent${{ steps.ext.outputs.ext }}" "$URL" + chmod +x "openaev-agent${{ steps.ext.outputs.ext }}" 2>/dev/null || true + + # ── Write fake config ──────────────────────────────────────────── + - name: Write smoke-test config + shell: bash + run: | + cat > openaev-agent-config.toml <<'EOF' + debug = false + + [openaev] + url = "http://localhost:1" + token = "smoke-test-fake-token" + unsecured_certificate = false + with_proxy = false + installation_mode = "session-user" + service_name = "OAEVAgentService" + service_full_name = "OAEVAgentService" + tenant_id = "00000000-0000-0000-0000-000000000000" + + [cleanup] + executing_max_time_minutes = 10 + directory_max_time_minutes = 10 + cleanup_interval_seconds = 180 + EOF + + # ── Unix smoke test ────────────────────────────────────────────── + - name: "Smoke test (Unix)" + if: inputs.os != 'windows' + shell: bash + run: | + set +e + PATTERN='connection refused|dial tcp|no such host|connect:|i/o timeout|unreachable|connection error|failed to connect|could not connect|error sending|request error' + + smoke_test() { + local binary="$1" + local log_file="$2" + + echo "🔥 Smoke-testing $binary ..." + ./"$binary" --config openaev-agent-config.toml > "$log_file" 2>&1 & + local pid=$! + + sleep 15 + kill "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + + echo "--- $binary logs ---" + cat "$log_file" + echo "--------------------" + + if grep -qiE "$PATTERN" "$log_file"; then + echo "✅ $binary: connection error detected (binary starts correctly)" + else + echo "❌ $binary: no connection error found in logs" + return 1 + fi + } + + smoke_test "openaev-agent${{ steps.ext.outputs.ext }}" "agent.log" + echo "✅ Smoke test passed" + + # ── Windows smoke test ─────────────────────────────────────────── + - name: "Smoke test (Windows)" + if: inputs.os == 'windows' + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $pattern = 'connection refused|dial tcp|no such host|connect:|i/o timeout|unreachable|connection error|failed to connect|could not connect|error sending|request error' + $failed = 0 + + function Invoke-SmokeTest { + param([string]$Binary, [string]$LogPrefix) + + Write-Host "🔥 Smoke-testing $Binary ..." + $stdout = "$LogPrefix-stdout.log" + $stderr = "$LogPrefix-stderr.log" + + $proc = Start-Process -FilePath ".\$Binary" ` + -ArgumentList "--config", "openaev-agent-config.toml" ` + -RedirectStandardOutput $stdout ` + -RedirectStandardError $stderr ` + -PassThru -NoNewWindow + + $exited = $proc | Wait-Process -Timeout 15 -ErrorAction SilentlyContinue + if (!$proc.HasExited) { + Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 2 + } + + Write-Host "--- $Binary stdout ---" + if (Test-Path $stdout) { Get-Content $stdout } + Write-Host "--- $Binary stderr ---" + if (Test-Path $stderr) { Get-Content $stderr } + Write-Host "----------------------" + + $allLogs = "" + if (Test-Path $stdout) { $allLogs += Get-Content $stdout -Raw } + if (Test-Path $stderr) { $allLogs += Get-Content $stderr -Raw } + + if ($allLogs -match $pattern) { + Write-Host "✅ ${Binary}: connection error detected (binary starts correctly)" + return $true + } else { + Write-Host "❌ ${Binary}: no connection error found in logs" + return $false + } + } + + if (-not (Invoke-SmokeTest "openaev-agent.exe" "agent")) { + Write-Host "❌ Smoke test failed" + exit 1 + } + Write-Host "✅ Smoke test passed" diff --git a/.github/workflows/agent-ci.yml b/.github/workflows/agent-ci.yml index 715d49f2..3ccf93c8 100644 --- a/.github/workflows/agent-ci.yml +++ b/.github/workflows/agent-ci.yml @@ -399,6 +399,55 @@ jobs: if-no-files-found: error retention-days: 14 + # ────────────────────────────────────────────────────────────────── + # Agent & Implant smoke tests (PR-level, latest runners only). + # + # Downloads released binaries from JFrog, starts them with a fake + # config pointing at localhost:1, and asserts they produce a + # connection error (proving startup + network-layer reach). + # ────────────────────────────────────────────────────────────────── + agent-implant-smoke-tests: + name: "💨 Smoke (${{ matrix.os }}-${{ matrix.arch }})" + runs-on: ${{ matrix.runner }} + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + - os: linux + arch: x86_64 + runner: ubuntu-latest + jfrog-arch: x86_64 + - os: linux + arch: arm64 + runner: ubuntu-24.04-arm + jfrog-arch: arm64 + - os: windows + arch: x86_64 + runner: windows-latest + jfrog-arch: x86_64 + - os: windows + arch: arm64 + runner: windows-11-arm + jfrog-arch: arm64 + - os: macos + arch: x86_64 + runner: macos-15-intel + jfrog-arch: x86_64 + - os: macos + arch: arm64 + runner: macos-latest + jfrog-arch: arm64 + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Run agent & implant smoke tests + uses: ./.github/actions/agent-implant-smoke + with: + os: ${{ matrix.os }} + jfrog-arch: ${{ matrix.jfrog-arch }} + # ────────────────────────────────────────────────────────────────── # OS-level installer / upgrade scripts (arch-independent). # Uploaded once per OS so the downstream workflow does not need to diff --git a/.github/workflows/nightly-ci.yml b/.github/workflows/nightly-ci.yml new file mode 100644 index 00000000..cf1fc0ed --- /dev/null +++ b/.github/workflows/nightly-ci.yml @@ -0,0 +1,92 @@ +name: "[Agent] Nightly CI" + +on: + schedule: + - cron: "0 3 * * *" # Every day at 3:00 AM UTC + workflow_dispatch: {} # Manual trigger for on-demand runs + +concurrency: + group: agent-nightly-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + # ────────────────────────────────────────────────────────────────── + # Agent & Implant smoke tests — full OS/arch/version matrix. + # + # Downloads released binaries from JFrog, starts them with a fake + # config pointing at localhost:1, and asserts they produce a + # connection error (proving startup + network-layer reach). + # + # 13 runners covering all supported OS versions and architectures. + # ────────────────────────────────────────────────────────────────── + agent-implant-smoke-tests: + name: "💨 Smoke (${{ matrix.label }})" + runs-on: ${{ matrix.runner }} + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + # ── Linux ──────────────────────────────────────────────── + - label: ubuntu-22.04-x64 + os: linux + runner: ubuntu-22.04 + jfrog-arch: x86_64 + - label: ubuntu-22.04-arm64 + os: linux + runner: ubuntu-22.04-arm + jfrog-arch: arm64 + - label: ubuntu-24.04-x64 + os: linux + runner: ubuntu-24.04 + jfrog-arch: x86_64 + - label: ubuntu-24.04-arm64 + os: linux + runner: ubuntu-24.04-arm + jfrog-arch: arm64 + # ── Windows ────────────────────────────────────────────── + - label: windows-2022-x64 + os: windows + runner: windows-2022 + jfrog-arch: x86_64 + - label: windows-2025-x64 + os: windows + runner: windows-2025 + jfrog-arch: x86_64 + - label: windows-arm64 + os: windows + runner: windows-11-arm + jfrog-arch: arm64 + # ── macOS ──────────────────────────────────────────────── + - label: macos-14-arm64 + os: macos + runner: macos-14 + jfrog-arch: arm64 + - label: macos-15-arm64 + os: macos + runner: macos-15 + jfrog-arch: arm64 + - label: macos-26-arm64 + os: macos + runner: macos-26 + jfrog-arch: arm64 + - label: macos-15-intel + os: macos + runner: macos-15-intel + jfrog-arch: x86_64 + - label: macos-26-intel + os: macos + runner: macos-26-intel + jfrog-arch: x86_64 + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Run agent & implant smoke tests + uses: ./.github/actions/agent-implant-smoke + with: + os: ${{ matrix.os }} + jfrog-arch: ${{ matrix.jfrog-arch }}