docker updates

This commit is contained in:
Graham McIntire 2025-06-15 17:44:31 -05:00
parent 7a8a5443f6
commit b8f61a9a06
No known key found for this signature in database
5 changed files with 363 additions and 60 deletions

View file

@ -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)
- 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

122
.github/workflows/dependency-updates.yml vendored Normal file
View file

@ -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

View file

@ -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" \

98
SYSTEM_UPDATES.md Normal file
View file

@ -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.

64
scripts/update-deps.sh Executable file
View file

@ -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"