commit 5203f014a09d68374c4709daffec026efca68cbb Author: Leopere Date: Mon Jul 14 14:10:19 2025 -0400 Initial commit of Postgre-TLS project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bdf91a --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# PostgreSQL data directories +data/ +logs/ + +# Secrets directory +secrets/ + +# SSL certificates +ssl/ +*.crt +*.key +*.pem + +# Environment files with secrets +.env +.env.local +.env.production + +# Temporary files +*.tmp +*.log +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Docker +.dockerignore + +# IDE files +.vscode/ +.idea/ +*.sublime-project +*.sublime-workspace + +# Backup files +*.bak +*.backup +*.sql.gz +*.sql.bz2 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..02d5364 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +FROM postgres:15-alpine + +# Install additional packages for encryption and utilities +RUN apk add --no-cache \ + openssl \ + curl \ + bash \ + && rm -rf /var/cache/apk/* + +# Set environment variables for non-interactive operation +ENV PGUSER=postgres \ + POSTGRES_HOST_AUTH_METHOD=scram-sha-256 \ + PAGER=cat \ + ENABLE_FALLBACK_SSL=true + +# Create necessary directories +RUN mkdir -p /var/lib/postgresql/ssl \ + && mkdir -p /var/log/postgresql \ + && mkdir -p /docker-entrypoint-initdb.d + +# Copy configuration files +COPY postgresql.conf /etc/postgresql/postgresql.conf +COPY entrypoint.sh /usr/local/bin/entrypoint.sh + +# Make scripts executable +RUN chmod +x /usr/local/bin/entrypoint.sh + +# Set proper ownership +RUN chown -R postgres:postgres /var/lib/postgresql/ssl \ + && chown -R postgres:postgres /var/log/postgresql \ + && chown postgres:postgres /etc/postgresql/postgresql.conf + +# Expose PostgreSQL port +EXPOSE 5432 + +# Set the custom start script as entrypoint +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..476d7d5 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# Postgre-TLS - Secure PostgreSQL Docker Setup with SSL/TLS + +A secure PostgreSQL Docker container with enforced SSL/TLS encryption, certificate verification, and advanced security features. + +## Features + +- **SSL/TLS Encryption**: TLSv1.3 with 256-bit AES-GCM encryption +- **Certificate Verification**: Full SSL certificate verification enabled +- **SCRAM-SHA-256 Authentication**: Secure password authentication +- **Row-Level Security**: Built-in support for fine-grained access control +- **Audit Logging**: Comprehensive audit trail for database operations +- **Data Integrity**: Checksums enabled for data corruption detection +- **Monitoring**: Built-in performance monitoring with pg_stat_statements +- **Encryption Functions**: pgcrypto extension for additional encryption capabilities + +## Quick Start + +1. **Start the PostgreSQL container**: + ```bash + ./start.sh + ``` + +2. **Connect to the database**: + ```bash + ./connect.sh + ``` + +3. **Stop the container**: + ```bash + docker-compose down + ``` + +## SSL Connection Details + +The setup provides enterprise-grade security with: +- **Encryption**: TLSv1.3 with TLS_AES_256_GCM_SHA384 cipher +- **Key Size**: 256-bit encryption +- **Certificate**: Self-signed with full verification +- **Authentication**: SCRAM-SHA-256 password hashing + +## Manual Connection + +You can also connect manually using psql: +```bash +psql "host=localhost port=5432 dbname=ploughgres user=ploughgres_user sslmode=verify-full sslrootcert=secrets/ca_crt" +``` + +For non-interactive connection, set the PGPASSWORD environment variable: +```bash +export PGPASSWORD=$(cat secrets/postgres_password || echo "change_me_in_production") +psql "host=localhost port=5432 dbname=ploughgres user=ploughgres_user sslmode=verify-full sslrootcert=secrets/ca_crt" +``` + +## Project Structure + +``` +``` \ No newline at end of file diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..5ef8c67 --- /dev/null +++ b/USAGE.md @@ -0,0 +1,80 @@ +# PloughGres Usage Guide + +## Quick Start + +1. **Start the PostgreSQL container:** + ```bash + ./start.sh + ``` + +2. **Connect to the database:** + ```bash + ./connect.sh + ``` + +3. **Stop the container:** + ```bash + docker-compose down + ``` + +## Common Operations + +### Database Management +- **View container status:** `docker ps` +- **View container logs:** `docker logs ploughgres-db` +- **Access PostgreSQL shell:** `./connect.sh` +- **Restart container:** `docker-compose restart` + +### SSL Connection Testing +The setup uses TLSv1.3 with 256-bit encryption. Connection details: +- **Host:** localhost +- **Port:** 5432 +- **Database:** ploughgres +- **User:** ploughgres_user +- **SSL Mode:** verify-full (certificate verification enabled) + +### Data Persistence +- Database data is stored in `./data/` directory +- SSL certificates are in `./secrets/` directory +- All data remains in the project directory (userspace) + +### Security Features +- ✅ SSL/TLS encryption (TLSv1.3) +- ✅ Certificate verification +- ✅ SCRAM-SHA-256 authentication +- ✅ Row-level security enabled +- ✅ Audit logging +- ✅ Encryption functions (pgcrypto) + +## Troubleshooting + +### Container Issues +- **Container won't start:** Check `docker logs ploughgres-db` +- **Port conflicts:** Ensure port 5432 is available +- **Permission issues:** Check file permissions in `secrets/` directory + +### SSL Connection Issues +- **Certificate errors:** Verify certificates exist in `secrets/` +- **Connection refused:** Ensure container is running and healthy +- **Authentication failed:** Check password in `secrets/postgres_password.txt` + +## Development + +### Making Changes +1. Stop the container: `docker-compose down` +2. Make your changes +3. Rebuild and restart: `./start.sh` + +### Viewing SSL Status +Connect to the database and run: +```sql +SELECT ssl, version, cipher, bits FROM pg_stat_ssl WHERE pid = pg_backend_pid(); +``` + +## Files Overview +- `docker-compose.yml` - Container configuration +- `start.sh` - Initialization and startup script +- `connect.sh` - SSL connection test script +- `Dockerfile` - Container image definition +- `secrets/` - SSL certificates and passwords +- `data/` - PostgreSQL data directory \ No newline at end of file diff --git a/connect.sh b/connect.sh new file mode 100755 index 0000000..c3f8032 --- /dev/null +++ b/connect.sh @@ -0,0 +1,62 @@ +#!/bin/bash +set -e + +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color +YELLOW='\033[0;33m' # Yellow + +echo -e "${GREEN}[Postgre-TLS] Testing SSL connection to PostgreSQL...${NC}" + +# Load password +PASSWORD=$([ -f secrets/postgres_password ] && cat secrets/postgres_password || echo "change_me_in_production") +export PGPASSWORD="$PASSWORD" + +# Test basic connection +OUTPUT=$(psql "host=localhost port=5432 dbname=ploughgres user=ploughgres_user sslmode=verify-full sslrootcert=secrets/ca.crt" \ + -c "SELECT version(), current_user, current_database();" -t) || { + echo -e "${RED}[Postgre-TLS] Connection failed!${NC}" + exit 1 +} +echo "$OUTPUT" + +# Check SSL details +echo -e "\n${GREEN}[Postgre-TLS] SSL Connection Details:${NC}" +SSL_DETAILS=$(psql "host=localhost port=5432 dbname=ploughgres user=ploughgres_user sslmode=verify-full sslrootcert=secrets/ca.crt" \ + -c "SELECT ssl, version as ssl_version, cipher as ssl_cipher, bits as ssl_bits FROM pg_stat_ssl WHERE pid = pg_backend_pid();" -t) || { + echo -e "${RED}[Postgre-TLS] Failed to get SSL details!${NC}" + exit 1 +} +echo "$SSL_DETAILS" + +# Test non-SSL connection (should fail) +echo -e "\n${YELLOW}[Postgre-TLS] Testing non-SSL connection (expected to fail):${NC}" +psql "host=localhost port=5432 dbname=ploughgres user=ploughgres_user sslmode=disable" -c "SELECT 1;" 2>&1 | grep "SSL" || echo -e "${GREEN}Non-SSL connection correctly refused.${NC}" + +# Advanced database operations over SSL +echo -e "\n${GREEN}[Postgre-TLS] Performing advanced tests over SSL:${NC}" + +# Create test table +psql "host=localhost port=5432 dbname=ploughgres user=ploughgres_user sslmode=verify-full sslrootcert=secrets/ca.crt" -c "CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, data TEXT);" || { echo -e "${RED}Failed to create test table!${NC}"; exit 1; } +echo "Test table created." + +# Insert data +psql "host=localhost port=5432 dbname=ploughgres user=ploughgres_user sslmode=verify-full sslrootcert=secrets/ca.crt" -c "INSERT INTO test_table (data) VALUES ('Hello, SSL World!');" || { echo -e "${RED}Failed to insert data!${NC}"; exit 1; } +echo "Data inserted." + +# Query data +QUERY_RESULT=$(psql "host=localhost port=5432 dbname=ploughgres user=ploughgres_user sslmode=verify-full sslrootcert=secrets/ca.crt" -c "SELECT data FROM test_table WHERE id = (SELECT MAX(id) FROM test_table);" -t) || { echo -e "${RED}Failed to query data!${NC}"; exit 1; } +echo "Queried data: $QUERY_RESULT" + +# Drop test table +psql "host=localhost port=5432 dbname=ploughgres user=ploughgres_user sslmode=verify-full sslrootcert=secrets/ca.crt" -c "DROP TABLE test_table;" || { echo -e "${RED}Failed to drop test table!${NC}"; exit 1; } +echo "Test table dropped." + +# Check if all tests passed +if [ $? -eq 0 ]; then + echo -e "\n${GREEN}[Postgre-TLS] All advanced SSL connection tests successful!${NC}" +else + echo -e "\n${RED}[Postgre-TLS] Advanced tests failed!${NC}" + exit 1 +fi \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..243da65 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +services: + postgres: + build: . + container_name: postgretls-db + restart: unless-stopped + environment: + POSTGRES_DB: ploughgres + POSTGRES_USER: ploughgres_user + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change_me_in_production} + # Enable SSL/TLS + POSTGRES_INITDB_ARGS: "--auth-local=password --auth-host=scram-sha-256" + # Enable fallback for local development + ENABLE_FALLBACK_SSL: "true" + ports: + - "5432:5432" + volumes: + - postgretls_data:/var/lib/postgresql/data + - postgretls_logs:/var/log/postgresql + - ./secrets:/run/secrets:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ploughgres_user -d ploughgres"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - postgretls-network + +volumes: + postgretls_data: + postgretls_logs: + +networks: + postgretls-network: + driver: bridge \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..1a7bb17 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,260 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}[Postgre-TLS] Starting PostgreSQL with encryption...${NC}" + +# Function to setup SSL certificates from Docker Swarm secrets +setup_ssl_certificates() { + echo -e "${YELLOW}[Postgre-TLS] Setting up SSL certificates from Docker Swarm secrets...${NC}" + + # Debug: list contents of /run/secrets + echo -e "${YELLOW}[Postgre-TLS] Listing contents of /run/secrets:${NC}" + ls -la /run/secrets/ || echo "No /run/secrets directory found" + + # Check if running in Docker Swarm mode + if [ -f "/run/secrets/server_crt" ] || [ -f "/run/secrets/server.crt" ]; then + echo -e "${YELLOW}[Postgre-TLS] Using SSL certificates from Docker Swarm secrets...${NC}" + + # Copy certificates from Docker secrets to expected locations + if [ -f "/run/secrets/ca_crt" ]; then + cp /run/secrets/ca_crt /var/lib/postgresql/ssl/ca.crt + elif [ -f "/run/secrets/ca.crt" ]; then + cp /run/secrets/ca.crt /var/lib/postgresql/ssl/ca.crt + fi + + if [ -f "/run/secrets/server_crt" ]; then + cp /run/secrets/server_crt /var/lib/postgresql/ssl/server.crt + elif [ -f "/run/secrets/server.crt" ]; then + cp /run/secrets/server.crt /var/lib/postgresql/ssl/server.crt + fi + + if [ -f "/run/secrets/server_key" ]; then + cp /run/secrets/server_key /var/lib/postgresql/ssl/server.key + elif [ -f "/run/secrets/server.key" ]; then + cp /run/secrets/server.key /var/lib/postgresql/ssl/server.key + fi + + # Set proper permissions + chmod 600 /var/lib/postgresql/ssl/server.key + chmod 644 /var/lib/postgresql/ssl/server.crt + chmod 644 /var/lib/postgresql/ssl/ca.crt + + # Set ownership to postgres user + chown postgres:postgres /var/lib/postgresql/ssl/* + + echo -e "${GREEN}[Postgre-TLS] SSL certificates from Docker Swarm configured successfully${NC}" + elif [ "$ENABLE_FALLBACK_SSL" = "true" ]; then + echo -e "${YELLOW}[Postgre-TLS] Generating fallback SSL certificates for local development...${NC}" + + # Generate CA private key + openssl genrsa -out /var/lib/postgresql/ssl/ca.key 2048 + + # Generate CA certificate + openssl req -new -x509 -key /var/lib/postgresql/ssl/ca.key \ + -out /var/lib/postgresql/ssl/ca.crt -days 365 \ + -subj "/C=US/ST=State/L=City/O=Postgre-TLS/CN=Postgre-TLS-CA" + + # Generate server private key + openssl genrsa -out /var/lib/postgresql/ssl/server.key 2048 + + # Generate server certificate signing request + openssl req -new -key /var/lib/postgresql/ssl/server.key \ + -out /var/lib/postgresql/ssl/server.csr \ + -subj "/C=US/ST=State/L=City/O=Postgre-TLS/CN=localhost" + + # Generate server certificate signed by CA + openssl x509 -req -in /var/lib/postgresql/ssl/server.csr \ + -CA /var/lib/postgresql/ssl/ca.crt \ + -CAkey /var/lib/postgresql/ssl/ca.key \ + -CAcreateserial -out /var/lib/postgresql/ssl/server.crt \ + -days 365 + + # Clean up CSR + rm /var/lib/postgresql/ssl/server.csr + + # Set proper permissions + chmod 600 /var/lib/postgresql/ssl/server.key + chmod 644 /var/lib/postgresql/ssl/server.crt + chmod 644 /var/lib/postgresql/ssl/ca.crt + + # Set ownership to postgres user + chown postgres:postgres /var/lib/postgresql/ssl/* + + echo -e "${GREEN}[Postgre-TLS] Fallback SSL certificates generated successfully${NC}" + else + echo -e "${RED}[Postgre-TLS] Error: SSL certificates must be provided via Docker Swarm secrets. Fallback generation is not supported.${NC}" + exit 1 + fi +} + +# Function to check if database needs initialization +needs_initialization() { + if [ ! -s "$PGDATA/PG_VERSION" ]; then + return 0 # true - needs initialization + else + return 1 # false - already initialized + fi +} + +# Function to load secrets from Docker Swarm or environment +load_secrets() { + echo -e "${YELLOW}[Postgre-TLS] Loading secrets...${NC}" + + # Check if secrets are available + if [ -f "/run/secrets/postgres_password" ]; then + echo -e "${YELLOW}[Postgre-TLS] Loading secrets from /run/secrets...${NC}" + export POSTGRES_PASSWORD=$(cat /run/secrets/postgres_password) + export BACKUP_ENCRYPTION_KEY=$(cat /run/secrets/backup_encryption_key 2>/dev/null || echo "") + export JWT_SECRET=$(cat /run/secrets/jwt_secret 2>/dev/null || echo "") + export APP_SECRET_KEY=$(cat /run/secrets/app_secret_key 2>/dev/null || echo "") + echo -e "${GREEN}[Postgre-TLS] Secrets loaded from /run/secrets${NC}" + echo "Loaded POSTGRES_PASSWORD: $POSTGRES_PASSWORD" + else + echo -e "${YELLOW}[Postgre-TLS] Using environment variables for secrets${NC}" + export POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-change_me_in_production}" + fi + + # Set default values for other variables + export POSTGRES_DB="${POSTGRES_DB:-ploughgres}" + export POSTGRES_USER="${POSTGRES_USER:-ploughgres_user}" +} + +# Function to initialize database with encryption +initialize_database() { + echo -e "${YELLOW}[Postgre-TLS] Initializing database...${NC}" + + # Create a temporary password file with proper ownership + su-exec postgres sh -c "echo '$POSTGRES_PASSWORD' > /tmp/pgpass" + su-exec postgres chmod 600 /tmp/pgpass + + # Initialize the database with proper encoding and authentication + # Create the main database during initdb + export POSTGRES_DB="${POSTGRES_DB:-ploughgres}" + su-exec postgres initdb \ + --pgdata="$PGDATA" \ + --username="$POSTGRES_USER" \ + --pwfile=/tmp/pgpass \ + --auth-local=scram-sha-256 \ + --auth-host=scram-sha-256 \ + --encoding=UTF8 \ + --locale=C.UTF-8 \ + --data-checksums + + # Clean up password file + rm -f /tmp/pgpass + + echo -e "${GREEN}[Postgre-TLS] Database initialized successfully${NC}" +} + +# Function to start PostgreSQL +start_postgresql() { + echo -e "${YELLOW}[Postgre-TLS] Starting PostgreSQL server...${NC}" + + # Start PostgreSQL with custom configuration + exec su-exec postgres postgres \ + -c config_file=/etc/postgresql/postgresql.conf \ + -c ssl=on \ + -c ssl_cert_file=/var/lib/postgresql/ssl/server.crt \ + -c ssl_key_file=/var/lib/postgresql/ssl/server.key \ + -c ssl_ca_file=/var/lib/postgresql/ssl/ca.crt \ + -c shared_preload_libraries=pg_stat_statements \ + -c log_statement=ddl \ + -c log_destination=stderr \ + -c logging_collector=on \ + -c log_directory=/var/log/postgresql \ + -c log_filename=postgresql.log +} + +# Main execution +main() { + # Set PGDATA if not set + export PGDATA="${PGDATA:-/var/lib/postgresql/data}" + + # Ensure data directory exists and has proper permissions + # mkdir -p "$PGDATA" + # chown postgres:postgres "$PGDATA" + # chmod 700 "$PGDATA" + + # Load secrets from Docker Swarm or environment + load_secrets + + # Setup SSL certificates + setup_ssl_certificates + + # Initialize database if needed + if needs_initialization; then + echo -e "${YELLOW}[Postgre-TLS] Database not found, initializing...${NC}" + initialize_database + + # Create temporary pg_hba.conf for trust authentication during init + echo 'local all all trust' > /tmp/pg_hba.conf + echo 'host all all 127.0.0.1/32 trust' >> /tmp/pg_hba.conf + echo 'host all all ::1/128 trust' >> /tmp/pg_hba.conf + + # Start PostgreSQL temporarily with temporary hba for initialization + echo -e "${YELLOW}[Postgre-TLS] Starting PostgreSQL temporarily for initialization...${NC}" + su-exec postgres postgres \ + -c config_file=/etc/postgresql/postgresql.conf \ + -c ssl=off \ + -c listen_addresses=localhost \ + -c port=5432 \ + -c hba_file=/tmp/pg_hba.conf & + + # Wait for PostgreSQL to start + until su-exec postgres pg_isready -h localhost -p 5432; do + echo -e "${YELLOW}[Postgre-TLS] Waiting for PostgreSQL to start...${NC}" + sleep 1 + done + + # Create the application database if it doesn't exist + echo -e "${YELLOW}[Postgre-TLS] Creating database: $POSTGRES_DB${NC}" + su-exec postgres createdb -h localhost -p 5432 -U "$POSTGRES_USER" "$POSTGRES_DB" 2>/dev/null || echo -e "${GREEN}[Postgre-TLS] Database $POSTGRES_DB already exists${NC}" + + # Run initialization scripts + for f in /docker-entrypoint-initdb.d/*; do + case "$f" in + *.sh) + echo -e "${YELLOW}[Postgre-TLS] Running $f${NC}" + bash "$f" + ;; + *.sql) + echo -e "${YELLOW}[Postgre-TLS] Running $f${NC}" + su-exec postgres psql -v ON_ERROR_STOP=1 -h localhost -p 5432 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < "$f" + ;; + *) + echo -e "${YELLOW}[Postgre-TLS] Ignoring $f${NC}" + ;; + esac + done + + # Stop the temporary PostgreSQL instance + su-exec postgres pg_ctl -D "$PGDATA" -m fast -w stop + + # Configure final pg_hba.conf for SSL connections + echo -e "${YELLOW}[Postgre-TLS] Configuring pg_hba.conf for SSL...${NC}" + cat > "$PGDATA/pg_hba.conf" < secrets/postgres_password + chmod 600 secrets/postgres_password + echo -e "${GREEN}[PloughGres] Generated password stored in secrets/postgres_password${NC}" + echo -e "${YELLOW}[PloughGres] Your PostgreSQL password is:${NC}" + cat secrets/postgres_password + echo "" +fi + +# Manage container +echo -e "${YELLOW}[PloughGres] Managing Docker container...${NC}" +docker-compose down -v || true # Graceful down with volume removal +docker-compose build +docker-compose up -d +echo -e "${GREEN}[PloughGres] Container started successfully!${NC}" +echo -e "${YELLOW}[PloughGres] Run ./connect.sh to test the connection.${NC}" \ No newline at end of file