Skip to content

Commit

Permalink
Add SSL support
Browse files Browse the repository at this point in the history
There are two cases when PostgreSQL with SSL enabled may come handy. The
first one is when PostgreSQL driver developers want to test SSL support
in their drivers. The second one is when you come to depend on some
self-signed certificates and you want to test your application
end-to-end, i.e. with certificates being used.

Co-authored-by: Ihor Kalnytskyi <[email protected]>
Signed-off-by: Ihor Kalnytskyi <[email protected]>
Signed-off-by: chandr-andr (Kiselev Aleksandr) <[email protected]>
  • Loading branch information
chandr-andr and ikalnytskyi committed Aug 19, 2024
1 parent 50da8e8 commit a16e5c7
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 7 deletions.
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@ jobs:

- name: Run tests
run: |
python3 -m pip install --upgrade pip pytest psycopg furl
python3 -m pip install --upgrade pip pytest psycopg furl cryptography
python3 -m pytest -vv test_action.py
env:
CONNECTION_URI: ${{ steps.postgres.outputs.connection-uri }}
SERVICE_NAME: ${{ steps.postgres.outputs.service-name }}
CERTIFICATE_PATH: ${{ steps.postgres.outputs.certificate-path }}
EXPECTED_CONNECTION_URI: postgresql://postgres:postgres@localhost:5432/postgres
EXPECTED_SERVICE_NAME: postgres
EXPECTED_SERVER_VERSION: "16"
EXPECTED_SSL: false

parametrized:
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -76,6 +78,7 @@ jobs:
database: jedi_order
port: 34837
postgres-version: ${{ matrix.postgres-version }}
ssl: true
id: postgres

- name: Run setup-python
Expand All @@ -85,11 +88,13 @@ jobs:

- name: Run tests
run: |
python3 -m pip install --upgrade pip pytest psycopg furl
python3 -m pip install --upgrade pip pytest psycopg furl cryptography
python3 -m pytest -vv test_action.py
env:
CONNECTION_URI: ${{ steps.postgres.outputs.connection-uri }}
SERVICE_NAME: ${{ steps.postgres.outputs.service-name }}
EXPECTED_CONNECTION_URI: postgresql://yoda:GrandMaster@localhost:34837/jedi_order
CERTIFICATE_PATH: ${{ steps.postgres.outputs.certificate-path }}
EXPECTED_CONNECTION_URI: postgresql://yoda:GrandMaster@localhost:34837/jedi_order?sslmode=verify-ca&sslrootcert=${{ steps.postgres.outputs.certificate-path }}
EXPECTED_SERVICE_NAME: yoda
EXPECTED_SERVER_VERSION: ${{ matrix.postgres-version }}
EXPECTED_SSL: true
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ By default PostgreSQL 15 is used.

#### Outputs

| Key | Description | Example |
|----------------|----------------------------------------------|-----------------------------------------------------|
| connection-uri | The connection URI to connect to PostgreSQL. | `postgresql://postgres:postgres@localhost/postgres` |
| service-name | The service name with connection parameters. | `postgres` |
| Key | Description | Example |
|------------------|--------------------------------------------------|-----------------------------------------------------|
| connection-uri | The connection URI to connect to PostgreSQL. | `postgresql://postgres:postgres@localhost/postgres` |
| service-name | The service name with connection parameters. | `postgres` |
| certificate-path | The path to the server certificate if SSL is on. | `/home/runner/work/_temp/pgdata/server.crt` |

#### User permissions

Expand All @@ -74,6 +75,7 @@ steps:
database: test
port: 34837
postgres-version: "14"
ssl: "on"
id: postgres

- run: pytest -vv tests/
Expand Down
37 changes: 37 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ inputs:
postgres-version:
description: The PostgreSQL major version to install. Either "14", "15", or "16".
default: "16"
ssl:
description: When "true", encrypt connections using SSL (TLS).
default: "false"
required: false
outputs:
connection-uri:
Expand All @@ -32,6 +35,9 @@ outputs:
service-name:
description: The service name with connection parameters.
value: ${{ steps.set-outputs.outputs.service-name }}
certificate-path:
description: The path to the server certificate if SSL is on.
value: ${{ steps.set-outputs.outputs.certificate-path }}
runs:
using: composite
steps:
Expand Down Expand Up @@ -132,6 +138,23 @@ runs:
# directory we have no permissions to (owned by system postgres user).
echo "unix_socket_directories = ''" >> "$PGDATA/postgresql.conf"
echo "port = ${{ inputs.port }}" >> "$PGDATA/postgresql.conf"
if [ "${{ inputs.ssl }}" = "true" ]; then
# On Windows, bash runs on top of MSYS2, which automatically converts
# Unix paths to Windows paths for every argument that appears to be a
# path. This behavior breaks the openssl invocation because the
# subject argument is mistakenly converted when it should not be.
# Therefore, we need to exclude it from the path conversion process
# by setting the MSYS2_ARG_CONV_EXCL environment variable.
#
# https://www.msys2.org/docs/filesystem-paths/#automatic-unix-windows-path-conversion
export MSYS2_ARG_CONV_EXCL="/CN"
openssl req -new -x509 -days 365 -nodes -text -subj "/CN=localhost" \
-out "$PGDATA/server.crt" -keyout "$PGDATA/server.key"
chmod og-rwx "$PGDATA/server.key" "$PGDATA/server.crt"
echo "ssl = on" >> "$PGDATA/postgresql.conf"
fi
pg_ctl start --pgdata="$PGDATA"
# Save required connection parameters for created superuser to the
Expand All @@ -154,6 +177,9 @@ runs:
password=${{ inputs.password }}
dbname=${{ inputs.database }}
EOF
if [ "${{ inputs.ssl }}" = "true" ]; then
echo "sslmode=verify-ca" >> "$PGDATA/pg_service.conf"
fi
echo "PGSERVICEFILE=$PGDATA/pg_service.conf" >> $GITHUB_ENV
shell: bash

Expand All @@ -173,6 +199,17 @@ runs:
- name: Set action outputs
run: |
CONNECTION_URI="postgresql://${{ inputs.username }}:${{ inputs.password }}@localhost:${{ inputs.port }}/${{ inputs.database }}"
CERTIFICATE_PATH="$RUNNER_TEMP/pgdata/server.crt"
if [ "${{ inputs.ssl }}" = "true" ]; then
# Although SSLMODE and SSLROOTCERT are specific to libpq options,
# most third-party drivers also support them. By default libpq
# prefers SSL but doesn't require it, thus it's important to set
# these options to ensure SSL is used and the certificate is
# verified.
CONNECTION_URI="$CONNECTION_URI?sslmode=verify-ca&sslrootcert=$CERTIFICATE_PATH"
echo "certificate-path=$CERTIFICATE_PATH" >> $GITHUB_OUTPUT
fi
echo "connection-uri=$CONNECTION_URI" >> $GITHUB_OUTPUT
echo "service-name=${{ inputs.username }}" >> $GITHUB_OUTPUT
Expand Down
22 changes: 22 additions & 0 deletions test_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import subprocess
import typing as t

import cryptography.x509 as x509
import psycopg
import furl
import pytest
Expand Down Expand Up @@ -87,6 +88,20 @@ def test_service_name(service_name: str):
assert service_name == os.getenv("EXPECTED_SERVICE_NAME")


def test_certificate_path():
"""Test that CERTIFICATE_PATH points to the certificate."""

certificate_path = os.getenv("CERTIFICATE_PATH")

if os.getenv("EXPECTED_SSL") == "true":
assert certificate_path
certificate_bytes = pathlib.Path(certificate_path).read_bytes()
certificate = x509.load_pem_x509_certificate(certificate_bytes)
assert certificate.subject.rfc4514_string() == "CN=localhost"
else:
assert not certificate_path


def test_server_encoding(connection: psycopg.Connection):
"""Test that PostgreSQL's encoding matches the one we passed to initdb."""

Expand Down Expand Up @@ -147,6 +162,13 @@ def test_server_version(connection: psycopg.Connection):
assert server_version.split(".")[0] == os.getenv("EXPECTED_SERVER_VERSION")


def test_server_ssl(connection: psycopg.Connection):
"""Test that connection is SSL encrypted."""

expected = os.getenv("EXPECTED_SSL") == "true"
assert connection.info.pgconn.ssl_in_use is expected


def test_user_permissions(connection: psycopg.Connection):
"""Test that a user has super/createdb permissions."""

Expand Down

0 comments on commit a16e5c7

Please sign in to comment.