pg_exporter 0.11.1

PostgreSQL metric exporter for Prometheus
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# TLS Collector Testing Guide

This guide explains how to test the TLS collector with SSL-enabled PostgreSQL using Podman.

## Overview

The TLS collector provides SSL/TLS monitoring for PostgreSQL with three sub-collectors:

1. **ServerTlsConfigCollector** - Server SSL configuration (works remotely)
2. **CertificateCollector** - Certificate expiration monitoring (requires local filesystem access)
3. **ConnectionTlsCollector** - Per-connection SSL statistics (works remotely, PostgreSQL 14+)

### Remote vs Local Installation

- **Local Installation** (exporter runs on same machine as PostgreSQL): All collectors work
- **Remote Installation** (exporter runs on different machine): Certificate collector will not export metrics (filesystem access required)

The certificate collector gracefully handles remote installations by logging debug messages when certificate files are inaccessible.

## Quick Start

### Using the Helper Scripts (Recommended)

```bash
# 1. Start SSL-enabled PostgreSQL (automatically generates certs if needed)
./tests/start-ssl-postgres.sh

# 2. Run tests
PG_EXPORTER_DSN='postgresql://postgres:postgres@localhost:5433/postgres?sslmode=require' \
  cargo test --test collectors_tests tls

# 3. Run integration tests
PG_EXPORTER_DSN='postgresql://postgres:postgres@localhost:5433/postgres?sslmode=require' \
  cargo test --test tls_metrics_integration

# 4. Clean up
./tests/stop-ssl-postgres.sh
```

### Manual Podman Setup

```bash
# 1. Generate SSL certificates
./tests/generate-ssl-certs.sh

# 2. Start PostgreSQL with SSL
podman run -d \
  --name pg-exporter-ssl-test \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_DB=postgres \
  -p 5433:5432 \
  -v ./tests/ssl-certs:/var/lib/postgresql/ssl:Z \
  -v ./tests/init-ssl.sh:/docker-entrypoint-initdb.d/init-ssl.sh:Z \
  postgres:16 \
  postgres \
  -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_ciphers='HIGH:MEDIUM:+3DES:!aNULL' \
  -c ssl_prefer_server_ciphers=on \
  -c ssl_min_protocol_version=TLSv1.2

# 3. Wait for PostgreSQL to be ready
until podman exec pg-exporter-ssl-test pg_isready -U postgres; do
  echo "Waiting for PostgreSQL..."
  sleep 1
done

# 4. Fix permissions inside container
podman exec pg-exporter-ssl-test chown postgres:postgres /var/lib/postgresql/ssl/server.key
podman exec pg-exporter-ssl-test chmod 600 /var/lib/postgresql/ssl/server.key
podman exec pg-exporter-ssl-test psql -U postgres -c "SELECT pg_reload_conf();"

# 5. Run tests
PG_EXPORTER_DSN='postgresql://postgres:postgres@localhost:5433/postgres?sslmode=require' \
  cargo test --test collectors_tests tls

# 6. Clean up
podman stop pg-exporter-ssl-test && podman rm pg-exporter-ssl-test
```

## TLS Collector Test Coverage

The TLS collector test suite includes:

### Unit Tests (`tests/collectors/tls/`)

1. **ServerTlsConfigCollector** (`server_config.rs`)
   - ✅ Metric registration
   - ✅ SSL status detection (on/off)
   - ✅ Graceful error handling

2. **CertificateCollector** (`certificate.rs`)
   - ✅ Metric registration
   - ✅ Certificate file reading
   - ✅ Expiration time calculation
   - ✅ Validity checking
   - ✅ Missing certificate handling (remote installations)

3. **ConnectionTlsCollector** (`connection_stats.rs`)
   - ✅ Metric registration
   - ✅ Per-connection SSL statistics
   - ✅ Version-aware (PostgreSQL 14+)
   - ✅ Cipher suite tracking
   - ✅ TLS version tracking

4. **TlsCollector** (`mod.rs`)
   - ✅ Main collector integration
   - ✅ Sub-collector orchestration
   - ✅ Disabled by default verification

### Integration Tests (`tests/tls_metrics_integration.rs`)

- ✅ Metrics endpoint exposure
- ✅ Prometheus format validation
- ✅ TLS collector enablement
- ✅ Metrics availability

## SSL Certificate Generation

### Self-Signed Certificates for Testing

The `generate-ssl-certs.sh` script creates self-signed certificates:

```bash
./tests/generate-ssl-certs.sh
```

This generates:
- `tests/ssl-certs/server.key` - Private key (600 permissions)
- `tests/ssl-certs/server.crt` - Certificate (valid for 365 days)
- `tests/ssl-certs/cert-info.txt` - Certificate details

### Manual Certificate Generation

```bash
# Create directory
mkdir -p tests/ssl-certs

# Generate private key
openssl genrsa -out tests/ssl-certs/server.key 2048

# Generate certificate (valid for 365 days)
openssl req -new -x509 \
  -key tests/ssl-certs/server.key \
  -out tests/ssl-certs/server.crt \
  -days 365 \
  -subj "/C=US/ST=Test/L=Test/O=Test/CN=localhost"

# Set permissions
chmod 600 tests/ssl-certs/server.key
chmod 644 tests/ssl-certs/server.crt
```

## Test Scenarios

### Scenario 1: SSL Disabled

```bash
# Start PostgreSQL without SSL
podman run -d --name pg-no-ssl \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_USER=postgres \
  -p 5432:5432 \
  postgres:16

# Run tests (should report SSL disabled)
PG_EXPORTER_DSN='postgresql://postgres:postgres@localhost:5432/postgres' \
  cargo test --test collectors_tests tls -- --nocapture

# Expected: pg_ssl_enabled = 0

# Clean up
podman stop pg-no-ssl && podman rm pg-no-ssl
```

### Scenario 2: SSL Enabled (Require Mode)

```bash
# Start PostgreSQL with SSL
./tests/start-ssl-postgres.sh

# Run tests with SSL required
PG_EXPORTER_DSN='postgresql://postgres:postgres@localhost:5433/postgres?sslmode=require' \
  cargo test --test collectors_tests tls -- --nocapture

# Expected:
# - pg_ssl_enabled = 1
# - pg_ssl_certificate_* metrics populated (if running locally)
# - pg_ssl_connections_total >= 1

# Clean up
./tests/stop-ssl-postgres.sh
```

### Scenario 3: Certificate Expiration Testing

```bash
# Generate an expired certificate
openssl req -new -x509 \
  -key tests/ssl-certs/server.key \
  -out tests/ssl-certs/server-expired.crt \
  -days -1 \
  -subj "/C=US/ST=Test/L=Test/O=Test/CN=localhost"

# Start PostgreSQL with expired cert
podman run -d --name pg-expired-cert \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_USER=postgres \
  -p 5433:5432 \
  -v ./tests/ssl-certs:/var/lib/postgresql/ssl:Z \
  postgres:16 \
  postgres \
  -c ssl=on \
  -c ssl_cert_file=/var/lib/postgresql/ssl/server-expired.crt \
  -c ssl_key_file=/var/lib/postgresql/ssl/server.key

# Fix permissions
podman exec pg-expired-cert chown postgres:postgres /var/lib/postgresql/ssl/server.key
podman exec pg-expired-cert chmod 600 /var/lib/postgresql/ssl/server.key

# Run tests
PG_EXPORTER_DSN='postgresql://postgres:postgres@localhost:5433/postgres?sslmode=require' \
  cargo test --test collectors_tests tls -- --nocapture

# Expected (if running locally):
# - pg_ssl_certificate_valid = 0
# - pg_ssl_certificate_expiry_seconds < 0 (negative = expired)

# Clean up
podman stop pg-expired-cert && podman rm pg-expired-cert
```

### Scenario 4: Remote Installation Simulation

```bash
# Start PostgreSQL with SSL (certificate files inside container)
./tests/start-ssl-postgres.sh

# Run exporter pointing to remote PostgreSQL (certificate files not accessible)
cargo run -- \
  --dsn 'postgresql://postgres:postgres@localhost:5433/postgres?sslmode=require' \
  --collector.tls \
  --port 9432

# Query metrics
curl http://localhost:9432/metrics | grep pg_ssl

# Expected:
# - pg_ssl_enabled = 1 (works remotely)
# - pg_ssl_connections_* metrics present (works remotely)
# - pg_ssl_certificate_* metrics NOT present (requires local filesystem)

# Check logs for debug message:
# "Certificate file not accessible: ... (this is expected when running remotely)"
```

### Scenario 5: Multiple PostgreSQL Versions

```bash
# Test with PostgreSQL 14
PG_VERSION=14 ./tests/start-ssl-postgres.sh
PG_EXPORTER_DSN='postgresql://postgres:postgres@localhost:5433/postgres?sslmode=require' \
  cargo test --test collectors_tests tls
./tests/stop-ssl-postgres.sh

# Test with PostgreSQL 15
PG_VERSION=15 ./tests/start-ssl-postgres.sh
PG_EXPORTER_DSN='postgresql://postgres:postgres@localhost:5433/postgres?sslmode=require' \
  cargo test --test collectors_tests tls
./tests/stop-ssl-postgres.sh

# Test with PostgreSQL 16 (default)
./tests/start-ssl-postgres.sh
PG_EXPORTER_DSN='postgresql://postgres:postgres@localhost:5433/postgres?sslmode=require' \
  cargo test --test collectors_tests tls
./tests/stop-ssl-postgres.sh
```

## Metrics Reference

### Server Configuration Metrics (Always Available)

```prometheus
# Whether SSL is enabled on the server
pg_ssl_enabled 1
```

### Certificate Metrics (Local Installation Only)

```prometheus
# Seconds until certificate expires (negative if expired)
pg_ssl_certificate_expiry_seconds 31536000  # ~365 days

# Whether certificate is currently valid
pg_ssl_certificate_valid 1

# Unix timestamp when certificate becomes valid
pg_ssl_certificate_not_before_timestamp 1704067200

# Unix timestamp when certificate expires
pg_ssl_certificate_not_after_timestamp 1735689600
```

### Connection Statistics (PostgreSQL 14+, Always Available)

```prometheus
# Total number of SSL connections
pg_ssl_connections_total 3

# Connections by TLS version
pg_ssl_connections_by_version{version="TLSv1.3"} 3

# Connections by cipher suite
pg_ssl_connections_by_cipher{cipher="TLS_AES_256_GCM_SHA384"} 3

# Average encryption bits across connections
pg_ssl_connection_bits_avg 256
```

## Troubleshooting

### PostgreSQL SSL Not Starting

```bash
# Check PostgreSQL logs
podman logs pg-exporter-ssl-test

# Common issues:
# 1. Certificate file permissions (should be 600 for key, 644 for cert)
# 2. Certificate ownership (should be owned by postgres user)
# 3. SELinux context (use :Z flag when mounting volumes)
```

### Tests Failing with Connection Errors

```bash
# Verify PostgreSQL is accessible
podman exec pg-exporter-ssl-test psql -U postgres -c "SELECT 1;"

# Check SSL is enabled
podman exec pg-exporter-ssl-test psql -U postgres -c "SHOW ssl;"

# Verify certificate paths
podman exec pg-exporter-ssl-test psql -U postgres -c "SHOW ssl_cert_file;"
podman exec pg-exporter-ssl-test psql -U postgres -c "SHOW ssl_key_file;"
```

### Certificate Metrics Not Showing (Remote Installation)

This is expected behavior when the exporter runs remotely. The certificate collector requires filesystem access to read certificate files.

**Debug output:**
```
Certificate file not accessible: /var/lib/postgresql/ssl/server.crt (this is expected when running remotely)
```

**Solutions:**
1. **Mount certificate files:** Use NFS/CIFS to mount PostgreSQL's certificate directory to the exporter machine
2. **Copy certificates:** Periodically copy certificate files to the exporter machine
3. **Accept limitation:** Use only the SSL configuration and connection statistics metrics

### Certificate Permission Issues

```bash
# Fix permissions inside container
podman exec pg-exporter-ssl-test chown postgres:postgres /var/lib/postgresql/ssl/server.key
podman exec pg-exporter-ssl-test chmod 600 /var/lib/postgresql/ssl/server.key
podman exec pg-exporter-ssl-test chmod 644 /var/lib/postgresql/ssl/server.crt

# Reload PostgreSQL configuration
podman exec pg-exporter-ssl-test psql -U postgres -c "SELECT pg_reload_conf();"
```

### Podman Permission Issues

```bash
# If you get permission denied errors with volumes:
# Use :Z flag for SELinux relabeling
-v ./tests/ssl-certs:/var/lib/postgresql/ssl:Z

# Or run with --privileged (not recommended)
podman run --privileged ...
```

## PostgreSQL SSL Configuration Reference

### Supported SSL Modes

- `disable` - No SSL
- `allow` - Try SSL, fallback to non-SSL
- `prefer` - Prefer SSL, fallback to non-SSL
- `require` - Require SSL (no certificate verification)
- `verify-ca` - Require SSL + verify CA
- `verify-full` - Require SSL + verify CA + verify hostname

### PostgreSQL SSL Parameters

```sql
-- Check SSL status
SHOW ssl;

-- Check SSL certificate paths
SHOW ssl_cert_file;
SHOW ssl_key_file;
SHOW ssl_ca_file;

-- Check SSL ciphers
SHOW ssl_ciphers;

-- Check TLS versions
SHOW ssl_min_protocol_version;
SHOW ssl_max_protocol_version;

-- View active SSL connections (PostgreSQL 14+)
SELECT * FROM pg_stat_ssl;
```

## CI/CD Integration

### GitHub Actions Example

```yaml
name: TLS Collector Tests

on: [push, pull_request]

jobs:
  test-tls:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Install Podman
        run: |
          sudo apt-get update
          sudo apt-get install -y podman

      - name: Start SSL-enabled PostgreSQL
        run: |
          ./tests/start-ssl-postgres.sh

      - name: Run TLS collector tests
        env:
          PG_EXPORTER_DSN: postgresql://postgres:postgres@localhost:5433/postgres?sslmode=require
        run: |
          cargo test --test collectors_tests tls
          cargo test --test tls_metrics_integration

      - name: Stop PostgreSQL
        if: always()
        run: |
          ./tests/stop-ssl-postgres.sh
```

### GitLab CI Example

```yaml
test:tls:
  image: rust:latest
  services:
    - name: postgres:16
      alias: postgres
      command: [
        "postgres",
        "-c", "ssl=on",
        "-c", "ssl_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem",
        "-c", "ssl_key_file=/etc/ssl/private/ssl-cert-snakeoil.key"
      ]
  variables:
    POSTGRES_PASSWORD: postgres
    POSTGRES_USER: postgres
    PG_EXPORTER_DSN: "postgresql://postgres:postgres@postgres:5432/postgres?sslmode=require"
  before_script:
    - apt-get update && apt-get install -y postgresql-client
  script:
    - cargo test --test collectors_tests tls
    - cargo test --test tls_metrics_integration
```

## Performance Considerations

- **Certificate reading**: Only happens once per scrape, minimal overhead (~1-2ms)
- **Connection stats**: Queries `pg_stat_ssl` view, very fast (<1ms)
- **No additional connections**: All collectors reuse the existing connection pool
- **Recommended scrape interval**: 30-60 seconds

## Security Notes

1. **Certificate file access**: Only required for local installations
2. **Connection string security**: Use environment variables for DSN
3. **Certificate validation**: Test with `verify-ca` and `verify-full` modes
4. **Certificate rotation**: Metrics automatically reflect new certificates on next scrape

## Helper Scripts Reference

### `start-ssl-postgres.sh`

Starts SSL-enabled PostgreSQL container with proper configuration.

**Options:**
```bash
# Use different port
PG_PORT=5434 ./tests/start-ssl-postgres.sh

# Use different PostgreSQL version
PG_VERSION=15 ./tests/start-ssl-postgres.sh

# Combine options
PG_PORT=5434 PG_VERSION=14 ./tests/start-ssl-postgres.sh
```

### `stop-ssl-postgres.sh`

Stops and removes the SSL-enabled PostgreSQL container.

### `generate-ssl-certs.sh`

Generates self-signed SSL certificates for testing.

### `init-ssl.sh`

Initialization script that runs inside the container to:
- Create pg_stat_statements extension
- Create test tables
- Display SSL configuration

## Additional Resources

- [PostgreSQL SSL Documentation]https://www.postgresql.org/docs/current/ssl-tcp.html
- [pg_stat_ssl View]https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-SSL-VIEW
- [OpenSSL Certificate Generation]https://www.openssl.org/docs/man1.1.1/man1/openssl-req.html
- [Podman Documentation]https://docs.podman.io/