fix(deploy): harden bootstrap for RHEL-family Linux
This commit is contained in:
1 file changed
+133
-15
+133
-15
@@ -46,6 +46,14 @@ STATE_DIR="/etc/felis"
|
|||||||
SECRETS_ENV="${STATE_DIR}/secrets.env"
|
SECRETS_ENV="${STATE_DIR}/secrets.env"
|
||||||
SRC_DIR="/opt/felis/src"
|
SRC_DIR="/opt/felis/src"
|
||||||
HOST_BIN="/usr/local/bin/felis"
|
HOST_BIN="/usr/local/bin/felis"
|
||||||
|
K3S_BIN_DIR="${K3S_BIN_DIR:-/usr/local/bin}"
|
||||||
|
K3S_BIN="${K3S_BIN_DIR}/k3s"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Clean PATH (sudo may strip /usr/local/bin)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
export PATH
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Logging
|
# Logging
|
||||||
@@ -55,7 +63,8 @@ ok() { printf '\033[1;32m[ ok ]\033[0m %s\n' "$*"; }
|
|||||||
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; }
|
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; }
|
||||||
die() { printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2; exit 1; }
|
die() { printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
kube() { k3s kubectl "$@"; }
|
k3s_cmd() { [ -x "$K3S_BIN" ] || die "k3s binary not found at ${K3S_BIN}"; "$K3S_BIN" "$@"; }
|
||||||
|
kube() { k3s_cmd kubectl "$@"; }
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 0. Privilege & host facts
|
# 0. Privilege & host facts
|
||||||
@@ -71,6 +80,8 @@ detect_os() {
|
|||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
OS_ID="${ID:-unknown}"
|
OS_ID="${ID:-unknown}"
|
||||||
OS_VERSION="${VERSION_ID:-unknown}"
|
OS_VERSION="${VERSION_ID:-unknown}"
|
||||||
|
OS_ID_LIKE="${ID_LIKE:-}"
|
||||||
|
OS_CODENAME="${VERSION_CODENAME:-${UBUNTU_CODENAME:-}}"
|
||||||
if command -v apt-get >/dev/null 2>&1; then
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
PKG="apt"
|
PKG="apt"
|
||||||
elif command -v dnf >/dev/null 2>&1; then
|
elif command -v dnf >/dev/null 2>&1; then
|
||||||
@@ -146,12 +157,83 @@ install_base() {
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 3. Docker (used only to build & export the felis image; k3s uses containerd)
|
# 3. Docker (used only to build & export the felis image; k3s uses containerd)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
docker_apt_repo_os() {
|
||||||
|
case "$OS_ID" in
|
||||||
|
debian|ubuntu)
|
||||||
|
printf '%s\n' "$OS_ID"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "Docker apt repository is not configured for ${OS_ID} ${OS_VERSION}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
install_docker_apt() {
|
||||||
|
local arch keyring repo_os
|
||||||
|
|
||||||
|
repo_os="$(docker_apt_repo_os)"
|
||||||
|
[ -n "$OS_CODENAME" ] || die "cannot determine apt codename for ${OS_ID} ${OS_VERSION}"
|
||||||
|
|
||||||
|
arch="$(dpkg --print-architecture)"
|
||||||
|
keyring="/etc/apt/keyrings/docker.asc"
|
||||||
|
|
||||||
|
log "installing docker apt repository"
|
||||||
|
install -m 0755 -d /etc/apt/keyrings
|
||||||
|
curl -fsSL "https://download.docker.com/linux/${repo_os}/gpg" -o "$keyring" \
|
||||||
|
|| die "failed to download Docker GPG key for ${repo_os}"
|
||||||
|
chmod a+r "$keyring"
|
||||||
|
|
||||||
|
cat > /etc/apt/sources.list.d/docker.list <<EOF
|
||||||
|
deb [arch=${arch} signed-by=${keyring}] https://download.docker.com/linux/${repo_os} ${OS_CODENAME} stable
|
||||||
|
EOF
|
||||||
|
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get update -y
|
||||||
|
pkg_install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_rpm_repo_url() {
|
||||||
|
case "$OS_ID" in
|
||||||
|
rhel)
|
||||||
|
printf '%s\n' "https://download.docker.com/linux/rhel/docker-ce.repo"
|
||||||
|
;;
|
||||||
|
centos|almalinux|rocky)
|
||||||
|
printf '%s\n' "https://download.docker.com/linux/centos/docker-ce.repo"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
case " ${OS_ID_LIKE} " in
|
||||||
|
*" rhel "*|*" centos "*)
|
||||||
|
printf '%s\n' "https://download.docker.com/linux/centos/docker-ce.repo"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "Docker rpm repository is not configured for ${OS_ID} ${OS_VERSION}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
install_docker_rpm() {
|
||||||
|
local repo_file repo_url
|
||||||
|
|
||||||
|
repo_file="/etc/yum.repos.d/docker-ce.repo"
|
||||||
|
repo_url="$(docker_rpm_repo_url)"
|
||||||
|
|
||||||
|
log "installing docker rpm repository"
|
||||||
|
mkdir -p "$(dirname "$repo_file")"
|
||||||
|
curl -fsSL "$repo_url" -o "$repo_file" \
|
||||||
|
|| die "failed to download Docker repo file: ${repo_url}"
|
||||||
|
pkg_install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||||
|
}
|
||||||
|
|
||||||
install_docker() {
|
install_docker() {
|
||||||
if command -v docker >/dev/null 2>&1; then
|
if command -v docker >/dev/null 2>&1; then
|
||||||
ok "docker already installed"
|
ok "docker already installed"
|
||||||
else
|
else
|
||||||
log "installing docker via get.docker.com"
|
case "$PKG" in
|
||||||
curl -fsSL https://get.docker.com | sh
|
apt) install_docker_apt ;;
|
||||||
|
dnf|yum) install_docker_rpm ;;
|
||||||
|
*) die "Docker installation is not supported with package manager: ${PKG}" ;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
systemctl enable --now docker
|
systemctl enable --now docker
|
||||||
ok "docker running"
|
ok "docker running"
|
||||||
@@ -163,14 +245,18 @@ install_docker() {
|
|||||||
# security claim, so we must NOT pass --disable-network-policy.
|
# security claim, so we must NOT pass --disable-network-policy.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
install_k3s() {
|
install_k3s() {
|
||||||
if command -v k3s >/dev/null 2>&1; then
|
if [ -x "$K3S_BIN" ]; then
|
||||||
ok "k3s already installed"
|
ok "k3s already installed at ${K3S_BIN}"
|
||||||
else
|
else
|
||||||
log "installing k3s (no traefik/servicelb/metrics-server)"
|
log "installing k3s into ${K3S_BIN_DIR} (no traefik/servicelb/metrics-server)"
|
||||||
curl -sfL https://get.k3s.io | \
|
curl -sfL https://get.k3s.io | \
|
||||||
|
INSTALL_K3S_BIN_DIR="$K3S_BIN_DIR" \
|
||||||
INSTALL_K3S_EXEC="--disable traefik --disable servicelb --disable metrics-server --write-kubeconfig-mode 644" \
|
INSTALL_K3S_EXEC="--disable traefik --disable servicelb --disable metrics-server --write-kubeconfig-mode 644" \
|
||||||
sh -
|
sh -
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
[ -x "$K3S_BIN" ] || die "k3s installation completed but ${K3S_BIN} is missing"
|
||||||
|
|
||||||
systemctl enable --now k3s
|
systemctl enable --now k3s
|
||||||
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
|
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
|
||||||
log "waiting for the node to become Ready"
|
log "waiting for the node to become Ready"
|
||||||
@@ -221,7 +307,7 @@ build_image() {
|
|||||||
chmod 0755 "$HOST_BIN"
|
chmod 0755 "$HOST_BIN"
|
||||||
|
|
||||||
log "importing ${FELIS_IMAGE} into k3s containerd"
|
log "importing ${FELIS_IMAGE} into k3s containerd"
|
||||||
docker save "$FELIS_IMAGE" | k3s ctr images import -
|
docker save "$FELIS_IMAGE" | k3s_cmd ctr images import -
|
||||||
|
|
||||||
# Reclaim the ~150 MiB the docker daemon holds; reruns restart it on demand.
|
# Reclaim the ~150 MiB the docker daemon holds; reruns restart it on demand.
|
||||||
systemctl stop docker docker.socket 2>/dev/null || true
|
systemctl stop docker docker.socket 2>/dev/null || true
|
||||||
@@ -232,6 +318,43 @@ build_image() {
|
|||||||
# 6. PostgreSQL on the host (apt/dnf). felis-api pods reach it at <node-ip>:5432;
|
# 6. PostgreSQL on the host (apt/dnf). felis-api pods reach it at <node-ip>:5432;
|
||||||
# migrations run from the host binary against 127.0.0.1.
|
# migrations run from the host binary against 127.0.0.1.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
write_pg_hba_block() {
|
||||||
|
local hba="$1" tmp node_cidr
|
||||||
|
|
||||||
|
node_cidr="${NODE_IP}/32"
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
|
||||||
|
awk \
|
||||||
|
-v db="$DB_NAME" \
|
||||||
|
-v user="$DB_USER" \
|
||||||
|
-v pod="$POD_CIDR" \
|
||||||
|
-v node="$node_cidr" '
|
||||||
|
$0 == "# BEGIN FELIS MANAGED HBA" { skip = 1; next }
|
||||||
|
$0 == "# END FELIS MANAGED HBA" { skip = 0; next }
|
||||||
|
skip { next }
|
||||||
|
|
||||||
|
# Clean up rules appended by older bootstrap versions.
|
||||||
|
$1 == "host" && $2 == db && $3 == user && $5 == "scram-sha-256" &&
|
||||||
|
($4 == "127.0.0.1/32" || $4 == pod || $4 == node) { next }
|
||||||
|
|
||||||
|
{ print }
|
||||||
|
' "$hba" > "$tmp"
|
||||||
|
|
||||||
|
{
|
||||||
|
printf "# BEGIN FELIS MANAGED HBA\n"
|
||||||
|
printf "# Felis rules must precede distro defaults such as 127.0.0.1 ident.\n"
|
||||||
|
printf "host %s %s 127.0.0.1/32 scram-sha-256\n" "$DB_NAME" "$DB_USER"
|
||||||
|
printf "host %s %s %s scram-sha-256\n" "$DB_NAME" "$DB_USER" "$POD_CIDR"
|
||||||
|
printf "host %s %s %s scram-sha-256\n" "$DB_NAME" "$DB_USER" "$node_cidr"
|
||||||
|
printf "# END FELIS MANAGED HBA\n"
|
||||||
|
printf "\n"
|
||||||
|
cat "$tmp"
|
||||||
|
} > "${tmp}.new"
|
||||||
|
|
||||||
|
cat "${tmp}.new" > "$hba"
|
||||||
|
rm -f "$tmp" "${tmp}.new"
|
||||||
|
}
|
||||||
|
|
||||||
install_postgres() {
|
install_postgres() {
|
||||||
if command -v psql >/dev/null 2>&1 && systemctl list-unit-files 2>/dev/null | grep -q '^postgresql'; then
|
if command -v psql >/dev/null 2>&1 && systemctl list-unit-files 2>/dev/null | grep -q '^postgresql'; then
|
||||||
ok "postgresql already installed"
|
ok "postgresql already installed"
|
||||||
@@ -263,17 +386,12 @@ configure_postgres() {
|
|||||||
# Listen on all interfaces (applied on restart). ALTER SYSTEM is idempotent.
|
# Listen on all interfaces (applied on restart). ALTER SYSTEM is idempotent.
|
||||||
sudo -u postgres psql -v ON_ERROR_STOP=1 -c "ALTER SYSTEM SET listen_addresses = '*';" >/dev/null
|
sudo -u postgres psql -v ON_ERROR_STOP=1 -c "ALTER SYSTEM SET listen_addresses = '*';" >/dev/null
|
||||||
|
|
||||||
# Allow the host loopback, the pod CIDR, and the node IP (covers SNAT either way).
|
# Allow the host loopback, the pod CIDR, and the node IP before broader distro defaults.
|
||||||
local line
|
write_pg_hba_block "$hba"
|
||||||
for line in \
|
|
||||||
"host ${DB_NAME} ${DB_USER} 127.0.0.1/32 scram-sha-256" \
|
|
||||||
"host ${DB_NAME} ${DB_USER} ${POD_CIDR} scram-sha-256" \
|
|
||||||
"host ${DB_NAME} ${DB_USER} ${NODE_IP}/32 scram-sha-256" ; do
|
|
||||||
grep -qF "$line" "$hba" || echo "$line" >> "$hba"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Role + database (idempotent), and (re)set the password to our generated one.
|
# Role + database (idempotent), and (re)set the password to our generated one.
|
||||||
sudo -u postgres psql -v ON_ERROR_STOP=1 <<SQL >/dev/null
|
sudo -u postgres psql -v ON_ERROR_STOP=1 <<SQL >/dev/null
|
||||||
|
SET password_encryption = 'scram-sha-256';
|
||||||
DO \$\$
|
DO \$\$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${DB_USER}') THEN
|
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${DB_USER}') THEN
|
||||||
|
|||||||
Reference in new issue
Block a user