Skip to content

fix(k8s): preserve NAS source IP, production values, pin image digest #32

fix(k8s): preserve NAS source IP, production values, pin image digest

fix(k8s): preserve NAS source IP, production values, pin image digest #32

Workflow file for this run

name: ci
on:
push:
branches: ["main"]
tags: ["v*.*.*"]
pull_request:
branches: ["main"]
workflow_dispatch:
env:
# FreeRADIUS version - must match the version installed in Dockerfile
# Actual version is verified post-build via freeradius -v
IMAGE_VERSION: "3.2.8"
# Docker Hub
DOCKERHUB_REGISTRY: docker.io
DOCKERHUB_IMAGE: cepatkilatteknologi/freeradius
# GitHub Container Registry
GHCR_REGISTRY: ghcr.io
GHCR_IMAGE: ${{ github.repository_owner }}/freeradius
jobs:
# ========================
# Job 1: Build & Push Docker Image
# ========================
build-and-push:
name: Build & Push Docker Image
runs-on: ubuntu-latest
environment: ci
permissions:
contents: read
packages: write
id-token: write
attestations: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Login to Docker Hub
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Login to GitHub Container Registry
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata for tags
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE }}
${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE }}
tags: |
type=raw,value=${{ env.IMAGE_VERSION }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=
# Build and push to both registries
- name: Build & Push
id: build-push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
provenance: true
sbom: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Scan for vulnerabilities
- name: Scan for vulnerabilities
uses: aquasecurity/trivy-action@master
if: ${{ github.event_name != 'pull_request' }}
with:
image-ref: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE }}:${{ env.IMAGE_VERSION }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '0'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
if: ${{ github.event_name != 'pull_request' }}
with:
sarif_file: 'trivy-results.sarif'
continue-on-error: true
# Detect actual FreeRADIUS version from built image
- name: Detect FreeRADIUS version
if: ${{ github.event_name != 'pull_request' }}
run: |
# Extract version from the amd64 image
VERSION=$(docker run --rm --platform linux/amd64 ${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE }}:${{ env.IMAGE_VERSION }} freeradius -v 2>/dev/null | grep -oP 'FreeRADIUS Version \K[0-9]+\.[0-9]+\.[0-9]+' || echo "${{ env.IMAGE_VERSION }}")
echo "Detected FreeRADIUS version: $VERSION"
if [ "$VERSION" != "${{ env.IMAGE_VERSION }}" ]; then
echo "::warning::IMAGE_VERSION (${{ env.IMAGE_VERSION }}) does not match actual FreeRADIUS version ($VERSION). Update IMAGE_VERSION in ci.yml."
fi
# Generate artifact attestation for Docker Hub
- name: Generate attestation (Docker Hub)
uses: actions/attest-build-provenance@v2
if: ${{ github.event_name != 'pull_request' }}
with:
subject-name: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE }}
subject-digest: ${{ steps.build-push.outputs.digest }}
push-to-registry: true
# Generate artifact attestation for GHCR
- name: Generate attestation (GHCR)
uses: actions/attest-build-provenance@v2
if: ${{ github.event_name != 'pull_request' }}
with:
subject-name: ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE }}
subject-digest: ${{ steps.build-push.outputs.digest }}
push-to-registry: true
- name: Summary
if: ${{ github.event_name != 'pull_request' }}
run: |
echo "## Docker Image Published 🐳" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Images" >> $GITHUB_STEP_SUMMARY
echo "| Registry | Image |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Docker Hub | \`${{ env.DOCKERHUB_IMAGE }}:${{ env.IMAGE_VERSION }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| GHCR | \`${{ env.GHCR_IMAGE }}:${{ env.IMAGE_VERSION }}\` |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Tags" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Digest:** \`${{ steps.build-push.outputs.digest }}\`" >> $GITHUB_STEP_SUMMARY
# ========================
# Job 2: Test Docker Image
# ========================
test:
name: Test Docker Image
runs-on: ubuntu-latest
needs: build-and-push
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/main' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build test image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
load: true
tags: freeradius:test
cache-from: type=gha
- name: Start services
working-directory: examples/docker
run: |
# Create test .env (no leading whitespace)
cat > .env <<EOF
MYSQL_USER=radius
MYSQL_PASSWORD=testpass123
MYSQL_HOST=db
MYSQL_PORT=3306
MYSQL_DBNAME=radius
MYSQL_ROOT_PASSWORD=rootpass123
RADIUS_SECRET=testing123
HEALTHCHECK_SECRET=testing123
TZ=UTC
RADIUS_ALLOW_PRIVATE_NETWORKS=true
ACCT_REDIS_ENABLED=false
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
EOF
# Strip leading whitespace from .env
sed -i 's/^[[:space:]]*//' .env
# Override image in compose
FREERADIUS_IMAGE=freeradius:test docker compose up -d
- name: Wait for services
working-directory: examples/docker
run: |
echo "Waiting for all services to be healthy..."
for i in $(seq 1 90); do
HEALTHY=$(docker compose ps --format json | grep -c '"healthy"' || true)
TOTAL=$(docker compose ps --format json | wc -l | tr -d ' ')
echo " Healthy: $HEALTHY / $TOTAL ($i/90)"
if [ "$HEALTHY" -ge 3 ] 2>/dev/null; then
echo "All services are healthy!"
break
fi
if [ "$i" -eq 90 ]; then
echo "Timeout waiting for services"
docker compose ps
docker compose logs
exit 1
fi
sleep 5
done
docker compose ps
- name: Test RADIUS authentication
working-directory: examples/docker
run: |
# Verify MySQL is actually accepting connections
docker compose exec -T db mysqladmin ping -uroot -prootpass123 --silent
# Add test user
docker compose exec -T db mysql -uroot -prootpass123 radius -e \
"INSERT INTO radcheck (username, attribute, op, value) VALUES ('testuser', 'Cleartext-Password', ':=', 'testpass') ON DUPLICATE KEY UPDATE value='testpass';"
# Test auth (secret must match clients.conf localhost default)
docker compose exec -T freeradius radtest testuser testpass localhost 0 testing123
- name: Cleanup
if: always()
working-directory: examples/docker
run: docker compose down -v --remove-orphans