Update integration testing for agent enrollments (#41981)
Add nightly testing across the following: OS: mac/Linux/Windows Updates: enabled/disabled Channels (for each of orbit/osquery\desktop): edge/stable Arch: arm/x86 Failures are alerted to Slack.
This commit is contained in:
@@ -0,0 +1,583 @@
|
||||
# This workflow tests enrolling of agents on the supported platforms.
|
||||
#
|
||||
# It starts the latest release of fleet with the "fleetctl preview" command.
|
||||
# It generates the installers for the latest version of fleetd with the
|
||||
# "fleetctl package" command.
|
||||
#
|
||||
# It tests across a matrix of configurations:
|
||||
# OS: mac/Linux/Windows
|
||||
# Updates: enabled/disabled
|
||||
# Channels (for each of orbit/osquery\desktop): edge/stable
|
||||
# Arch: arm/x86
|
||||
#
|
||||
# Troubleshooting
|
||||
# The top two errors seen while developing this:
|
||||
# 1) Jobs are queued waiting for runners long enough for the entire workflow to fail. Scheduling for the middle of the night attempts to mitigate this. Timeouts have been tuned to try to manage it as well.
|
||||
# 2) Network issues (commonly related to Cloudflare tunnels) cause some request to fail.
|
||||
#
|
||||
# Upon failure, the workflow will automatically retry up to 3 times. Notifications are sent to Slack upon failure, and also after the failure has been resolved. After 4 failures, a stronger message will be logged to Slack.
|
||||
name: E2E Test Agents
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Manual
|
||||
inputs:
|
||||
retry:
|
||||
description: 'Number of retries attempted so far'
|
||||
type: number
|
||||
default: 0
|
||||
schedule:
|
||||
- cron: '0 5 * * *' # Nightly 5AM UTC
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/e2e-agent.yml'
|
||||
|
||||
# Each cron schedule gets its own concurrency group. workflow_dispatch and pull_request also get their own.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id}}-${{ github.event.schedule || github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
# fail-fast using bash -eo pipefail. See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
|
||||
# Generate a random UUID to be used for the Cloudflare tunnel subdomain and make it available to later jobs.
|
||||
gen:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
subdomain: ${{ steps.gen.outputs.subdomain }}
|
||||
address: ${{ steps.gen.outputs.address }}
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- id: gen
|
||||
run: |
|
||||
UUID=$(uuidgen)
|
||||
echo "subdomain=fleet-test-$UUID" >> $GITHUB_OUTPUT
|
||||
echo "address=https://fleet-test-$UUID.fleetuem.com" >> $GITHUB_OUTPUT
|
||||
|
||||
run-server:
|
||||
timeout-minutes: 240
|
||||
runs-on: ubuntu-latest
|
||||
needs: gen
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Start tunnel
|
||||
env:
|
||||
CERT_PEM: ${{ secrets.CLOUDFLARE_TUNNEL_FLEETUEM_CERT_B64 }}
|
||||
run: |
|
||||
# Increase maximum receive buffer size to roughly 2.5 MB.
|
||||
# Cloudflared uses quic-go. This buffer holds packets that have been received by the kernel,
|
||||
# but not yet read by the application (quic-go in this case). Once this buffer fills up, the
|
||||
# kernel will drop any new incoming packet.
|
||||
# See https://github.com/quic-go/quic-go/wiki/UDP-Receive-Buffer-Size.
|
||||
sudo sysctl -w net.core.rmem_max=2500000
|
||||
|
||||
# Install cloudflared and run tunnel
|
||||
wget https://github.com/cloudflare/cloudflared/releases/download/2026.3.0/cloudflared-linux-amd64.deb
|
||||
sudo dpkg -i cloudflared-linux-amd64.deb
|
||||
echo "$CERT_PEM" | base64 -d > cert.pem
|
||||
cloudflared tunnel --origincert cert.pem create ${{ needs.gen.outputs.subdomain }}
|
||||
cloudflared tunnel --origincert cert.pem route dns ${{ needs.gen.outputs.subdomain }} ${{ needs.gen.outputs.subdomain }}
|
||||
cloudflared tunnel --origincert cert.pem --url http://localhost:1337 --logfile cloudflared.log run ${{ needs.gen.outputs.subdomain }} &
|
||||
until [[ $(cloudflared tunnel --origincert cert.pem info -o json ${{ needs.gen.outputs.subdomain }} | jq '.conns[0].conns[0].is_pending_reconnect') = false ]]; do
|
||||
echo "Awaiting tunnel ready..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Run Fleet server
|
||||
run: |
|
||||
npm install -g fleetctl
|
||||
fleetctl preview --no-hosts --disable-open-browser
|
||||
fleetctl config set --address ${{ needs.gen.outputs.address }}
|
||||
fleetctl get enroll-secret
|
||||
docker compose -f ~/.fleet/preview/docker-compose.yml logs --follow fleet01 fleet02 &
|
||||
|
||||
# Ensure Fleet server is responding before waiting for enrollments
|
||||
echo "Checking Fleet server health..."
|
||||
HEALTH_CHECK_COUNT=0
|
||||
until HTTP_CODE=$(curl -sS -o /dev/null -w "%{http_code}" http://localhost:1337/healthz) && [[ "$HTTP_CODE" == "200" ]]; do
|
||||
HEALTH_CHECK_COUNT=$((HEALTH_CHECK_COUNT + 1))
|
||||
if [ $HEALTH_CHECK_COUNT -ge 30 ]; then
|
||||
echo "ERROR: Fleet server not responding after 150 seconds"
|
||||
docker ps -a --filter "name=fleet"
|
||||
exit 1
|
||||
fi
|
||||
echo "Health check ${HEALTH_CHECK_COUNT}/30 (HTTP status: ${HTTP_CODE:-connection failed})"
|
||||
sleep 5
|
||||
done
|
||||
echo "Fleet server is responding"
|
||||
|
||||
# Wait for all hosts to enroll, then keep the server alive until the summary job completes.
|
||||
EXPECTED=96 # This needs to be updated when the matrix strategies are updated.
|
||||
START=$(date +%s)
|
||||
while true; do
|
||||
ELAPSED=$(( $(date +%s) - START ))
|
||||
|
||||
# Check and display enrollment status
|
||||
fleetctl get hosts || true
|
||||
HOST_COUNT=$(fleetctl get hosts --json | (grep -v "No hosts found" || true) | wc -l | tr -d ' ')
|
||||
echo "Hosts enrolled: ${HOST_COUNT} / $EXPECTED (${ELAPSED}s)"
|
||||
|
||||
# Check summary job status
|
||||
JOBS_JSON=$(gh api "/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs?per_page=100")
|
||||
SUMMARY_STATUS=$(echo "$JOBS_JSON" | jq -r '[.jobs[] | select(.name == "summary")] | if length > 0 then .[0].status else "not_started" end')
|
||||
echo "Summary job status: $SUMMARY_STATUS"
|
||||
if [ "$SUMMARY_STATUS" = "completed" ]; then
|
||||
echo "Summary job completed, exiting."
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
done
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Show enrolled hosts
|
||||
if: always()
|
||||
run: |
|
||||
fleetctl get hosts
|
||||
fleetctl get hosts --json | jq
|
||||
|
||||
- name: Cleanup tunnel
|
||||
if: always()
|
||||
run: cloudflared tunnel --origincert cert.pem delete --force ${{ needs.gen.outputs.subdomain }} || true
|
||||
|
||||
- name: Print cloudflared logs
|
||||
if: always()
|
||||
run: cat cloudflared.log || true
|
||||
|
||||
- name: Cancel workflow if run-server fails
|
||||
if: failure()
|
||||
run: gh run cancel ${{ github.run_id }} --repo fleetdm/fleet
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FLEET_RELEASE_GITHUB_PAT }}
|
||||
|
||||
login:
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
needs: gen
|
||||
outputs:
|
||||
token: ${{ steps.login.outputs.token }}
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
# Login only here and share the token because otherwise we could hit rate limits.
|
||||
- name: Set Cloudflare DNS
|
||||
run: |
|
||||
# Use Cloudflare's DNS resolver (1.1.1.1) since the tunnel DNS record is managed
|
||||
# by Cloudflare. Apply to all non-loopback interfaces in case traffic routes
|
||||
# through one other than eth0.
|
||||
for iface in $(ip -o link show | awk -F': ' '{print $2}' | grep -v lo); do
|
||||
sudo resolvectl dns "$iface" 1.1.1.1 2>/dev/null || true
|
||||
done
|
||||
|
||||
- id: login
|
||||
name: Attempt login
|
||||
run: |
|
||||
npm install -g fleetctl
|
||||
fleetctl config set --address ${{ needs.gen.outputs.address }}
|
||||
|
||||
# Wait for DNS to propagate by querying Cloudflare's DoH endpoint over HTTPS.
|
||||
# This avoids relying on UDP/53 to 1.1.1.1, which may be blocked on runners.
|
||||
HOSTNAME=$(echo "${{ needs.gen.outputs.address }}" | sed 's|https://||')
|
||||
echo "Waiting for DNS propagation..."
|
||||
DNS_START=$(date +%s)
|
||||
until curl -sf "https://1.1.1.1/dns-query?name=${HOSTNAME}&type=A" \
|
||||
-H 'accept: application/dns-json' | jq -e '.Status == 0' > /dev/null; do
|
||||
ELAPSED=$(( $(date +%s) - DNS_START ))
|
||||
echo "DNS not yet propagated... (${ELAPSED}s)"
|
||||
sleep 2
|
||||
done
|
||||
echo "DNS propagated."
|
||||
|
||||
# Wait for Fleet server to be reachable
|
||||
echo "Waiting for Fleet server to pass health check..."
|
||||
HEALTH_CHECK_START=$(date +%s)
|
||||
until curl -s -o /dev/null -w "%{http_code}" ${{ needs.gen.outputs.address }}/healthz | grep -q "200"; do
|
||||
ELAPSED=$(( $(date +%s) - HEALTH_CHECK_START ))
|
||||
echo "Health check failed... (${ELAPSED}s)"
|
||||
sleep 1
|
||||
done
|
||||
echo "Fleet server is responding, attempting login..."
|
||||
|
||||
LOGIN_START=$(date +%s)
|
||||
until fleetctl login --email admin@example.com --password preview1337#; do
|
||||
ELAPSED=$(( $(date +%s) - LOGIN_START ))
|
||||
echo "Login attempt failed... (${ELAPSED}s)"
|
||||
sleep 1
|
||||
done
|
||||
TOKEN=$(fleetctl config get token | awk '{print $3}')
|
||||
echo "token=$TOKEN" >> $GITHUB_OUTPUT
|
||||
|
||||
fleetd-macos:
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
runner: [ 'macos-15', 'macos-15-intel' ]
|
||||
orbit-channel: [ 'stable', 'edge' ]
|
||||
osqueryd-channel: [ 'stable', 'edge' ]
|
||||
desktop-channel: [ 'stable', 'edge' ]
|
||||
disable-updates: [ true, false ]
|
||||
runs-on: ${{ matrix.runner }}
|
||||
needs: [gen, login]
|
||||
steps:
|
||||
- name: Install fleetctl
|
||||
run: |
|
||||
npm install -g fleetctl
|
||||
fleetctl config set --address ${{ needs.gen.outputs.address }} --token ${{ needs.login.outputs.token }}
|
||||
|
||||
- name: Set Cloudflare DNS
|
||||
run: |
|
||||
# Use Cloudflare's DNS resolver (1.1.1.1) since the tunnel DNS record is managed
|
||||
# by Cloudflare — their resolver sees the new record immediately.
|
||||
for svc in $(networksetup -listallnetworkservices | tail -n +2); do
|
||||
sudo networksetup -setdnsservers "$svc" 1.1.1.1 2>/dev/null || true
|
||||
done
|
||||
sudo dscacheutil -flushcache
|
||||
sudo killall -HUP mDNSResponder || true
|
||||
|
||||
|
||||
- name: Install fleetd
|
||||
run: |
|
||||
ARCH=$(uname -m)
|
||||
sudo hostname macos-${ARCH}-${{ matrix.orbit-channel }}-${{ matrix.osqueryd-channel }}-${{ matrix.desktop-channel }}-${{ matrix.disable-updates }}
|
||||
SECRET_JSON=$(fleetctl get enroll_secret --json --debug)
|
||||
echo $SECRET_JSON
|
||||
SECRET=$(echo $SECRET_JSON | jq -r '.spec.secrets[0].secret')
|
||||
echo "Secret: $SECRET"
|
||||
echo "Hostname: $(hostname -s)"
|
||||
# Instance identifier is needed because macOS runners share UUIDs
|
||||
fleetctl package --type pkg --fleet-url=${{ needs.gen.outputs.address }} --enroll-secret=$SECRET --orbit-channel=${{ matrix.orbit-channel }} --osqueryd-channel=${{ matrix.osqueryd-channel }} --desktop-channel=${{ matrix.desktop-channel }} --fleet-desktop --debug --host-identifier=instance --disable-updates=${{ matrix.disable-updates }}
|
||||
sudo installer -pkg fleet-osquery.pkg -target /
|
||||
ENROLLMENT_START=$(date +%s)
|
||||
until fleetctl get hosts | grep -iF $(hostname -s);
|
||||
do
|
||||
CURRENT_TIME=$(date +%s)
|
||||
ELAPSED=$((CURRENT_TIME - ENROLLMENT_START))
|
||||
echo "Awaiting enrollment... (${ELAPSED}s)"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Check processes
|
||||
run: |
|
||||
sleep 30
|
||||
sudo tail -60 /var/log/orbit/orbit.stderr.log
|
||||
|
||||
echo "Checking if osqueryd is running..."
|
||||
pgrep -x osqueryd || (echo "ERROR: osqueryd is not running" && exit 1)
|
||||
echo "Checking if orbit is running..."
|
||||
pgrep -x orbit || (echo "ERROR: orbit is not running" && exit 1)
|
||||
echo "Checking if fleet-desktop is running..."
|
||||
pgrep -x fleet-desktop || (echo "ERROR: fleet-desktop is not running" && exit 1)
|
||||
echo "All processes are running."
|
||||
|
||||
- name: Print orbit logs
|
||||
if: always()
|
||||
run: |
|
||||
sudo cat /var/log/orbit/*
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
sparse-checkout: |
|
||||
it-and-security/lib/macos/scripts/uninstall-fleetd-macos.sh
|
||||
|
||||
- name: Uninstall Orbit
|
||||
run: |
|
||||
sudo ./it-and-security/lib/macos/scripts/uninstall-fleetd-macos.sh
|
||||
|
||||
fleetd-ubuntu:
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
runner: [ 'ubuntu-24.04', 'ubuntu-24.04-arm' ]
|
||||
orbit-channel: [ 'stable', 'edge' ]
|
||||
osqueryd-channel: [ 'stable', 'edge' ]
|
||||
desktop-channel: [ 'stable', 'edge' ]
|
||||
disable-updates: [ true, false ]
|
||||
runs-on: ${{ matrix.runner }}
|
||||
needs: [gen, login]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Install fleetctl
|
||||
run: |
|
||||
npm install -g fleetctl
|
||||
fleetctl config set --address ${{ needs.gen.outputs.address }} --token ${{ needs.login.outputs.token }}
|
||||
|
||||
- name: Set Cloudflare DNS
|
||||
run: |
|
||||
# Use Cloudflare's DNS resolver (1.1.1.1) since the tunnel DNS record is managed
|
||||
# by Cloudflare. Apply to all non-loopback interfaces in case traffic routes
|
||||
# through one other than eth0.
|
||||
for iface in $(ip -o link show | awk -F': ' '{print $2}' | grep -v lo); do
|
||||
sudo resolvectl dns "$iface" 1.1.1.1 2>/dev/null || true
|
||||
done
|
||||
|
||||
- name: Install Orbit
|
||||
run: |
|
||||
ARCH=$(uname -m)
|
||||
if [ "$ARCH" = "x86_64" ]; then FLEET_ARCH="amd64"; else FLEET_ARCH="arm64"; fi
|
||||
sudo hostnamectl set-hostname ubuntu-${ARCH}-${{ matrix.orbit-channel }}-${{ matrix.osqueryd-channel }}-${{ matrix.desktop-channel }}-${{ matrix.disable-updates }}
|
||||
SECRET_JSON=$(fleetctl get enroll_secret --json --debug)
|
||||
echo $SECRET_JSON
|
||||
SECRET=$(echo $SECRET_JSON | jq -r '.spec.secrets[0].secret')
|
||||
echo "Secret: $SECRET"
|
||||
echo "Hostname: $(hostname -s)"
|
||||
fleetctl package --type deb --fleet-url=${{ needs.gen.outputs.address }} --enroll-secret=$SECRET --orbit-channel=${{ matrix.orbit-channel }} --osqueryd-channel=${{ matrix.osqueryd-channel }} --desktop-channel=${{ matrix.desktop-channel }} --fleet-desktop --debug --arch=$FLEET_ARCH --disable-updates=${{ matrix.disable-updates }}
|
||||
sudo dpkg -i fleet-osquery*
|
||||
ENROLLMENT_START=$(date +%s)
|
||||
until fleetctl get hosts | grep -iF $(hostname -s); do
|
||||
CURRENT_TIME=$(date +%s)
|
||||
ELAPSED=$((CURRENT_TIME - ENROLLMENT_START))
|
||||
echo "Waiting for enrollment... (${ELAPSED}s)"
|
||||
sudo systemctl status orbit.service || true
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Check processes
|
||||
run: |
|
||||
sudo systemctl status orbit.service
|
||||
sleep 30
|
||||
sudo systemctl status orbit.service
|
||||
|
||||
echo "Checking if osqueryd is running..."
|
||||
pgrep -x osqueryd || (echo "ERROR: osqueryd is not running" && exit 1)
|
||||
echo "Checking if orbit is running..."
|
||||
pgrep -x orbit || (echo "ERROR: orbit is not running" && exit 1)
|
||||
# Don't check for Fleet Desktop as it doesn't run in the windowless CI environment.
|
||||
echo "All processes are running."
|
||||
|
||||
- name: Print orbit logs
|
||||
if: always()
|
||||
run: |
|
||||
sudo journalctl -u orbit.service --no-pager
|
||||
|
||||
- name: Uninstall Orbit
|
||||
run: |
|
||||
sudo apt remove fleet-osquery -y
|
||||
|
||||
fleetd-windows:
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
runner: [ 'windows-2025', 'windows-11-arm' ]
|
||||
orbit-channel: [ 'stable', 'edge' ]
|
||||
osqueryd-channel: [ 'stable', 'edge' ]
|
||||
desktop-channel: [ 'stable', 'edge' ]
|
||||
disable-updates: [ true, false ]
|
||||
needs: [gen, login]
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
# We need to use some shenanigans to rename the Windows computer without restarting. Note: Windows computers should not get names longer than 15 characters (confirmed this breaks networking).
|
||||
- name: Rename computer
|
||||
shell: powershell
|
||||
run: |
|
||||
$orbit = "${{ matrix.orbit-channel }}"
|
||||
$osqueryd = "${{ matrix.osqueryd-channel }}"
|
||||
$desktop = "${{ matrix.desktop-channel }}"
|
||||
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'a' } else { 'x' }
|
||||
$disableUpdates = if ("${{ matrix.disable-updates }}" -eq "true") { "t" } else { "f" }
|
||||
$ComputerName = "win-$arch-$($orbit[0])-$($osqueryd[0])-$($desktop[0])-$disableUpdates"
|
||||
echo "Setting computer name to $ComputerName"
|
||||
|
||||
Remove-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -name "Hostname"
|
||||
Remove-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -name "NV Hostname"
|
||||
|
||||
Set-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\Computername\Computername" -name "Computername" -value $ComputerName
|
||||
Set-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Control\Computername\ActiveComputername" -name "Computername" -value $ComputerName
|
||||
Set-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -name "Hostname" -value $ComputerName
|
||||
Set-ItemProperty -path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -name "NV Hostname" -value $ComputerName
|
||||
Set-ItemProperty -path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -name "AltDefaultDomainName" -value $ComputerName
|
||||
Set-ItemProperty -path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -name "DefaultDomainName" -value $ComputerName
|
||||
|
||||
- name: Set Cloudflare DNS
|
||||
shell: powershell
|
||||
run: |
|
||||
# Use Cloudflare's DNS resolver (1.1.1.1) since the tunnel DNS record is managed
|
||||
# by Cloudflare — their resolver sees the new record immediately.
|
||||
# -ErrorAction SilentlyContinue skips adapters (e.g. Hyper-V virtual/internal)
|
||||
# that have no associated DNS client address object.
|
||||
Get-NetAdapter | ForEach-Object { Set-DnsClientServerAddress -InterfaceIndex $_.InterfaceIndex -ServerAddresses "1.1.1.1" -ErrorAction SilentlyContinue }
|
||||
Clear-DnsClientCache
|
||||
|
||||
- name: Install fleetctl
|
||||
shell: bash
|
||||
# On Windows we need to set rootca or tls-skip verify. Since this is a test environment we can skip TLS verification.
|
||||
run: |
|
||||
npm install -g fleetctl
|
||||
fleetctl config set --address ${{ needs.gen.outputs.address }} --token ${{ needs.login.outputs.token }} --tls-skip-verify
|
||||
|
||||
- name: Install WiX toolset (arm runner only)
|
||||
if: matrix.runner == 'windows-11-arm'
|
||||
shell: powershell
|
||||
run: |
|
||||
Invoke-WebRequest -Uri "https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe" -OutFile wix314.exe
|
||||
Start-Process -Wait -FilePath .\wix314.exe -ArgumentList "/quiet"
|
||||
"WIX=C:\Program Files (x86)\WiX Toolset v3.14\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: Build MSI
|
||||
shell: bash
|
||||
run: |
|
||||
SECRET_JSON=$(fleetctl get enroll_secret --json --debug)
|
||||
echo "$SECRET_JSON"
|
||||
# Strip any prefix before the JSON (e.g. "Installing fleetctl... Install completed. ") in case
|
||||
# fleetctl auto-updates and writes an install message to stdout on the same line as the JSON.
|
||||
SECRET=$(echo "$SECRET_JSON" | sed 's/^[^{]*//' | jq -r '.spec.secrets[0].secret')
|
||||
echo "Secret: $SECRET"
|
||||
ARCH=$(echo "$PROCESSOR_ARCHITECTURE" | tr '[:upper:]' '[:lower:]')
|
||||
# WIX env var points to the WiX Toolset install dir (pre-installed on windows-11 runner, installed above on windows-11-arm)
|
||||
fleetctl package --type msi --fleet-url=${{ needs.gen.outputs.address }} --enroll-secret=$SECRET --orbit-channel=${{ matrix.orbit-channel }} --osqueryd-channel=${{ matrix.osqueryd-channel }} --desktop-channel=${{ matrix.desktop-channel }} --fleet-desktop --debug --local-wix-dir="${WIX}bin" --arch=$ARCH --disable-updates=${{ matrix.disable-updates }}
|
||||
|
||||
- name: Install Orbit
|
||||
shell: cmd
|
||||
run: |
|
||||
msiexec /i fleet-osquery.msi /quiet /passive /lv log.txt
|
||||
|
||||
- name: Wait for enrollment
|
||||
shell: powershell
|
||||
run: |
|
||||
$orbit = "${{ matrix.orbit-channel }}"
|
||||
$osqueryd = "${{ matrix.osqueryd-channel }}"
|
||||
$desktop = "${{ matrix.desktop-channel }}"
|
||||
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'a' } else { 'x' }
|
||||
$disableUpdates = if ("${{ matrix.disable-updates }}" -eq "true") { "t" } else { "f" }
|
||||
$ComputerName = "win-$arch-$($orbit[0])-$($osqueryd[0])-$($desktop[0])-$disableUpdates"
|
||||
|
||||
$StartTime = Get-Date
|
||||
do {
|
||||
$hosts = fleetctl get hosts
|
||||
if ($hosts -match $ComputerName) {
|
||||
Write-Host "Success! $ComputerName enrolled."
|
||||
break
|
||||
}
|
||||
$Elapsed = [math]::Round(((Get-Date) - $StartTime).TotalSeconds)
|
||||
Write-Host "Waiting for enrollment... (${Elapsed}s)"
|
||||
Start-Sleep -Seconds 1
|
||||
} while ($true)
|
||||
|
||||
- name: Check processes
|
||||
shell: powershell
|
||||
run: |
|
||||
Start-Sleep -Seconds 30
|
||||
Write-Host "Checking if osqueryd is running..."
|
||||
if (-not (Get-Process -Name "osqueryd" -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "ERROR: osqueryd is not running"
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Checking if orbit is running..."
|
||||
if (-not (Get-Process -Name "orbit" -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "ERROR: orbit is not running"
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Checking if fleet-desktop is running..."
|
||||
if (-not (Get-Process -Name "fleet-desktop" -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "ERROR: fleet-desktop is not running"
|
||||
exit 1
|
||||
}
|
||||
Write-Host "All processes are running."
|
||||
|
||||
- name: Print orbit install log
|
||||
if: always()
|
||||
shell: powershell
|
||||
run: Get-Content log.txt -ErrorAction SilentlyContinue
|
||||
|
||||
- name: Print Orbit logs
|
||||
if: always()
|
||||
shell: powershell
|
||||
run: Get-Content "C:\Windows\system32\config\systemprofile\AppData\Local\FleetDM\Orbit\Logs\orbit-osquery.log" -ErrorAction SilentlyContinue
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
sparse-checkout: |
|
||||
it-and-security/lib/windows/scripts/uninstall-fleetd-windows.ps1
|
||||
|
||||
- name: Uninstall Orbit
|
||||
shell: powershell
|
||||
run: |
|
||||
.\it-and-security\lib\windows\scripts\uninstall-fleetd-windows.ps1
|
||||
|
||||
summary:
|
||||
needs: [fleetd-macos, fleetd-ubuntu, fleetd-windows]
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
steps:
|
||||
- name: Compute next retry
|
||||
id: next-retry
|
||||
run: echo "value=$(( ${{ inputs.retry || 0 }} + 1 ))" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Slack Notification (failure with retries remaining)
|
||||
if: (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && (inputs.retry || 0) > 0)) && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) && (inputs.retry || 0) < 3
|
||||
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_G_HELP_ENGINEERING_WEBHOOK_URL }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: |
|
||||
blocks:
|
||||
- type: "section"
|
||||
text:
|
||||
type: "mrkdwn"
|
||||
text: "*Agent E2E test FAILED* (attempt ${{ steps.next-retry.outputs.value }}/4, retrying...)\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View workflow run>\nThis may not need investigation if it self-resolves on the retry. Look for the next notification of success/failure."
|
||||
|
||||
- name: Slack Notification (all retries exhausted)
|
||||
if: (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) && (inputs.retry || 0) >= 3
|
||||
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_G_HELP_ENGINEERING_WEBHOOK_URL }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: |
|
||||
blocks:
|
||||
- type: "header"
|
||||
text:
|
||||
type: "plain_text"
|
||||
text: "🚨 ALL RETRIES EXHAUSTED — MANUAL INVESTIGATION REQUIRED 🚨"
|
||||
- type: "section"
|
||||
text:
|
||||
type: "mrkdwn"
|
||||
text: "*Agent E2E test FAILED all 4 attempts* :rotating_light:\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View workflow run>"
|
||||
|
||||
- name: Slack Notification (retry success)
|
||||
if: ${{!contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && (inputs.retry || 0) > 0 }}
|
||||
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_G_HELP_ENGINEERING_WEBHOOK_URL }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: |
|
||||
blocks:
|
||||
- type: "section"
|
||||
text:
|
||||
type: "mrkdwn"
|
||||
text: "*Agent E2E test PASSED after ${{ inputs.retry }} retr${{ inputs.retry == 1 && 'y' || 'ies' }}* :white_check_mark:\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View workflow run>\nThe above failure appears to have been transient. No investigation needed unless you see a pattern of repeated failures."
|
||||
|
||||
- name: Retry workflow on failure
|
||||
# Only retry scheduled runs or manual runs that are retries for scheduled runs (inputs.retry > 0)
|
||||
if: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && (inputs.retry || 0) > 0)) && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) && (inputs.retry || 0) < 3 }}
|
||||
run: |
|
||||
gh workflow run e2e-agent.yml --repo ${{ github.repository }} --ref ${{ github.head_ref || github.ref_name }} -f retry=${{ steps.next-retry.outputs.value }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FLEET_RELEASE_GITHUB_PAT }}
|
||||
|
||||
- name: Cancel workflow if any job failed
|
||||
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
|
||||
run: gh run cancel ${{ github.run_id }} --repo fleetdm/fleet
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FLEET_RELEASE_GITHUB_PAT }}
|
||||
@@ -1,483 +0,0 @@
|
||||
# This workflow tests enrolling of agents on the supported platforms,
|
||||
# using the latest version of fleet, fleetctl and orbit.
|
||||
#
|
||||
# It starts the latest release of fleet with the "fleetctl preview" command.
|
||||
# It generates the installers for the latest version of Orbit with the
|
||||
# "fleetctl package" command.
|
||||
name: Test Fleetctl, Orbit & Preview
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Manual
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # Nightly 2AM UTC
|
||||
|
||||
# This allows a subsequently queued workflow run to interrupt previous runs
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id}}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
# fail-fast using bash -eo pipefail. See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
|
||||
shell: bash
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
gen:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
subdomain: ${{ steps.gen.outputs.subdomain }}
|
||||
address: ${{ steps.gen.outputs.address }}
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- id: gen
|
||||
run: |
|
||||
UUID=$(uuidgen)
|
||||
echo "subdomain=fleet-test-$UUID" >> $GITHUB_OUTPUT
|
||||
echo "address=https://fleet-test-$UUID.fleetuem.com" >> $GITHUB_OUTPUT
|
||||
|
||||
run-server:
|
||||
runs-on: ubuntu-latest
|
||||
needs: gen
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Start tunnel
|
||||
env:
|
||||
CERT_PEM: ${{ secrets.CLOUDFLARE_TUNNEL_FLEETUEM_CERT_B64 }}
|
||||
run: |
|
||||
# Increase maximum receive buffer size to roughly 2.5 MB.
|
||||
# Cloudflared uses quic-go. This buffer holds packets that have been received by the kernel,
|
||||
# but not yet read by the application (quic-go in this case). Once this buffer fills up, the
|
||||
# kernel will drop any new incoming packet.
|
||||
# See https://github.com/quic-go/quic-go/wiki/UDP-Receive-Buffer-Size.
|
||||
sudo sysctl -w net.core.rmem_max=2500000
|
||||
|
||||
# Install cloudflared
|
||||
#
|
||||
# We pin to version 2025.5.0 because something broke with 2025.6.1.
|
||||
# 2025.6.1 fails with "failed to create tunnel: Unknown output format 'default'"
|
||||
wget https://github.com/cloudflare/cloudflared/releases/download/2025.5.0/cloudflared-linux-amd64.deb
|
||||
sudo dpkg -i cloudflared-linux-amd64.deb
|
||||
|
||||
# Add secret
|
||||
echo "$CERT_PEM" | base64 -d > cert.pem
|
||||
# Start tunnel
|
||||
cloudflared tunnel --origincert cert.pem --hostname ${{ needs.gen.outputs.subdomain }} --url http://localhost:1337 --name ${{ needs.gen.outputs.subdomain }} --logfile cloudflared.log &
|
||||
until [[ $(cloudflared tunnel --origincert cert.pem info -o json ${{ needs.gen.outputs.subdomain }} | jq '.conns[0].conns[0].is_pending_reconnect') = false ]]; do
|
||||
echo "Awaiting tunnel ready..."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# Download fleet and fleetctl binaries from last successful build on main
|
||||
- name: Download binaries
|
||||
uses: dawidd6/action-download-artifact@5e780fc7bbd0cac69fc73271ed86edf5dcb72d67
|
||||
with:
|
||||
workflow: build-binaries.yaml
|
||||
branch: main
|
||||
name: build
|
||||
path: build
|
||||
check_artifacts: true
|
||||
|
||||
- name: Run Fleet server
|
||||
timeout-minutes: 15
|
||||
run: |
|
||||
chmod +x ./build/fleetctl
|
||||
./build/fleetctl preview --no-hosts --disable-open-browser
|
||||
./build/fleetctl config set --address ${{ needs.gen.outputs.address }}
|
||||
./build/fleetctl get enroll-secret
|
||||
docker compose -f ~/.fleet/preview/docker-compose.yml logs --follow fleet01 fleet02 &
|
||||
|
||||
# Ensure Fleet server is responding before waiting for enrollments
|
||||
echo "Checking Fleet server health..."
|
||||
HEALTH_CHECK_COUNT=0
|
||||
until curl -s -o /dev/null -w "%{http_code}" http://localhost:1337/healthz | grep -q "200"; do
|
||||
HEALTH_CHECK_COUNT=$((HEALTH_CHECK_COUNT + 1))
|
||||
if [ $HEALTH_CHECK_COUNT -ge 30 ]; then
|
||||
echo "ERROR: Fleet server not responding after 150 seconds"
|
||||
docker ps -a --filter "name=fleet"
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
echo "Fleet server is responding"
|
||||
|
||||
# Wait for all of the hosts to be enrolled
|
||||
EXPECTED=3
|
||||
ENROLLMENT_START=$(date +%s)
|
||||
until [ $(./build/fleetctl get hosts --json | grep -v "No hosts found" | wc -l | tee hostcount) -ge $EXPECTED ]; do
|
||||
CURRENT_TIME=$(date +%s)
|
||||
ELAPSED=$((CURRENT_TIME - ENROLLMENT_START))
|
||||
echo -n "Waiting for hosts to enroll (${ELAPSED}s): "
|
||||
cat hostcount | xargs echo -n
|
||||
echo " / $EXPECTED"
|
||||
|
||||
# Show diagnostic info every 60 seconds
|
||||
if [ $((ELAPSED % 60)) -lt 10 ]; then
|
||||
./build/fleetctl get hosts --json || true
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
done
|
||||
echo "Success! $EXPECTED hosts enrolled."
|
||||
|
||||
- name: Show enrolled hosts
|
||||
if: always()
|
||||
run: |
|
||||
./build/fleetctl get hosts --json
|
||||
|
||||
- name: Slack Notification
|
||||
if: failure()
|
||||
uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head.html_url }}",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "Integration test result: ${{ job.status }}\nhttps://github.com/fleetdm/fleet/actions/runs/${{ github.run_id }}\n${{ github.event.pull_request.html_url || github.event.head.html_url }}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_G_HELP_ENGINEERING_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
|
||||
- name: Cleanup tunnel
|
||||
if: always()
|
||||
run: cloudflared tunnel --origincert cert.pem delete --force ${{ needs.gen.outputs.subdomain }}
|
||||
|
||||
- name: Upload cloudflared logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
with:
|
||||
name: cloudflared.log
|
||||
path: cloudflared.log
|
||||
|
||||
login:
|
||||
runs-on: ubuntu-latest
|
||||
needs: gen
|
||||
outputs:
|
||||
token: ${{ steps.login.outputs.token }}
|
||||
steps:
|
||||
# Download fleet and fleetctl binaries from last successful build on main
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Download binaries
|
||||
uses: dawidd6/action-download-artifact@5e780fc7bbd0cac69fc73271ed86edf5dcb72d67
|
||||
with:
|
||||
workflow: build-binaries.yaml
|
||||
branch: main
|
||||
name: build
|
||||
path: build
|
||||
check_artifacts: true
|
||||
|
||||
# Login only here and share the token because otherwise we could hit rate limits.
|
||||
- id: login
|
||||
name: Attempt login
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
chmod +x ./build/fleetctl
|
||||
./build/fleetctl config set --address ${{ needs.gen.outputs.address }}
|
||||
|
||||
# Wait for Fleet server to be reachable first
|
||||
echo "Waiting for Fleet server to be ready..."
|
||||
ATTEMPT=0
|
||||
until curl -s -o /dev/null -w "%{http_code}" ${{ needs.gen.outputs.address }}/healthz | grep -q "200"; do
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
if [ $ATTEMPT -ge 60 ]; then
|
||||
echo "ERROR: Fleet server not reachable after 5 minutes"
|
||||
exit 1
|
||||
fi
|
||||
echo "Waiting for server... attempt $ATTEMPT/60"
|
||||
sleep 5
|
||||
done
|
||||
echo "Fleet server is responding, attempting login..."
|
||||
|
||||
ATTEMPT=0
|
||||
until ./build/fleetctl login --email admin@example.com --password preview1337#; do
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
if [ $ATTEMPT -ge 30 ]; then
|
||||
echo "ERROR: Failed to login after $ATTEMPT attempts"
|
||||
exit 1
|
||||
fi
|
||||
echo "Login attempt $ATTEMPT failed, retrying in 5s..."
|
||||
sleep 5
|
||||
done
|
||||
TOKEN=$(cat ~/.fleet/config| grep token | awk '{ print $2 }')
|
||||
echo "token=$TOKEN" >> $GITHUB_OUTPUT
|
||||
|
||||
orbit-macos:
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
# To run multiple VMs that have the same UUID we need to implement
|
||||
# https://github.com/fleetdm/fleet/issues/8021 (otherwise orbit and osqueryd
|
||||
# in the same host are enrolled as two hosts in Fleet).
|
||||
# Until then we will just test the `stable` channel in all components.
|
||||
#
|
||||
# Alternatively, we can bring back the `edge` channel when we decide to upgrade
|
||||
# our worker to macOS 13 in the future, as they changed the virtualization
|
||||
# layer for 13 and now it has random UUIDs (https://github.com/actions/runner-images/issues/7591).
|
||||
orbit-channel: [ 'stable' ]
|
||||
osqueryd-channel: [ 'stable' ]
|
||||
desktop-channel: [ 'stable' ]
|
||||
runs-on: macos-latest
|
||||
needs: [gen, login]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm install -g fleetctl
|
||||
fleetctl config set --address ${{ needs.gen.outputs.address }} --token ${{ needs.login.outputs.token }}
|
||||
|
||||
- name: Wait until fleet address is reachable and fleet responds
|
||||
run: |
|
||||
until curl -v -fail ${{ needs.gen.outputs.address }}/version;
|
||||
do
|
||||
echo "Awaiting until fleet server responds..."
|
||||
sleep 10
|
||||
done
|
||||
|
||||
- name: Install Orbit
|
||||
run: |
|
||||
sudo hostname macos-orbit-${{ matrix.orbit-channel }}-osqueryd-${{ matrix.osqueryd-channel }}
|
||||
SECRET_JSON=$(fleetctl get enroll_secret --json --debug)
|
||||
echo $SECRET_JSON
|
||||
SECRET=$(echo $SECRET_JSON | jq -r '.spec.secrets[0].secret')
|
||||
echo "Secret: $SECRET"
|
||||
echo "Hostname: $(hostname -s)"
|
||||
fleetctl package --type pkg --fleet-url=${{ needs.gen.outputs.address }} --enroll-secret=$SECRET --orbit-channel=${{ matrix.orbit-channel }} --osqueryd-channel=${{ matrix.osqueryd-channel }} --desktop-channel=${{ matrix.desktop-channel }} --fleet-desktop --debug
|
||||
sudo installer -pkg fleet-osquery.pkg -target /
|
||||
ENROLLMENT_START=$(date +%s)
|
||||
until fleetctl get hosts | grep -iF $(hostname -s);
|
||||
do
|
||||
CURRENT_TIME=$(date +%s)
|
||||
ELAPSED=$((CURRENT_TIME - ENROLLMENT_START))
|
||||
echo "Awaiting enrollment... (${ELAPSED}s)"
|
||||
sleep 10
|
||||
done
|
||||
|
||||
- name: Collect orbit logs
|
||||
if: always()
|
||||
run: |
|
||||
mkdir orbit-logs
|
||||
sudo cp /var/log/orbit/* orbit-logs/
|
||||
|
||||
- name: Upload Orbit logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
with:
|
||||
name: orbit-macos-${{ matrix.orbit-channel }}-${{ matrix.osqueryd-channel }}-${{ matrix.desktop-channel }}-logs
|
||||
path: |
|
||||
orbit-logs
|
||||
|
||||
- name: Uninstall Orbit
|
||||
run: |
|
||||
sudo ./it-and-security/lib/macos/scripts/uninstall-fleetd-macos.sh
|
||||
|
||||
orbit-ubuntu:
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
# To run multiple VMs that have the same UUID we need to implement
|
||||
# https://github.com/fleetdm/fleet/issues/8021 (otherwise orbit and osqueryd
|
||||
# in the same host are enrolled as two hosts in Fleet).
|
||||
# Until then we will just test the `stable` channel in all components.
|
||||
orbit-channel: [ 'stable' ]
|
||||
osqueryd-channel: [ 'stable' ]
|
||||
desktop-channel: [ 'stable' ]
|
||||
runs-on: ubuntu-latest
|
||||
needs: [gen, login]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm install -g fleetctl
|
||||
fleetctl config set --address ${{ needs.gen.outputs.address }} --token ${{ needs.login.outputs.token }}
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Build Fleetctl
|
||||
run: make fleetctl
|
||||
|
||||
- name: Wait until fleet address is reachable and fleet responds
|
||||
run: |
|
||||
until curl -v -fail ${{ needs.gen.outputs.address }}/version; do
|
||||
echo "Awaiting until fleet server responds..."
|
||||
sleep 10
|
||||
done
|
||||
|
||||
- name: Install Orbit
|
||||
run: |
|
||||
sudo hostname ubuntu-orbit-${{ matrix.orbit-channel }}-osqueryd-${{ matrix.osqueryd-channel }}
|
||||
chmod +x ./build/fleetctl
|
||||
SECRET_JSON=$(fleetctl get enroll_secret --json --debug)
|
||||
echo $SECRET_JSON
|
||||
SECRET=$(echo $SECRET_JSON | jq -r '.spec.secrets[0].secret')
|
||||
echo "Secret: $SECRET"
|
||||
echo "Hostname: $(hostname -s)"
|
||||
./build/fleetctl package --type deb --fleet-url=${{ needs.gen.outputs.address }} --enroll-secret=$SECRET --orbit-channel=${{ matrix.orbit-channel }} --osqueryd-channel=${{ matrix.osqueryd-channel }} --desktop-channel=${{ matrix.desktop-channel }} --fleet-desktop --debug
|
||||
sudo dpkg -i fleet-osquery*
|
||||
ENROLLMENT_START=$(date +%s)
|
||||
until fleetctl get hosts | grep -iF $(hostname -s); do
|
||||
CURRENT_TIME=$(date +%s)
|
||||
ELAPSED=$((CURRENT_TIME - ENROLLMENT_START))
|
||||
echo "Awaiting enrollment... (${ELAPSED}s)"
|
||||
sudo systemctl status orbit.service || true
|
||||
sleep 10
|
||||
done
|
||||
|
||||
- name: Collect orbit logs
|
||||
if: always()
|
||||
run: |
|
||||
sudo journalctl -u orbit.service > orbit-logs
|
||||
|
||||
- name: Upload Orbit logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
with:
|
||||
name: orbit-ubuntu-${{ matrix.orbit-channel }}-${{ matrix.osqueryd-channel }}-${{ matrix.desktop-channel }}-logs
|
||||
path: |
|
||||
orbit-logs
|
||||
|
||||
- name: Uninstall Orbit
|
||||
run: |
|
||||
sudo apt remove fleet-osquery -y
|
||||
|
||||
orbit-windows-build:
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
# To run multiple VMs that have the same UUID we need to implement
|
||||
# https://github.com/fleetdm/fleet/issues/8021 (otherwise orbit and osqueryd
|
||||
# in the same host are enrolled as two hosts in Fleet).
|
||||
# Until then we will just test the `stable` channel in all components.
|
||||
orbit-channel: [ 'stable' ]
|
||||
osqueryd-channel: [ 'stable' ]
|
||||
desktop-channel: [ 'stable' ]
|
||||
runs-on: ubuntu-latest
|
||||
needs: [gen, login]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
docker pull fleetdm/wix:latest &
|
||||
npm install -g fleetctl
|
||||
fleetctl config set --address ${{ needs.gen.outputs.address }} --token ${{ needs.login.outputs.token }}
|
||||
|
||||
- name: Wait until fleet address is reachable and fleet responds
|
||||
run: |
|
||||
until curl -v -fail ${{ needs.gen.outputs.address }}/version;
|
||||
do
|
||||
echo "Awaiting until fleet server responds..."
|
||||
sleep 10
|
||||
done
|
||||
|
||||
- name: Build Orbit
|
||||
run: |
|
||||
SECRET_JSON=$(fleetctl get enroll_secret --json --debug)
|
||||
echo $SECRET_JSON
|
||||
SECRET=$(echo $SECRET_JSON | jq -r '.spec.secrets[0].secret')
|
||||
echo "Secret: $SECRET"
|
||||
echo "Hostname: $(hostname -s)"
|
||||
fleetctl package --type msi --fleet-url=${{ needs.gen.outputs.address }} --enroll-secret=$SECRET --orbit-channel=${{ matrix.orbit-channel }} --osqueryd-channel=${{ matrix.osqueryd-channel }} --desktop-channel=${{ matrix.desktop-channel }} --fleet-desktop --debug
|
||||
mv fleet-osquery.msi orbit-${{ matrix.orbit-channel }}-osqueryd-${{ matrix.osqueryd-channel }}-desktop-${{ matrix.desktop-channel }}.msi
|
||||
|
||||
- name: Upload MSI
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
with:
|
||||
name: orbit-${{ matrix.orbit-channel }}-osqueryd-${{ matrix.osqueryd-channel }}-desktop-${{ matrix.desktop-channel }}.msi
|
||||
path: orbit-${{ matrix.orbit-channel }}-osqueryd-${{ matrix.osqueryd-channel }}-desktop-${{ matrix.desktop-channel }}.msi
|
||||
|
||||
orbit-windows:
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
# To run multiple VMs that have the same UUID we need to implement
|
||||
# https://github.com/fleetdm/fleet/issues/8021 (otherwise orbit and osqueryd
|
||||
# in the same host are enrolled as two hosts in Fleet).
|
||||
# Until then we will just test the `stable` channel in all components.
|
||||
orbit-channel: [ 'stable' ]
|
||||
osqueryd-channel: [ 'stable' ]
|
||||
desktop-channel: [ 'stable' ]
|
||||
needs: [gen, login, orbit-windows-build]
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
npm install -g fleetctl
|
||||
fleetctl config set --address ${{ needs.gen.outputs.address }} --token ${{ needs.login.outputs.token }} --tls-skip-verify
|
||||
|
||||
- name: Download MSI
|
||||
id: download
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
name: orbit-${{ matrix.orbit-channel }}-osqueryd-${{ matrix.osqueryd-channel }}-desktop-${{ matrix.desktop-channel }}.msi
|
||||
|
||||
- name: Install Orbit
|
||||
shell: cmd
|
||||
run: |
|
||||
msiexec /i ${{steps.download.outputs.download-path}}\orbit-${{ matrix.orbit-channel }}-osqueryd-${{ matrix.osqueryd-channel }}-desktop-${{ matrix.desktop-channel }}.msi /quiet /passive /lv log.txt
|
||||
sleep 120
|
||||
|
||||
# We can't very accurately check the install on these Windows hosts since the hostnames tend to
|
||||
# overlap and we can't control the hostnames. Instead we just return and have the run-server job
|
||||
# wait until the expected number of hosts enroll.
|
||||
|
||||
- name: Upload orbit install log
|
||||
if: always()
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
with:
|
||||
name: msiexec-install-log
|
||||
path: log.txt
|
||||
|
||||
- name: Upload Orbit logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||
with:
|
||||
name: orbit-windows-${{ matrix.orbit-channel }}-${{ matrix.osqueryd-channel }}-${{ matrix.desktop-channel }}-logs
|
||||
path: C:\Windows\system32\config\systemprofile\AppData\Local\FleetDM\Orbit\Logs\orbit-osquery.log
|
||||
Reference in New Issue
Block a user