Files
actions/docker-publish/action.yml

262 lines
8.8 KiB
YAML

# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
# permissions:
# - packages: write # Required for publishing to Docker registries
# - contents: read # Required for checking out repository
#
# Security Considerations:
#
# Trust Model: This action should only be used in trusted workflows controlled by repository owners.
# Do not pass untrusted user input (e.g., PR labels, comments, external webhooks) to the `context`
# or `dockerfile` parameters.
#
# Input Validation: The action validates `context` and `dockerfile` inputs to prevent code injection attacks:
#
# - `context`: Must be a relative path (e.g., `.`, `./app`, `subdir/`). Absolute paths are rejected.
# Remote URLs trigger a warning and should only be used from trusted sources.
# - `dockerfile`: Must be a relative path (e.g., `Dockerfile`, `./docker/Dockerfile`). Absolute paths
# and URLs are rejected.
#
# These validations help prevent malicious actors from:
# - Building Docker images from arbitrary file system locations
# - Fetching malicious Dockerfiles from untrusted remote sources
# - Executing code injection attacks through build context manipulation
#
# Best Practices:
# 1. Only use hard-coded values or trusted workflow variables for `context` and `dockerfile`
# 2. Never accept these values from PR comments, labels, or external webhooks
# 3. Review workflow permissions before granting write access to this action
# 4. Use SHA-pinned action references: `ivuorinen/actions/docker-publish@<commit-sha>`
---
name: Docker Publish
description: Simple wrapper to publish Docker images to GitHub Packages and/or Docker Hub
author: Ismo Vuorinen
branding:
icon: upload-cloud
color: blue
inputs:
registry:
description: 'Registry to publish to (dockerhub, github, or both)'
required: false
default: 'both'
image-name:
description: 'Docker image name (defaults to repository name)'
required: false
tags:
description: 'Comma-separated list of tags (e.g., latest,v1.0.0)'
required: false
default: 'latest'
platforms:
description: 'Platforms to build for (comma-separated)'
required: false
default: 'linux/amd64,linux/arm64'
context:
description: 'Build context path'
required: false
default: '.'
dockerfile:
description: 'Path to Dockerfile'
required: false
default: 'Dockerfile'
build-args:
description: 'Build arguments (newline-separated KEY=VALUE pairs)'
required: false
push:
description: 'Whether to push the image'
required: false
default: 'true'
token:
description: 'GitHub token for authentication (for GitHub registry)'
required: false
default: ''
dockerhub-username:
description: 'Docker Hub username (required if publishing to Docker Hub)'
required: false
dockerhub-token:
description: 'Docker Hub token (required if publishing to Docker Hub)'
required: false
outputs:
image-name:
description: 'Full image name with registry'
value: ${{ steps.meta.outputs.image-name }}
tags:
description: 'Tags that were published'
value: ${{ steps.meta.outputs.tags }}
digest:
description: 'Image digest'
value: ${{ steps.build.outputs.digest }}
metadata:
description: 'Build metadata'
value: ${{ steps.build.outputs.metadata }}
runs:
using: composite
steps:
- name: Validate Inputs
id: validate
shell: sh
env:
INPUT_REGISTRY: ${{ inputs.registry }}
INPUT_DOCKERHUB_USERNAME: ${{ inputs.dockerhub-username }}
INPUT_DOCKERHUB_TOKEN: ${{ inputs.dockerhub-token }}
INPUT_TOKEN: ${{ inputs.token }}
INPUT_CONTEXT: ${{ inputs.context }}
INPUT_DOCKERFILE: ${{ inputs.dockerfile }}
run: |
set -eu
# Validate registry input
case "$INPUT_REGISTRY" in
dockerhub|github|both)
;;
*)
echo "::error::Invalid registry value. Must be 'dockerhub', 'github', or 'both'"
exit 1
;;
esac
# Validate Docker Hub credentials if needed
if [ "$INPUT_REGISTRY" = "dockerhub" ] || [ "$INPUT_REGISTRY" = "both" ]; then
if [ -z "$INPUT_DOCKERHUB_USERNAME" ] || [ -z "$INPUT_DOCKERHUB_TOKEN" ]; then
echo "::error::Docker Hub username and token are required when publishing to Docker Hub"
exit 1
fi
fi
# Validate GitHub token if needed
if [ "$INPUT_REGISTRY" = "github" ] || [ "$INPUT_REGISTRY" = "both" ]; then
token="${INPUT_TOKEN:-${GITHUB_TOKEN:-}}"
if [ -z "$token" ]; then
echo "::error::GitHub token is required when publishing to GitHub Packages"
exit 1
fi
fi
# Validate context input for security
INPUT_CONTEXT="${INPUT_CONTEXT:-.}"
case "$INPUT_CONTEXT" in
.|./*|*/*)
# Relative paths are allowed
;;
/*)
echo "::error::Context cannot be an absolute path: '$INPUT_CONTEXT'"
echo "::error::Use relative paths (e.g., '.', './app') to prevent code injection"
exit 1
;;
*://*)
echo "::warning::Context is a remote URL: '$INPUT_CONTEXT'"
echo "::warning::Ensure this URL is from a trusted source to prevent code injection"
;;
esac
# Validate dockerfile input for security
INPUT_DOCKERFILE="${INPUT_DOCKERFILE:-Dockerfile}"
case "$INPUT_DOCKERFILE" in
Dockerfile|*/Dockerfile|*.dockerfile|*/*.dockerfile)
# Common dockerfile patterns are allowed
;;
/*)
echo "::error::Dockerfile path cannot be absolute: '$INPUT_DOCKERFILE'"
echo "::error::Use relative paths (e.g., 'Dockerfile', './docker/Dockerfile')"
exit 1
;;
*://*)
echo "::error::Dockerfile path cannot be a URL: '$INPUT_DOCKERFILE'"
exit 1
;;
esac
echo "Input validation completed successfully"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Determine Image Names and Tags
id: meta
shell: sh
env:
INPUT_REGISTRY: ${{ inputs.registry }}
INPUT_IMAGE_NAME: ${{ inputs.image-name }}
INPUT_TAGS: ${{ inputs.tags }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
set -eu
# Determine base image name
if [ -n "$INPUT_IMAGE_NAME" ]; then
base_name="$INPUT_IMAGE_NAME"
else
# Use repository name (lowercase)
base_name=$(echo "$GITHUB_REPOSITORY" | tr '[:upper:]' '[:lower:]')
fi
# Build full image names based on registry
image_names=""
case "$INPUT_REGISTRY" in
dockerhub)
image_names="docker.io/${base_name}"
;;
github)
image_names="ghcr.io/${base_name}"
;;
both)
image_names="docker.io/${base_name},ghcr.io/${base_name}"
;;
esac
# Build full tags (image:tag format)
tags=""
IFS=','
for image in $image_names; do
for tag in $INPUT_TAGS; do
tag=$(echo "$tag" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [ -n "$tags" ]; then
tags="$(printf '%s\n%s' "$tags" "${image}:${tag}")"
else
tags="${image}:${tag}"
fi
done
done
# Output results
printf 'image-name=%s\n' "$base_name" >> "$GITHUB_OUTPUT"
{
echo 'tags<<EOF'
echo "$tags"
echo 'EOF'
} >> "$GITHUB_OUTPUT"
echo "Image name: $base_name"
echo "Tags:"
echo "$tags"
- name: Login to Docker Hub
if: inputs.registry == 'dockerhub' || inputs.registry == 'both'
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
username: ${{ inputs.dockerhub-username }}
password: ${{ inputs.dockerhub-token }}
- name: Login to GitHub Container Registry
if: inputs.registry == 'github' || inputs.registry == 'both'
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ inputs.token || github.token }}
- name: Build and Push Docker Image
id: build
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }}
platforms: ${{ inputs.platforms }}
push: ${{ inputs.push }}
tags: ${{ steps.meta.outputs.tags }}
build-args: ${{ inputs.build-args }}
cache-from: type=gha
cache-to: type=gha,mode=max