an advanced reverse proxy server written in Go
See the codeNote: The primary documentation for Dito will be migrated to an mdBook format. Please be patient 🙂
Dito is an advanced, highly extensible reverse proxy server written in Go. It features a robust plugin-based architecture, custom certificate handling for backend connections, dynamic configuration reloading, and more. Plugins can manage their own dependencies and provide custom middleware functionality.
dito/
├── cmd/ # Entry points (main application & plugin-signer)
├── app/ # Core application logic
├── config/ # Configuration loader & hot-reload
├── handlers/ # Request routing & proxy handlers
├── middlewares/ # Built-in middlewares (plugins can add more)
├── plugin/ # Plugin loading, signing, and verification
├── plugins/ # Plugin implementations
├── transport/ # HTTP transport configuration
├── websocket/ # WebSocket proxy support
├── writer/ # Response writers and buffering
├── metrics/ # Prometheus metrics
├── logging/ # Structured logging
├── deployments/ # Deployment configurations
│ ├── kubernetes/ # Basic Kubernetes deployments
│ ├── openshift/ # OpenShift production deployments
│ └── docker/ # Docker Compose for development
├── configs/ # Configuration files and templates
│ └── templates/ # Configuration templates
├── scripts/ # Deployment and utility scripts
└── bin/ # Built binaries and runtime files
├── plugins/ # Example and community plugins
├── transport/ # HTTP transport customization
├── websockets/ # WebSocket support
├── writer/ # Custom response writers
├── logging/ # Logging utilities
└── metrics/ # Prometheus metrics collection
Ensure you have Go (>= 1.27.1) and make installed. Plugins must be built with exactly the same Go toolchain as the Dito binary.
# Clone repo
git clone https://github.com/andrearaponi/dito.git && cd dito
# One-command setup & start
make quick-start
This will:
# 1. Clone repo
git clone https://github.com/andrearaponi/dito.git && cd dito
# 2. Setup (build, keys, plugins, config)
make setup
# 3. Start server
make run
| Category | Command | Description |
|---|---|---|
| 🚀 Quick | quick-start | Clean, setup everything and start (recommended) |
setup | Full development setup (build, keys, plugins, config) | |
setup-prod | Full production setup (persistent keys, prod config) | |
run | Start the Dito server | |
fix-config | Quick command to fix configuration after setup | |
| 🔨 Build | build | Build Dito binary only |
build-plugins | Build all plugins | |
build-plugin-signer | Build plugin-signer tool | |
| 🔑 Security | generate-keys | Generate Ed25519 key pair for development |
generate-prod-keys | Generate persistent Ed25519 key pair for production | |
sign-plugins | Sign all plugins with development keys | |
sign-plugins-prod | Sign all plugins with production keys | |
update-config | Update bin/config.yaml with development key paths/hashes | |
update-prod-config | Update bin/config-prod.yaml with production key paths/hashes | |
update-k8s-config | Create configs/config-prod-k8s.yaml for Kubernetes deployment | |
| 🚀 OpenShift | deploy-ocp | Complete OpenShift production deployment |
deploy-ocp-dev | Quick development deployment to OpenShift | |
status-ocp | Check OpenShift deployment status | |
logs-ocp | View OpenShift deployment logs | |
clean-ocp | Clean up OpenShift resources | |
| 🔍 Debug | debug-config | Debug configuration issues |
help | Show all commands with detailed descriptions | |
| 🧹 Cleanup | clean | Remove all build artifacts |
clean-plugins | Clean plugin binaries only | |
| 🧪 Development | test | Run tests |
vet | Run go vet on the main module and on the plugins | |
fmt | Format code | |
sonar | Run SonarQube analysis | |
| 🔁 CI | ci | Run every CI check except the image build, stopping at the first failure |
ci-selftest | Prove that every CI gate fails on the defect it must catch | |
image | Build the linux/amd64 container image without pushing it | |
lint-all | Show every golangci-lint issue, including the pre-existing ones | |
e2e | Run the end-to-end scenarios of the proxy (SCENARIO=<regex> selects some) |
Build Dito:
go build -o bin/dito ./cmd/dito/main.go
(Note: Ensure the path to main.go is correct, e.g., ./cmd/dito/main.go if main.go is in cmd/dito/)
Build plugin-signer:
cd cmd/plugin-signer && go build -o ../../bin/plugin-signer . && cd ../..
Generate keys:
./bin/plugin-signer generate-keys
(This will create keys in the current directory, presumably bin/ if run from there, or the project root. Move them to bin/ if needed or specify paths)
Build plugins:
find plugins -mindepth 1 -maxdepth 1 -type d -exec sh -c 'cd "$1" && go build -buildmode=plugin -o "$(basename "$1").so"' sh {} \;
Sign plugins:
find plugins -name "*.so" -exec ./bin/plugin-signer sign {} \;
(Ensure plugin-signer can find the private keys; you might need to specify -privateKey path/to/key)
Update config.yaml (ensure public_key_path & public_key_hash are correct).
Run Dito:
./bin/dito
Start with default config:
make run
Or directly:
./bin/dito -f /path/to/custom-config.yaml -enable-profiler
cmd/config.yaml (or the correct path to your template)bin/config.yaml (auto-updated by make setup or make quick-start)Key fields:
port: '8081'
hot_reload: true
metrics:
enabled: true
path: "/metrics"
logging:
enabled: true
verbose: false
level: "info"
plugins:
directory: "./plugins" # Relative to where Dito is run, or absolute path
public_key_path: "./ed25519_public.key" # Relative to where Dito is run, or absolute path
public_key_hash: "<SHA256_HASH>" # The SHA256 hash of the public key
transport:
http:
idle_conn_timeout: 90s
max_idle_conns: 1000
max_idle_conns_per_host: 200
max_conns_per_host: 0
tls_handshake_timeout: 2s
response_header_timeout: 2s
expect_continue_timeout: 500ms
disable_compression: false
dial_timeout: 2s
keep_alive: 30s
force_http2: true
locations:
- path: "^/test-ws$"
target_url: "wss://echo.websocket.org"
enable_websocket: true
replace_path: true
- path: "^/dito$"
target_url: "https://httpbin.org/get"
replace_path: true
transport:
http:
disable_compression: true
additional_headers:
X-Custom: "true"
excluded_headers:
- Cookie
middlewares:
- hello-plugin # Plugin name as defined in its directory
Dito provides flexible response body size limits that can be configured both globally and per location to prevent memory issues and control resource usage.
Set default limits for all locations in the main configuration:
# Global response limits configuration
response_limits:
max_response_body_size: 100000 # 100KB default limit for all locations
Override global limits for specific locations with custom settings:
locations:
- path: "^/api/small"
target_url: "https://api.example.com"
max_response_body_size: 1024 # 1KB limit for this specific endpoint
disable_response_buffering: false # Enable response buffering (default)
- path: "^/api/large"
target_url: "https://api.example.com"
max_response_body_size: 52428800 # 50MB limit for large responses
disable_response_buffering: true # Disable buffering for streaming
413 Request Entity Too Large status code when limits are exceededContent-Length header before processing to fail fastWhen a response exceeds the configured limit, Dito returns a standardized JSON error:
{
"error": {
"code": 413,
"message": "Response body size exceeds limit",
"details": {
"limit_bytes": 90,
"path": "/api/endpoint"
}
}
}
The disable_response_buffering option controls how responses are handled:
false (default): Responses are buffered in memory before sending to client
true: Responses are streamed directly to client
Dito uses Go plugins (.so files). Each plugin must:
plugins/.<plugin-name>.so<plugin-name>.so.sig (signature file)config.yaml (plugin-specific config, optional but common)Steps (if done manually):
Generate key pair:
./bin/plugin-signer generate-keys -privateKey ed25519_private.key -publicKey ed25519_public.key
(Save these keys securely, e.g., in bin/ or a dedicated directory)
Compute public key hash:
shasum -a 256 ed25519_public.key | awk '{print $1}'
(Ensure the path to ed25519_public.key is correct)
Update Dito's config.yaml with public_key_path (e.g., ./bin/ed25519_public.key) and the computed public_key_hash.
Sign plugin:
./bin/plugin-signer sign -plugin path/to/plugin.so -privateKey path/to/ed25519_private.key
(This will create a .sig file next to the plugin's .so file)
public key integrity validation failed: Regenerate hash and update config. Ensure the hash exactly matches the specified public key file.failed to read public key: Check public key file path in config.yaml & file permissions.plugin signature verification failed: Re-sign with correct private key. Ensure public key in config.yaml matches the private key used for signing.This allows for fine-grained control over how Dito connects to backend services, including:
Dito supports WebSocket proxying, allowing you to seamlessly forward WebSocket connections to your backend servers. This can be configured per location, enabling WebSocket support on specific routes.
To enable WebSocket support, add enable_websocket: true to the location configuration. Here’s an example:
# List of location configurations for proxying requests.
locations:
- path: "^/test-ws$" # Regex pattern to match the request path.
target_url: "wss://echo.websocket.org" # The target URL to which the request will be proxied.
enable_websocket: true # Enable WebSocket support for this location.
replace_path: true # Replace the matched path with the target URL.
Future versions of Dito will include more advanced WebSocket features, such as:
These features aim to provide full control, security, and reliability for WebSocket connections in Dito, enhancing the overall communication experience.
Dito supports mTLS (mutual TLS) for secure connections to backends. You can specify:
cert_file: The client certificate.key_file: The client private key.ca_file: The certificate authority (CA) for verifying the backend.Dito supports monitoring through Prometheus by exposing various metrics related to the proxy's performance and behavior. The metrics are accessible at the configured path (default is /metrics).
Dito provides both custom metrics and standard metrics from the Go runtime and Prometheus libraries:
http_requests_total: Total number of HTTP requests processed, partitioned by method, path, and status code.http_request_duration_seconds: Duration of HTTP requests in seconds, with predefined buckets.active_connections: Number of active connections currently being handled by the proxy.data_transferred_bytes_total: Total amount of data transferred in bytes, partitioned by direction (inbound or outbound).go_goroutines: Number of goroutines currently running.go_memstats_alloc_bytes: Number of bytes allocated in the heap.go_gc_duration_seconds: Duration of garbage collection cycles.promhttp_metric_handler_requests_total: Total number of HTTP requests handled by the metrics endpoint.promhttp_metric_handler_requests_in_flight: Current number of scrapes being served.To enable metrics, make sure the following section is present in the config.yaml file:
metrics:
enabled: true # Enable or disable metrics.
path: "/metrics" # The path on which the metrics will be exposed.
Every pull request to main, every push to main and a weekly scheduled run (Mondays, 06:00 UTC) run the CI workflow (.github/workflows/ci.yml). Each job runs exactly one make target, so every check can be reproduced locally:
| Job | Local command | What it checks |
|---|---|---|
tools | make tools-check | Verification tools at the versions pinned in tools.mk, built with the Go version of go.mod |
build | make build-check | The proxy, plugin-signer and the plugins compile |
vet | make vet | go vet on the main module and on the plugins |
modules | make modules | Tidy go.mod/go.sum, one Go version everywhere, same versions for modules linked into both the proxy and a plugin |
test | make test-race | Tests with the race detector, in random order, then the end-to-end scenarios |
hermetic | make test-hermetic | Tests and end-to-end scenarios with outbound network traffic restricted to loopback |
lint | make lint | golangci-lint, reporting only the issues introduced by the change |
vuln | make vuln | govulncheck on the main module and on the plugins |
coverage | make coverage | Coverage of every package against the floors in scripts/ci/coverage-floors.txt, then the end-to-end scenarios |
smoke | make smoke-plugins | Builds, signs (throwaway keys) and loads the example plugin, then proxies a request through it |
image | make image | Container image build (linux/amd64, not pushed) |
workflows | make workflows | actionlint plus the project policies: actions pinned by SHA, read-only permissions, tool versions, Renovate configuration |
make ci runs all of them except image, in the same order, and stops at the first failure. make ci-selftest runs the mutation harness: for every gate it injects the defect the gate must catch (a compile error, a data race, an untidy go.mod, a tampered plugin signature, a coverage drop, ...) and verifies that the gate fails.
GOTOOLCHAIN=auto and uses the version declared in go.mod, even if the installed Go is older.curl and jq.make test-hermetic: on macOS it uses the built-in sandbox-exec; on Linux it needs sudo, unshare, setpriv and ip (util-linux, iproute2).make image: Docker or Podman.tools.mk. Tools run with go run, so they never enter go.mod, whose versions are shared with the plugins.scripts/ci/coverage-floors.txt when you add tests, and remove a line only when its package is deleted.make lint-all shows everything.Enable branch protection on main (Settings → Branches) and mark these checks as required: tools, build, vet, modules, test, hermetic, lint, vuln, coverage, smoke, image, workflows. Validate Walden runs only when .walden/ changes, so it must not be required.
Renovate keeps Go modules, GitHub Actions (pinned by commit SHA), Docker base images (pinned by digest) and the tools in tools.mk up to date. Install the Renovate GitHub App on the repository; its configuration is renovate.json: non-major updates are grouped in one weekly pull request, and go mod tidy also runs in the plugin module.
The e2e/ package checks the proxy as a client sees it: every scenario starts real backends on loopback, a proxy with a real configuration, and observes status, headers, bodies (compared by hash), framing, streaming, WebSocket traffic and the proxy logs. The catalog covers every feature of this README; each scenario carries its reproduction ID (R-xx) or probe (P-xx) when it has one.
make e2e # the whole suite, with the race detector, in random order
make e2e SCENARIO=^TestLimits_ # one area
make e2e SCENARIO=_R05_ # one scenario, by its ID
The suite also runs inside make test-race, make test-hermetic and make coverage, so every pull request runs it. It ends with a summary such as e2e summary: 48 passed, 0 failed, 27 known bugs (F-01: 6, ...).
A scenario is a Go test in e2e/, in the file of its area, named Test<Area>_<ID>_<Description>:
func TestRouting_ReplacePathTrue(t *testing.T) {
Run(t, func(s *S) {
api := s.Backend("api", text("ok"))
p := s.Proxy(`
- path: "^/old/path$"
target_url: "{{backend "api"}}/new/path"
replace_path: true`)
s.Get(p.URL + "/old/path")
assert.Equal(s, "/new/path", onlyRequest(s, api).Path)
})
}
s.Proxy(locations, opts...) runs the handler chain of the binary in-process; s.Binary(...) runs the compiled proxy, for configuration files, hot reload (WithHotReload, s.Reload), signals and signed plugins (WithSignedPlugin). Options: WithMetrics, WithLogging, WithConfig, WithTransport, WithPlugins.{{backend "name"}}, {{ws "name"}}, {{ca "name"}}, {{clientCert}} and {{clientKey}} are replaced with the addresses and TLS files of the scenario.s.Backend(name, handler) records every request (Requests()); WithTLS and WithClientAuth serve HTTPS and mutual TLS; deterministicBody(n, framing) serves bodies of any size with a known hash; s.RawBackend sends raw bytes.s.Get, s.Head, s.Do, s.Stream (line by line, with timing), s.DialWebSocket, s.DoPartialUpload; logs with s.WaitLog and s.NoLog.s to testify (assert.Equal(s, ...), require.NoError(s, ...)). Use s.Fatalf only for harness errors (a backend that does not start, a timeout).A behavior still broken by an open finding stays in the catalog, marked with the finding:
func TestRouting_R05_ReplacePathFalse(t *testing.T) {
KnownBug("F-04").Run(t, func(s *S) { /* assertions on the correct behavior */ })
}
KNOWN BUG F-04 and the suite passes.scenario passes: remove KnownBug("F-04"): the change that fixes a finding also removes its markers.TestLimits_P04_OverLimitAfterStart).If you encounter any issues while using Dito, please follow these steps to open an issue on the GitHub repository:
Go Version: Specify the version of Go you are using.
go version in your terminal.Error Details: Provide a detailed description of the error or issue you encountered. Include:
Configuration File: Include the config.yaml file you are using.
git checkout -b feature/AmazingFeature).git commit -m 'Add some AmazingFeature').git push origin feature/AmazingFeature).Dito is fully containerized and optimized for OpenShift Container Platform (OCP) with enterprise-grade security and deployment practices.
For a complete OpenShift deployment with automatic key management and configuration:
# Make the deployment script executable
chmod +x scripts/deploy-ocp.sh
# Deploy with defaults (namespace: dito, version: v2.0.0-production)
./scripts/deploy-ocp.sh
# Deploy to custom namespace with specific version
./scripts/deploy-ocp.sh -n my-dito -v latest
# Force key regeneration
./scripts/deploy-ocp.sh -f
# Only update configuration (no deployment)
./scripts/deploy-ocp.sh -c
# Only deploy (skip config/key generation)
./scripts/deploy-ocp.sh -d
# Build for OpenShift with automatic registry login and push
./docker-build.sh
# Or with custom settings
VERSION=v2.1.0 NAMESPACE=my-dito ./docker-build.sh
# Generate persistent production keys (only if they don't exist)
make generate-prod-keys
# Create namespace
oc new-project dito
# Create Secret for keys
oc create secret generic dito-keys \
--from-file=ed25519_public.key=bin/ed25519_public_prod.key \
--from-file=ed25519_private.key=bin/ed25519_private_prod.key
# Create ConfigMap for application config
# First, create Kubernetes-specific config from template
HASH=$(shasum -a 256 bin/ed25519_public_prod.key | awk '{print $1}')
sed "s/PLACEHOLDER_HASH_TO_BE_REPLACED/$HASH/" configs/templates/application.yaml > configs/config-prod-k8s.yaml
oc create configmap dito-config \
--from-file=config.yaml=configs/config-prod-k8s.yaml
# Deploy the application
oc apply -f deployments/openshift/production-deployment.yaml
| File | Use Case | Features |
|---|---|---|
deployments/kubernetes/basic-deployment.yaml | Basic deployment | Simple setup, minimal security |
deployments/openshift/production-deployment.yaml | Production deployment | Plugin signing, proper secrets, health checks |
Dito supports multiple configuration approaches:
configs/templates/application.yaml with hash substitutionconfigs/See configs/README.md for detailed configuration management.
This project is licensed under the Apache License 2.0. See the LICENSE file for details.
Made with ❤️
137 followers · starred Oct 2024
4,259 followers · starred Oct 2024
391 followers · starred Oct 2024
175 followers · starred Oct 2024
an advanced reverse proxy server written in Go
See the codeNote: The primary documentation for Dito will be migrated to an mdBook format. Please be patient 🙂
Dito is an advanced, highly extensible reverse proxy server written in Go. It features a robust plugin-based architecture, custom certificate handling for backend connections, dynamic configuration reloading, and more. Plugins can manage their own dependencies and provide custom middleware functionality.
dito/
├── cmd/ # Entry points (main application & plugin-signer)
├── app/ # Core application logic
├── config/ # Configuration loader & hot-reload
├── handlers/ # Request routing & proxy handlers
├── middlewares/ # Built-in middlewares (plugins can add more)
├── plugin/ # Plugin loading, signing, and verification
├── plugins/ # Plugin implementations
├── transport/ # HTTP transport configuration
├── websocket/ # WebSocket proxy support
├── writer/ # Response writers and buffering
├── metrics/ # Prometheus metrics
├── logging/ # Structured logging
├── deployments/ # Deployment configurations
│ ├── kubernetes/ # Basic Kubernetes deployments
│ ├── openshift/ # OpenShift production deployments
│ └── docker/ # Docker Compose for development
├── configs/ # Configuration files and templates
│ └── templates/ # Configuration templates
├── scripts/ # Deployment and utility scripts
└── bin/ # Built binaries and runtime files
├── plugins/ # Example and community plugins
├── transport/ # HTTP transport customization
├── websockets/ # WebSocket support
├── writer/ # Custom response writers
├── logging/ # Logging utilities
└── metrics/ # Prometheus metrics collection
Ensure you have Go (>= 1.27.1) and make installed. Plugins must be built with exactly the same Go toolchain as the Dito binary.
# Clone repo
git clone https://github.com/andrearaponi/dito.git && cd dito
# One-command setup & start
make quick-start
This will:
# 1. Clone repo
git clone https://github.com/andrearaponi/dito.git && cd dito
# 2. Setup (build, keys, plugins, config)
make setup
# 3. Start server
make run
| Category | Command | Description |
|---|---|---|
| 🚀 Quick | quick-start | Clean, setup everything and start (recommended) |
setup | Full development setup (build, keys, plugins, config) | |
setup-prod | Full production setup (persistent keys, prod config) | |
run | Start the Dito server | |
fix-config | Quick command to fix configuration after setup | |
| 🔨 Build | build | Build Dito binary only |
build-plugins | Build all plugins | |
build-plugin-signer | Build plugin-signer tool | |
| 🔑 Security | generate-keys | Generate Ed25519 key pair for development |
generate-prod-keys | Generate persistent Ed25519 key pair for production | |
sign-plugins | Sign all plugins with development keys | |
sign-plugins-prod | Sign all plugins with production keys | |
update-config | Update bin/config.yaml with development key paths/hashes | |
update-prod-config | Update bin/config-prod.yaml with production key paths/hashes | |
update-k8s-config | Create configs/config-prod-k8s.yaml for Kubernetes deployment | |
| 🚀 OpenShift | deploy-ocp | Complete OpenShift production deployment |
deploy-ocp-dev | Quick development deployment to OpenShift | |
status-ocp | Check OpenShift deployment status | |
logs-ocp | View OpenShift deployment logs | |
clean-ocp | Clean up OpenShift resources | |
| 🔍 Debug | debug-config | Debug configuration issues |
help | Show all commands with detailed descriptions | |
| 🧹 Cleanup | clean | Remove all build artifacts |
clean-plugins | Clean plugin binaries only | |
| 🧪 Development | test | Run tests |
vet | Run go vet on the main module and on the plugins | |
fmt | Format code | |
sonar | Run SonarQube analysis | |
| 🔁 CI | ci | Run every CI check except the image build, stopping at the first failure |
ci-selftest | Prove that every CI gate fails on the defect it must catch | |
image | Build the linux/amd64 container image without pushing it | |
lint-all | Show every golangci-lint issue, including the pre-existing ones | |
e2e | Run the end-to-end scenarios of the proxy (SCENARIO=<regex> selects some) |
Build Dito:
go build -o bin/dito ./cmd/dito/main.go
(Note: Ensure the path to main.go is correct, e.g., ./cmd/dito/main.go if main.go is in cmd/dito/)
Build plugin-signer:
cd cmd/plugin-signer && go build -o ../../bin/plugin-signer . && cd ../..
Generate keys:
./bin/plugin-signer generate-keys
(This will create keys in the current directory, presumably bin/ if run from there, or the project root. Move them to bin/ if needed or specify paths)
Build plugins:
find plugins -mindepth 1 -maxdepth 1 -type d -exec sh -c 'cd "$1" && go build -buildmode=plugin -o "$(basename "$1").so"' sh {} \;
Sign plugins:
find plugins -name "*.so" -exec ./bin/plugin-signer sign {} \;
(Ensure plugin-signer can find the private keys; you might need to specify -privateKey path/to/key)
Update config.yaml (ensure public_key_path & public_key_hash are correct).
Run Dito:
./bin/dito
Start with default config:
make run
Or directly:
./bin/dito -f /path/to/custom-config.yaml -enable-profiler
cmd/config.yaml (or the correct path to your template)bin/config.yaml (auto-updated by make setup or make quick-start)Key fields:
port: '8081'
hot_reload: true
metrics:
enabled: true
path: "/metrics"
logging:
enabled: true
verbose: false
level: "info"
plugins:
directory: "./plugins" # Relative to where Dito is run, or absolute path
public_key_path: "./ed25519_public.key" # Relative to where Dito is run, or absolute path
public_key_hash: "<SHA256_HASH>" # The SHA256 hash of the public key
transport:
http:
idle_conn_timeout: 90s
max_idle_conns: 1000
max_idle_conns_per_host: 200
max_conns_per_host: 0
tls_handshake_timeout: 2s
response_header_timeout: 2s
expect_continue_timeout: 500ms
disable_compression: false
dial_timeout: 2s
keep_alive: 30s
force_http2: true
locations:
- path: "^/test-ws$"
target_url: "wss://echo.websocket.org"
enable_websocket: true
replace_path: true
- path: "^/dito$"
target_url: "https://httpbin.org/get"
replace_path: true
transport:
http:
disable_compression: true
additional_headers:
X-Custom: "true"
excluded_headers:
- Cookie
middlewares:
- hello-plugin # Plugin name as defined in its directory
Dito provides flexible response body size limits that can be configured both globally and per location to prevent memory issues and control resource usage.
Set default limits for all locations in the main configuration:
# Global response limits configuration
response_limits:
max_response_body_size: 100000 # 100KB default limit for all locations
Override global limits for specific locations with custom settings:
locations:
- path: "^/api/small"
target_url: "https://api.example.com"
max_response_body_size: 1024 # 1KB limit for this specific endpoint
disable_response_buffering: false # Enable response buffering (default)
- path: "^/api/large"
target_url: "https://api.example.com"
max_response_body_size: 52428800 # 50MB limit for large responses
disable_response_buffering: true # Disable buffering for streaming
413 Request Entity Too Large status code when limits are exceededContent-Length header before processing to fail fastWhen a response exceeds the configured limit, Dito returns a standardized JSON error:
{
"error": {
"code": 413,
"message": "Response body size exceeds limit",
"details": {
"limit_bytes": 90,
"path": "/api/endpoint"
}
}
}
The disable_response_buffering option controls how responses are handled:
false (default): Responses are buffered in memory before sending to client
true: Responses are streamed directly to client
Dito uses Go plugins (.so files). Each plugin must:
plugins/.<plugin-name>.so<plugin-name>.so.sig (signature file)config.yaml (plugin-specific config, optional but common)Steps (if done manually):
Generate key pair:
./bin/plugin-signer generate-keys -privateKey ed25519_private.key -publicKey ed25519_public.key
(Save these keys securely, e.g., in bin/ or a dedicated directory)
Compute public key hash:
shasum -a 256 ed25519_public.key | awk '{print $1}'
(Ensure the path to ed25519_public.key is correct)
Update Dito's config.yaml with public_key_path (e.g., ./bin/ed25519_public.key) and the computed public_key_hash.
Sign plugin:
./bin/plugin-signer sign -plugin path/to/plugin.so -privateKey path/to/ed25519_private.key
(This will create a .sig file next to the plugin's .so file)
public key integrity validation failed: Regenerate hash and update config. Ensure the hash exactly matches the specified public key file.failed to read public key: Check public key file path in config.yaml & file permissions.plugin signature verification failed: Re-sign with correct private key. Ensure public key in config.yaml matches the private key used for signing.This allows for fine-grained control over how Dito connects to backend services, including:
Dito supports WebSocket proxying, allowing you to seamlessly forward WebSocket connections to your backend servers. This can be configured per location, enabling WebSocket support on specific routes.
To enable WebSocket support, add enable_websocket: true to the location configuration. Here’s an example:
# List of location configurations for proxying requests.
locations:
- path: "^/test-ws$" # Regex pattern to match the request path.
target_url: "wss://echo.websocket.org" # The target URL to which the request will be proxied.
enable_websocket: true # Enable WebSocket support for this location.
replace_path: true # Replace the matched path with the target URL.
Future versions of Dito will include more advanced WebSocket features, such as:
These features aim to provide full control, security, and reliability for WebSocket connections in Dito, enhancing the overall communication experience.
Dito supports mTLS (mutual TLS) for secure connections to backends. You can specify:
cert_file: The client certificate.key_file: The client private key.ca_file: The certificate authority (CA) for verifying the backend.Dito supports monitoring through Prometheus by exposing various metrics related to the proxy's performance and behavior. The metrics are accessible at the configured path (default is /metrics).
Dito provides both custom metrics and standard metrics from the Go runtime and Prometheus libraries:
http_requests_total: Total number of HTTP requests processed, partitioned by method, path, and status code.http_request_duration_seconds: Duration of HTTP requests in seconds, with predefined buckets.active_connections: Number of active connections currently being handled by the proxy.data_transferred_bytes_total: Total amount of data transferred in bytes, partitioned by direction (inbound or outbound).go_goroutines: Number of goroutines currently running.go_memstats_alloc_bytes: Number of bytes allocated in the heap.go_gc_duration_seconds: Duration of garbage collection cycles.promhttp_metric_handler_requests_total: Total number of HTTP requests handled by the metrics endpoint.promhttp_metric_handler_requests_in_flight: Current number of scrapes being served.To enable metrics, make sure the following section is present in the config.yaml file:
metrics:
enabled: true # Enable or disable metrics.
path: "/metrics" # The path on which the metrics will be exposed.
Every pull request to main, every push to main and a weekly scheduled run (Mondays, 06:00 UTC) run the CI workflow (.github/workflows/ci.yml). Each job runs exactly one make target, so every check can be reproduced locally:
| Job | Local command | What it checks |
|---|---|---|
tools | make tools-check | Verification tools at the versions pinned in tools.mk, built with the Go version of go.mod |
build | make build-check | The proxy, plugin-signer and the plugins compile |
vet | make vet | go vet on the main module and on the plugins |
modules | make modules | Tidy go.mod/go.sum, one Go version everywhere, same versions for modules linked into both the proxy and a plugin |
test | make test-race | Tests with the race detector, in random order, then the end-to-end scenarios |
hermetic | make test-hermetic | Tests and end-to-end scenarios with outbound network traffic restricted to loopback |
lint | make lint | golangci-lint, reporting only the issues introduced by the change |
vuln | make vuln | govulncheck on the main module and on the plugins |
coverage | make coverage | Coverage of every package against the floors in scripts/ci/coverage-floors.txt, then the end-to-end scenarios |
smoke | make smoke-plugins | Builds, signs (throwaway keys) and loads the example plugin, then proxies a request through it |
image | make image | Container image build (linux/amd64, not pushed) |
workflows | make workflows | actionlint plus the project policies: actions pinned by SHA, read-only permissions, tool versions, Renovate configuration |
make ci runs all of them except image, in the same order, and stops at the first failure. make ci-selftest runs the mutation harness: for every gate it injects the defect the gate must catch (a compile error, a data race, an untidy go.mod, a tampered plugin signature, a coverage drop, ...) and verifies that the gate fails.
GOTOOLCHAIN=auto and uses the version declared in go.mod, even if the installed Go is older.curl and jq.make test-hermetic: on macOS it uses the built-in sandbox-exec; on Linux it needs sudo, unshare, setpriv and ip (util-linux, iproute2).make image: Docker or Podman.tools.mk. Tools run with go run, so they never enter go.mod, whose versions are shared with the plugins.scripts/ci/coverage-floors.txt when you add tests, and remove a line only when its package is deleted.make lint-all shows everything.Enable branch protection on main (Settings → Branches) and mark these checks as required: tools, build, vet, modules, test, hermetic, lint, vuln, coverage, smoke, image, workflows. Validate Walden runs only when .walden/ changes, so it must not be required.
Renovate keeps Go modules, GitHub Actions (pinned by commit SHA), Docker base images (pinned by digest) and the tools in tools.mk up to date. Install the Renovate GitHub App on the repository; its configuration is renovate.json: non-major updates are grouped in one weekly pull request, and go mod tidy also runs in the plugin module.
The e2e/ package checks the proxy as a client sees it: every scenario starts real backends on loopback, a proxy with a real configuration, and observes status, headers, bodies (compared by hash), framing, streaming, WebSocket traffic and the proxy logs. The catalog covers every feature of this README; each scenario carries its reproduction ID (R-xx) or probe (P-xx) when it has one.
make e2e # the whole suite, with the race detector, in random order
make e2e SCENARIO=^TestLimits_ # one area
make e2e SCENARIO=_R05_ # one scenario, by its ID
The suite also runs inside make test-race, make test-hermetic and make coverage, so every pull request runs it. It ends with a summary such as e2e summary: 48 passed, 0 failed, 27 known bugs (F-01: 6, ...).
A scenario is a Go test in e2e/, in the file of its area, named Test<Area>_<ID>_<Description>:
func TestRouting_ReplacePathTrue(t *testing.T) {
Run(t, func(s *S) {
api := s.Backend("api", text("ok"))
p := s.Proxy(`
- path: "^/old/path$"
target_url: "{{backend "api"}}/new/path"
replace_path: true`)
s.Get(p.URL + "/old/path")
assert.Equal(s, "/new/path", onlyRequest(s, api).Path)
})
}
s.Proxy(locations, opts...) runs the handler chain of the binary in-process; s.Binary(...) runs the compiled proxy, for configuration files, hot reload (WithHotReload, s.Reload), signals and signed plugins (WithSignedPlugin). Options: WithMetrics, WithLogging, WithConfig, WithTransport, WithPlugins.{{backend "name"}}, {{ws "name"}}, {{ca "name"}}, {{clientCert}} and {{clientKey}} are replaced with the addresses and TLS files of the scenario.s.Backend(name, handler) records every request (Requests()); WithTLS and WithClientAuth serve HTTPS and mutual TLS; deterministicBody(n, framing) serves bodies of any size with a known hash; s.RawBackend sends raw bytes.s.Get, s.Head, s.Do, s.Stream (line by line, with timing), s.DialWebSocket, s.DoPartialUpload; logs with s.WaitLog and s.NoLog.s to testify (assert.Equal(s, ...), require.NoError(s, ...)). Use s.Fatalf only for harness errors (a backend that does not start, a timeout).A behavior still broken by an open finding stays in the catalog, marked with the finding:
func TestRouting_R05_ReplacePathFalse(t *testing.T) {
KnownBug("F-04").Run(t, func(s *S) { /* assertions on the correct behavior */ })
}
KNOWN BUG F-04 and the suite passes.scenario passes: remove KnownBug("F-04"): the change that fixes a finding also removes its markers.TestLimits_P04_OverLimitAfterStart).If you encounter any issues while using Dito, please follow these steps to open an issue on the GitHub repository:
Go Version: Specify the version of Go you are using.
go version in your terminal.Error Details: Provide a detailed description of the error or issue you encountered. Include:
Configuration File: Include the config.yaml file you are using.
git checkout -b feature/AmazingFeature).git commit -m 'Add some AmazingFeature').git push origin feature/AmazingFeature).Dito is fully containerized and optimized for OpenShift Container Platform (OCP) with enterprise-grade security and deployment practices.
For a complete OpenShift deployment with automatic key management and configuration:
# Make the deployment script executable
chmod +x scripts/deploy-ocp.sh
# Deploy with defaults (namespace: dito, version: v2.0.0-production)
./scripts/deploy-ocp.sh
# Deploy to custom namespace with specific version
./scripts/deploy-ocp.sh -n my-dito -v latest
# Force key regeneration
./scripts/deploy-ocp.sh -f
# Only update configuration (no deployment)
./scripts/deploy-ocp.sh -c
# Only deploy (skip config/key generation)
./scripts/deploy-ocp.sh -d
# Build for OpenShift with automatic registry login and push
./docker-build.sh
# Or with custom settings
VERSION=v2.1.0 NAMESPACE=my-dito ./docker-build.sh
# Generate persistent production keys (only if they don't exist)
make generate-prod-keys
# Create namespace
oc new-project dito
# Create Secret for keys
oc create secret generic dito-keys \
--from-file=ed25519_public.key=bin/ed25519_public_prod.key \
--from-file=ed25519_private.key=bin/ed25519_private_prod.key
# Create ConfigMap for application config
# First, create Kubernetes-specific config from template
HASH=$(shasum -a 256 bin/ed25519_public_prod.key | awk '{print $1}')
sed "s/PLACEHOLDER_HASH_TO_BE_REPLACED/$HASH/" configs/templates/application.yaml > configs/config-prod-k8s.yaml
oc create configmap dito-config \
--from-file=config.yaml=configs/config-prod-k8s.yaml
# Deploy the application
oc apply -f deployments/openshift/production-deployment.yaml
| File | Use Case | Features |
|---|---|---|
deployments/kubernetes/basic-deployment.yaml | Basic deployment | Simple setup, minimal security |
deployments/openshift/production-deployment.yaml | Production deployment | Plugin signing, proper secrets, health checks |
Dito supports multiple configuration approaches:
configs/templates/application.yaml with hash substitutionconfigs/See configs/README.md for detailed configuration management.
This project is licensed under the Apache License 2.0. See the LICENSE file for details.
Made with ❤️
137 followers · starred Oct 2024
4,259 followers · starred Oct 2024
391 followers · starred Oct 2024
175 followers · starred Oct 2024