Query Exporter handles sensitive information like database credentials and connects to critical infrastructure. This guide covers security best practices for production deployments.
SSL/TLS Configuration
Query Exporter supports HTTPS for the metrics endpoint, encrypting communication between Prometheus and the exporter.
Enabling HTTPS
Via command-line options:
query-exporter \
--ssl-private-key /path/to/private.key \
--ssl-public-key /path/to/public.crt \
--ssl-ca /path/to/ca.crt
Via environment variables:
export QE_SSL_PRIVATE_KEY=/path/to/private.key
export QE_SSL_PUBLIC_KEY=/path/to/public.crt
export QE_SSL_CA=/path/to/ca.crt
query-exporter --config config.yaml
Via .env file:
# .env
QE_SSL_PRIVATE_KEY=/path/to/private.key
QE_SSL_PUBLIC_KEY=/path/to/public.crt
QE_SSL_CA=/path/to/ca.crt
SSL Options
| Option | Environment Variable | Description |
|---|
--ssl-private-key | QE_SSL_PRIVATE_KEY | Full path to SSL private key file |
--ssl-public-key | QE_SSL_PUBLIC_KEY | Full path to SSL public key/certificate file |
--ssl-ca | QE_SSL_CA | Full path to SSL Certificate Authority file |
Certificate Requirements
- Private key: PEM format, unencrypted
- Public key: PEM format certificate
- CA certificate: PEM format (optional, for client authentication)
Example with Self-Signed Certificate
Generate certificates:
# Generate private key
openssl genrsa -out private.key 2048
# Generate self-signed certificate
openssl req -new -x509 -key private.key -out public.crt -days 365
Configure Query Exporter:
query-exporter \
--ssl-private-key ./private.key \
--ssl-public-key ./public.crt
Scrape with Prometheus:
# prometheus.yml
scrape_configs:
- job_name: 'query-exporter'
scheme: https
tls_config:
insecure_skip_verify: true # Only for self-signed certs
static_configs:
- targets: ['localhost:9560']
Never use self-signed certificates in production without proper validation. Use certificates from a trusted CA.
Credential Management
Never hardcode database credentials in configuration files. Use environment variables and secure secret management.
Using Environment Variables
Recommended approach with !env tag:
# config.yaml
databases:
production_db:
dsn: !env DATABASE_DSN
Set in environment:
export DATABASE_DSN="postgresql://user:password@host:5432/db"
Using .env Files
For local development and Docker deployments:
Create .env file:
Configure .gitignore:
# .gitignore
.env
.env.local
*.key
*.crt
secrets/
Load automatically:
# Query Exporter automatically loads .env from current directory
query-exporter --config config.yaml
Custom .env location:
export QE_DOTENV=/opt/query-exporter/.env
query-exporter --config config.yaml
Partial DSN with Environment Variables
Break down credentials into separate variables:
# config.yaml
databases:
app_db:
dsn:
dialect: postgresql
user: !env DB_USER
password: !env DB_PASSWORD
host: !env DB_HOST
port: !env DB_PORT
database: !env DB_NAME
options:
sslmode: require
# .env
DB_USER=myapp
DB_PASSWORD=super-secret-password
DB_HOST=db.example.com
DB_PORT=5432
DB_NAME=production
Docker Secrets
For Docker Swarm deployments:
# docker-compose.yml
version: '3.8'
services:
query-exporter:
image: adonato/query-exporter:latest
secrets:
- db_password
environment:
DATABASE_DSN: postgresql://user:$(cat /run/secrets/db_password)@host/db
volumes:
- ./config.yaml:/config/config.yaml
secrets:
db_password:
external: true
Kubernetes Secrets
For Kubernetes deployments:
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: query-exporter-secrets
type: Opaque
stringData:
database-dsn: postgresql://user:password@host:5432/db
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: query-exporter
spec:
template:
spec:
containers:
- name: query-exporter
image: adonato/query-exporter:latest
env:
- name: DATABASE_DSN
valueFrom:
secretKeyRef:
name: query-exporter-secrets
key: database-dsn
Database Connection Security
Use Read-Only Accounts
Create dedicated read-only database users for Query Exporter:
PostgreSQL:
-- Create read-only user
CREATE ROLE query_exporter_ro WITH LOGIN PASSWORD 'secure-password';
-- Grant connection
GRANT CONNECT ON DATABASE mydb TO query_exporter_ro;
-- Grant read-only access
GRANT USAGE ON SCHEMA public TO query_exporter_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO query_exporter_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO query_exporter_ro;
MySQL:
-- Create read-only user
CREATE USER 'query_exporter_ro'@'%' IDENTIFIED BY 'secure-password';
-- Grant read-only access
GRANT SELECT ON mydb.* TO 'query_exporter_ro'@'%';
FLUSH PRIVILEGES;
SQL Server:
-- Create read-only user
CREATE LOGIN query_exporter_ro WITH PASSWORD = 'secure-password';
USE mydb;
CREATE USER query_exporter_ro FOR LOGIN query_exporter_ro;
EXEC sp_addrolemember 'db_datareader', 'query_exporter_ro';
SSL/TLS for Database Connections
Always use encrypted connections to databases:
PostgreSQL with SSL:
databases:
secure_db:
dsn: postgresql://user:pass@host:5432/db?sslmode=require
SSL modes for PostgreSQL:
disable - No SSL (not recommended)
require - SSL required, no certificate verification
verify-ca - SSL with CA verification
verify-full - SSL with full certificate verification
MySQL with SSL:
databases:
secure_db:
dsn: mysql://user:pass@host:3306/db?ssl_mode=REQUIRED
SQL Server with encryption:
databases:
secure_db:
dsn: mssql+pymssql://user:pass@host:1433/db?encrypt=true
Network Security
Restrict database access:
# PostgreSQL pg_hba.conf
# Allow only from Query Exporter host
host mydb query_exporter_ro 10.0.1.50/32 md5
Use firewall rules:
# iptables - allow only from specific IP
iptables -A INPUT -p tcp --dport 5432 -s 10.0.1.50 -j ACCEPT
iptables -A INPUT -p tcp --dport 5432 -j DROP
Metrics Endpoint Security
Bind to Specific Interface
Don’t expose metrics publicly:
# Bind to localhost only
query-exporter --host 127.0.0.1 --port 9560
# Or use environment variable
export QE_HOST=127.0.0.1
In Docker:
# docker-compose.yml
services:
query-exporter:
image: adonato/query-exporter:latest
ports:
- "127.0.0.1:9560:9560" # Only accessible from host
Reverse Proxy with Authentication
Use Nginx or similar to add authentication:
# nginx.conf
server {
listen 443 ssl;
server_name metrics.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location /metrics {
auth_basic "Metrics Access";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:9560;
}
}
Prometheus Authentication
Configure Prometheus to use basic auth:
# prometheus.yml
scrape_configs:
- job_name: 'query-exporter'
scheme: https
basic_auth:
username: prometheus
password: secret
static_configs:
- targets: ['metrics.example.com']
Logging Security
Avoid Logging Sensitive Data
Query Exporter logs don’t include credentials by default, but be careful with query parameters:
Configure log level:
# Use 'info' or 'warning' in production, not 'debug'
query-exporter --log-level info
JSON logging for log aggregation:
query-exporter --log-format json
Secure Log Storage
Docker logging:
# docker-compose.yml
services:
query-exporter:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
File Permissions
Protect configuration files and SSL certificates:
# Configuration files
chmod 600 config.yaml
chmod 600 .env
# SSL certificates
chmod 600 private.key
chmod 644 public.crt
# Ensure correct ownership
chown query-exporter:query-exporter config.yaml .env
chown query-exporter:query-exporter private.key public.crt
Docker Security
Run as Non-Root User
# Dockerfile
FROM adonato/query-exporter:latest
USER nobody:nogroup
Read-Only Filesystem
# docker-compose.yml
services:
query-exporter:
image: adonato/query-exporter:latest
read_only: true
security_opt:
- no-new-privileges:true
Secrets Management
# docker-compose.yml
services:
query-exporter:
image: adonato/query-exporter:latest
secrets:
- source: config
target: /config/config.yaml
mode: 0400
- source: dotenv
target: /config/.env
mode: 0400
secrets:
config:
file: ./config.yaml
dotenv:
file: ./.env
Security Checklist
Example Secure Configuration
Here’s a complete secure configuration:
# config.yaml
databases:
postgres_prod:
dsn: !env POSTGRES_PROD_DSN
connection-pool:
size: 2
max-overflow: 0
postgres_replica:
dsn: !env POSTGRES_REPLICA_DSN
connection-pool:
size: 2
max-overflow: 0
metrics:
query_success:
type: counter
description: Successful query executions
labels: [query_name]
queries:
health_check:
interval: 30
timeout: 5
databases: [postgres_replica]
metrics: [query_success]
sql: SELECT 1 as query_success, 'health_check' as query_name
# .env (never commit this)
POSTGRES_PROD_DSN=postgresql://readonly:[email protected]:5432/app?sslmode=verify-full
POSTGRES_REPLICA_DSN=postgresql://readonly:[email protected]:5432/app?sslmode=verify-full
# Start with SSL and restrictive binding
query-exporter \
--config config.yaml \
--host 127.0.0.1 \
--log-level info \
--log-format json \
--ssl-private-key /etc/ssl/private/exporter.key \
--ssl-public-key /etc/ssl/certs/exporter.crt
Further Reading