Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

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.

## Development Commands

### Build and Run
```bash
# Run locally
go run main.go

# Build binary
go build -o go-sql-proxy

# Build Docker image
make build TAG=1.0.0

# Push to registry
make push TAG=1.0.0

# Build and push
make all TAG=1.0.0
```

### Testing
Currently, there are no unit tests in the codebase. When adding new functionality, consider creating appropriate test files.

## Architecture

### Core Components

1. **main.go**: Entry point that initializes configuration, starts metrics server, creates proxy instance, and handles graceful shutdown
2. **pkg/proxy**: Core proxy logic - accepts client connections, establishes upstream connections, and manages bidirectional data transfer
3. **pkg/protocol**: MySQL protocol decoding/encoding for packet inspection
4. **pkg/metrics**: Prometheus metrics collection and HTTP endpoints
5. **pkg/health**: Health check endpoints that verify proxy and upstream connectivity
6. **pkg/config**: Environment-based configuration management

### Connection Flow

1. Client connects to proxy on `BIND_PORT` (default: 3306)
2. Proxy establishes connection to `SOURCE_DATABASE_SERVER:SOURCE_DATABASE_PORT`
3. Data is transferred bidirectionally using `io.Copy` with optional protocol decoding
4. Metrics are collected for bytes transferred and connection counts

### Key Design Patterns

- **Context-based lifecycle management**: Uses Go contexts for graceful shutdown
- **Concurrent connection handling**: Each client connection runs in its own goroutine
- **Centralized logging**: All packages use the shared Logrus logger from pkg/logging
- **Environment configuration**: All settings come from environment variables

### Environment Variables

- `DEBUG`: Enable debug logging
- `METRICS_PORT`: Port for metrics/health endpoints (default: 9090)
- `SOURCE_DATABASE_SERVER`: Target MySQL server hostname
- `SOURCE_DATABASE_PORT`: Target MySQL server port (default: 25060)
- `SOURCE_DATABASE_USER`: MySQL username
- `SOURCE_DATABASE_PASSWORD`: MySQL password
- `SOURCE_DATABASE_NAME`: Default database name
- `BIND_ADDRESS`: Proxy bind address (default: 0.0.0.0)
- `BIND_PORT`: Proxy listening port (default: 3306)
- `USE_SSL`: Enable SSL/TLS connection to upstream MySQL (default: false)
- `SSL_SKIP_VERIFY`: Skip SSL certificate verification (default: false)
- `SSL_CA_FILE`: Path to CA certificate file for SSL verification
- `SSL_CERT_FILE`: Path to client certificate file for mutual TLS
- `SSL_KEY_FILE`: Path to client key file for mutual TLS

### Metrics and Health Endpoints

Available on `METRICS_PORT`:
- `/metrics`: Prometheus metrics
- `/healthz`: Liveness check (tests proxy connectivity)
- `/readyz`: Readiness check (tests upstream MySQL connectivity)
- `/version`: Version information

### Important Implementation Details

- The proxy uses `io.Copy` for efficient data transfer between connections
- Protocol decoding is optional and controlled by configuration
- Connection errors are logged but don't crash the proxy
- Each connection tracks bytes transferred in both directions
- Version information is injected at build time using LDFLAGS
- SSL/TLS support is controlled by the `USE_SSL` flag instead of port-based logic
- Health checks also respect SSL settings when connecting to the database
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,43 @@ To run the server, execute the following command:
go run main.go
```

You can customize the server configurations by setting the environment variables as defined in `pkg/config/config.go`.
## Configuration

The proxy is configured through environment variables:

### Basic Configuration
- `DEBUG`: Enable debug logging (default: false)
- `METRICS_PORT`: Port for metrics/health endpoints (default: 9090)
- `BIND_ADDRESS`: Proxy bind address (default: 0.0.0.0)
- `BIND_PORT`: Proxy listening port (default: 3306)

### Database Connection
- `SOURCE_DATABASE_SERVER`: Target MySQL server hostname
- `SOURCE_DATABASE_PORT`: Target MySQL server port (default: 25060)
- `SOURCE_DATABASE_USER`: MySQL username
- `SOURCE_DATABASE_PASSWORD`: MySQL password
- `SOURCE_DATABASE_NAME`: Default database name

### SSL/TLS Configuration
- `USE_SSL`: Enable SSL/TLS connection to upstream MySQL (default: false)
- `SSL_SKIP_VERIFY`: Skip SSL certificate verification (default: false)
- `SSL_CA_FILE`: Path to CA certificate file for SSL verification
- `SSL_CERT_FILE`: Path to client certificate file for mutual TLS
- `SSL_KEY_FILE`: Path to client key file for mutual TLS

### Example: Connecting to PlanetScale

```bash
export SOURCE_DATABASE_SERVER=your-database.planetscale.com
export SOURCE_DATABASE_PORT=3306
export SOURCE_DATABASE_USER=your-username
export SOURCE_DATABASE_PASSWORD=your-password
export SOURCE_DATABASE_NAME=your-database
export USE_SSL=true
export SSL_SKIP_VERIFY=true

go run main.go
```

## Note

Expand Down
2 changes: 1 addition & 1 deletion charts/go-sql-proxy/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
version: 0.2.0

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
Expand Down
73 changes: 73 additions & 0 deletions charts/go-sql-proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# go-sql-proxy Helm Chart

This Helm chart deploys the go-sql-proxy MySQL proxy server on Kubernetes.

## Installation

```bash
helm install my-proxy ./charts/go-sql-proxy
```

## Configuration

The following table lists the configurable parameters of the go-sql-proxy chart and their default values.

| Parameter | Description | Default |
| --------- | ----------- | ------- |
| `settings.bind.host` | Bind address for proxy | `0.0.0.0` |
| `settings.bind.port` | Bind port for proxy | `3306` |
| `settings.debug` | Enable debug logging | `false` |
| `settings.metrics.enabled` | Enable metrics endpoint | `true` |
| `settings.metrics.port` | Metrics port | `9090` |
| `settings.source.host` | Target MySQL server hostname | `example.db.ondigitalocean.com` |
| `settings.source.port` | Target MySQL server port | `25060` |
| `settings.source.user` | MySQL username | `doadmin` |
| `settings.source.password` | MySQL password | `password` |
| `settings.source.database` | Default database name | `defaultdb` |
| `settings.ssl.enabled` | Enable SSL/TLS connection | `false` |
| `settings.ssl.skipVerify` | Skip SSL certificate verification | `false` |
| `settings.ssl.caFile` | Path to CA certificate file | `""` |
| `settings.ssl.certFile` | Path to client certificate file | `""` |
| `settings.ssl.keyFile` | Path to client key file | `""` |

## SSL/TLS Configuration

To connect to SSL-enabled MySQL servers (like PlanetScale), enable SSL:

```yaml
settings:
source:
host: your-database.planetscale.com
port: 3306
ssl:
enabled: true
skipVerify: true # For self-signed certificates
```
For proper certificate verification, provide CA certificate:
```yaml
settings:
ssl:
enabled: true
skipVerify: false
caFile: /path/to/ca.pem
```
For mutual TLS authentication:
```yaml
settings:
ssl:
enabled: true
certFile: /path/to/client-cert.pem
keyFile: /path/to/client-key.pem
```
## Monitoring
The proxy exposes Prometheus metrics on the configured metrics port:
- `/metrics` - Prometheus metrics
- `/healthz` - Liveness probe
- `/readyz` - Readiness probe
- `/version` - Version information
34 changes: 34 additions & 0 deletions charts/go-sql-proxy/questions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,37 @@ questions:
label: "Metrics Port"
type: int
group: "Metrics settings"
- variable: settings.ssl.enabled
default: false
description: "Enable SSL/TLS connection to upstream MySQL server"
label: "Enable SSL"
type: bool
group: "SSL/TLS settings"
- variable: settings.ssl.skipVerify
default: false
description: "Skip SSL certificate verification (use for self-signed certificates)"
label: "Skip SSL Verify"
type: bool
group: "SSL/TLS settings"
show_if: "settings.ssl.enabled=true"
- variable: settings.ssl.caFile
default: ""
description: "Path to CA certificate file for SSL verification (optional)"
label: "CA Certificate File"
type: string
group: "SSL/TLS settings"
show_if: "settings.ssl.enabled=true"
- variable: settings.ssl.certFile
default: ""
description: "Path to client certificate file for mutual TLS (optional)"
label: "Client Certificate File"
type: string
group: "SSL/TLS settings"
show_if: "settings.ssl.enabled=true"
- variable: settings.ssl.keyFile
default: ""
description: "Path to client key file for mutual TLS (optional)"
label: "Client Key File"
type: string
group: "SSL/TLS settings"
show_if: "settings.ssl.enabled=true"
16 changes: 16 additions & 0 deletions charts/go-sql-proxy/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@ spec:
value: "{{ .Values.settings.bind.port }}"
- name: METRICS_PORT
value: "{{ .Values.settings.metrics.port }}"
- name: USE_SSL
value: "{{ .Values.settings.ssl.enabled }}"
- name: SSL_SKIP_VERIFY
value: "{{ .Values.settings.ssl.skipVerify }}"
{{- if .Values.settings.ssl.caFile }}
- name: SSL_CA_FILE
value: "{{ .Values.settings.ssl.caFile }}"
{{- end }}
{{- if .Values.settings.ssl.certFile }}
- name: SSL_CERT_FILE
value: "{{ .Values.settings.ssl.certFile }}"
{{- end }}
{{- if .Values.settings.ssl.keyFile }}
- name: SSL_KEY_FILE
value: "{{ .Values.settings.ssl.keyFile }}"
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.volumeMounts }}
Expand Down
6 changes: 6 additions & 0 deletions charts/go-sql-proxy/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ settings:
user: "doadmin"
password: "password"
database: "defaultdb"
ssl:
enabled: false
skipVerify: false
caFile: ""
certFile: ""
keyFile: ""

replicaCount: 1

Expand Down
11 changes: 1 addition & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,7 @@ func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // Ensure cancel is called to release resources if main exits before signal

var useSSL bool
if config.CFG.SourceDatabasePort == 3306 {
logger.Println("SourceDatabasePort is 3306, disabling SSL")
useSSL = false
} else {
logger.Println("Enabling SSL for non-default port")
useSSL = true
}

p := proxy.NewProxy(ctx, config.CFG.SourceDatabaseServer, config.CFG.SourceDatabasePort, useSSL)
p := proxy.NewProxy(ctx, config.CFG.SourceDatabaseServer, config.CFG.SourceDatabasePort, config.CFG.UseSSL)
p.EnableDecoding = true

var wg sync.WaitGroup
Expand Down
10 changes: 10 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ type AppConfig struct {
SourceDatabaseName string `json:"sourceDatabaseName"`
BindAddress string `json:"bindAddress"`
BindPort int `json:"bindPort"`
UseSSL bool `json:"useSSL"`
SSLSkipVerify bool `json:"sslSkipVerify"`
SSLCAFile string `json:"sslCAFile"`
SSLCertFile string `json:"sslCertFile"`
SSLKeyFile string `json:"sslKeyFile"`
}

// CFG is the global configuration object.
Expand All @@ -33,6 +38,11 @@ func LoadConfiguration() {
CFG.SourceDatabaseName = getEnvOrDefault("SOURCE_DATABASE_NAME", "defaultdb")
CFG.BindAddress = getEnvOrDefault("BIND_ADDRESS", "0.0.0.0")
CFG.BindPort = parseEnvInt("BIND_PORT", 3306)
CFG.UseSSL = parseEnvBool("USE_SSL", false)
CFG.SSLSkipVerify = parseEnvBool("SSL_SKIP_VERIFY", false)
CFG.SSLCAFile = getEnvOrDefault("SSL_CA_FILE", "")
CFG.SSLCertFile = getEnvOrDefault("SSL_CERT_FILE", "")
CFG.SSLKeyFile = getEnvOrDefault("SSL_KEY_FILE", "")
}

func getEnvOrDefault(key, defaultValue string) string {
Expand Down
25 changes: 23 additions & 2 deletions pkg/health/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

// Import MySQL driver for database connectivity
_ "github.com/go-sql-driver/mysql"
"github.com/supporttools/go-sql-proxy/pkg/config"
"github.com/supporttools/go-sql-proxy/pkg/logging"
)

Expand Down Expand Up @@ -35,7 +36,7 @@ func HealthzHandler(username, password, host string, port int, database string)
logger.Info("HealthzHandler")

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

// Open a new database connection
conn, err := sql.Open("mysql", dsn)
Expand Down Expand Up @@ -65,7 +66,7 @@ func ReadyzHandler(username, password, host string, port int, database string) h
logger.Info("ReadyzHandler")

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

// Open a new database connection
conn, err := sql.Open("mysql", dsn)
Expand Down Expand Up @@ -107,3 +108,23 @@ func VersionHandler() http.HandlerFunc {
}
}
}

// buildDSN constructs a MySQL DSN with optional TLS parameters
func buildDSN(username, password, host string, port int, database string) string {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", username, password, host, port, database)

if config.CFG.UseSSL {
// Add TLS parameters to the DSN
tlsConfig := "?tls=true"

// For custom CA verification, we'd need to register a custom TLS config
// with the MySQL driver, but for basic SSL with skip-verify, this works
if config.CFG.SSLSkipVerify {
tlsConfig = "?tls=skip-verify"
}

dsn += tlsConfig
}

return dsn
}
Loading
Loading