Skip to content
Open
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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ PMM_PORT_HTTPS=443
# PMM_POSTGRES_SSL_CERT_PATH=/tmp/certs/pmm-managed.crt
# PMM_DISABLE_BUILTIN_POSTGRES=1

# Expose the built-in PostgreSQL to SEP running in a side container on the same bridge
# network. PMM creates a dedicated `sep` database owned by a non-superuser `sep` role and
# accepts connections for it from the container's Docker subnets only; nothing is
# published on the host. Both variables are required to enable it.
# PMM_ENABLE_SEP=1
# PMM_SEP_POSTGRES_PASSWORD=<password>

# Use SSL certificates for PMM Server's internal database connection (PostgreSQL)
# GF_DATABASE_SSL_MODE=verify-full
# GF_DATABASE_CA_CERT_PATH=/tmp/certs/root.crt
Expand Down
17 changes: 9 additions & 8 deletions build/ansible/roles/postgres/files/postgres-migration
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
set -o errexit
set -o pipefail

declare POSTGRES_DATA_DIR="${POSTGRES_DATA_DIR:-/srv/postgres14}"
declare POSTGRES_PASSWORD_FILE="${POSTGRES_PASSWORD_FILE:-/srv/.postgres_password}"
declare POSTGRES_DATA_DIR="${POSTGRES_DATA_DIR:?must be exported by the entrypoint}"
declare POSTGRES_PASSWORD_FILE="${POSTGRES_PASSWORD_FILE:?must be exported by the entrypoint}"
declare POSTGRES_BIN_DIR="${POSTGRES_BIN_DIR:?must be exported by the entrypoint}"

ensure_postgres_password() {
# This check is to verify if the data directory is empty.
Expand All @@ -20,19 +21,19 @@ ensure_postgres_password() {
echo "Generating postgres superuser password..."
POSTGRES_PASSWORD=$(openssl rand -hex 16)

if ! /usr/pgsql-14/bin/pg_ctl status -D "$POSTGRES_DATA_DIR" > /dev/null 2>&1; then
/usr/pgsql-14/bin/pg_ctl start -D "$POSTGRES_DATA_DIR" -o "-c logging_collector=off"
if ! "$POSTGRES_BIN_DIR/pg_ctl" status -D "$POSTGRES_DATA_DIR" > /dev/null 2>&1; then
"$POSTGRES_BIN_DIR/pg_ctl" start -D "$POSTGRES_DATA_DIR" -o "-c logging_collector=off"
STARTED=1
fi

PGPASSWORD="$POSTGRES_PASSWORD" /usr/bin/psql -U postgres -h /run/postgresql -d postgres \
PGPASSWORD="$POSTGRES_PASSWORD" "$POSTGRES_BIN_DIR/psql" -U postgres -h /run/postgresql -d postgres \
-c "ALTER USER postgres WITH PASSWORD '${POSTGRES_PASSWORD}'"

echo -n "$POSTGRES_PASSWORD" > "$POSTGRES_PASSWORD_FILE"
chmod 600 "$POSTGRES_PASSWORD_FILE"

if [ "$STARTED" -eq 1 ]; then
/usr/pgsql-14/bin/pg_ctl stop -D "$POSTGRES_DATA_DIR"
"$POSTGRES_BIN_DIR/pg_ctl" stop -D "$POSTGRES_DATA_DIR"
fi
}

Expand All @@ -53,8 +54,8 @@ update_pg_hba_auth() {
sed -E 's/^([[:space:]]*host[[:space:]].*[[:space:]])trust([[:space:]]*)$/\1scram-sha-256\2/' "$hba" > "$tmp"
mv "$tmp" "$hba"

if /usr/pgsql-14/bin/pg_ctl status -D "$POSTGRES_DATA_DIR" > /dev/null 2>&1; then
/usr/pgsql-14/bin/pg_ctl reload -D "$POSTGRES_DATA_DIR"
if "$POSTGRES_BIN_DIR/pg_ctl" status -D "$POSTGRES_DATA_DIR" > /dev/null 2>&1; then
"$POSTGRES_BIN_DIR/pg_ctl" reload -D "$POSTGRES_DATA_DIR"
fi
}

Expand Down
122 changes: 122 additions & 0 deletions build/ansible/roles/postgres/files/postgres-sep
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/bin/bash
#
# Makes the embedded PostgreSQL reachable by SEP running in a side container on a
# shared Docker network, and provisions a dedicated low-privilege role/database for it.
#
# Everything below is a no-op unless PMM_ENABLE_SEP is set. When it is unset again,
# the configuration this script added is reverted on the next start; the sep role and
# database are left untouched.

set -o errexit
set -o pipefail

declare POSTGRES_DATA_DIR="${POSTGRES_DATA_DIR:?must be exported by the entrypoint}"
declare POSTGRES_PASSWORD_FILE="${POSTGRES_PASSWORD_FILE:?must be exported by the entrypoint}"
declare POSTGRES_BIN_DIR="${POSTGRES_BIN_DIR:?must be exported by the entrypoint}"
declare PG_CONF="$POSTGRES_DATA_DIR/postgresql.conf"
declare PG_HBA="$POSTGRES_DATA_DIR/pg_hba.conf"
declare BEGIN_MARKER="# BEGIN PMM SEP"
declare END_MARKER="# END PMM SEP"

is_enabled() { [ "$1" = "1" ] || [ "$1" = "true" ]; }

# Replaces the marker-delimited block of $1 with $2, appending it at the end of the
# file so it takes precedence over anything set earlier. An empty $2 drops the block.
write_block() {
local file="$1" body="$2" tmp

if [ ! -f "$file" ]; then
echo "FATAL: $file not found." >&2
exit 1
fi

if [ -z "$body" ] && ! grep -q "^${BEGIN_MARKER}$" "$file"; then
return
fi

tmp=$(mktemp "$POSTGRES_DATA_DIR/.pmm-sep.XXXXXX")
sed "/^${BEGIN_MARKER}$/,/^${END_MARKER}$/d" "$file" > "$tmp"
if [ -n "$body" ]; then
{
echo "$BEGIN_MARKER"
echo "$body"
echo "$END_MARKER"
} >> "$tmp"
fi
mv "$tmp" "$file"
}

# Prints the IPv4 CIDR of every Docker network this container is attached to.
container_subnets() {
ip -o -4 route show scope link | awk '$1 ~ /\// && $1 !~ /^127\./ { print $1 }'
}

psql_postgres() {
"$POSTGRES_BIN_DIR/psql" -X -q -v ON_ERROR_STOP=1 -U postgres -h /run/postgresql -d postgres "$@"
}

provision_sep_role() {
local started=0 role_exists db_exists

if ! "$POSTGRES_BIN_DIR/pg_ctl" status -D "$POSTGRES_DATA_DIR" > /dev/null 2>&1; then
"$POSTGRES_BIN_DIR/pg_ctl" start -D "$POSTGRES_DATA_DIR" -o "-c logging_collector=off -c listen_addresses="
started=1
fi

PGPASSWORD=$(< "$POSTGRES_PASSWORD_FILE")
export PGPASSWORD

role_exists=$(psql_postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname = 'sep'")
if [ -z "$role_exists" ]; then
psql_postgres -c "CREATE ROLE sep"
fi

# Re-applied on every start so rotating PMM_SEP_POSTGRES_PASSWORD takes effect.
# Fed via stdin, not -c: psql only interpolates :'sep_password' when reading a script.
psql_postgres -v sep_password="$PMM_SEP_POSTGRES_PASSWORD" <<'SQL'
ALTER ROLE sep WITH LOGIN NOSUPERUSER NOCREATEROLE NOCREATEDB PASSWORD :'sep_password';
SQL

db_exists=$(psql_postgres -tAc "SELECT 1 FROM pg_database WHERE datname = 'sep'")
if [ -z "$db_exists" ]; then
psql_postgres -c "CREATE DATABASE sep OWNER sep"
fi

unset PGPASSWORD

if [ "$started" -eq 1 ]; then
"$POSTGRES_BIN_DIR/pg_ctl" stop -D "$POSTGRES_DATA_DIR"
fi
}

if ! is_enabled "$PMM_ENABLE_SEP"; then
write_block "$PG_CONF" ""
write_block "$PG_HBA" ""
exit 0
fi

if [ -z "$PMM_SEP_POSTGRES_PASSWORD" ]; then
echo "FATAL: PMM_ENABLE_SEP is set but PMM_SEP_POSTGRES_PASSWORD is empty." >&2
echo "Please set PMM_SEP_POSTGRES_PASSWORD to the password SEP will connect with and try again." >&2
exit 1
fi

declare -a SUBNETS
mapfile -t SUBNETS < <(container_subnets)
if [ ${#SUBNETS[@]} -eq 0 ]; then
echo "FATAL: PMM_ENABLE_SEP is set but this container is not attached to any network." >&2
echo "Please attach pmm-server to the bridge network shared with SEP and try again." >&2
exit 1
fi

echo "Exposing PostgreSQL to SEP on ${SUBNETS[*]}..."

write_block "$PG_CONF" "listen_addresses = '*'"

declare HBA_BODY=""
for subnet in "${SUBNETS[@]}"; do
HBA_BODY+="host sep sep ${subnet} scram-sha-256"$'\n'
done
write_block "$PG_HBA" "${HBA_BODY%$'\n'}"

provision_sep_role
21 changes: 16 additions & 5 deletions build/docker/server/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ declare CURRENT_GID CURRENT_UID CURRENT_USER
is_enabled() { [ "$1" = "1" ] || [ "$1" = "true" ]; }
declare POSTGRES_DATA_DIR="/srv/postgres14"
declare POSTGRES_PASSWORD_FILE="/srv/.postgres_password"
declare POSTGRES_BIN_DIR="/usr/pgsql-14/bin"

# Get current user info - handle cases where user doesn't exist in passwd
CURRENT_UID=$(id -u)
Expand Down Expand Up @@ -97,12 +98,12 @@ if [ ! -f "$DIST_FILE" ]; then
chmod 600 "$POSTGRES_PASSWORD_FILE"

# Initialize database with password authentication
/usr/pgsql-14/bin/initdb -D "$POSTGRES_DATA_DIR" --auth-host=scram-sha-256 --auth-local=trust --username=postgres --pwfile="$POSTGRES_PASSWORD_FILE"
"$POSTGRES_BIN_DIR/initdb" -D "$POSTGRES_DATA_DIR" --auth-host=scram-sha-256 --auth-local=trust --username=postgres --pwfile="$POSTGRES_PASSWORD_FILE"

echo "Enabling pg_stat_statements extension for PostgreSQL..."
/usr/pgsql-14/bin/pg_ctl start -D "$POSTGRES_DATA_DIR" -o "-c logging_collector=off"
PGPASSWORD="$POSTGRES_PASSWORD" /usr/bin/psql -U postgres -h /run/postgresql -d postgres -c 'CREATE EXTENSION pg_stat_statements SCHEMA public'
/usr/pgsql-14/bin/pg_ctl stop -D "$POSTGRES_DATA_DIR"
"$POSTGRES_BIN_DIR/pg_ctl" start -D "$POSTGRES_DATA_DIR" -o "-c logging_collector=off"
PGPASSWORD="$POSTGRES_PASSWORD" "$POSTGRES_BIN_DIR/psql" -U postgres -h /run/postgresql -d postgres -c 'CREATE EXTENSION pg_stat_statements SCHEMA public'
"$POSTGRES_BIN_DIR/pg_ctl" stop -D "$POSTGRES_DATA_DIR"

# Clean up password from environment
unset POSTGRES_PASSWORD
Expand Down Expand Up @@ -145,7 +146,17 @@ elif is_enabled "$PMM_DISABLE_BUILTIN_POSTGRES"; then
else
mkdir -p /run/postgresql
chmod 750 "$POSTGRES_DATA_DIR" || true
bash /opt/ansible/roles/postgres/files/postgres-migration
# Scoped to this subshell so the helper scripts inherit them without polluting
# the environment that supervisord and its children are started with.
(
export POSTGRES_DATA_DIR POSTGRES_PASSWORD_FILE POSTGRES_BIN_DIR
bash /opt/ansible/roles/postgres/files/postgres-migration
bash /opt/ansible/roles/postgres/files/postgres-sep
)
fi

if is_enabled "$PMM_ENABLE_SEP" && { is_enabled "$PMM_HA_ENABLE" || is_enabled "$PMM_DISABLE_BUILTIN_POSTGRES"; }; then
echo "WARNING: ignoring PMM_ENABLE_SEP, the embedded PostgreSQL is not in use." >&2
fi

echo "Generating self-signed certificates for nginx..."
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ services:
- PMM_POSTGRES_USERNAME
- PMM_POSTGRES_DBPASSWORD
- PMM_DISABLE_BUILTIN_POSTGRES
# To expose the built-in PostgreSQL to SEP running on the same bridge network
- PMM_ENABLE_SEP
- PMM_SEP_POSTGRES_PASSWORD
# To discover RDS databases via UI
- AWS_ACCESS_KEY
- AWS_SECRET_KEY
Expand Down
3 changes: 3 additions & 0 deletions managed/utils/envvars/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ func ParseEnvVars(envs []string) (*models.ChangeSettingsParams, []error, []strin
"PMM_DISABLE_BUILTIN_POSTGRES":
// skip env variables for external postgres
continue
case "PMM_ENABLE_SEP", "PMM_SEP_POSTGRES_PASSWORD":
// skip env variables consumed by the entrypoint to expose postgres to SEP
continue
case "PERCONA_TELEMETRY_DISABLE":
// skip the Pillars telemetry environment variable
continue
Expand Down
12 changes: 12 additions & 0 deletions managed/utils/envvars/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ func TestEnvVarValidator(t *testing.T) {
assert.Equal(t, expectedWarns, gotWarns)
})

t.Run("SEP env variables", func(t *testing.T) {
t.Parallel()

envs := []string{"PMM_ENABLE_SEP=1", "PMM_SEP_POSTGRES_PASSWORD=s3cr3t"}
expectedEnvVars := &models.ChangeSettingsParams{}

gotEnvVars, gotErrs, gotWarns := ParseEnvVars(envs)
assert.Equal(t, expectedEnvVars, gotEnvVars)
assert.Nil(t, gotErrs)
assert.Nil(t, gotWarns)
})

t.Run("Default env vars", func(t *testing.T) {
t.Parallel()

Expand Down
Loading