Skip to content

Commit 5feb8f8

Browse files
mattmattoxclaude
andcommitted
Add SSL/TLS support with flag-based control
- Added SSL configuration options (USE_SSL, SSL_SKIP_VERIFY, SSL_CA_FILE, SSL_CERT_FILE, SSL_KEY_FILE) - Implemented SSL/TLS connection logic in HandleConnection.go - Updated health checks to support SSL connections - Updated Helm chart with SSL configuration options - Added comprehensive documentation for SSL usage - Replaced port-based SSL logic with explicit USE_SSL flag This enables support for services like PlanetScale that use SSL on port 3306. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ad66041 commit 5feb8f8

File tree

10 files changed

+336
-5
lines changed

10 files changed

+336
-5
lines changed

CLAUDE.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Overview
6+
7+
go-sql-proxy is a MySQL proxy server that acts as an intermediary between MySQL clients and servers. It provides transparent traffic forwarding with protocol decoding capabilities, metrics collection, health checks, and connection management.
8+
9+
## Development Commands
10+
11+
### Build and Run
12+
```bash
13+
# Run locally
14+
go run main.go
15+
16+
# Build binary
17+
go build -o go-sql-proxy
18+
19+
# Build Docker image
20+
make build TAG=1.0.0
21+
22+
# Push to registry
23+
make push TAG=1.0.0
24+
25+
# Build and push
26+
make all TAG=1.0.0
27+
```
28+
29+
### Testing
30+
Currently, there are no unit tests in the codebase. When adding new functionality, consider creating appropriate test files.
31+
32+
## Architecture
33+
34+
### Core Components
35+
36+
1. **main.go**: Entry point that initializes configuration, starts metrics server, creates proxy instance, and handles graceful shutdown
37+
2. **pkg/proxy**: Core proxy logic - accepts client connections, establishes upstream connections, and manages bidirectional data transfer
38+
3. **pkg/protocol**: MySQL protocol decoding/encoding for packet inspection
39+
4. **pkg/metrics**: Prometheus metrics collection and HTTP endpoints
40+
5. **pkg/health**: Health check endpoints that verify proxy and upstream connectivity
41+
6. **pkg/config**: Environment-based configuration management
42+
43+
### Connection Flow
44+
45+
1. Client connects to proxy on `BIND_PORT` (default: 3306)
46+
2. Proxy establishes connection to `SOURCE_DATABASE_SERVER:SOURCE_DATABASE_PORT`
47+
3. Data is transferred bidirectionally using `io.Copy` with optional protocol decoding
48+
4. Metrics are collected for bytes transferred and connection counts
49+
50+
### Key Design Patterns
51+
52+
- **Context-based lifecycle management**: Uses Go contexts for graceful shutdown
53+
- **Concurrent connection handling**: Each client connection runs in its own goroutine
54+
- **Centralized logging**: All packages use the shared Logrus logger from pkg/logging
55+
- **Environment configuration**: All settings come from environment variables
56+
57+
### Environment Variables
58+
59+
- `DEBUG`: Enable debug logging
60+
- `METRICS_PORT`: Port for metrics/health endpoints (default: 9090)
61+
- `SOURCE_DATABASE_SERVER`: Target MySQL server hostname
62+
- `SOURCE_DATABASE_PORT`: Target MySQL server port (default: 25060)
63+
- `SOURCE_DATABASE_USER`: MySQL username
64+
- `SOURCE_DATABASE_PASSWORD`: MySQL password
65+
- `SOURCE_DATABASE_NAME`: Default database name
66+
- `BIND_ADDRESS`: Proxy bind address (default: 0.0.0.0)
67+
- `BIND_PORT`: Proxy listening port (default: 3306)
68+
- `USE_SSL`: Enable SSL/TLS connection to upstream MySQL (default: false)
69+
- `SSL_SKIP_VERIFY`: Skip SSL certificate verification (default: false)
70+
- `SSL_CA_FILE`: Path to CA certificate file for SSL verification
71+
- `SSL_CERT_FILE`: Path to client certificate file for mutual TLS
72+
- `SSL_KEY_FILE`: Path to client key file for mutual TLS
73+
74+
### Metrics and Health Endpoints
75+
76+
Available on `METRICS_PORT`:
77+
- `/metrics`: Prometheus metrics
78+
- `/healthz`: Liveness check (tests proxy connectivity)
79+
- `/readyz`: Readiness check (tests upstream MySQL connectivity)
80+
- `/version`: Version information
81+
82+
### Important Implementation Details
83+
84+
- The proxy uses `io.Copy` for efficient data transfer between connections
85+
- Protocol decoding is optional and controlled by configuration
86+
- Connection errors are logged but don't crash the proxy
87+
- Each connection tracks bytes transferred in both directions
88+
- Version information is injected at build time using LDFLAGS
89+
- SSL/TLS support is controlled by the `USE_SSL` flag instead of port-based logic
90+
- Health checks also respect SSL settings when connecting to the database

README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,43 @@ To run the server, execute the following command:
4343
go run main.go
4444
```
4545

46-
You can customize the server configurations by setting the environment variables as defined in `pkg/config/config.go`.
46+
## Configuration
47+
48+
The proxy is configured through environment variables:
49+
50+
### Basic Configuration
51+
- `DEBUG`: Enable debug logging (default: false)
52+
- `METRICS_PORT`: Port for metrics/health endpoints (default: 9090)
53+
- `BIND_ADDRESS`: Proxy bind address (default: 0.0.0.0)
54+
- `BIND_PORT`: Proxy listening port (default: 3306)
55+
56+
### Database Connection
57+
- `SOURCE_DATABASE_SERVER`: Target MySQL server hostname
58+
- `SOURCE_DATABASE_PORT`: Target MySQL server port (default: 25060)
59+
- `SOURCE_DATABASE_USER`: MySQL username
60+
- `SOURCE_DATABASE_PASSWORD`: MySQL password
61+
- `SOURCE_DATABASE_NAME`: Default database name
62+
63+
### SSL/TLS Configuration
64+
- `USE_SSL`: Enable SSL/TLS connection to upstream MySQL (default: false)
65+
- `SSL_SKIP_VERIFY`: Skip SSL certificate verification (default: false)
66+
- `SSL_CA_FILE`: Path to CA certificate file for SSL verification
67+
- `SSL_CERT_FILE`: Path to client certificate file for mutual TLS
68+
- `SSL_KEY_FILE`: Path to client key file for mutual TLS
69+
70+
### Example: Connecting to PlanetScale
71+
72+
```bash
73+
export SOURCE_DATABASE_SERVER=your-database.planetscale.com
74+
export SOURCE_DATABASE_PORT=3306
75+
export SOURCE_DATABASE_USER=your-username
76+
export SOURCE_DATABASE_PASSWORD=your-password
77+
export SOURCE_DATABASE_NAME=your-database
78+
export USE_SSL=true
79+
export SSL_SKIP_VERIFY=true
80+
81+
go run main.go
82+
```
4783

4884
## Note
4985

charts/go-sql-proxy/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type: application
1515
# This is the chart version. This version number should be incremented each time you make changes
1616
# to the chart and its templates, including the app version.
1717
# Versions are expected to follow Semantic Versioning (https://semver.org/)
18-
version: 0.1.0
18+
version: 0.2.0
1919

2020
# This is the version number of the application being deployed. This version number should be
2121
# incremented each time you make changes to the application. Versions are not expected to

charts/go-sql-proxy/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# go-sql-proxy Helm Chart
2+
3+
This Helm chart deploys the go-sql-proxy MySQL proxy server on Kubernetes.
4+
5+
## Installation
6+
7+
```bash
8+
helm install my-proxy ./charts/go-sql-proxy
9+
```
10+
11+
## Configuration
12+
13+
The following table lists the configurable parameters of the go-sql-proxy chart and their default values.
14+
15+
| Parameter | Description | Default |
16+
| --------- | ----------- | ------- |
17+
| `settings.bind.host` | Bind address for proxy | `0.0.0.0` |
18+
| `settings.bind.port` | Bind port for proxy | `3306` |
19+
| `settings.debug` | Enable debug logging | `false` |
20+
| `settings.metrics.enabled` | Enable metrics endpoint | `true` |
21+
| `settings.metrics.port` | Metrics port | `9090` |
22+
| `settings.source.host` | Target MySQL server hostname | `example.db.ondigitalocean.com` |
23+
| `settings.source.port` | Target MySQL server port | `25060` |
24+
| `settings.source.user` | MySQL username | `doadmin` |
25+
| `settings.source.password` | MySQL password | `password` |
26+
| `settings.source.database` | Default database name | `defaultdb` |
27+
| `settings.ssl.enabled` | Enable SSL/TLS connection | `false` |
28+
| `settings.ssl.skipVerify` | Skip SSL certificate verification | `false` |
29+
| `settings.ssl.caFile` | Path to CA certificate file | `""` |
30+
| `settings.ssl.certFile` | Path to client certificate file | `""` |
31+
| `settings.ssl.keyFile` | Path to client key file | `""` |
32+
33+
## SSL/TLS Configuration
34+
35+
To connect to SSL-enabled MySQL servers (like PlanetScale), enable SSL:
36+
37+
```yaml
38+
settings:
39+
source:
40+
host: your-database.planetscale.com
41+
port: 3306
42+
ssl:
43+
enabled: true
44+
skipVerify: true # For self-signed certificates
45+
```
46+
47+
For proper certificate verification, provide CA certificate:
48+
49+
```yaml
50+
settings:
51+
ssl:
52+
enabled: true
53+
skipVerify: false
54+
caFile: /path/to/ca.pem
55+
```
56+
57+
For mutual TLS authentication:
58+
59+
```yaml
60+
settings:
61+
ssl:
62+
enabled: true
63+
certFile: /path/to/client-cert.pem
64+
keyFile: /path/to/client-key.pem
65+
```
66+
67+
## Monitoring
68+
69+
The proxy exposes Prometheus metrics on the configured metrics port:
70+
- `/metrics` - Prometheus metrics
71+
- `/healthz` - Liveness probe
72+
- `/readyz` - Readiness probe
73+
- `/version` - Version information

charts/go-sql-proxy/questions.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,37 @@ questions:
5959
label: "Metrics Port"
6060
type: int
6161
group: "Metrics settings"
62+
- variable: settings.ssl.enabled
63+
default: false
64+
description: "Enable SSL/TLS connection to upstream MySQL server"
65+
label: "Enable SSL"
66+
type: bool
67+
group: "SSL/TLS settings"
68+
- variable: settings.ssl.skipVerify
69+
default: false
70+
description: "Skip SSL certificate verification (use for self-signed certificates)"
71+
label: "Skip SSL Verify"
72+
type: bool
73+
group: "SSL/TLS settings"
74+
show_if: "settings.ssl.enabled=true"
75+
- variable: settings.ssl.caFile
76+
default: ""
77+
description: "Path to CA certificate file for SSL verification (optional)"
78+
label: "CA Certificate File"
79+
type: string
80+
group: "SSL/TLS settings"
81+
show_if: "settings.ssl.enabled=true"
82+
- variable: settings.ssl.certFile
83+
default: ""
84+
description: "Path to client certificate file for mutual TLS (optional)"
85+
label: "Client Certificate File"
86+
type: string
87+
group: "SSL/TLS settings"
88+
show_if: "settings.ssl.enabled=true"
89+
- variable: settings.ssl.keyFile
90+
default: ""
91+
description: "Path to client key file for mutual TLS (optional)"
92+
label: "Client Key File"
93+
type: string
94+
group: "SSL/TLS settings"
95+
show_if: "settings.ssl.enabled=true"

charts/go-sql-proxy/templates/deployment.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@ spec:
8484
value: "{{ .Values.settings.bind.port }}"
8585
- name: METRICS_PORT
8686
value: "{{ .Values.settings.metrics.port }}"
87+
- name: USE_SSL
88+
value: "{{ .Values.settings.ssl.enabled }}"
89+
- name: SSL_SKIP_VERIFY
90+
value: "{{ .Values.settings.ssl.skipVerify }}"
91+
{{- if .Values.settings.ssl.caFile }}
92+
- name: SSL_CA_FILE
93+
value: "{{ .Values.settings.ssl.caFile }}"
94+
{{- end }}
95+
{{- if .Values.settings.ssl.certFile }}
96+
- name: SSL_CERT_FILE
97+
value: "{{ .Values.settings.ssl.certFile }}"
98+
{{- end }}
99+
{{- if .Values.settings.ssl.keyFile }}
100+
- name: SSL_KEY_FILE
101+
value: "{{ .Values.settings.ssl.keyFile }}"
102+
{{- end }}
87103
resources:
88104
{{- toYaml .Values.resources | nindent 12 }}
89105
{{- with .Values.volumeMounts }}

charts/go-sql-proxy/values.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ settings:
1616
user: "doadmin"
1717
password: "password"
1818
database: "defaultdb"
19+
ssl:
20+
enabled: false
21+
skipVerify: false
22+
caFile: ""
23+
certFile: ""
24+
keyFile: ""
1925

2026
replicaCount: 1
2127

pkg/config/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ type AppConfig struct {
1717
SourceDatabaseName string `json:"sourceDatabaseName"`
1818
BindAddress string `json:"bindAddress"`
1919
BindPort int `json:"bindPort"`
20+
UseSSL bool `json:"useSSL"`
21+
SSLSkipVerify bool `json:"sslSkipVerify"`
22+
SSLCAFile string `json:"sslCAFile"`
23+
SSLCertFile string `json:"sslCertFile"`
24+
SSLKeyFile string `json:"sslKeyFile"`
2025
}
2126

2227
var CFG AppConfig
@@ -32,6 +37,11 @@ func LoadConfiguration() {
3237
CFG.SourceDatabaseName = getEnvOrDefault("SOURCE_DATABASE_NAME", "defaultdb")
3338
CFG.BindAddress = getEnvOrDefault("BIND_ADDRESS", "0.0.0.0")
3439
CFG.BindPort = parseEnvInt("BIND_PORT", 3306)
40+
CFG.UseSSL = parseEnvBool("USE_SSL", false)
41+
CFG.SSLSkipVerify = parseEnvBool("SSL_SKIP_VERIFY", false)
42+
CFG.SSLCAFile = getEnvOrDefault("SSL_CA_FILE", "")
43+
CFG.SSLCertFile = getEnvOrDefault("SSL_CERT_FILE", "")
44+
CFG.SSLKeyFile = getEnvOrDefault("SSL_KEY_FILE", "")
3545
}
3646

3747
func getEnvOrDefault(key, defaultValue string) string {

pkg/health/health.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88

99
_ "github.com/go-sql-driver/mysql"
10+
"github.com/supporttools/go-sql-proxy/pkg/config"
1011
"github.com/supporttools/go-sql-proxy/pkg/logging"
1112
)
1213

@@ -30,7 +31,7 @@ func HealthzHandler(username, password, host string, port int, database string)
3031
logger.Info("HealthzHandler")
3132

3233
// Construct the DSN (Data Source Name) string
33-
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", username, password, host, port, database)
34+
dsn := buildDSN(username, password, host, port, database)
3435

3536
// Open a new database connection
3637
conn, err := sql.Open("mysql", dsn)
@@ -60,7 +61,7 @@ func ReadyzHandler(username, password, host string, port int, database string) h
6061
logger.Info("ReadyzHandler")
6162

6263
// Construct the DSN (Data Source Name) string
63-
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", username, password, host, port, database)
64+
dsn := buildDSN(username, password, host, port, database)
6465

6566
// Open a new database connection
6667
conn, err := sql.Open("mysql", dsn)
@@ -102,3 +103,23 @@ func VersionHandler() http.HandlerFunc {
102103
}
103104
}
104105
}
106+
107+
// buildDSN constructs a MySQL DSN with optional TLS parameters
108+
func buildDSN(username, password, host string, port int, database string) string {
109+
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", username, password, host, port, database)
110+
111+
if config.CFG.UseSSL {
112+
// Add TLS parameters to the DSN
113+
tlsConfig := "?tls=true"
114+
115+
// For custom CA verification, we'd need to register a custom TLS config
116+
// with the MySQL driver, but for basic SSL with skip-verify, this works
117+
if config.CFG.SSLSkipVerify {
118+
tlsConfig = "?tls=skip-verify"
119+
}
120+
121+
dsn += tlsConfig
122+
}
123+
124+
return dsn
125+
}

0 commit comments

Comments
 (0)