Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
# - run from a pristine git checkout (this script refuses to build from a
# workspace with modified, untracked, or ignored files; see
# scripts/release_lib.sh)
# - PATH must contain only directories that are not writable by other users
# (this script refuses to build with an unsafe PATH; see
# scripts/release_lib.sh)
# - activate a conda environment
# - have conda-build installed, e.g. `conda install conda-build`
#
Expand All @@ -21,13 +24,27 @@
# # cd into the snowflake-telemetry-python git root dir
# export SNOWFLAKE_TELEMETRY_DIR=$(pwd)
#
# For local test builds only, the workspace hygiene check can be bypassed with:
# For local test builds only, the workspace hygiene and PATH safety checks can
# be bypassed with:
# export SNOWFLAKE_TELEMETRY_ALLOW_DIRTY_WORKSPACE=1
# Never set this in the Jenkins release job.
# export SNOWFLAKE_TELEMETRY_ALLOW_UNSAFE_PATH=1
# Never set these in the Jenkins release job.

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Resolve the repo root with shell builtins only, so no external command runs
# via the ambient PATH before assert_secure_path has validated it.
case "${BASH_SOURCE[0]}" in
*/*) _script_dir="${BASH_SOURCE[0]%/*}" ;;
*) _script_dir="." ;;
esac
REPO_ROOT="$(cd "$_script_dir" && pwd)"
unset _script_dir
source "${REPO_ROOT}/scripts/release_lib.sh"

# This gate must run before anything that resolves commands through the
# ambient PATH, so release builds only use tools from directories that are
# not writable by other users.
assert_secure_path || exit 1

# Remove our own previous build output so it does not trip the hygiene gate.
rm -rf ./anaconda/dist

Expand Down
20 changes: 17 additions & 3 deletions pypi-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,27 @@
# # cd into the snowflake-telemetry-python git root dir
# export SNOWFLAKE_TELEMETRY_DIR=$(pwd)
#
# For local test builds only, the workspace hygiene check can be bypassed with:
# For local test builds only, the workspace hygiene and PATH safety checks can
# be bypassed with:
# export SNOWFLAKE_TELEMETRY_ALLOW_DIRTY_WORKSPACE=1
# Never set this in the Jenkins release job.
# export SNOWFLAKE_TELEMETRY_ALLOW_UNSAFE_PATH=1
# Never set these in the Jenkins release job.

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Resolve the repo root with shell builtins only, so no external command runs
# via the ambient PATH before assert_secure_path has validated it.
case "${BASH_SOURCE[0]}" in
*/*) _script_dir="${BASH_SOURCE[0]%/*}" ;;
*) _script_dir="." ;;
esac
REPO_ROOT="$(cd "$_script_dir" && pwd)"
unset _script_dir
source "${REPO_ROOT}/scripts/release_lib.sh"

# This gate must run before anything that resolves commands through the
# ambient PATH, so release builds only use tools from directories that are
# not writable by other users.
assert_secure_path || exit 1

# Remove our own previous build outputs so they do not trip the hygiene gate.
rm -rf ./dist ./build venv_* src/*.egg-info

Expand Down
106 changes: 106 additions & 0 deletions scripts/release_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,112 @@ assert_clean_workspace() {
fi
}

# assert_secure_path [path_to_check]
#
# Fail if any directory on the PATH to check (default: $PATH) is unsuitable
# for a release build. A PATH entry is unsuitable when it is empty (meaning
# the current directory), relative, not an existing directory, writable by
# group or others, or owned by anyone other than root or the current user; or
# when any parent directory up to / is writable by group or others without
# the sticky bit (the sticky bit, as on /tmp, prevents non-owners from
# replacing the child) or owned by another user.
#
# The checker's own external commands run from a minimal set of base system
# directories so the result does not depend on the PATH being checked.
#
# For local test builds only, set SNOWFLAKE_TELEMETRY_ALLOW_UNSAFE_PATH=1 to
# bypass this check. Never set it in the Jenkins release job.
assert_secure_path() {
local path_to_check="${1:-$PATH}"

if [ "${SNOWFLAKE_TELEMETRY_ALLOW_UNSAFE_PATH:-0}" = "1" ]; then
echo "WARNING: SNOWFLAKE_TELEMETRY_ALLOW_UNSAFE_PATH=1 is set; skipping the PATH safety check." >&2
echo "WARNING: never set this for official release builds." >&2
return 0
fi

# Run the checker's own external commands from base system directories
# only, so the result does not depend on the PATH being checked. PATH
# keeps its export attribute when reassigned.
local saved_path="$PATH"
PATH="/usr/bin:/bin:/usr/sbin:/sbin"

local current_user
if ! current_user="$(id -un 2>/dev/null)" || [ -z "$current_user" ]; then
PATH="$saved_path"
echo "Unable to determine the current user; refusing to build." >&2
return 1
fi

local failures=""
local old_ifs="$IFS"
IFS=':'
set -f
# shellcheck disable=SC2086
set -- $path_to_check
set +f
IFS="$old_ifs"

local entry
for entry in "$@"; do
if [ -z "$entry" ]; then
failures="${failures} <empty entry> (an empty PATH entry means the current directory)\n"
continue
fi
case "$entry" in
/*) ;;
*)
failures="${failures} $entry (relative PATH entry)\n"
continue
;;
esac

# Canonicalize (resolving symlinks) and confirm it is a directory.
local dir
if ! dir="$(cd "$entry" 2>/dev/null && pwd -P)"; then
failures="${failures} $entry (does not exist or is not a directory)\n"
continue
fi

# The PATH entry itself must not be writable by group/others at all:
# anyone who can add files to it can shadow build tools.
# (-perm /022 = any of the group/other write bits set.)
if [ -n "$(find "$dir" -prune -perm /022 2>/dev/null)" ]; then
failures="${failures} $dir (writable by group or others)\n"
continue
fi

# Walk every component up to /: each must be owned by root or the
# current user, and must not be writable by group/others unless it is
# sticky (e.g. /tmp), where the sticky bit stops non-owners from
# replacing the child.
local component="$dir"
while :; do
if [ -n "$(find "$component" -prune ! -user root ! -user "$current_user" 2>/dev/null)" ]; then
failures="${failures} $component (owned by an untrusted user, on PATH via $entry)\n"
break
fi
if [ -n "$(find "$component" -prune -perm /022 ! -perm -1000 2>/dev/null)" ]; then
failures="${failures} $component (writable by group or others without sticky bit, on PATH via $entry)\n"
break
fi
[ "$component" = "/" ] && break
component="$(dirname "$component")"
done
done

PATH="$saved_path"

if [ -n "$failures" ]; then
echo "Refusing to build release artifacts with an unsafe PATH." >&2
echo "Release builds require PATH directories that are not writable by other" >&2
echo "users. Unsafe entries:" >&2
printf '%b' "$failures" >&2
echo "Fix the permissions/ownership above or remove the entries from PATH." >&2
return 1
fi
}

# write_sha256_manifest <dist_dir>
#
# Write <dist_dir>/SHA256SUMS containing the SHA-256 digest of every file
Expand Down
Loading