@@ -26,59 +26,13 @@ jobs:
2626 - name : Install host packages
2727 run : sudo apt-get update && sudo apt-get -y install jq
2828
29- - name : Install LXD
30- run : |
31- sudo snap install lxd
32- # Wait for LXD daemon to start
33- echo "Waiting for LXD daemon to start..."
34- sleep 15
35- # Initialize LXD with default settings
36- sudo lxd init --auto
37- # Add runner to lxd group
38- sudo usermod -a -G lxd runner
39- # Fix socket permissions for CI environment
40- sudo chmod 666 /var/snap/lxd/common/lxd/unix.socket
41-
42- echo "Configuring LXD networking (lxdbr0 with IPv4 NAT, IPv6 disabled)"
43- # Ensure managed bridge exists and is configured for IPv4 NAT and DNS
44- sudo lxc network show lxdbr0 || true
45- sudo lxc network set lxdbr0 ipv4.address auto
46- sudo lxc network set lxdbr0 ipv4.nat true
47- sudo lxc network set lxdbr0 ipv6.address none || true
48- sudo lxc network set lxdbr0 ipv6.nat false || true
49- sudo lxc network set lxdbr0 dns.mode managed || true
50- sudo lxc network set lxdbr0 dns.domain lxd || true
51- sudo lxc network set lxdbr0 dns.nameservers 1.1.1.1,8.8.8.8 || true
52- # Make sure default profile attaches NIC to lxdbr0
53- sudo lxc profile device add default eth0 nic name=eth0 network=lxdbr0 || true
54-
55- # Helpful sysctl in some CI hosts (usually already enabled by LXD rules)
56- sudo sysctl -w net.ipv4.ip_forward=1 || true
57- sudo sysctl -w net.ipv4.conf.all.forwarding=1 net.ipv4.conf.default.forwarding=1 || true
58-
59- # Ensure NAT and forwarding rules for lxdbr0 on CI hosts with restrictive FORWARD policies
60- EXTIF=$(ip -4 route show default | awk '{print $5}' | head -n1)
61- SUBNET=$(ip -4 addr show dev lxdbr0 | awk '/inet /{print $2}')
62- echo "Ensuring iptables NAT and FORWARD rules for $SUBNET -> $EXTIF"
63- sudo iptables -t nat -C POSTROUTING -s "$SUBNET" -o "$EXTIF" -j MASQUERADE 2>/dev/null || sudo iptables -t nat -A POSTROUTING -s "$SUBNET" -o "$EXTIF" -j MASQUERADE
64- sudo iptables -C FORWARD -i lxdbr0 -o "$EXTIF" -j ACCEPT 2>/dev/null || sudo iptables -I FORWARD 1 -i lxdbr0 -o "$EXTIF" -j ACCEPT
65- sudo iptables -C FORWARD -i "$EXTIF" -o lxdbr0 -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || sudo iptables -I FORWARD 1 -i "$EXTIF" -o lxdbr0 -m state --state RELATED,ESTABLISHED -j ACCEPT
66-
67- # Some hosts default to DROP on FORWARD; set ACCEPT and enable bridge nf hooks
68- sudo modprobe br_netfilter || true
69- sudo sysctl -w net.bridge.bridge-nf-call-iptables=1 net.bridge.bridge-nf-call-ip6tables=1 || true
70- sudo iptables -P FORWARD ACCEPT || true
71-
72- # Relax reverse path filtering that can drop forwarded replies in some clouds
73- sudo sysctl -w net.ipv4.conf.all.rp_filter=0 net.ipv4.conf.default.rp_filter=0 || true
74- sudo sysctl -w net.ipv4.conf.lxdbr0.rp_filter=0 || true
75- sudo sysctl -w net.ipv4.conf.$EXTIF.rp_filter=0 || true
76-
77- # Test basic LXD functionality
78- sudo lxc list
79-
8029 - name : Check out repository
81- uses : actions/checkout@v4
30+ uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
31+ with :
32+ persist-credentials : false
33+
34+ - name : Install and configure LXD
35+ run : sudo ./.github/scripts/setup-lxd.sh
8236
8337 - name : Clone full repository so we can push
8438 run : git fetch --prune --unshallow
@@ -146,136 +100,58 @@ jobs:
146100 ssh_pwauth: false
147101 EOF
148102
149- sudo lxc launch ${LXD_IMAGE} "$NAME" --vm -c user.user-data="$(cat user-data.yaml)"
103+ sudo lxc launch ${LXD_IMAGE} "$NAME" --vm -c user.user-data="$(cat user-data.yaml)" limits.memory=4GiB
150104
151- - name : Wait for VM to be ready
105+ - name : Wait for VM
152106 run : |
153- echo "Waiting for VM to be fully initialized..."
154-
155- # Debug: Check VM status
156- echo "=== VM Status ==="
107+ set -euo pipefail
157108 NAME="${{ matrix.suite }}-${{ env.PLATFORM }}"
158- sudo lxc list
159- sudo lxc info $NAME
160-
161- # We check for basic readiness instead of waiting for cloud-init completion
162- timeout=300
163- elapsed=0
164- while [ $elapsed -lt $timeout ]; do
165- # Debug: Show what we're checking
166- echo "=== Checking VM readiness (attempt $((elapsed/10 + 1))) ==="
167-
168- # Check if VM is running and we can execute commands
169- if sudo lxc list --format=csv | grep -q "$NAME.*RUNNING"; then
170- echo "VM is in RUNNING state"
171-
172- # Try to execute a simple command to verify VM is responsive
173- if sudo lxc exec $NAME -- echo "VM is responsive" 2>/dev/null; then
174- echo "VM is responsive to commands"
175-
176- # Check if basic system is ready (systemd, if available)
177- if sudo lxc exec $NAME -- systemctl is-system-running --wait 2>/dev/null || true; then
178- echo "VM system is ready!"
179- break
180- else
181- echo "VM system not fully ready yet, but responsive"
182- # For basic VMs, being responsive might be enough
183- if [ $elapsed -ge 60 ]; then # After 60 seconds, if responsive, consider ready
184- echo "VM has been responsive for sufficient time, considering ready"
185- break
186- fi
187- fi
188- else
189- echo "VM not responsive to commands yet"
190- fi
191- else
192- echo "VM not in RUNNING state yet"
193- fi
194-
195- echo "Waiting for VM to be ready... ($elapsed/$timeout seconds)"
109+ for i in $(seq 1 30); do
110+ lxc list --format=csv | grep -q "$NAME.*RUNNING" && break
196111 sleep 10
197- elapsed=$((elapsed + 10))
198112 done
113+ lxc list --format=csv | grep -q "$NAME.*RUNNING" || { echo "VM never reached RUNNING"; lxc info "$NAME"; exit 1; }
199114
200- if [ $elapsed -ge $timeout ]; then
201- echo "=== TIMEOUT DEBUGGING ==="
202- echo "Final VM status:"
203- sudo lxc list
204- sudo lxc info $NAME
205- echo "Timeout waiting for VM to be ready"
206- exit 1
207- fi
208-
209- echo "VM is ready for use!"
210-
211- - name : Export VM IP for Kitchen (KITCHEN_HOST)
115+ - name : Export VM IPv4 for Kitchen
212116 run : |
213117 set -euo pipefail
214118 NAME="${{ matrix.suite }}-${{ env.PLATFORM }}"
215-
216- # Wait up to 120s for an IP address to appear and scan all interfaces, not just eth0
217- timeout=120
218- interval=5
219- ipv4=""
220- ipv6=""
221- elapsed=0
222- while [ $elapsed -lt $timeout ]; do
223- ipv4=$(sudo lxc list "$NAME" --format=json | jq -r '.[0].state.network | to_entries[] | .value.addresses[]? | select(.family=="inet" and .scope=="global") | .address' | head -n1 || true)
224- ipv6=$(sudo lxc list "$NAME" --format=json | jq -r '.[0].state.network | to_entries[] | .value.addresses[]? | select(.family=="inet6" and .scope=="global") | .address' | head -n1 || true)
225- if [ -n "$ipv4" ] || [ -n "$ipv6" ]; then
226- break
227- fi
228- echo "Waiting for VM IP... ($elapsed/$timeout seconds)"
229- sleep $interval
230- elapsed=$((elapsed + interval))
119+ for i in $(seq 1 24); do
120+ ipv4=$(lxc list "$NAME" --format=json | jq -r '.[0].state.network | to_entries[] | .value.addresses[]? | select(.family=="inet" and .scope=="global") | .address' | head -n1)
121+ [ -n "$ipv4" ] && break
122+ sleep 5
231123 done
232-
233- # Fallback: query IPs from inside the VM if LXD hasn't reported them yet
234- if [ -z "$ipv4" ] && [ -z "$ipv6" ]; then
235- echo "Falling back to querying IPs from inside the VM..."
236- set +e
237- ipv4=$(sudo lxc exec "$NAME" -- bash -lc "ip -o -4 addr show scope global | awk '{print \\4}' | cut -d/ -f1 | head -n1" 2>/dev/null)
238- ipv6=$(sudo lxc exec "$NAME" -- bash -lc "ip -o -6 addr show scope global | awk '{print \\4}' | cut -d/ -f1 | head -n1" 2>/dev/null)
239- set -e
240- fi
241-
242- if [ -n "$ipv4" ]; then
243- echo "Using IPv4 $ipv4 for KITCHEN_HOST"
244- echo "KITCHEN_HOST=$ipv4" >> "$GITHUB_ENV"
245- elif [ -n "$ipv6" ]; then
246- echo "Using IPv6 $ipv6 for KITCHEN_HOST"
247- echo "KITCHEN_HOST=$ipv6" >> "$GITHUB_ENV"
248- else
249- echo "Failed to discover a VM IP address"
250- sudo lxc list "$NAME"
251- sudo lxc info "$NAME" || true
252- exit 1
253- fi
124+ [ -n "$ipv4" ] || { echo "No VM IPv4 found"; lxc info "$NAME"; exit 1; }
125+ echo "KITCHEN_HOST=$ipv4" >> "$GITHUB_ENV"
254126
255127 - name : Run kitchen test
256128 if : ${{ !contains(steps.commit.outputs.message, 'only-validate-profile') }}
257129 continue-on-error : true
258130 run : bundle exec kitchen test --destroy=always ${{ matrix.suite }}-${{ env.PLATFORM }}
259131
260- - name : Save our ${{ matrix.suite }} results summary
132+ - name : Save results summary JSON
261133 continue-on-error : true
262- if : ${{ !contains(steps.commit.outputs.message, 'only-validate-profile') }}
263- uses : mitre/saf_action@v1.5.2
134+ uses : mitre/saf_action@a19f76ed3721c14aa8a1afb8eded117d8bf16a5d # v1.9.0
264135 with :
265- command_string : " view summary -j -i spec/results/${{ env.PLATFORM }}_${{ matrix.suite }}.json -o spec/results/${{ env.PLATFORM }}_${{ matrix.suite }}-data.json"
136+ command_string : " view summary -f json -i spec/results/${{ env.PLATFORM }}_${{ matrix.suite }}.json -o spec/results/${{ env.PLATFORM }}_${{ matrix.suite }}-data.json"
266137
267- - name : Save Test Result JSON
268- if : ${{ !contains(steps.commit.outputs.message, 'only-validate-profile') }}
269- uses : actions/upload-artifact@v4
138+ - name : Upload Test Result JSON
139+ uses : actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
270140 with :
271141 name : ${{ env.PLATFORM }}_${{ matrix.suite }}.json
272142 path : spec/results/
273143
274- - name : Upload ${{ matrix.suite }} to Heimdall
275- if : ${{ !contains(steps.commit.outputs.message, 'only-validate-profile') }}
144+ - name : Upload to Heimdall
276145 continue-on-error : true
146+ env :
147+ SAF_HEIMDALL_UPLOAD_KEY : ${{ secrets.SAF_HEIMDALL_UPLOAD_KEY }}
277148 run : |
278- curl -# -s -F data=@spec/results/${{ env.PLATFORM }}_${{ matrix.suite }}.json -F "filename=${{ env.PLATFORM }}_${{ matrix.suite }}-${{ env.COMMIT_SHORT_SHA }}.json" -F "public=true" -F "evaluationTags=${{ env.COMMIT_SHORT_SHA }},${{ github.repository }},${{ github.workflow }}" -H "Authorization: Api-Key ${{ secrets.SAF_HEIMDALL_UPLOAD_KEY }}" "${{ vars.SAF_HEIMDALL_URL }}/evaluations"
149+ curl -# -s -F data=@spec/results/${{ env.PLATFORM }}_${{ matrix.suite }}.json \
150+ -F "filename=${{ env.PLATFORM }}_${{ matrix.suite }}-${{ env.COMMIT_SHORT_SHA }}.json" \
151+ -F "public=true" \
152+ -F "evaluationTags=${{ env.COMMIT_SHORT_SHA }},${{ github.repository }},${{ github.workflow }}" \
153+ -H "Authorization: Api-Key $SAF_HEIMDALL_UPLOAD_KEY" \
154+ "${{ vars.SAF_HEIMDALL_URL }}/evaluations"
279155
280156 - name : Display our ${{ matrix.suite }} results summary
281157 if : ${{ !contains(steps.commit.outputs.message, 'only-validate-profile') }}
@@ -290,13 +166,11 @@ jobs:
290166 cat spec/results/${{ env.PLATFORM }}_${{ matrix.suite }}-data.json | python markdown-summary.py > spec/results/${{ env.PLATFORM }}_${{ matrix.suite }}-markdown-summary.md
291167 cat spec/results/${{ env.PLATFORM }}_${{ matrix.suite }}-markdown-summary.md >> $GITHUB_STEP_SUMMARY
292168
293- - name : Ensure the scan meets our ${{ matrix.suite }} results threshold
294- if : ${{ !contains(steps.commit.outputs.message, 'only-validate-profile') }}
295- uses : mitre/saf_action@v1.5.2
169+ - name : Validate scan threshold
170+ uses : mitre/saf_action@a19f76ed3721c14aa8a1afb8eded117d8bf16a5d # v1.9.0
296171 with :
297172 command_string : " validate threshold -i spec/results/${{ env.PLATFORM }}_${{ matrix.suite }}.json -F ${{ matrix.suite }}.threshold.yml"
298173
299174 - name : Cleanup ephemeral SSH key
300175 if : always()
301- run : |
302- rm -f "$RUNNER_TEMP/kitchen_ed25519" "$RUNNER_TEMP/kitchen_ed25519.pub"
176+ run : rm -f "$RUNNER_TEMP/kitchen_ed25519" "$RUNNER_TEMP/kitchen_ed25519.pub"
0 commit comments