Skip to content

Commit

Permalink
[#125] Enhance Transport Level Security support
Browse files Browse the repository at this point in the history
  • Loading branch information
jesperpedersen committed Feb 8, 2024
1 parent c835327 commit 9038f1c
Show file tree
Hide file tree
Showing 11 changed files with 380 additions and 50 deletions.
3 changes: 3 additions & 0 deletions doc/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ See a [sample](./etc/pgexporter.conf) configuration for running `pgexporter` on
| user | | String | Yes | The user name |
| data_dir | | String | No | The location of the data directory |
| wal_dir | | String | No | The location of the WAL directory |
| tls_cert_file | | String | No | Certificate file for TLS. This file must be owned by either the user running pgmoneta or root. |
| tls_key_file | | String | No | Private key file for TLS. This file must be owned by either the user running pgmoneta or root. Additionally permissions must be at least `0640` when owned by root or `0600` otherwise. |
| tls_ca_file | | String | No | Certificate Authority (CA) file for TLS. This file must be owned by either the user running pgmoneta or root. |

Note, that PostgreSQL 10+ is required.

Expand Down
1 change: 1 addition & 0 deletions doc/GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ There are some short tutorials available to help you better understand and confi
- [Installing pgexporter](https://github.com/pgexporter/pgexporter/blob/main/doc/tutorial/01_install.md)
- [Custom metrics](https://github.com/pgexporter/pgexporter/blob/main/doc/tutorial/02_custom_metrics.md)
- [Grafana Dashboard](https://github.com/pgexporter/pgexporter/blob/main/doc/tutorial/03_grafana.md)
- [Using Transport Level Security](https://github.com/pgexporter/pgexporter/blob/main/doc/tutorial/04_tls.md)

## Closing

Expand Down
131 changes: 131 additions & 0 deletions doc/tutorial/04_tls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Use of Transport Level Security (TLS)

This tutorial is about using Transport Level Security (TLS) in PostgreSQL, and how it affects pgmoneta.

**Note, that this tutorial is an example on how to setup a PostgreSQL TLS environment for development use only !**

## Preface

This tutorial assumes that you have an installation of PostgreSQL 12+, OpenSSL and pgmoneta.

See [Install pgexporter](https://github.com/pgexporter/pgexporter/blob/main/doc/tutorial/01_install.md)
for more detail.

## PostgreSQL

Generate the server key

```
openssl genrsa -aes256 8192 > server.key
```

Remove the passphase

```
openssl rsa -in server.key -out server.key
```

Set the server key permission

```
chmod 400 server.key
```

Generate the server certificate

```
openssl req -new -key server.key -days 3650 -out server.crt -x509
```

Use the server certificate as the root certificate (self-signed)

```
cp server.crt root.crt
```

In `postgresql.conf` change the following settings

```
listen_addresses = '*'
ssl = on
ssl_ca_file = '/path/to/root.crt'
ssl_cert_file = '/path/to/server.crt'
ssl_key_file = '/path/to/server.key'
ssl_prefer_server_ciphers = on
```

In `pg_hba.conf` change

```
host all all 0.0.0.0/0 scram-sha-256
```

to

```
hostssl all all 0.0.0.0/0 scram-sha-256
```

In this scenario there are no changes to the `pgexporter.conf` configuration file.

## Using client certificate

Create the client key
```
openssl ecparam -name prime256v1 -genkey -noout -out client.key
```

Create the client request - remember that the `CN` has to have the name of the replication user
```
openssl req -new -sha256 -key client.key -out client.csr -subj "/CN=repl"
```

Generate the client certificate
```
openssl x509 -req -in client.csr -CA root.crt -CAkey server.key -CAcreateserial -out client.crt -days 3650 -sha256
```

You can test your setup by copying the files into the default PostgreSQL client directory, like

```
mkdir ~/.postgresql
cp client.crt ~/.postgresql/postgresql.crt
cp client.key ~/.postgresql/postgresql.key
cp root.crt ~/.postgresql/ca.crt
chmod 0600 ~/.postgresql/postgresql.crt ~/.postgresql/postgresql.key ~/.postgresql/ca.crt
```

and then test with the `psql` command.

In `pg_hba.conf` change

```
hostssl all all 0.0.0.0/0 scram-sha-256
```

to

```
hostssl all all 0.0.0.0/0 scram-sha-256 clientcert=verify-ca
```

In `pgexporter.conf` add the paths to the server in question, like

```
[pgexporter]
...
[primary]
host=...
port=...
user=pgexporter
tls_cert_file=/path/to/home/.postgresql/postgresql.crt
tls_key_file=/path/to/home/.postgresql/postgresql.key
tls_ca_file=/path/to/home/.postgresql/ca.crt
```


## More information

* [Secure TCP/IP Connections with SSL](https://www.postgresql.org/docs/12/ssl-tcp.html)
* [The pg_hba.conf File](https://www.postgresql.org/docs/12/auth-pg-hba-conf.html)
3 changes: 2 additions & 1 deletion src/include/message.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,12 @@ pgexporter_free_copy_message(message_t* msg);

/**
* Is the connection valid
* @param ssl The SSL struct
* @param socket The socket descriptor
* @return true upon success, otherwise false
*/
bool
pgexporter_connection_isvalid(int socket);
pgexporter_connection_isvalid(SSL* ssl, int socket);

/**
* Log a message
Expand Down
4 changes: 4 additions & 0 deletions src/include/pgexporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,15 @@ typedef struct
char username[MAX_USERNAME_LENGTH]; /**< The user name */
char data[MISC_LENGTH]; /**< The data directory */
char wal[MISC_LENGTH]; /**< The WAL directory */
SSL* ssl; /**< The SSL structure */
int fd; /**< The socket descriptor */
bool new; /**< Is the connection new */
bool extension; /**< Is the pgexporter_ext extension installed */
int state; /**< The state of the server */
char version; /**< The PostgreSQL version (char for minimum bytes)*/
char tls_cert_file[MISC_LENGTH]; /**< TLS certificate path */
char tls_key_file[MISC_LENGTH]; /**< TLS key path */
char tls_ca_file[MISC_LENGTH]; /**< TLS CA certificate path */
} __attribute__ ((aligned (64))) server_t;

/** @struct
Expand Down
10 changes: 9 additions & 1 deletion src/include/security.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ extern "C" {
* @param database The database
* @param username The username
* @param password The password
* @param ssl The resulting SSL structure
* @param fd The resulting socket
* @return AUTH_SUCCESS, AUTH_BAD_PASSWORD or AUTH_ERROR
*/
int
pgexporter_server_authenticate(int server, char* database, char* username, char* password, int* fd);
pgexporter_server_authenticate(int server, char* database, char* username, char* password, SSL** ssl, int* fd);

/**
* Authenticate a remote management user
Expand Down Expand Up @@ -109,6 +110,13 @@ pgexporter_decrypt(char* ciphertext, int ciphertext_length, char* password, char
int
pgexporter_tls_valid(void);

/**
* Close a SSL structure
* @param ssl The SSL structure
*/
void
pgexporter_close_ssl(SSL* ssl);

#ifdef __cplusplus
}
#endif
Expand Down
49 changes: 49 additions & 0 deletions src/libpgexporter/configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,21 @@ pgexporter_read_configuration(void* shm, char* filename)
}
memcpy(config->tls_ca_file, value, max);
}
else if (strlen(section) > 0)
{
max = strlen(section);
if (max > MISC_LENGTH - 1)
{
max = MISC_LENGTH - 1;
}
memcpy(&srv.name, section, max);
max = strlen(value);
if (max > MISC_LENGTH - 1)
{
max = MISC_LENGTH - 1;
}
memcpy(&srv.tls_ca_file, value, max);
}
else
{
unknown = true;
Expand All @@ -385,6 +400,21 @@ pgexporter_read_configuration(void* shm, char* filename)
}
memcpy(config->tls_cert_file, value, max);
}
else if (strlen(section) > 0)
{
max = strlen(section);
if (max > MISC_LENGTH - 1)
{
max = MISC_LENGTH - 1;
}
memcpy(&srv.name, section, max);
max = strlen(value);
if (max > MISC_LENGTH - 1)
{
max = MISC_LENGTH - 1;
}
memcpy(&srv.tls_cert_file, value, max);
}
else
{
unknown = true;
Expand All @@ -401,6 +431,21 @@ pgexporter_read_configuration(void* shm, char* filename)
}
memcpy(config->tls_key_file, value, max);
}
else if (strlen(section) > 0)
{
max = strlen(section);
if (max > MISC_LENGTH - 1)
{
max = MISC_LENGTH - 1;
}
memcpy(&srv.name, section, max);
max = strlen(value);
if (max > MISC_LENGTH - 1)
{
max = MISC_LENGTH - 1;
}
memcpy(&srv.tls_key_file, value, max);
}
else
{
unknown = true;
Expand Down Expand Up @@ -733,6 +778,10 @@ pgexporter_read_configuration(void* shm, char* filename)
key = NULL;
value = NULL;
}
else
{
warnx("Unknown: Section=%s, Line=%s", strlen(section) > 0 ? section : "<unknown>", line);
}
}
}
}
Expand Down
34 changes: 28 additions & 6 deletions src/libpgexporter/message.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ pgexporter_free_copy_message(message_t* msg)
}

bool
pgexporter_connection_isvalid(int socket)
pgexporter_connection_isvalid(SSL* ssl, int socket)
{
int status;
int size = 15;
Expand All @@ -145,13 +145,27 @@ pgexporter_connection_isvalid(int socket)
msg.length = size;
msg.data = &valid;

status = write_message(socket, &msg);
if (ssl != NULL)
{
status = ssl_write_message(ssl, &msg);
}
else
{
status = write_message(socket, &msg);
}
if (status != MESSAGE_STATUS_OK)
{
goto error;
}

status = read_message(socket, true, 0, &reply);
if (ssl != NULL)
{
status = ssl_read_message(ssl, 0, &reply);
}
else
{
status = read_message(socket, true, 0, &reply);
}
if (status != MESSAGE_STATUS_OK)
{
goto error;
Expand Down Expand Up @@ -761,6 +775,7 @@ ssl_read_message(SSL* ssl, int timeout, message_t** msg)
ssize_t numbytes;
time_t start_time;
message_t* m = NULL;
unsigned long err;

if (unlikely(timeout > 0))
{
Expand All @@ -783,8 +798,6 @@ ssl_read_message(SSL* ssl, int timeout, message_t** msg)
}
else
{
int err;

pgexporter_memory_free();

err = SSL_get_error(ssl, numbytes);
Expand Down Expand Up @@ -818,12 +831,16 @@ ssl_read_message(SSL* ssl, int timeout, message_t** msg)
keep_read = true;
break;
case SSL_ERROR_SYSCALL:
err = ERR_get_error();
pgexporter_log_error("SSL_ERROR_SYSCALL: %s (%d)", strerror(errno), SSL_get_fd(ssl));
pgexporter_log_error("Reason: %s", ERR_reason_error_string(err));
errno = 0;
keep_read = false;
break;
case SSL_ERROR_SSL:
err = ERR_get_error();
pgexporter_log_error("SSL_ERROR_SSL: %s (%d)", strerror(errno), SSL_get_fd(ssl));
pgexporter_log_error("Reason: %s", ERR_reason_error_string(err));
keep_read = false;
break;
}
Expand All @@ -843,6 +860,7 @@ ssl_write_message(SSL* ssl, message_t* msg)
int offset;
ssize_t totalbytes;
ssize_t remaining;
int err;

#ifdef DEBUG
assert(msg != NULL);
Expand Down Expand Up @@ -878,7 +896,7 @@ ssl_write_message(SSL* ssl, message_t* msg)
}
else
{
int err = SSL_get_error(ssl, numbytes);
err = SSL_get_error(ssl, numbytes);

switch (err)
{
Expand All @@ -901,12 +919,16 @@ ssl_write_message(SSL* ssl, message_t* msg)
keep_write = true;
break;
case SSL_ERROR_SYSCALL:
err = ERR_get_error();
pgexporter_log_error("SSL_ERROR_SYSCALL: %s (%d)", strerror(errno), SSL_get_fd(ssl));
pgexporter_log_error("Reason: %s", ERR_reason_error_string(err));
errno = 0;
keep_write = false;
break;
case SSL_ERROR_SSL:
err = ERR_get_error();
pgexporter_log_error("SSL_ERROR_SSL: %s (%d)", strerror(errno), SSL_get_fd(ssl));
pgexporter_log_error("Reason: %s", ERR_reason_error_string(err));
errno = 0;
keep_write = false;
break;
Expand Down

0 comments on commit 9038f1c

Please sign in to comment.