docker updates
This commit is contained in:
parent
158ec1ec2b
commit
c5617b6e8d
4 changed files with 323 additions and 37 deletions
86
.github/DOCKER_SECURITY.md
vendored
Normal file
86
.github/DOCKER_SECURITY.md
vendored
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
# Docker Security Strategy for APRS.me
|
||||||
|
|
||||||
|
This document outlines our approach to maintaining secure Docker images for the APRS.me application.
|
||||||
|
|
||||||
|
## System Dependencies Update Strategy
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
2. **Continuous Integration Checks**
|
||||||
|
- Weekly automated builds via GitHub Actions
|
||||||
|
- Security scanning on every build with Trivy
|
||||||
|
- Detection of outdated dependencies with separate vulnerability reports
|
||||||
|
- 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
## How Updates Are Applied
|
||||||
|
|
||||||
|
System dependencies are updated at multiple points:
|
||||||
|
|
||||||
|
1. **During Image Build**
|
||||||
|
```dockerfile
|
||||||
|
RUN apt-get update -y && \
|
||||||
|
apt-get upgrade -y --security && \
|
||||||
|
apt-get dist-upgrade -y
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Automatically at Runtime**
|
||||||
|
- The `unattended-upgrades` package applies security updates automatically
|
||||||
|
- Configuration prioritizes official security updates
|
||||||
|
|
||||||
|
3. **Via Scheduled Rebuilds**
|
||||||
|
- Weekly GitHub Actions workflow rebuilds the image with latest dependencies
|
||||||
|
- Base image is pulled with `--pull` flag to ensure latest version
|
||||||
|
|
||||||
|
## Manual Update Process
|
||||||
|
|
||||||
|
To manually update the Docker image with the latest system dependencies:
|
||||||
|
|
||||||
|
1. Run the update script:
|
||||||
|
```bash
|
||||||
|
./scripts/update-docker-image.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
3. Verify that tests pass with the updated image before deployment
|
||||||
|
|
||||||
|
## Security Monitoring
|
||||||
|
|
||||||
|
We continuously monitor for security issues through:
|
||||||
|
|
||||||
|
1. GitHub Security tab showing Trivy scan results
|
||||||
|
2. Weekly automated security scans
|
||||||
|
3. Dependency updates via Dependabot
|
||||||
|
4. Container registry vulnerability scanning
|
||||||
|
|
||||||
|
## 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)
|
||||||
151
.github/workflows/docker-security-scan.yml
vendored
151
.github/workflows/docker-security-scan.yml
vendored
|
|
@ -2,22 +2,22 @@ name: Docker Security Scan
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main" ]
|
branches: ["main"]
|
||||||
paths:
|
paths:
|
||||||
- 'Dockerfile'
|
- "Dockerfile"
|
||||||
- '.github/workflows/docker-security-scan.yml'
|
- ".github/workflows/docker-security-scan.yml"
|
||||||
- 'mix.exs'
|
- "mix.exs"
|
||||||
- 'mix.lock'
|
- "mix.lock"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main" ]
|
branches: ["main"]
|
||||||
paths:
|
paths:
|
||||||
- 'Dockerfile'
|
- "Dockerfile"
|
||||||
- '.github/workflows/docker-security-scan.yml'
|
- ".github/workflows/docker-security-scan.yml"
|
||||||
- 'mix.exs'
|
- "mix.exs"
|
||||||
- 'mix.lock'
|
- "mix.lock"
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * 0' # Run weekly on Sundays at midnight
|
- cron: "0 0 * * 0" # Run weekly on Sundays at midnight
|
||||||
workflow_dispatch: # Allow manual triggers
|
workflow_dispatch: # Allow manual triggers
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
scan:
|
scan:
|
||||||
|
|
@ -33,29 +33,132 @@ jobs:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
run: docker build -t aprs:${{ github.sha }} .
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
tags: aprs:${{ github.sha }}
|
||||||
|
load: true
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scanner
|
- name: Run Trivy vulnerability scanner
|
||||||
uses: aquasecurity/trivy-action@master
|
uses: aquasecurity/trivy-action@master
|
||||||
with:
|
with:
|
||||||
image-ref: aprs:${{ github.sha }}
|
image-ref: aprs:${{ github.sha }}
|
||||||
format: 'sarif'
|
format: "sarif"
|
||||||
output: 'trivy-results.sarif'
|
output: "trivy-results.sarif"
|
||||||
severity: 'CRITICAL,HIGH'
|
severity: "CRITICAL,HIGH"
|
||||||
timeout: '10m'
|
timeout: "10m"
|
||||||
scanners: 'vuln,config,secret'
|
scanners: "vuln,config,secret"
|
||||||
|
|
||||||
- name: Upload Trivy scan results to GitHub Security tab
|
- name: Upload Trivy scan results to GitHub Security tab
|
||||||
uses: github/codeql-action/upload-sarif@v3
|
uses: github/codeql-action/upload-sarif@v3
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
sarif_file: 'trivy-results.sarif'
|
sarif_file: "trivy-results.sarif"
|
||||||
category: 'Trivy Scan'
|
category: "Trivy Scan"
|
||||||
|
|
||||||
|
- name: Check for outdated base images
|
||||||
|
uses: aquasecurity/trivy-action@master
|
||||||
|
with:
|
||||||
|
image-ref: aprs:${{ github.sha }}
|
||||||
|
format: "table"
|
||||||
|
output: "outdated-dependencies.txt"
|
||||||
|
exit-code: "0" # Don't fail the build yet
|
||||||
|
severity: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL"
|
||||||
|
scanners: "vuln"
|
||||||
|
vuln-type: "os"
|
||||||
|
|
||||||
|
- name: Check for outdated application dependencies
|
||||||
|
uses: aquasecurity/trivy-action@master
|
||||||
|
with:
|
||||||
|
image-ref: aprs:${{ github.sha }}
|
||||||
|
format: "table"
|
||||||
|
output: "outdated-app-dependencies.txt"
|
||||||
|
exit-code: "0" # Don't fail the build yet
|
||||||
|
severity: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL"
|
||||||
|
scanners: "vuln"
|
||||||
|
vuln-type: "library"
|
||||||
|
|
||||||
|
- name: Upload outdated dependencies report
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: dependency-reports
|
||||||
|
path: |
|
||||||
|
outdated-dependencies.txt
|
||||||
|
outdated-app-dependencies.txt
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Check for update availability in base image
|
||||||
|
run: |
|
||||||
|
# Extract base image from Dockerfile
|
||||||
|
BASE_IMAGE=$(grep 'RUNNER_IMAGE=' Dockerfile | cut -d'"' -f2)
|
||||||
|
echo "Base image: $BASE_IMAGE"
|
||||||
|
|
||||||
|
# Pull latest version of the base image
|
||||||
|
docker pull $BASE_IMAGE
|
||||||
|
|
||||||
|
# Compare the digests
|
||||||
|
CURRENT_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $BASE_IMAGE)
|
||||||
|
echo "Current digest: $CURRENT_DIGEST"
|
||||||
|
|
||||||
|
# Log the findings
|
||||||
|
echo "Base image update check completed"
|
||||||
|
echo "It's recommended to periodically update the base image in your Dockerfile"
|
||||||
|
|
||||||
|
- name: Scan for security misconfigurations
|
||||||
|
uses: aquasecurity/trivy-action@master
|
||||||
|
with:
|
||||||
|
image-ref: aprs:${{ github.sha }}
|
||||||
|
format: "table"
|
||||||
|
output: "misconfig-results.txt"
|
||||||
|
exit-code: "0"
|
||||||
|
scanners: "config"
|
||||||
|
|
||||||
|
- name: Upload misconfiguration report
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: misconfig-report
|
||||||
|
path: misconfig-results.txt
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Generate comprehensive security report
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "# Docker Image Security Report" > security-report.md
|
||||||
|
echo "## Image: aprs:${{ github.sha }}" >> security-report.md
|
||||||
|
echo "## Date: $(date)" >> security-report.md
|
||||||
|
echo "## Base Image Update Status" >> security-report.md
|
||||||
|
echo "\`\`\`" >> security-report.md
|
||||||
|
cat outdated-dependencies.txt >> security-report.md
|
||||||
|
echo "\`\`\`" >> security-report.md
|
||||||
|
echo "## Application Dependencies Status" >> security-report.md
|
||||||
|
echo "\`\`\`" >> security-report.md
|
||||||
|
cat outdated-app-dependencies.txt >> security-report.md
|
||||||
|
echo "\`\`\`" >> security-report.md
|
||||||
|
echo "## Configuration Issues" >> security-report.md
|
||||||
|
echo "\`\`\`" >> security-report.md
|
||||||
|
cat misconfig-results.txt >> security-report.md
|
||||||
|
echo "\`\`\`" >> security-report.md
|
||||||
|
|
||||||
|
- name: Upload comprehensive security report
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: security-report
|
||||||
|
path: security-report.md
|
||||||
|
retention-days: 14
|
||||||
|
|
||||||
- name: Fail on CRITICAL vulnerabilities
|
- name: Fail on CRITICAL vulnerabilities
|
||||||
if: ${{ failure() }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "Critical vulnerabilities found in the Docker image."
|
if grep -q "CRITICAL" trivy-results.sarif; then
|
||||||
echo "Please review the scan results in the Security tab."
|
echo "Critical vulnerabilities found in the Docker image."
|
||||||
exit 1
|
echo "Please review the scan results in the Security tab."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
|
||||||
37
Dockerfile
37
Dockerfile
|
|
@ -11,9 +11,10 @@ ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
|
||||||
|
|
||||||
FROM ${BUILDER_IMAGE} AS builder
|
FROM ${BUILDER_IMAGE} AS builder
|
||||||
|
|
||||||
# install build dependencies
|
# install build dependencies with full security updates
|
||||||
RUN apt-get update -y && \
|
RUN apt-get update -y && \
|
||||||
apt-get upgrade -y && \
|
apt-get upgrade -y --security && \
|
||||||
|
apt-get dist-upgrade -y && \
|
||||||
apt-get install -y build-essential git && \
|
apt-get install -y build-essential git && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
@ -64,19 +65,31 @@ FROM ${RUNNER_IMAGE}
|
||||||
# Install security updates and required packages
|
# Install security updates and required packages
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
RUN apt-get update -y && \
|
RUN apt-get update -y && \
|
||||||
apt-get upgrade -y && \
|
# Upgrade all packages with focus on security updates
|
||||||
|
apt-get upgrade -y --security && \
|
||||||
|
apt-get dist-upgrade -y && \
|
||||||
|
# Install unattended-upgrades for automatic security updates
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
libstdc++6 \
|
libstdc++6 \
|
||||||
openssl \
|
openssl \
|
||||||
libncurses5 \
|
libncurses5 \
|
||||||
locales \
|
locales \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tini && \
|
tini \
|
||||||
|
unattended-upgrades \
|
||||||
|
apt-listchanges && \
|
||||||
|
# Configure unattended-upgrades to only install security updates
|
||||||
|
echo 'APT::Periodic::Update-Package-Lists "1";' > /etc/apt/apt.conf.d/20auto-upgrades && \
|
||||||
|
echo 'APT::Periodic::Unattended-Upgrade "1";' >> /etc/apt/apt.conf.d/20auto-upgrades && \
|
||||||
|
echo 'Unattended-Upgrade::Origins-Pattern { "origin=Debian,codename=${distro_codename},label=Debian-Security"; };' > /etc/apt/apt.conf.d/50unattended-upgrades && \
|
||||||
|
echo 'Unattended-Upgrade::Remove-Unused-Dependencies "true";' >> /etc/apt/apt.conf.d/50unattended-upgrades && \
|
||||||
|
# Remove unnecessary packages
|
||||||
|
apt-get autoremove -y && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
# Create a non-root user and group with specific ID
|
# Create a non-root user and group with specific ID
|
||||||
groupadd -g 1000 aprs && \
|
groupadd -g ${GROUP_ID} aprs && \
|
||||||
useradd -r -g aprs -u 1000 -s /bin/false -M aprs && \
|
useradd -r -g aprs -u ${USER_ID} -s /bin/false -M aprs && \
|
||||||
# Remove setuid and setgid permissions
|
# Remove setuid and setgid permissions
|
||||||
find / -perm /6000 -type f -exec chmod a-s {} \; || true && \
|
find / -perm /6000 -type f -exec chmod a-s {} \; || true && \
|
||||||
# Secure system configurations
|
# Secure system configurations
|
||||||
|
|
@ -113,20 +126,18 @@ ENV MIX_ENV="prod" \
|
||||||
PHX_SERVER=true
|
PHX_SERVER=true
|
||||||
|
|
||||||
# Only copy the final release from the build stage
|
# Only copy the final release from the build stage
|
||||||
COPY --from=builder --chown=1000:1000 /app/_build/${MIX_ENV}/rel/aprs ./
|
COPY --from=builder --chown=${USER_ID}:${GROUP_ID} /app/_build/${MIX_ENV}/rel/aprs ./
|
||||||
|
|
||||||
USER 1000
|
USER ${USER_ID}
|
||||||
|
|
||||||
# Ensure the server binary is executable
|
# Ensure the server binary is executable
|
||||||
RUN chmod +x /app/bin/server
|
RUN chmod +x /app/bin/server
|
||||||
|
|
||||||
# If using an environment that doesn't automatically reap zombie processes, it is
|
# Use tini as init process to handle signals and zombie processes
|
||||||
# advised to add an init process such as tini via `apt-get install`
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
# above and adding an entrypoint. See https://github.com/krallin/tini for details
|
|
||||||
# ENTRYPOINT ["/tini", "--"]
|
|
||||||
|
|
||||||
# Add specific capabilities needed by the app
|
# Add specific capabilities needed by the app
|
||||||
CMD /app/bin/server
|
CMD ["/app/bin/server"]
|
||||||
|
|
||||||
# Add security-related metadata
|
# Add security-related metadata
|
||||||
LABEL org.opencontainers.image.vendor="APRS.me" \
|
LABEL org.opencontainers.image.vendor="APRS.me" \
|
||||||
|
|
|
||||||
86
scripts/update-docker-image.sh
Executable file
86
scripts/update-docker-image.sh
Executable file
|
|
@ -0,0 +1,86 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# update-docker-image.sh - Script to update Docker image with latest dependencies
|
||||||
|
#
|
||||||
|
# This script helps ensure that your Docker image has the latest system dependencies
|
||||||
|
# by rebuilding the image with the latest base image and system packages.
|
||||||
|
|
||||||
|
set -e # Exit immediately if a command exits with a non-zero status
|
||||||
|
|
||||||
|
# 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 Docker Image Update Tool ===${NC}"
|
||||||
|
echo "This script will update your Docker image with the latest dependencies"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Get the latest version of the base image from Dockerfile
|
||||||
|
BASE_IMAGE=$(grep 'DEBIAN_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"')
|
||||||
|
if [ -z "$BASE_IMAGE" ]; then
|
||||||
|
echo -e "${RED}Error: Could not determine base image from Dockerfile${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Current base image:${NC} $BASE_IMAGE"
|
||||||
|
|
||||||
|
# Check if we need to update the Dockerfile
|
||||||
|
echo -e "${YELLOW}Checking for newer Debian versions...${NC}"
|
||||||
|
latest_debian=$(curl -s https://hub.docker.com/v2/repositories/library/debian/tags/ | \
|
||||||
|
grep -o '"name":"[^"]*-slim"' | grep bullseye | sort -r | head -1 | cut -d'"' -f4)
|
||||||
|
|
||||||
|
if [ -z "$latest_debian" ]; then
|
||||||
|
echo -e "${YELLOW}Could not determine latest Debian version, keeping current version${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}Latest Debian version:${NC} $latest_debian"
|
||||||
|
|
||||||
|
# Update Dockerfile with latest Debian version
|
||||||
|
current_version=$(grep 'DEBIAN_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"')
|
||||||
|
if [ "$current_version" != "$latest_debian" ]; then
|
||||||
|
echo -e "${YELLOW}Updating Dockerfile with latest Debian version...${NC}"
|
||||||
|
sed -i "s/ARG DEBIAN_VERSION=.*/ARG DEBIAN_VERSION=$latest_debian/" Dockerfile
|
||||||
|
echo -e "${GREEN}Updated Dockerfile with new base image:${NC} $latest_debian"
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}Already using the latest Debian version${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for latest Elixir and OTP versions
|
||||||
|
echo -e "${YELLOW}Checking for newer Elixir and OTP versions...${NC}"
|
||||||
|
current_elixir=$(grep 'ELIXIR_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"')
|
||||||
|
current_otp=$(grep 'OTP_VERSION=' Dockerfile | head -1 | cut -d'=' -f2 | tr -d '"')
|
||||||
|
|
||||||
|
echo -e "${GREEN}Current Elixir version:${NC} $current_elixir"
|
||||||
|
echo -e "${GREEN}Current OTP version:${NC} $current_otp"
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Building Docker image with latest dependencies...${NC}"
|
||||||
|
docker build --no-cache --pull -t aprs:latest .
|
||||||
|
|
||||||
|
echo -e "${GREEN}Running security scan on updated image...${NC}"
|
||||||
|
if command -v trivy &> /dev/null; then
|
||||||
|
trivy image --severity HIGH,CRITICAL aprs:latest
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}Trivy not installed. Skipping security scan.${NC}"
|
||||||
|
echo "Install Trivy with: brew install aquasecurity/trivy/trivy (macOS) or similar for your OS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}=== Update Complete ===${NC}"
|
||||||
|
echo "The Docker image has been rebuilt with the latest dependencies"
|
||||||
|
echo "Recommendations:"
|
||||||
|
echo "1. Run tests to ensure everything still works"
|
||||||
|
echo "2. Check for any security issues reported above"
|
||||||
|
echo "3. If all is well, commit and push your changes"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Check if git is available and we're in a git repository
|
||||||
|
if command -v git &> /dev/null && git rev-parse --is-inside-work-tree &> /dev/null; then
|
||||||
|
echo "Git changes:"
|
||||||
|
git diff Dockerfile
|
||||||
|
fi
|
||||||
Loading…
Add table
Reference in a new issue