From 911f785e2c36d808b6c8ce9c129456f55f466163 Mon Sep 17 00:00:00 2001 From: Amir Raminfar Date: Sun, 19 Oct 2025 12:15:50 -0700 Subject: [PATCH] feat: support setting the path to certs (#4198) --- docs/guide/agent.md | 46 ++++++++++++++++++++-- internal/support/cli/agent_command.go | 2 +- internal/support/cli/agent_test_command.go | 2 +- internal/support/cli/args.go | 2 + internal/support/cli/certs.go | 8 ++-- internal/support/cli/clients.go | 2 +- internal/support/cli/health_command.go | 2 +- main.go | 2 +- 8 files changed, 54 insertions(+), 12 deletions(-) diff --git a/docs/guide/agent.md b/docs/guide/agent.md index 962f61af..72813263 100644 --- a/docs/guide/agent.md +++ b/docs/guide/agent.md @@ -35,7 +35,6 @@ services: > [!NOTE] Docker Socket Proxy users > If you are using a remote agent you **CANNOT** add a socket proxy on top of the agent. Dozzle agents **REPLACE** using a proxy, see [Remote Hosts](/guide/remote-hosts.md) for more info and how to use a socket proxy instead of an agent. - The agent will start and listen on port `7007`. You can connect to the agent using the Dozzle UI by providing the agent's IP address and port. The agent will only show the containers that are available on the host where the agent is running. > [!TIP] @@ -153,7 +152,9 @@ This will restrict the agent to displaying only containers with the label `color By default, Dozzle uses self-signed certificates for communication between agents. This is a private certificate which is only valid to other Dozzle instances. This is secure and recommended for most use cases. However, if Dozzle is exposed externally and an attacker knows exactly which port the agent is running on, then they can set up their own Dozzle instance and connect to the agent. To prevent this, you can provide your own certificates. -To provide custom certificates, you need to mount or use secrets to provide the certificates. Here is an example: +To provide custom certificates, you need to mount or use secrets to provide the certificates. By default, Dozzle looks for certificates at `/dozzle_cert.pem` and `/dozzle_key.pem`, but you can customize these paths using the `--cert` and `--key` flags or the `DOZZLE_CERT` and `DOZZLE_KEY` environment variables. + +Here is an example using the default paths: ```yml services: @@ -176,10 +177,49 @@ secrets: file: ./key.pem ``` +Or using custom paths with environment variables: + +```yml +services: + agent: + image: amir20/dozzle:latest + command: agent + environment: + - DOZZLE_CERT=/certs/my-cert.pem + - DOZZLE_KEY=/certs/my-key.pem + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./certs:/certs + ports: + - 7007:7007 +``` + +Or using command-line flags: + +::: code-group + +```sh +docker run -v /var/run/docker.sock:/var/run/docker.sock -v ./certs:/certs -p 7007:7007 amir20/dozzle:latest agent --cert /certs/my-cert.pem --key /certs/my-key.pem +``` + +```yaml [docker-compose.yml] +services: + agent: + image: amir20/dozzle:latest + command: agent --cert /certs/my-cert.pem --key /certs/my-key.pem + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./certs:/certs + ports: + - 7007:7007 +``` + +::: + > [!TIP] > Docker secrets are preferred for providing certificates. They can be created using `docker secret create` command or as the example above using `docker-compose.yml`. The same certificates should be provided to the Dozzle instance connecting to the agent. -This will mount the `cert.pem` and `key.pem` files to the agent. The agent will use these certificates for communication. The same certificates should be provided to the Dozzle instance connecting to the agent. +This will mount the certificate and key files to the agent. The agent will use these certificates for communication. The same certificates should be provided to the Dozzle instance connecting to the agent. To generate certificates, you can use the following command: diff --git a/internal/support/cli/agent_command.go b/internal/support/cli/agent_command.go index a17a0f53..d2417657 100644 --- a/internal/support/cli/agent_command.go +++ b/internal/support/cli/agent_command.go @@ -27,7 +27,7 @@ func (a *AgentCmd) Run(args Args, embeddedCerts embed.FS) error { if err != nil { return fmt.Errorf("failed to create docker client: %w", err) } - certs, err := ReadCertificates(embeddedCerts) + certs, err := ReadCertificates(embeddedCerts, args.CertPath, args.KeyPath) if err != nil { return fmt.Errorf("failed to read certificates: %w", err) } diff --git a/internal/support/cli/agent_test_command.go b/internal/support/cli/agent_test_command.go index 358a1e88..d152a64b 100644 --- a/internal/support/cli/agent_test_command.go +++ b/internal/support/cli/agent_test_command.go @@ -14,7 +14,7 @@ type AgentTestCmd struct { } func (at *AgentTestCmd) Run(args Args, embeddedCerts embed.FS) error { - certs, err := ReadCertificates(embeddedCerts) + certs, err := ReadCertificates(embeddedCerts, args.CertPath, args.KeyPath) if err != nil { return fmt.Errorf("error reading certificates: %w", err) } diff --git a/internal/support/cli/args.go b/internal/support/cli/args.go index 3dc7ff30..0baad012 100644 --- a/internal/support/cli/args.go +++ b/internal/support/cli/args.go @@ -36,6 +36,8 @@ type Args struct { TimeoutString string `arg:"--timeout,env:DOZZLE_TIMEOUT" default:"10s" help:"sets the timeout for docker client"` Timeout time.Duration `arg:"-"` Namespace []string `arg:"env:DOZZLE_NAMESPACE" help:"sets the namespace to use in k8s"` + CertPath string `arg:"--cert,env:DOZZLE_CERT" default:"dozzle_cert.pem" help:"path to custom TLS certificate"` + KeyPath string `arg:"--key,env:DOZZLE_KEY" default:"dozzle_key.pem" help:"path to custom TLS key"` Healthcheck *HealthcheckCmd `arg:"subcommand:healthcheck" help:"checks if the server is running"` Generate *GenerateCmd `arg:"subcommand:generate" help:"generates a configuration file for simple auth"` Agent *AgentCmd `arg:"subcommand:agent" help:"starts the agent"` diff --git a/internal/support/cli/certs.go b/internal/support/cli/certs.go index 507f0126..1b6a7131 100644 --- a/internal/support/cli/certs.go +++ b/internal/support/cli/certs.go @@ -8,13 +8,13 @@ import ( "github.com/rs/zerolog/log" ) -func ReadCertificates(certs embed.FS) (tls.Certificate, error) { - if pair, err := tls.LoadX509KeyPair("dozzle_cert.pem", "dozzle_key.pem"); err == nil { - log.Info().Msg("Loaded custom dozzle certificate and key") +func ReadCertificates(certs embed.FS, certPath, keyPath string) (tls.Certificate, error) { + if pair, err := tls.LoadX509KeyPair(certPath, keyPath); err == nil { + log.Info().Str("cert", certPath).Str("key", keyPath).Msg("Loaded custom dozzle certificate and key") return pair, nil } else { if !os.IsNotExist(err) { - log.Fatal().Err(err).Msg("Failed to load custom dozzle certificate and key. Stopping...") + log.Fatal().Err(err).Str("cert", certPath).Str("key", keyPath).Msg("Failed to load custom dozzle certificate and key. Stopping...") } } diff --git a/internal/support/cli/clients.go b/internal/support/cli/clients.go index 98f8212a..c6dcdd77 100644 --- a/internal/support/cli/clients.go +++ b/internal/support/cli/clients.go @@ -51,7 +51,7 @@ func CreateMultiHostService(embeddedCerts embed.FS, args Args) *docker_support.M go StartEvent(args, "server", localClient, "") } - certs, err := ReadCertificates(embeddedCerts) + certs, err := ReadCertificates(embeddedCerts, args.CertPath, args.KeyPath) if err != nil { log.Fatal().Err(err).Msg("Could not read certificates") } diff --git a/internal/support/cli/health_command.go b/internal/support/cli/health_command.go index 4abe72e4..4311f5f6 100644 --- a/internal/support/cli/health_command.go +++ b/internal/support/cli/health_command.go @@ -20,7 +20,7 @@ func (h *HealthcheckCmd) Run(args Args, embeddedCerts embed.FS) error { return fmt.Errorf("failed to read file: %w", err) } agentAddress := string(data) - certs, err := ReadCertificates(embeddedCerts) + certs, err := ReadCertificates(embeddedCerts, args.CertPath, args.KeyPath) if err != nil { return fmt.Errorf("failed to read certificates: %w", err) } diff --git a/main.go b/main.go index d5ef5356..cdab7fff 100644 --- a/main.go +++ b/main.go @@ -68,7 +68,7 @@ func main() { if err != nil { log.Fatal().Err(err).Msg("Could not create docker client") } - certs, err := cli.ReadCertificates(certs) + certs, err := cli.ReadCertificates(certs, args.CertPath, args.KeyPath) if err != nil { log.Fatal().Err(err).Msg("Could not read certificates") }