From ba7c1588ae4119a885a26412e81ec5b028db9acc Mon Sep 17 00:00:00 2001 From: Abhijeet <41686026+abhvsn@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:49:27 +0530 Subject: [PATCH] fix: Add password based auth for postgres (#37068) --- deploy/docker/fs/opt/appsmith/entrypoint.sh | 13 +- deploy/docker/fs/opt/appsmith/pg-utils.sh | 52 ++-- .../opt/appsmith/postgres/appsmith_hba.conf | 24 ++ deploy/docker/tests/.gitignore | 1 + deploy/docker/tests/composes.sh | 81 +++++ deploy/docker/tests/test-pg-auth.sh | 279 ++++++++++++++++++ deploy/docker/tests/test-pg-utils.sh | 13 +- scripts/local_testing.sh | 17 +- 8 files changed, 454 insertions(+), 26 deletions(-) create mode 100644 deploy/docker/fs/opt/appsmith/postgres/appsmith_hba.conf create mode 100644 deploy/docker/tests/.gitignore create mode 100644 deploy/docker/tests/composes.sh create mode 100755 deploy/docker/tests/test-pg-auth.sh diff --git a/deploy/docker/fs/opt/appsmith/entrypoint.sh b/deploy/docker/fs/opt/appsmith/entrypoint.sh index ce39e43598..77ab4cd5de 100644 --- a/deploy/docker/fs/opt/appsmith/entrypoint.sh +++ b/deploy/docker/fs/opt/appsmith/entrypoint.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash +# Source the helper script +source pg-utils.sh + set -e tlog "Running as: $(id)" @@ -440,6 +443,12 @@ init_postgres() { tlog "Initializing local Postgres data folder" su postgres -c "env PATH='$PATH' initdb -D $POSTGRES_DB_PATH" fi + cp /opt/appsmith/postgres/appsmith_hba.conf "$POSTGRES_DB_PATH/pg_hba.conf" + # PostgreSQL requires strict file permissions for the pg_hba.conf file. Add file permission settings after copying the configuration file. + # 600 is the recommended permission for pg_hba.conf file for read and write access to the owner only. + chown postgres:postgres "$POSTGRES_DB_PATH/pg_hba.conf" + chmod 600 "$POSTGRES_DB_PATH/pg_hba.conf" + create_appsmith_pg_db "$POSTGRES_DB_PATH" else runEmbeddedPostgres=0 @@ -477,7 +486,9 @@ create_appsmith_pg_db() { local max_attempts=300 local attempt=0 - until su postgres -c "env PATH='$PATH' pg_isready -h 127.0.0.1"; do + local unix_socket_directory=$(get_unix_socket_directory "$POSTGRES_DB_PATH") + echo "Unix socket directory is $unix_socket_directory" + until su postgres -c "env PATH='$PATH' pg_isready -h $unix_socket_directory"; do if (( attempt >= max_attempts )); then echo "Postgres failed to start within 300 seconds." return 1 diff --git a/deploy/docker/fs/opt/appsmith/pg-utils.sh b/deploy/docker/fs/opt/appsmith/pg-utils.sh index 27ad26efdc..4bb5dec16f 100755 --- a/deploy/docker/fs/opt/appsmith/pg-utils.sh +++ b/deploy/docker/fs/opt/appsmith/pg-utils.sh @@ -6,7 +6,8 @@ DB_HOST="127.0.0.1" DB_PORT="5432" DB_SCHEMA="appsmith" DB_NAME="appsmith" -postgres_admin_user="postgres" +POSTGRES_ADMIN_USER="postgres" +POSTGRES_DB_PATH="/appsmith-stacks/data/postgres/main" waitForPostgresAvailability() { if [ -z "$PG_DB_HOST" ]; then @@ -17,8 +18,9 @@ waitForPostgresAvailability() { MAX_RETRIES=50 RETRYSECONDS=10 retry_count=0 + local unix_socket_directory=$(get_unix_socket_directory "$POSTGRES_DB_PATH") while true; do - su postgres -c "pg_isready -h '${PG_DB_HOST}' -p '${PG_DB_PORT}'" + su postgres -c "pg_isready -h $unix_socket_directory -p '${PG_DB_PORT}'" status=$? case $status in @@ -106,31 +108,34 @@ init_pg_db() { # Check if the DB_HOST is local (localhost or 127.0.0.1) if [[ "$PG_DB_HOST" == "localhost" || "$PG_DB_HOST" == "127.0.0.1" ]]; then + local unix_socket_directory=$(get_unix_socket_directory "$POSTGRES_DB_PATH") # Check if the database exists - DB_CHECK=$(psql -h "$PG_DB_HOST" -p "$PG_DB_PORT" -U postgres -d "postgres" -tAc "SELECT 1 FROM pg_database WHERE datname='$PG_DB_NAME'") + DB_CHECK=$(psql -h "$unix_socket_directory" -p "$PG_DB_PORT" -U "$POSTGRES_ADMIN_USER" -tAc "SELECT 1 FROM pg_database WHERE datname='$PG_DB_NAME'") if [ "$DB_CHECK" != "1" ]; then echo "Database $PG_DB_NAME does not exist. Creating database..." - psql -h "$PG_DB_HOST" -p "$PG_DB_PORT" -U postgres -d "postgres" -c "CREATE DATABASE $PG_DB_NAME;" + psql -h "$unix_socket_directory" -p "$PG_DB_PORT" -U "$POSTGRES_ADMIN_USER" -c "CREATE DATABASE $PG_DB_NAME;" else echo "Database $PG_DB_NAME already exists." fi # Check if the schema exists - SCHEMA_CHECK=$(psql -h "$PG_DB_HOST" -p "$PG_DB_PORT" -U postgres -d "$PG_DB_NAME" -tAc "SELECT 1 FROM information_schema.schemata WHERE schema_name='appsmith'") + SCHEMA_CHECK=$(psql -h "$unix_socket_directory" -p "$PG_DB_PORT" -U "$POSTGRES_ADMIN_USER" -d "$PG_DB_NAME" -tAc "SELECT 1 FROM information_schema.schemata WHERE schema_name='appsmith'") # Create schema and user if not exists if [ "$SCHEMA_CHECK" != "1" ]; then echo "Creating user '$PG_DB_USER' with password " - psql -h "$PG_DB_HOST" -p "$PG_DB_PORT" -U postgres -d "$PG_DB_NAME" -c "CREATE USER \"$PG_DB_USER\" WITH PASSWORD '$PG_DB_PASSWORD';" + psql -h "$unix_socket_directory" -p "$PG_DB_PORT" -U "$POSTGRES_ADMIN_USER" -d "$PG_DB_NAME" -c "CREATE USER \"$PG_DB_USER\" WITH PASSWORD '$PG_DB_PASSWORD';" echo "Schema 'appsmith' does not exist. Creating schema..." - psql -h "$PG_DB_HOST" -p "$PG_DB_PORT" -U postgres -d "$PG_DB_NAME" -c "CREATE SCHEMA appsmith;" + psql -h "$unix_socket_directory" -p "$PG_DB_PORT" -U "$POSTGRES_ADMIN_USER" -d "$PG_DB_NAME" -c "CREATE SCHEMA appsmith;" fi - USER=$PG_DB_USER SCHEMA="appsmith" DB=$PG_DB_NAME HOST=$PG_DB_HOST PORT=$PG_DB_PORT grant_permissions_for_schema - echo "Creating pg_trgm extension..." - psql -h "$PG_DB_HOST" -p "$PG_DB_PORT" -U postgres -d "$PG_DB_NAME" -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" + psql -h "$unix_socket_directory" -p "$PG_DB_PORT" -U "$POSTGRES_ADMIN_USER" -d "$PG_DB_NAME" -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" + + # Grant permissions to the user on the schema + USER=$PG_DB_USER SCHEMA="appsmith" DB=$PG_DB_NAME HOST=$PG_DB_HOST PORT=$PG_DB_PORT grant_permissions_for_local_db_schema + else echo "Remote PostgreSQL detected, running as current user." PGPASSWORD=$PG_DB_PASSWORD psql -h "$PG_DB_HOST" -p "$PG_DB_PORT" -U "$PG_DB_USER" -d "$PG_DB_NAME" -c "CREATE SCHEMA IF NOT EXISTS appsmith;" @@ -160,18 +165,31 @@ init_pg_db() { # Returns: # None # Example: -# USER="user" SCHEMA="schema" DB="db" HOST="host" PORT="port" grant_permissions_for_schema -grant_permissions_for_schema() { +# USER="user" SCHEMA="schema" DB="db" HOST="host" PORT="port" grant_permissions_for_local_db_schema +grant_permissions_for_local_db_schema() { local user=${USER-$DB_USER} schema=${SCHEMA-$DB_SCHEMA} db=${DB-$DB_NAME} host=${HOST-$DB_HOST} port=${PORT-$DB_PORT} + local unix_socket_directory=$(get_unix_socket_directory "$POSTGRES_DB_PATH") tlog "Granting permissions to user '${user}' on schema '$schema' in database '$db' on host '$host' and port '$port'..." - psql -h ${host} -p ${port} -U ${postgres_admin_user} -d ${db} -c "GRANT ALL PRIVILEGES ON SCHEMA ${schema} TO ${user};" - psql -h ${host} -p ${port} -U ${postgres_admin_user} -d ${db} -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA ${schema} TO ${user};" - psql -h ${host} -p ${port} -U ${postgres_admin_user} -d ${db} -c "ALTER DEFAULT PRIVILEGES IN SCHEMA ${schema} GRANT ALL PRIVILEGES ON TABLES TO ${user};" - psql -h ${host} -p ${port} -U ${postgres_admin_user} -d ${db} -c "GRANT CONNECT ON DATABASE ${db} TO ${user};" + psql -h "$unix_socket_directory" -p "$port" -U "$POSTGRES_ADMIN_USER" -d "$db" -c "GRANT ALL PRIVILEGES ON SCHEMA ${schema} TO ${user};" + psql -h "$unix_socket_directory" -p "$port" -U "$POSTGRES_ADMIN_USER" -d "$db" -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA ${schema} TO ${user};" + psql -h "$unix_socket_directory" -p "$port" -U "$POSTGRES_ADMIN_USER" -d "$db" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA ${schema} GRANT ALL PRIVILEGES ON TABLES TO ${user};" + psql -h "$unix_socket_directory" -p "$port" -U "$POSTGRES_ADMIN_USER" -d "$db" -c "GRANT CONNECT ON DATABASE ${db} TO ${user};" +} + +get_unix_socket_directory() { + local postgres_db_path=${1:-"$POSTGRES_DB_PATH"} + local unix_socket_directory + unix_socket_directory=$(grep -E "^unix_socket_directories" "$postgres_db_path/postgresql.conf" | sed -E "s/.*= (.*).*/\1/" | cut -d',' -f1) + # If unix_socket_directory is empty, default to /var/run/postgresql + if [ -z "$unix_socket_directory" ]; then + unix_socket_directory="/var/run/postgresql" + fi + echo "$unix_socket_directory" } # Example usage of the functions # waitForPostgresAvailability # extract_postgres_db_params "postgresql://user:password@localhost:5432/dbname" # init_pg_db -# USER="user" SCHEMA="schema" DB="db" HOST="host" PORT="port" grant_permissions_for_schema +# USER="user" SCHEMA="schema" DB="db" HOST="host" PORT="port" grant_permissions_for_local_db_schema +# get_unix_socket_directory "/var/lib/postgresql/12/main" \ No newline at end of file diff --git a/deploy/docker/fs/opt/appsmith/postgres/appsmith_hba.conf b/deploy/docker/fs/opt/appsmith/postgres/appsmith_hba.conf new file mode 100644 index 0000000000..6112c796d0 --- /dev/null +++ b/deploy/docker/fs/opt/appsmith/postgres/appsmith_hba.conf @@ -0,0 +1,24 @@ +# This is a custom configuration for Embedded PostgreSQL for Appsmith. +# This file will be used to override the default pg_hba.conf file on restart. + +# What is the meaning of this configuration? +# This configuration changes how PostgreSQL authenticates users +# connecting to the database. For user "postgres", we are allowing +# all connections from all addresses without any password on the unix +# socket. For all other users, we are allowing connections from IPv4 +# and IPv6 with a password. + +# TYPE DATABASE USER ADDRESS METHOD +# "local" is for Unix domain socket connections only +local all postgres trust +# IPv4 local connections: +host appsmith appsmith 127.0.0.1/32 scram-sha-256 +host postgres appsmith 127.0.0.1/32 scram-sha-256 +# IPv6 local connections: +host appsmith appsmith ::1/128 scram-sha-256 +host postgres appsmith ::1/128 scram-sha-256 +# Allow replication connections from localhost, by a user with the +# replication privilege. +local replication all scram-sha-256 +host replication all 127.0.0.1/32 scram-sha-256 +host replication all ::1/128 scram-sha-256 \ No newline at end of file diff --git a/deploy/docker/tests/.gitignore b/deploy/docker/tests/.gitignore new file mode 100644 index 0000000000..412c257475 --- /dev/null +++ b/deploy/docker/tests/.gitignore @@ -0,0 +1 @@ +docker-compose.yml \ No newline at end of file diff --git a/deploy/docker/tests/composes.sh b/deploy/docker/tests/composes.sh new file mode 100644 index 0000000000..c1d1b19077 --- /dev/null +++ b/deploy/docker/tests/composes.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# set -o errexit +# set -x + +generate_compose_file() { + local version=$1 + check_appsmith_edition + cat <${docker_compose_path} +services: + appsmith: + image: index.docker.io/appsmith/appsmith-$edition:$version + container_name: $container_name + ports: + - "80:80" + - "443:443" + volumes: + - ${stacks_path}:/appsmith-stacks + environment: + - APPSMITH_CLOUD_SERVICES_BASE_URL=https://release-cs.appsmith.com + restart: unless-stopped +EOF +} + +compose_appsmith_version() { + local version=$1 + generate_compose_file $version + docker compose up -d +} + +compose_appsmith_latest() { + local version=latest + check_appsmith_edition + + generate_compose_file $version + docker compose pull && + docker compose up -d +} + +compose_appsmith_local() { + local version=latest + check_appsmith_edition + + cat <${docker_compose_path} +services: + appsmith: + image: appsmith/appsmith-local-$edition:$version + container_name: $container_name + ports: + - "80:80" + - "443:443" + volumes: + - ${stacks_path}:/appsmith-stacks + environment: + - APPSMITH_CLOUD_SERVICES_BASE_URL=https://release-cs.appsmith.com + restart: unless-stopped +EOF + + docker compose up -d + + # return container name + echo "$container_name" +} + +cleanup() { + echo "Starting fresh. Cleaning up the environment." + docker rm -f $container_name || true + sudo rm -rf ${stacks_path} || true +} + +check_appsmith_edition() { + export edition=ce + if [[ "$(git remote get-url origin)" == *appsmithorg/appsmith-ee* ]]; then + export edition=ee + fi + echo "Edition: $edition" +} + +container_name="appsmith-docker-test" +# mkdir -p /tmp/$container_name +stacks_path="/tmp/$container_name-stacks" +docker_compose_path="docker-compose.yml" \ No newline at end of file diff --git a/deploy/docker/tests/test-pg-auth.sh b/deploy/docker/tests/test-pg-auth.sh new file mode 100755 index 0000000000..854de8a962 --- /dev/null +++ b/deploy/docker/tests/test-pg-auth.sh @@ -0,0 +1,279 @@ +#!/bin/bash +set -o errexit +# set -x + +source ./composes.sh + + +# Function to update the APPSMITH_DB_URL in docker.env +# Once postgres is the default db, the APPSMITH_POSTGRES_DB_URL will be removed and this step won't be required anymore +# Check run-java.sh for more details why we need to update the APPSMITH_DB_URL to point to postgres +update_db_url() { + docker exec "${container_name}" bash -c "sed -i 's|^APPSMITH_DB_URL=mongodb|# &|' /appsmith-stacks/configuration/docker.env" + docker exec "${container_name}" bash -c "sed -i 's|^APPSMITH_POSTGRES_DB_URL=|APPSMITH_DB_URL=|' /appsmith-stacks/configuration/docker.env" +} + +# Function to check if the Appsmith instance is up +is_appsmith_instance_ready() { + local max_retries=200 + local retry_count=0 + local response_code + + while [ $retry_count -lt $max_retries ]; do + response_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/health) + if [[ $response_code -eq 200 ]]; then + echo "Appsmith instance is ready." + return 0 + fi + echo "Waiting for Appsmith instance to be ready... (Attempt: $((retry_count + 1)))" + retry_count=$((retry_count + 1)) + sleep 2 + done + return 1 +} + +# Function to wait until the postgres is ready +wait_for_postgres() { + local max_retries=200 + local retry_count=0 + + while [ $retry_count -lt $max_retries ]; do + if docker exec "${container_name}" pg_isready; then + echo "Postgres is ready." + return 0 + fi + echo "Waiting for Postgres to be ready... (Attempt: $((retry_count + 1)))" + retry_count=$((retry_count + 1)) + sleep 2 + done +} + +# Function to read the password from the PostgreSQL URL in docker.env.sh +get_appsmith_password() { + local password + password=$(docker exec "${container_name}" bash -c "grep -i 'APPSMITH_DB_URL' /appsmith-stacks/configuration/docker.env | sed -n 's/^.*\/\/appsmith:\([^@]*\)@.*$/\1/p'") + printf "%s" "$password" +} + +# Function to check the read access to databases +check_user_datasource_access_with_auth() { + local password + local appsmith_user_local_access + local appsmith_user_remote_access + password=$(get_appsmith_password) + docker exec -i "${container_name}" bash -c "psql -h 127.0.0.1 -p 5432 -U appsmith -c '\l'" <