Skip to content

Commit

Permalink
Improve security through avoiding env leaks
Browse files Browse the repository at this point in the history
This passes the environment, where the PAT is located, through an
environment file instead of a command line argument. This is a security
improvement, as it avoids leaking the PAT through the process list. The
environment file is automatically removed before the runner process is
created, ensuring that it cannot be accessed from within workflows.
  • Loading branch information
efrecon committed Feb 11, 2024
1 parent 8a4a2aa commit 9642c42
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 32 deletions.
1 change: 1 addition & 0 deletions lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ get_env() (
)

run_krunvm() {
debug "Running krunvm $*"
buildah unshare krunvm "$@"
}

Expand Down
67 changes: 44 additions & 23 deletions orchestrator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ ORCHESTRATOR_DNS=${ORCHESTRATOR_DNS:-"1.1.1.1"}

ORCHESTRATOR_MOUNT=${ORCHESTRATOR_MOUNT:-""}

ORCHESTRATOR_ISOLATION=${ORCHESTRATOR_ISOLATION:-"1"}

# GitHub host, e.g. github.com or github.example.com
RUNNER_GITHUB=${RUNNER_GITHUB:-"github.com"}

Expand Down Expand Up @@ -80,7 +82,7 @@ RUNNER_UPDATE=${RUNNER_UPDATE:-"0"}
KRUNVM_RUNNER_MAIN="Run several krunvm-based GitHub runners on a single host"


while getopts "c:d:g:G:i:l:L:m:M:n:p:s:t:T:u:Uvh-" opt; do
while getopts "c:d:g:G:i:Il:L:m:M:n:p:s:t:T:u:Uvh-" opt; do
case "$opt" in
c) # Number of CPUs to allocate to the VM
ORCHESTRATOR_CPUS="$OPTARG";;
Expand All @@ -92,6 +94,8 @@ while getopts "c:d:g:G:i:l:L:m:M:n:p:s:t:T:u:Uvh-" opt; do
RUNNER_GROUP="$OPTARG";;
i) # Fully-qualified name of the OCI image to use
ORCHESTRATOR_IMAGE="$OPTARG";;
I) # Turn off variables isolation (not recommended, security risk)
ORCHESTRATOR_ISOLATION=0;;
l) # Where to send logs
ORCHESTRATOR_LOG="$OPTARG";;
L) # Comma separated list of labels to attach to the runner
Expand Down Expand Up @@ -128,6 +132,13 @@ shift $((OPTIND-1))
KRUNVM_RUNNER_LOG=$ORCHESTRATOR_LOG
KRUNVM_RUNNER_VERBOSE=$ORCHESTRATOR_VERBOSE

cleanup() {
if [ -n "${ORCHESTRATOR_ENVIRONMENT:-}" ]; then
verbose "Removing isolation environment $ORCHESTRATOR_ENVIRONMENT"
rm -rf "$ORCHESTRATOR_ENVIRONMENT"
fi
}

if [ "$#" = 0 ]; then
error "You need to specify the number of runners to create"
fi
Expand All @@ -144,23 +155,27 @@ if run_krunvm list | grep -qE "^$ORCHESTRATOR_NAME"; then
run_krunvm delete "$ORCHESTRATOR_NAME"
fi

verbose "Creating micro VM $ORCHESTRATOR_NAME, $ORCHESTRATOR_CPUS vCPUs, ${ORCHESTRATOR_MEMORY}M memory"
if [ -z "$ORCHESTRATOR_MOUNT" ]; then
run_krunvm create \
--cpus "$ORCHESTRATOR_CPUS" \
--mem "$ORCHESTRATOR_MEMORY" \
--dns "$ORCHESTRATOR_DNS" \
--name "$ORCHESTRATOR_NAME" \
"$ORCHESTRATOR_IMAGE"
else
run_krunvm create \
"$ORCHESTRATOR_IMAGE" \
--cpus "$ORCHESTRATOR_CPUS" \
--mem "$ORCHESTRATOR_MEMORY" \
--dns "$ORCHESTRATOR_DNS" \
--name "$ORCHESTRATOR_NAME" \
--volume "${ORCHESTRATOR_ROOTDIR}:${ORCHESTRATOR_MOUNT}"
# Remember number of runners
runners=$1

# Create isolation mount point
if [ "$ORCHESTRATOR_ISOLATION" = 1 ]; then
ORCHESTRATOR_ENVIRONMENT=$(mktemp -d)
trap cleanup INT TERM QUIT
fi

verbose "Creating $runners micro VM(s) $ORCHESTRATOR_NAME, $ORCHESTRATOR_CPUS vCPUs, ${ORCHESTRATOR_MEMORY}M memory"
# Note: reset arguments!
set -- \
--cpus "$ORCHESTRATOR_CPUS" \
--mem "$ORCHESTRATOR_MEMORY" \
--dns "$ORCHESTRATOR_DNS" \
--name "$ORCHESTRATOR_NAME"
set -- "$@" --volume "${ORCHESTRATOR_ROOTDIR}:${ORCHESTRATOR_MOUNT}"
if [ -n "${ORCHESTRATOR_ENVIRONMENT:-}" ]; then
set -- "$@" --volume "${ORCHESTRATOR_ENVIRONMENT}:/_environment"
fi
run_krunvm create "$ORCHESTRATOR_IMAGE" "$@"

# Export all RUNNER_ variables
while IFS= read -r varname; do
Expand All @@ -170,21 +185,27 @@ done <<EOF
$(set | grep '^RUNNER_' | sed 's/=.*//')
EOF

# Pass verbosity and log configuration also
RUNNER_VERBOSE=$ORCHESTRATOR_VERBOSE
export RUNNER_VERBOSE

# Remember number of runners and reset positional parameters
runners=$1; set --
RUNNER_LOG=$ORCHESTRATOR_LOG
export RUNNER_VERBOSE RUNNER_LOG

# Create runner loops
# Create runner loops in the background. One per runner. Each loop will
# indefinitely create ephemeral runners. Looping is implemented in runner.sh,
# in the same directory as this script.
set --; # Reset arguments
for i in $(seq 1 "$runners"); do
if [ -n "${RUNNER_PAT:-}" ]; then
verbose "Creating runner loop $i"
"$ORCHESTRATOR_ROOTDIR/runner.sh" -n "$ORCHESTRATOR_NAME" -M "$ORCHESTRATOR_MOUNT" &
"$ORCHESTRATOR_ROOTDIR/runner.sh" \
-n "$ORCHESTRATOR_NAME" \
-M "$ORCHESTRATOR_MOUNT" \
-E "${ORCHESTRATOR_ENVIRONMENT:-}" &
set -- "$@" "$!"
fi
done

# TODO: Trap signals to send kill signals to the runners
verbose "Waiting for runners to die"
for pid in "$@"; do
wait "$pid"
Expand Down
38 changes: 30 additions & 8 deletions runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,20 @@ RUNNER_NAME=${RUNNER_NAME:-"runner"}

RUNNER_MOUNT=${RUNNER_MOUNT:-""}

# Location (at host) where to place environment files for each run.
RUNNER_ENVIRONMENT=${RUNNER_ENVIRONMENT:-""}

# shellcheck source=lib/common.sh
. "$RUNNER_ROOTDIR/lib/common.sh"

# shellcheck disable=SC2034 # Used in sourced scripts
KRUNVM_RUNNER_MAIN="Create runners forever using krunvm"


while getopts "g:G:l:L:M:n:p:s:T:u:Uvh-" opt; do
while getopts "E:g:G:l:L:M:n:p:s:T:u:Uvh-" opt; do
case "$opt" in
E) # Location (at host) where to place environment files for each run.
RUNNER_ENVIRONMENT="$OPTARG";;
g) # GitHub host, e.g. github.com or github.example.com
RUNNER_GITHUB="$OPTARG";;
G) # Group to attach the runner to
Expand Down Expand Up @@ -111,16 +116,31 @@ shift $((OPTIND-1))
KRUNVM_RUNNER_LOG=$RUNNER_LOG
KRUNVM_RUNNER_VERBOSE=$RUNNER_VERBOSE

# Decide which runner.sh implementation (this is the "entrypoint" of the
# microVM) to use: the one from the mount point, or the built-in one.
if [ -z "$RUNNER_MOUNT" ]; then
runner=/opt/gh-runner-krunvm/bin/runner.sh
else
runner=${RUNNER_MOUNT%/}/runner/runner.sh
fi

while true; do
verbose "Starting microVM $RUNNER_NAME to run an ephemeral GitHub runner"
run_krunvm start "$RUNNER_NAME" \
"$runner" \
-- \
if [ -n "$RUNNER_ENVIRONMENT" ]; then
# Create an env file with most of the RUNNER_ variables. This works because
# the `runner.sh` script that will be called uses the same set of variables.
id=$(random_string)
verbose "Creating isolation environment ${RUNNER_ENVIRONMENT}/${id}.env"
while IFS= read -r varset; do
# shellcheck disable=SC2163 # We want to expand the variable
printf '%s\n' "$varset" >> "${RUNNER_ENVIRONMENT}/${id}.env"
done <<EOF
$(set | grep '^RUNNER_' | grep -v -e '^RUNNER_ROOTDIR' -e '^RUNNER_ENVIRONMENT')
EOF

set -- -E "/_environment/${id}.env"
else
set -- \
-e \
-g "$RUNNER_GITHUB" \
-G "$RUNNER_GROUP" \
Expand All @@ -129,8 +149,10 @@ while true; do
-p "$RUNNER_PRINCIPAL" \
-s "$RUNNER_SCOPE" \
-T "$RUNNER_PAT" \
-u "$RUNNER_USER" \
-vv \
-- \
/opt/actions-runner/bin/Runner.Listener run --startuptype service
-u "$RUNNER_USER"
for _ in $(seq 1 "$RUNNER_VERBOSE"); do
set -- -v "$@"
done
fi
run_krunvm start "$RUNNER_NAME" "$runner" -- "$@"
done
30 changes: 29 additions & 1 deletion runner/runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ RUNNER_INSTALL=${RUNNER_INSTALL:-"/opt/actions-runner"}
# Should the runner auto-update
RUNNER_UPDATE=${RUNNER_UPDATE:-"0"}

# Environment file to read configuration from (will override command-line
# options!). The environment file is automatically removed after reading.
RUNNER_ENVFILE=${RUNNER_ENVFILE:-""}

RUNNER_TOOL_CACHE=${RUNNER_TOOL_CACHE:-"${AGENT_TOOLSDIRECTORY:-"/opt/hostedtoolcache"}"}

# shellcheck source=../lib/common.sh
Expand All @@ -88,10 +92,12 @@ RUNNER_TOOL_CACHE=${RUNNER_TOOL_CACHE:-"${AGENT_TOOLSDIRECTORY:-"/opt/hostedtool
KRUNVM_RUNNER_MAIN="Configure and run the installed GitHub runner"


while getopts "eg:G:l:L:n:p:s:t:T:u:Uvh-" opt; do
while getopts "eE:g:G:l:L:n:p:s:t:T:u:Uvh-" opt; do
case "$opt" in
e) # Ephemeral runner
RUNNER_EPHEMERAL=1;;
E) # Environment file to read configuration from, will be removed after reading
RUNNER_ENVFILE="$OPTARG";;
g) # GitHub host, e.g. github.com or github.example.com
RUNNER_GITHUB="$OPTARG";;
G) # Group to attach the runner to
Expand Down Expand Up @@ -209,6 +215,25 @@ runas() {
fi
}

# Read environment file, if set. Do this early on so we can override any other
# variable that would have come from environment or script options.
if [ -n "$RUNNER_ENVFILE" ]; then
if [ -f "$RUNNER_ENVFILE" ]; then
verbose "Reading environment file $RUNNER_ENVFILE"
# shellcheck disable=SC1090 # File has been created by runner.sh loop
. "$RUNNER_ENVFILE"
rm -f "$RUNNER_ENVFILE"
debug "Removed environment file $RUNNER_ENVFILE"
# Pass logging configuration and level to imported scripts (again!) since we
# might have modified in the .env file.
KRUNVM_RUNNER_LOG=$RUNNER_LOG
KRUNVM_RUNNER_VERBOSE=$RUNNER_VERBOSE
else
error "Environment file $RUNNER_ENVFILE does not exist"
fi
fi

# Check requirements.
check_command "$RUNNER_ROOTDIR/token.sh"
if [ -z "$RUNNER_PRINCIPAL" ]; then
error "Principal must be set to name of repo, org or enterprise"
Expand All @@ -219,6 +244,8 @@ if [ "$#" = 0 ]; then
set -- /opt/actions-runner/bin/Runner.Listener run --startuptype service
fi

# Setup variables that would have been missing. These depends on the main
# variables, so we do it here rather than at the top of the script.
debug "Setting up missing defaults"
distro=$(get_env "/etc/os-release" "ID")
RUNNER_DISTRO=${RUNNER_DISTRO:-"${distro:-"unknown}"}"}
Expand All @@ -232,6 +259,7 @@ else
RUNNER_LABELS=${RUNNER_LABELS:-"krunvm"}
fi

# Construct the runner URL, i.e. where the runner will be registered
debug "Constructing runner URL"
RUNNER_SCOPE=$(to_lower "$RUNNER_SCOPE")
case "$RUNNER_SCOPE" in
Expand Down

0 comments on commit 9642c42

Please sign in to comment.