diff --git a/.github/workflows/pre-merge.yml b/.github/workflows/pre-merge.yml index 59e8eeab2..7304b5a18 100644 --- a/.github/workflows/pre-merge.yml +++ b/.github/workflows/pre-merge.yml @@ -20,6 +20,17 @@ on: docker_images_metadata: description: 'Array of structured Docker images metadata that were published' value: ${{ jobs.collect-images-metadata.outputs.docker_images_metadata }} + workflow_dispatch: + inputs: + release_tag: + description: 'Release tag to build' + required: false + type: string + publish_image: + description: 'Publish Docker image to GHCR' + required: false + type: boolean + default: false jobs: build-and-test: diff --git a/debian/docker-entrypoint.sh b/debian/docker-entrypoint.sh index d0a21fe4d..ada2f6589 100755 --- a/debian/docker-entrypoint.sh +++ b/debian/docker-entrypoint.sh @@ -90,6 +90,167 @@ fix_perms_and_owner() { done } +first_arg_is_special() { + if [ "$1" = "-v" ] \ + || [ "$1" = "--version" ] \ + || [ "$1" = "-h" ] \ + || [ "$1" = "--help" ] \ + || [ "$1" = "--test-memory" ] \ + || [ "$1" = "--check-system" ]; then \ + return 0 + fi + return 1 +} + +requirepass_arg_present() { + # TODO: maybe better to check that provided password is not empty? + for arg in "$@"; do + if [ "$arg" = "--requirepass" ]; then + return 0 + fi + done + return 1 +} + +# Assume that requirepass, include or user directives means that we have +# security settings and there is no point to show empty password warning. +config_has_security_settings() { + if [ ! -r "$1" ]; then + return 1 + fi + # TODO: skip user for sentinel as it always has one + grep -q -e '^[[:space:]]*\(requirepass\|include\|user\)[[:space:]][[:space:]]*[^[:space:]]' "$1" +} + +sleep_and_print_empty_password_warning() { + if [ -n "$1" ]; then + sleep "$1" + fi + cat >&2 <<-'EOF' + ******************************************************************************** + ******************************************************************************** + ** ** + ** ##### WARNING ##### ** + ** ** + ** NO PASSWORD HAS BEEN SET! ** + ** ** + ** Redis is starting WITHOUT authentication enabled. ** + ** Anyone with network access to this container can connect ** + ** to Redis without a password and execute commands. ** + ** ** + ** This is INSECURE and should only be used in development environments. ** + ** ** + ** To secure your Redis instance, set a password using one of: ** + ** - REDIS_PASSWORD or REDIS_PASSWORD_FILE environment variable ** + ** - requirepass directive in redis.conf ** + ** - --requirepass command line option ** + ** - ACL users configuration ** + ** ** + ** Use SKIP_PASSWORD_WARNING=1 to hide this warning. ** + ** ** + ******************************************************************************** + ******************************************************************************** +EOF +} + +show_empty_password_warning() { + local config="$1" + shift + local pw + pw=$(get_provided_password) + + if [ -z "$SKIP_PASSWORD_WARNING" ] && [ -z "$pw" ] && ! requirepass_arg_present "$@" && ! config_has_security_settings "$config"; then + export -f sleep_and_print_empty_password_warning + setsid -f bash -c 'sleep_and_print_empty_password_warning 1' + fi +} + +enquote_config_value() { + # TODO: properly escape and quote password for config + echo "$1" +} + +# Provide error handling +get_password_from_file() { + if [ ! -r "$1" ]; then + return 1 + fi + # TODO: check for multiline, null bytes, etc + head -1 "$1" +} + +get_provided_password() { + # TODO: Handle cases when both are set with priority or error + if [ -n "$REDIS_PASSWORD" ]; then + echo "$REDIS_PASSWORD" + return 0 + fi + # TODO: Make sure to fail if file is not readable and not optional + if [ -n "$REDIS_PASSWORD_FILE" ]; then + get_password_from_file "$REDIS_PASSWORD_FILE" || : + return 0 + fi +} + +create_password_arg() { + # nameref to caller's variable + local -n _out=$1 + local no_include_conf="$2" + + local pw + pw=$(get_provided_password) + if [ -z "$pw" ]; then + _out=() + return 0 + fi + + # Try to use config file for password, to avoid showing it in process list. + if [ -z "$no_include_conf" ] && [ -w /tmp ] && touch /tmp/requirepass.conf && chmod 0600 /tmp/requirepass.conf; then + echo "requirepass $(enquote_config_value "$pw")" > /tmp/requirepass.conf + _out=(--include /tmp/requirepass.conf) + return 0 + else + # Fallback to --requirepass if /tmp is not writable. + _out=(--requirepass "$pw") + fi +} + +create_module_args() { + # nameref to caller's variable + local -n _out=$1 + _out=() + + modules_dir="/usr/local/lib/redis/modules" + if [ ! -d "$modules_dir" ]; then + echo "Warning: Default Redis modules directory $modules_dir does not exist." + elif [ -n "$(ls -A $modules_dir 2>/dev/null)" ]; then + for module in "$modules_dir"/*.so; + do + if [ ! -s "$module" ]; then + echo "Skipping module $module: file has no size." + continue + fi + + if [ -d "$module" ]; then + echo "Skipping module $module: is a directory." + continue + fi + + if [ ! -r "$module" ]; then + echo "Skipping module $module: file is not readable." + continue + fi + + if [ ! -x "$module" ]; then + echo "Warning: Module $module is not executable." + continue + fi + + _out+=("--loadmodule" "$module") + done + fi +} + # first arg is `-f` or `--some-option` # or first arg is `something.conf` if [ "${1#-}" != "$1" ] || [ "${1%.conf}" != "$1" ]; then @@ -106,7 +267,7 @@ if check_for_sentinel "$CMD" "$@"; then fi # if is server and its first arg is not an option then it's a config -if [ "$IS_REDIS_SERVER" ] && [ "${2#-}" = "$2" ]; then +if [ "$IS_REDIS_SERVER" ] || [ "$IS_REDIS_SENTINEL" ] && [ "${2#-}" = "$2" ]; then CONFIG="$2" fi @@ -147,38 +308,39 @@ if [ "$um" = '0022' ]; then umask 0077 fi +# inject generated arguments before user arguments to keep override ability +current_args=("$@") +head_args=("${current_args[@]}") +tail_args=() +if [ "$IS_REDIS_SERVER" ] || [ "$IS_REDIS_SENTINEL" ]; then + if first_arg_is_special "$2"; then + exec "${head_args[@]}" + fi + # head_args: command and (optionally) config + # tail_args: user supplied arguments + if [ -n "$CONFIG" ]; then + head_args=("${current_args[@]:0:2}") + tail_args=("${current_args[@]:2}") + else + head_args=("${current_args[@]:0:1}") + tail_args=("${current_args[@]:1}") + fi + + pass_arg=() + # sentinel doesn't support --include + create_password_arg pass_arg $IS_REDIS_SENTINEL + head_args+=("${pass_arg[@]}") + + show_empty_password_warning "$CONFIG" "$@" +fi + if [ "$IS_REDIS_SERVER" ] && ! [ "$IS_REDIS_SENTINEL" ]; then echo "Starting Redis Server" - modules_dir="/usr/local/lib/redis/modules/" - - if [ ! -d "$modules_dir" ]; then - echo "Warning: Default Redis modules directory $modules_dir does not exist." - elif [ -n "$(ls -A $modules_dir 2>/dev/null)" ]; then - for module in "$modules_dir"/*.so; - do - if [ ! -s "$module" ]; then - echo "Skipping module $module: file has no size." - continue - fi - - if [ -d "$module" ]; then - echo "Skipping module $module: is a directory." - continue - fi - - if [ ! -r "$module" ]; then - echo "Skipping module $module: file is not readable." - continue - fi - if [ ! -x "$module" ]; then - echo "Warning: Module $module is not executable." - continue - fi - - set -- "$@" --loadmodule "$module" - done - fi + module_args=() + create_module_args module_args + + head_args+=("${module_args[@]}") fi -exec "$@" +exec "${head_args[@]}" "${tail_args[@]}"