hashicorp-vault-image/docker-entrypoint.sh
2024-06-11 09:50:05 +03:00

406 lines
12 KiB
Bash
Executable File

#!/bin/sh
# SPDX-License-Identifier: Apache-2.0
# (c) 2024, Konstantin Demin
set -ef
# Prevent core dumps
set +e
ulimit -Sc 0
ulimit -Hc 0
set -e
# not needed anyway
unset LD_PRELOAD
binary=$(command -v vault) ; : "${binary:?}"
binary=$(readlink -e "${binary}") ; : "${binary:?}"
have_capability() {
# local cap=$1
setpriv -d | awk -F: '/^Capability bounding set:/{print $2}' | grep -Eiq "(^\s*|,)$1(,|\$)"
}
have_ipc_lock=1
have_capability ipc_lock || have_ipc_lock=0
# ref: https://pkg.go.dev/strconv#ParseBool
gobool_to_int() {
# local value=$1
# local default=$2
case "$1" in
1 | t | T | TRUE | true | True ) echo 1 ;;
0 | f | F | FALSE | false | False ) echo 0 ;;
* ) echo "${2:-error}" ;;
esac
}
int_to_gobool() {
# local int=$1
case "$1" in
0 ) echo false ;;
1 ) echo true ;;
esac
}
# ref: https://developer.hashicorp.com/vault/docs/configuration#disable_mlock
with_mlock=$(gobool_to_int "${VAULT_DISABLE_MLOCK:-0}" 1)
with_mlock=$((1 - with_mlock))
# VAULT_CONFIG_DIR isn't exposed as a volume but you can compose additional
# config files in there if you use this image as a base, or use
# VAULT_LOCAL_CONFIG below.
VAULT_CONFIG_DIR=${VAULT_CONFIG_DIR:-/vault/config}
case "$1" in
vault )
# no adjustments are required - running Vault with directly specified command and options
:
;;
-* )
# running with options
set -- vault "$@"
;;
server )
shift
dev_mode=0
have_dev_listen=0
for i ; do
case "$i" in
-dev | -dev-tls )
dev_mode=1
;;
-dev-listen-address )
have_dev_listen=1
;;
esac
done ; unset i
# adjust environment for dev mode
if [ "${dev_mode}" = 1 ] && [ "${have_dev_listen}" = 0 ] ; then
export VAULT_DEV_LISTEN_ADDRESS="${VAULT_DEV_LISTEN_ADDRESS:-"0.0.0.0:8200"}"
fi
# not needed anymore
unset dev_mode have_dev_listen
set -- vault server \
-config="${VAULT_CONFIG_DIR}" \
"$@"
;;
version )
set -- vault "$@"
;;
* )
if vault --help "$1" 2>&1 | grep -Fq "vault $1" ; then
# We can't use the return code to check for the existence of a subcommand, so
# we have to use grep to look for a pattern in the help output.
set -- vault "$@"
fi
;;
esac
if [ "$1" != 'vault' ] ; then
unset SKIP_PERMCHECK SKIP_IPC_LOCK SKIP_VAULT_PRECONF SKIP_CHOWN SKIP_SETCAP
SKIP_PERMCHECK=1 SKIP_IPC_LOCK=1 SKIP_VAULT_PRECONF=1 SKIP_CHOWN=1 SKIP_SETCAP=1
fi
if [ "${SKIP_PERMCHECK:-0}" != 1 ] ; then
# Due to OpenShift environment compatibility, we have to allow group write
# access to the Vault configuration. This requires us to disable the stricter
# file permissions checks introduced in Vault v1.11.0.
# ref: https://developer.hashicorp.com/vault/docs/configuration
with_permcheck=0
x=none
# Vault 1.11 and older
if [ -n "${VAULT_DISABLE_FILE_PERMISSIONS_CHECK}" ] ; then
x=$(gobool_to_int "${VAULT_DISABLE_FILE_PERMISSIONS_CHECK}")
case "$x" in
0 | 1 ) with_permcheck=$((1 - x)) ;;
esac
fi
# Vault 1.12 and newer
if [ -n "${VAULT_ENABLE_FILE_PERMISSIONS_CHECK}" ] ; then
x=$(gobool_to_int "${VAULT_ENABLE_FILE_PERMISSIONS_CHECK}")
case "$x" in
0 | 1 ) with_permcheck=$x ;;
esac
fi
unset x
# adjust variables
set -a
VAULT_ENABLE_FILE_PERMISSIONS_CHECK=$(int_to_gobool "${with_permcheck}")
VAULT_DISABLE_FILE_PERMISSIONS_CHECK=$(int_to_gobool $((1 - with_permcheck)) )
set +a
unset with_permcheck
fi
unset SKIP_PERMCHECK
if [ "${SKIP_IPC_LOCK:-0}" != 1 ] ; then
if [ "${have_ipc_lock}" = 1 ] ; then
set +e
echo "Limits before:"
# sed -En '1p;/[Ll]ocked/p' < /proc/$$/limits
awk 'NR==1{print} /[Ll]ocked/{print}' /proc/$$/limits
ulimit -Hl "${MEMLOCK_LIMIT:-unlimited}"
x=$(ulimit -Hl)
ulimit -Sl "$x"
unset x
echo "Limits after:"
# sed -En '1p;/[Ll]ocked/p' < /proc/$$/limits
awk 'NR==1{print} /[Ll]ocked/{print}' /proc/$$/limits
set -e
fi >&2
if [ "${with_mlock}" = 1 ] && [ "${have_ipc_lock}" = 0 ] ; then
echo
echo '!!!'
echo
echo "missing CAP_IPC_LOCK privilege: please run container with \`--cap-add IPC_LOCK'"
echo 'ref: https://developer.hashicorp.com/vault/docs/configuration#disable_mlock'
echo
echo '!!!'
echo
fi >&2
fi
unset SKIP_IPC_LOCK
if [ "${SKIP_VAULT_PRECONF:-0}" != 1 ] ; then
# You can also set the VAULT_LOCAL_CONFIG environment variable to pass some
# Vault configuration JSON without having to bind any volumes.
VAULT_LOCAL_CONFIG_FILE=${VAULT_LOCAL_CONFIG_FILE:-"${VAULT_CONFIG_DIR}/local.json"}
while [ -n "${VAULT_LOCAL_CONFIG}" ] ; do
touch "${VAULT_LOCAL_CONFIG_FILE}" || break
printf '%s' "${VAULT_LOCAL_CONFIG}" > "${VAULT_LOCAL_CONFIG_FILE}" || break
break
done
unset VAULT_LOCAL_CONFIG VAULT_LOCAL_CONFIG_FILE
# Allow setting VAULT_REDIRECT_ADDR and VAULT_CLUSTER_ADDR using an interface
# name instead of an IP address. The interface name is specified using
# VAULT_REDIRECT_INTERFACE and VAULT_CLUSTER_INTERFACE environment variables. If
# VAULT_*_ADDR is also set, the resulting URI will combine the protocol and port
# number with the IP of the named interface.
have_iproute2=1
command -v ip >/dev/null 2>&1 || have_iproute2=0
err_iproute2_missing() {
printf '%s\n' "$1: iproute2 (/usr/sbin/ip) is missing" >&2
}
err_iface_missing() {
printf '%s\n' "$1: interface error" >&2
}
err_ipv4_addr_missing() {
printf '%s\n' "$1: no IPv4 addresses are present on interface" >&2
}
iface_exist() {
# local if_name=$1
ip link show dev "$1" >/dev/null
}
get_iface_ipv4_addr () {
# local if_name=$1
# local uri_template=$2
ip address show dev "$1" \
| awk -v "uri=$2" \
'/^\s*inet\s/ {
ip=gensub(/(.+)\/.+/, "\\1", "g", $2)
print gensub(/^(.+:\/\/).+(:.+)$/, "\\1" ip "\\2", "g", uri)
exit
}'
}
while [ -n "${VAULT_REDIRECT_INTERFACE}" ] ; do
new_vault_redirect_addr=${VAULT_REDIRECT_ADDR:-"http://0.0.0.0:8200"}
errmsg_vault_redirect_interface="unable to adjust VAULT_REDIRECT_ADDR='${new_vault_redirect_addr}' with VAULT_REDIRECT_INTERFACE='${VAULT_REDIRECT_INTERFACE}'"
if [ "${have_iproute2}" = 0 ] ; then
err_iproute2_missing "${errmsg_vault_redirect_interface}"
break
fi
if ! iface_exist "${VAULT_REDIRECT_INTERFACE}" ; then
err_iface_missing "${errmsg_vault_redirect_interface}"
break
fi
set +e
new_vault_redirect_addr=$(get_iface_ipv4_addr "${VAULT_REDIRECT_INTERFACE}" "${new_vault_redirect_addr}")
set -e
if [ -z "${new_vault_redirect_addr}" ] ; then
err_ipv4_addr_missing "${errmsg_vault_redirect_interface}"
break
fi
if [ -n "${VAULT_REDIRECT_ADDR}" ] && [ "${VAULT_REDIRECT_ADDR}" != "${new_vault_redirect_addr}" ]; then
echo "changing VAULT_REDIRECT_ADDR: ${VAULT_REDIRECT_ADDR} -> ${new_vault_redirect_addr}" >&2
fi
export VAULT_REDIRECT_ADDR="${new_vault_redirect_addr}"
echo "using interface ${VAULT_REDIRECT_INTERFACE} for VAULT_REDIRECT_ADDR: ${VAULT_REDIRECT_ADDR}" >&2
break
done
unset VAULT_REDIRECT_INTERFACE new_vault_redirect_addr errmsg_vault_redirect_interface
while [ -n "${VAULT_CLUSTER_INTERFACE}" ] ; do
new_vault_cluster_addr=${VAULT_CLUSTER_ADDR:-"https://0.0.0.0:8201"}
errmsg_vault_cluster_interface="unable to adjust VAULT_CLUSTER_ADDR='${new_vault_cluster_addr}' with VAULT_CLUSTER_INTERFACE='${VAULT_CLUSTER_INTERFACE}'"
if [ "${have_iproute2}" = 0 ] ; then
err_iproute2_missing "${errmsg_vault_cluster_interface}"
break
fi
if ! iface_exist "${VAULT_CLUSTER_INTERFACE}" ; then
err_iface_missing "${errmsg_vault_cluster_interface}"
break
fi
set +e
new_vault_cluster_addr=$(get_iface_ipv4_addr "${VAULT_CLUSTER_INTERFACE}" "${new_vault_cluster_addr}")
set -e
if [ -z "${new_vault_cluster_addr}" ] ; then
err_ipv4_addr_missing "${errmsg_vault_cluster_interface}"
break
fi
if [ -n "${VAULT_CLUSTER_ADDR}" ] && [ "${VAULT_CLUSTER_ADDR}" != "${new_vault_cluster_addr}" ] ; then
echo "changing VAULT_CLUSTER_ADDR: ${VAULT_CLUSTER_ADDR} -> ${new_vault_cluster_addr}" >&2
fi
export VAULT_CLUSTER_ADDR="${new_vault_cluster_addr}"
echo "using ${VAULT_CLUSTER_INTERFACE} for VAULT_CLUSTER_ADDR: ${VAULT_CLUSTER_ADDR}"
break
done
unset VAULT_CLUSTER_INTERFACE new_vault_cluster_addr errmsg_vault_cluster_interface
fi
unset SKIP_VAULT_PRECONF
while [ "${SKIP_CHOWN:-0}" != 1 ] ; do
vault_uid=$(id -u vault) || break
fix_ownership() {
x=0
for i ; do
[ -n "$i" ] || continue
if ! [ -d "$i" ] ; then
# NB: running through `env' to avoid shell glitch
env printf 'not a directory: %q\n' "$i" >&2
continue
fi
# NB: running through `env' to avoid shell glitch
x=$(env stat -Lc '%u' "$i")
if [ "$x" = "${vault_uid}" ] ; then
# nothing to fix (probably)
continue
fi
if ! chown -R vault:vault "$i" ; then
# NB: running through `env' to avoid shell glitch
env printf 'Could not chown %q (may not have appropriate permissions)\n' "$i" >&2
fi
done
unset x i
}
find_fast() {
find "$@" -printf . -quit | grep -Fq .
}
check_ownership() {
for i ; do
[ -n "$i" ] || continue
if ! [ -d "$i" ] ; then
# NB: running through `env' to avoid shell glitch
env printf 'not a directory: %q\n' "$i" >&2
continue
fi
find_fast "$i/" ! -uid "${vault_uid}" || continue
echo "files not owned by Vault:" >&2
set +e
find "$i/" ! -uid "${vault_uid}" -ls
set -e
done
unset i
}
fix_ownership /vault/config /vault/logs /vault/file
check_ownership /vault/config /vault/logs /vault/file
break
done
unset SKIP_CHOWN
if [ "${SKIP_SETCAP:-0}" != 1 ] ; then
have_libcap2=1
command -v setcap >/dev/null 2>&1 || have_libcap2=0
err_libcap2_missing() {
printf '%s\n' "$1: libcap2-bin (/usr/sbin/setcap) is missing" >&2
}
drop_ipc_lock=0
# try running Vault with CAP_IPC_LOCK
while [ "${with_mlock}" = 1 ] ; do
errmsg_vault_mlock="unable to run Vault with CAP_IPC_LOCK"
if [ "${have_libcap2}" = 0 ] ; then
err_libcap2_missing "${errmsg_vault_mlock}"
break
fi
if ! setcap cap_ipc_lock=+ep "${binary}" ; then
echo "${errmsg_vault_mlock}: setcap failed (read-only filesystem?)"
break
fi
# In the case vault has been started in a container without IPC_LOCK privileges
if ! "${binary}" -version >/dev/null 2>&1 ; then
echo "${errmsg_vault_mlock}"
drop_ipc_lock=1
break
fi
break
done
unset errmsg_vault_mlock
# not needed anymore
unset have_libcap2
if [ "${drop_ipc_lock}" = 1 ] ; then
# ignore any error
setcap cap_ipc_lock=-ep "${binary}" || :
fi
unset drop_ipc_lock
fi
unset SKIP_SETCAP
# not needed anymore
unset have_ipc_lock have_iproute2 with_mlock
# In case of Docker, where swap may be enabled, we
# still require mlocking to be available. So this script
# was executed as root to make this happen, however,
# we're now rerunning the entrypoint script as the Vault
# user but no longer need to run setup code for setcap
# or chowning directories (previously done on the first run).
if [ "$(id -u)" = '0' ] ; then
export SKIP_PERMCHECK=1 SKIP_IPC_LOCK=1 SKIP_VAULT_PRECONF=1 SKIP_CHOWN=1 SKIP_SETCAP=1
exec \
setpriv --reuid=vault --regid=vault --init-groups \
env USER=vault LOGNAME=vault HOME=/home/vault SHELL=/bin/sh \
"$0" "$@"
else
exec "$@"
fi