mirror of
https://github.com/ivuorinen/actions.git
synced 2026-01-26 11:34:00 +00:00
262 lines
8.8 KiB
YAML
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
|