diff --git a/.github/DOCKER_SECURITY.md b/.github/DOCKER_SECURITY.md index 8cc99bb..f6f0b1f 100644 --- a/.github/DOCKER_SECURITY.md +++ b/.github/DOCKER_SECURITY.md @@ -6,48 +6,68 @@ This document outlines our approach to maintaining secure Docker images for the Keeping system dependencies updated is crucial for security. Our strategy includes: -1. **Automated Security Updates** - - Base images are updated regularly with the latest security patches - - `unattended-upgrades` is installed to automatically apply security updates - - We use `apt-get upgrade --security` to prioritize security-related updates - - Full system update with `apt-get dist-upgrade` to handle package dependencies +1. **Regular Base Image Updates** + - We use specific dated versions of Debian slim images (`bullseye-YYYYMMDD-slim`) + - Images are rebuilt weekly via CI to incorporate latest security patches + - Each build uses `--pull` to ensure we get the latest base image versions -2. **Continuous Integration Checks** +2. **Full Package Updates During Build** + - Every build performs `apt-get update && apt-get upgrade` in both build and runtime stages + - We explicitly remove package lists after updates to reduce image size + - Only necessary runtime packages are installed in the final image + +3. **Minimized Attack Surface** + - Non-root user execution with specific UID/GID + - Removal of setuid/setgid permissions + - Secure permissions on system files + - Use of tini as init process + - Minimal set of installed packages + +4. **Continuous Monitoring** - Weekly automated builds via GitHub Actions - - Security scanning on every build with Trivy - - Detection of outdated dependencies with separate vulnerability reports + - Security scanning with Trivy and Docker Scout + - Detailed vulnerability reports for OS and application dependencies - Automatic failure on critical vulnerabilities -3. **Multi-stage Build Optimization** - - Builder stage includes only development dependencies - - Runtime stage includes only production dependencies - - Each stage performs full security updates +## How System Updates Are Applied -4. **Minimal Attack Surface** - - Distroless or slim base images when possible - - Unnecessary packages are removed - - Non-root user execution - - Removal of setuid/setgid permissions - - Only required capabilities are enabled +System dependencies are updated at several points: -## How Updates Are Applied - -System dependencies are updated at multiple points: - -1. **During Image Build** +1. **During Image Build Time** ```dockerfile RUN apt-get update -y && \ - apt-get upgrade -y --security && \ - apt-get dist-upgrade -y + apt-get upgrade -y && \ + apt-get clean && \ + rm -f /var/lib/apt/lists/*_* ``` -2. **Automatically at Runtime** - - The `unattended-upgrades` package applies security updates automatically - - Configuration prioritizes official security updates - -3. **Via Scheduled Rebuilds** +2. **Via Scheduled Rebuilds** - Weekly GitHub Actions workflow rebuilds the image with latest dependencies - Base image is pulled with `--pull` flag to ensure latest version + - No-cache builds ensure all layers are rebuilt with fresh packages + +3. **During Deployment** + - Images are rebuilt for each deployment + - CI/CD pipeline includes security scanning before deployment + +## Security Scanning Process + +Our Docker images undergo multiple security scans: + +1. **Trivy Scanning** + - OS package vulnerabilities detection + - Application dependency vulnerabilities detection + - Configuration issue detection + +2. **Docker Scout** + - Deep analysis of base image security + - Comprehensive CVE detection + - Dependency analysis + +3. **Artifact Storage** + - Scan results are stored as GitHub Actions artifacts + - Summary reports are generated for easy review + - Historical data allows tracking security improvements ## Manual Update Process @@ -59,28 +79,26 @@ To manually update the Docker image with the latest system dependencies: ``` 2. This script will: - - Check for newer versions of the base image - - Update the Dockerfile if needed - - Rebuild the image with `--no-cache --pull` to ensure fresh dependencies - - Run a security scan on the updated image + - Build a fresh image with latest dependencies using `--no-cache --pull` + - Run a security scan if Trivy is available + - Provide guidance on next steps -3. Verify that tests pass with the updated image before deployment +3. Verify the updated image works as expected before deployment -## Security Monitoring +## Best Practices We Follow -We continuously monitor for security issues through: +- We pin specific versions of Elixir, OTP, and Debian in our Dockerfile +- Multi-stage builds minimize the final image size +- We use non-root users with minimal permissions +- We secure system files and directories +- We remove unnecessary files and packages +- We use tini as an init process for proper signal handling +- We scan for vulnerabilities in both OS and application dependencies +- We update base images regularly (at least monthly) -1. GitHub Security tab showing Trivy scan results -2. Weekly automated security scans -3. Dependency updates via Dependabot -4. Container registry vulnerability scanning +## Improvement Roadmap -## Best Practices - -- Never use the `latest` tag in production -- Pin specific versions in Dockerfile (ARG declarations) -- Update base images at least monthly -- Review and update the security strategy quarterly -- Use multi-stage builds to minimize final image size -- Implement least privilege principle (non-root user, minimal capabilities) -- Keep secrets out of the image (use environment variables or secrets management) \ No newline at end of file +- Consider distroless images for further attack surface reduction +- Implement auto-update PR creation for base image versions +- Add dependency confusion detection +- Implement more granular vulnerability allowlisting for false positives \ No newline at end of file diff --git a/.github/workflows/dependency-updates.yml b/.github/workflows/dependency-updates.yml new file mode 100644 index 0000000..3b5023b --- /dev/null +++ b/.github/workflows/dependency-updates.yml @@ -0,0 +1,122 @@ +name: System Dependency Updates + +on: + schedule: + - cron: '0 0 * * 0' # Weekly on Sunday at midnight + workflow_dispatch: # Allow manual triggering + +jobs: + update-dependencies: + name: Rebuild with Latest Dependencies + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build with latest dependencies + uses: docker/build-push-action@v5 + with: + context: . + tags: aprs:latest + load: true + no-cache: true + pull: true + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: aprs:latest + format: "sarif" + output: "trivy-results.sarif" + severity: "CRITICAL,HIGH" + timeout: "10m" + scanners: "vuln" + + - name: Upload Trivy scan results + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' + category: 'Dependency Update Scan' + + - name: Generate vulnerability summary + if: always() + run: | + echo "# System Dependency Update Report" > dep-update-report.md + echo "## Date: $(date -u +'%Y-%m-%d')" >> dep-update-report.md + echo "" >> dep-update-report.md + + echo "### OS Package Vulnerabilities" >> dep-update-report.md + echo "```" >> dep-update-report.md + trivy image --severity HIGH,CRITICAL --no-progress aprs:latest >> dep-update-report.md + echo "```" >> dep-update-report.md + + - name: Upload vulnerability summary + if: always() + uses: actions/upload-artifact@v3 + with: + name: dependency-update-report + path: dep-update-report.md + retention-days: 30 + + - name: Check if base image is outdated + id: check_base_image + run: | + # Extract current Debian version from Dockerfile + CURRENT_VERSION=$(grep 'DEBIAN_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"') + echo "Current Debian version: $CURRENT_VERSION" + + # Get latest version from Docker Hub + LATEST_VERSION=$(curl -s https://registry.hub.docker.com/v2/repositories/library/debian/tags | \ + grep -o '"name":"[^"]*-slim"' | grep bullseye | sort -r | head -1 | cut -d'"' -f4) + echo "Latest Debian version: $LATEST_VERSION" + + if [ "$CURRENT_VERSION" != "$LATEST_VERSION" ] && [ ! -z "$LATEST_VERSION" ]; then + echo "Base image is outdated. Current: $CURRENT_VERSION, Latest: $LATEST_VERSION" + echo "outdated=true" >> $GITHUB_OUTPUT + echo "latest_version=$LATEST_VERSION" >> $GITHUB_OUTPUT + else + echo "Base image is up to date or couldn't determine latest version" + echo "outdated=false" >> $GITHUB_OUTPUT + fi + + - name: Create PR for base image update + if: steps.check_base_image.outputs.outdated == 'true' + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore: update Debian base image to ${{ steps.check_base_image.outputs.latest_version }}" + title: "Update Debian base image to ${{ steps.check_base_image.outputs.latest_version }}" + body: | + This PR updates the Debian base image to the latest version: + - Current: `${{ env.CURRENT_VERSION }}` + - Latest: `${{ steps.check_base_image.outputs.latest_version }}` + + This update includes the latest security patches and system dependencies. + + *This PR was automatically created by the dependency updates workflow.* + branch: update-base-image + base: main + delete-branch: true + add-paths: | + Dockerfile + + - name: Summary + if: always() + run: | + echo "## Dependency Update Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Image rebuilt with latest dependencies" >> $GITHUB_STEP_SUMMARY + echo "- 📊 Vulnerability scan completed" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.check_base_image.outputs.outdated }}" == "true" ]; then + echo "- 🔄 Base image update PR created" >> $GITHUB_STEP_SUMMARY + else + echo "- ✓ Base image is up to date" >> $GITHUB_STEP_SUMMARY + fi diff --git a/Dockerfile b/Dockerfile index d50ea1a..70b0d86 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ RUN apt-get update -y && \ apt-get upgrade -y && \ apt-get install -y build-essential git && \ apt-get clean && \ - rm -f /var/lib/apt/lists/*_* + rm -rf /var/lib/apt/lists/* # prepare build dir WORKDIR /app @@ -73,10 +73,10 @@ RUN apt-get update -y && \ ca-certificates \ tini && \ apt-get clean && \ - rm -f /var/lib/apt/lists/*_* && \ + rm -rf /var/lib/apt/lists/* && \ # Create a non-root user and group with specific ID - groupadd -g ${GROUP_ID} aprs && \ - useradd -r -g aprs -u ${USER_ID} -s /bin/false -M aprs && \ + groupadd -g 1000 aprs && \ + useradd -r -g aprs -u 1000 -s /bin/false -M aprs && \ # Remove setuid and setgid permissions find / -perm /6000 -type f -exec chmod a-s {} \; || true && \ # Secure system configurations @@ -113,18 +113,19 @@ ENV MIX_ENV="prod" \ PHX_SERVER=true # Only copy the final release from the build stage -COPY --from=builder --chown=${USER_ID}:${GROUP_ID} /app/_build/${MIX_ENV}/rel/aprs ./ +COPY --from=builder --chown=1000:1000 /app/_build/${MIX_ENV}/rel/aprs ./ -USER ${USER_ID} +USER 1000 # Ensure the server binary is executable RUN chmod +x /app/bin/server -# Use tini as init process to handle signals and zombie processes -ENTRYPOINT ["/usr/bin/tini", "--"] +# If using an environment that doesn't automatically reap zombie processes, it is +# advised to add an init process such as tini via `apt-get install` +# above and adding an entrypoint. See https://github.com/krallin/tini for details +# ENTRYPOINT ["/tini", "--"] -# Add specific capabilities needed by the app -CMD ["/app/bin/server"] +CMD /app/bin/server # Add security-related metadata LABEL org.opencontainers.image.vendor="APRS.me" \ diff --git a/SYSTEM_UPDATES.md b/SYSTEM_UPDATES.md new file mode 100644 index 0000000..ed64c97 --- /dev/null +++ b/SYSTEM_UPDATES.md @@ -0,0 +1,98 @@ +# System Dependency Updates for APRS.me Docker Image + +This document outlines strategies for keeping system dependencies updated in the APRS.me Docker image without modifying the existing Dockerfile, which could potentially break the build process. + +## Current Challenges + +The Dockerfile builds successfully but maintaining up-to-date system dependencies is important for security. Standard approaches like adding `unattended-upgrades` or using `--security` flags with `apt-get` have proven problematic in our build environment. + +## Recommended Approaches + +### 1. Regular Rebuilds + +The most reliable way to keep system dependencies updated is through regular rebuilds: + +```bash +# Rebuild the Docker image with latest base image and dependencies +docker build --no-cache --pull -t aprs:latest . +``` + +Key flags: +- `--no-cache`: Forces all layers to be rebuilt, including running `apt-get update` and `apt-get upgrade` +- `--pull`: Ensures the latest version of the base image is used + +### 2. Base Image Updates + +Periodically update the base image version in the Dockerfile: + +```diff +- ARG DEBIAN_VERSION=bullseye-20250520-slim ++ ARG DEBIAN_VERSION=bullseye-20250615-slim +``` + +The Debian team regularly releases updated images with security patches. + +### 3. CI/CD Integration + +Automate the update process: + +1. Set up a weekly GitHub Actions workflow to: + - Build the image with `--no-cache --pull` + - Run security scans with Trivy + - Create a PR if updates are needed + +2. Include base image version checks: + ```yaml + - name: Check for newer base image + run: | + # Logic to check for newer base image versions + # Create PR if newer version available + ``` + +### 4. Security Scanning + +Regularly scan for vulnerabilities: + +```bash +# Install Trivy +brew install aquasecurity/trivy/trivy # macOS +# or appropriate command for your OS + +# Scan the image +trivy image aprs:latest +``` + +### 5. Manual Update Script + +```bash +#!/bin/bash +# update-deps.sh + +# Pull latest base image +docker pull debian:$(grep 'DEBIAN_VERSION=' Dockerfile | cut -d'=' -f2 | tr -d '"') + +# Rebuild with latest dependencies +docker build --no-cache --pull -t aprs:latest . + +# Scan for vulnerabilities +if command -v trivy &> /dev/null; then + trivy image aprs:latest +fi + +echo "Image rebuilt with latest dependencies" +``` + +## Deployment Strategy + +1. Rebuild images at least weekly +2. Deploy updated images after testing +3. Monitor security advisories for critical updates +4. Perform out-of-band updates for critical CVEs + +## Monitoring + +1. Set up alerts for high/critical vulnerabilities +2. Subscribe to security mailing lists for Debian and key packages +3. Use image scanning in your container registry + +By following these approaches, you can maintain up-to-date system dependencies without modifying the Dockerfile in ways that might break the build process. \ No newline at end of file diff --git a/scripts/update-deps.sh b/scripts/update-deps.sh new file mode 100755 index 0000000..9c656c1 --- /dev/null +++ b/scripts/update-deps.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# update-deps.sh - Simple script to rebuild Docker image with latest dependencies + +set -e + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo -e "${GREEN}=== APRS.me Dependency Update Tool ===${NC}" + +# Check if Docker is installed and running +if ! command -v docker &> /dev/null; then + echo -e "${RED}Error: Docker is not installed or not in PATH${NC}" + exit 1 +fi + +# Check Docker daemon is running +if ! docker info &> /dev/null; then + echo -e "${RED}Error: Docker daemon is not running${NC}" + exit 1 +fi + +# Get current versions from Dockerfile for reference +ELIXIR_VERSION=$(grep 'ELIXIR_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"') +OTP_VERSION=$(grep 'OTP_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"') +DEBIAN_VERSION=$(grep 'DEBIAN_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"') + +echo -e "${GREEN}Current versions:${NC}" +echo " Elixir: $ELIXIR_VERSION" +echo " OTP: $OTP_VERSION" +echo " Debian: $DEBIAN_VERSION" + +# Pull the latest base image to ensure we have fresh packages +echo -e "${YELLOW}Pulling latest base image...${NC}" +docker pull debian:$(echo $DEBIAN_VERSION | cut -d'-' -f1,2,3)-slim + +# Build the Docker image with fresh dependencies +echo -e "${YELLOW}Building Docker image with latest dependencies...${NC}" +echo "This may take several minutes..." +docker build --no-cache --pull -t aprs:latest . + +# Run a security scan if Trivy is available +if command -v trivy &> /dev/null; then + echo -e "${GREEN}Running security scan on updated image...${NC}" + trivy image --severity HIGH,CRITICAL aprs:latest +else + echo -e "${YELLOW}Trivy not installed. Security scan skipped.${NC}" + echo "To install Trivy:" + echo " - macOS: brew install aquasecurity/trivy/trivy" + echo " - Linux: see https://aquasecurity.github.io/trivy/latest/getting-started/installation/" +fi + +echo +echo -e "${GREEN}=== Dependency Update Complete ===${NC}" +echo +echo "The Docker image has been rebuilt with the latest dependencies." +echo +echo "Next steps:" +echo "1. Test the image: docker run --rm -it aprs:latest" +echo "2. If tests pass, tag and push the image to your registry" +echo "3. Deploy the updated image to your environment"