Skip to content
Open
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
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,79 @@ Memorising docker commands is hard. Memorising aliases is slightly less hard. Ke
- Docker >= **1.13** (API >= **1.25**)
- Docker-Compose >= **1.23.2** (optional)

## TLS Configuration

Lazydocker supports secure connections to Docker daemon using TLS. To enable TLS, you can configure it in your config.yml:

### Basic TLS Configuration

```yaml
tls:
enable: true
caCertPath: "/path/to/ca.pem"
certPath: "/path/to/cert.pem"
keyPath: "/path/to/key.pem"
```

### Complete TLS Configuration

```yaml
tls:
enable: true # Enable TLS connections to Docker daemon
caCertPath: "/path/to/ca.pem" # Path to Certificate Authority (CA) certificate
certPath: "/path/to/cert.pem" # Path to client certificate
keyPath: "/path/to/key.pem" # Path to client private key
host: "docker.example.com" # Hostname or IP for certificate validation (ServerName)
insecureSkipVerify: false # Skip TLS certificate verification (NOT recommended for production)
```

### TLS Configuration Options

- **`enable`**: Set to `true` to enable TLS connections to the Docker daemon
- **`caCertPath`**: Path to the Certificate Authority (CA) certificate file used to verify the Docker daemon's certificate
- **`certPath`**: Path to the client certificate file for mutual TLS authentication
- **`keyPath`**: Path to the client private key file corresponding to the client certificate
- **`host`**: The hostname or IP address expected on the Docker daemon's certificate. This must match the Common Name (CN) or a Subject Alternative Name (SAN) in the server's certificate
- **`insecureSkipVerify`**: When set to `true`, skips certificate verification (useful for testing, but insecure for production use)

### Important Notes

1. **Two Different "Hosts"**: Don't confuse the `tls.host` config field with the `DOCKER_HOST` environment variable:
- `DOCKER_HOST` environment variable: Tells Lazydocker **where to connect** (e.g., `tcp://192.168.1.100:2376`)
- `tls.host` in config.yml: Tells Lazydocker **what name to expect on the certificate** (e.g., `docker.example.com` or `192.168.1.100`)

2. **Certificate Validation**: The `host` field must match a name in your Docker daemon's certificate:
- Common Name (CN) in the certificate's Subject field
- Or one of the Subject Alternative Names (SANs) in the certificate

3. **Security**: Always use `insecureSkipVerify: false` in production environments. Only set it to `true` for testing purposes.

### Example for Remote Docker Daemon

When connecting to a Docker daemon running on a remote machine with TLS:

1. Set the connection endpoint:
```bash
export DOCKER_HOST=tcp://docker.example.com:2376
```

2. Configure TLS in `config.yml`:
```yaml
tls:
enable: true
caCertPath: "/home/user/.docker/ca.pem"
certPath: "/home/user/.docker/cert.pem"
keyPath: "/home/user/.docker/key.pem"
host: "docker.example.com" # Must match the name on the server's certificate
insecureSkipVerify: false
```

### Troubleshooting TLS Issues

- **"x509: certificate is valid for ..., not ..."**: The `host` field doesn't match any name on the server's certificate. Check your server certificate's CN and SANs.
- **"certificate signed by unknown authority"**: The CA certificate path is incorrect or the server's certificate wasn't signed by the specified CA.
- **"no such host"**: This is a network connectivity issue with `DOCKER_HOST`, not a TLS configuration problem.

## Installation

### Homebrew
Expand Down
88 changes: 88 additions & 0 deletions docs/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ logs:
timestamps: false
since: '60m' # set to '' to show all logs
tail: '' # set to 200 to show last 200 lines of logs
tls:
enable: false # Set to true to enable TLS connections to Docker daemon
caCertPath: "" # Path to Certificate Authority (CA) certificate file
certPath: "" # Path to client certificate file for mutual TLS authentication
keyPath: "" # Path to client private key file
host: "" # Hostname or IP for certificate validation (must match server certificate)
insecureSkipVerify: false # Skip certificate verification (NOT recommended for production)
commandTemplates:
dockerCompose: docker compose # Determines the Docker Compose command to run, referred to as .DockerCompose in commandTemplates
restartService: '{{ .DockerCompose }} restart {{ .Service.Name }}'
Expand Down Expand Up @@ -97,6 +104,87 @@ stats:

## To see what all of the config options mean, and what other options you can set, see [here](https://godoc.org/github.com/jesseduffield/lazydocker/pkg/config)

## TLS Configuration

Lazydocker supports secure connections to Docker daemon using TLS (Transport Layer Security). This is essential when connecting to remote Docker daemons or when your Docker daemon requires client certificate authentication.

### Configuration Options

The `tls` section in your config supports the following options:

- **`enable`**: Boolean flag to enable/disable TLS connections
- **`caCertPath`**: Path to the Certificate Authority (CA) certificate file used to verify the Docker daemon's certificate
- **`certPath`**: Path to the client certificate file for mutual TLS authentication
- **`keyPath`**: Path to the client private key file corresponding to the client certificate
- **`host`**: The hostname or IP address expected on the Docker daemon's certificate. This must match the Common Name (CN) or a Subject Alternative Name (SAN) in the server's certificate
- **`insecureSkipVerify`**: When set to `true`, skips certificate verification. **Only use this for testing - it's insecure for production!**

### Basic Example

```yaml
tls:
enable: true
caCertPath: "/home/user/.docker/ca.pem"
certPath: "/home/user/.docker/cert.pem"
keyPath: "/home/user/.docker/key.pem"
host: "docker.example.com"
insecureSkipVerify: false
```

### Remote Docker Daemon Setup

When connecting to a remote Docker daemon with TLS:

1. **Set the Docker Host**: Use the `DOCKER_HOST` environment variable to specify where to connect:
```bash
export DOCKER_HOST=tcp://docker.example.com:2376
```

2. **Configure TLS**: Update your `config.yml`:
```yaml
tls:
enable: true
caCertPath: "/path/to/ca.pem"
certPath: "/path/to/cert.pem"
keyPath: "/path/to/key.pem"
host: "docker.example.com" # Must match certificate
insecureSkipVerify: false
```

### Important Notes

1. **Don't confuse two different "hosts"**:
- `DOCKER_HOST` environment variable: **WHERE** to connect (e.g., `tcp://192.168.1.100:2376`)
- `tls.host` config field: **WHAT NAME** to expect on the certificate (e.g., `docker.example.com`)

2. **Certificate Validation**: The `host` field must match a name in your Docker daemon's certificate. You can check your certificate's valid names with:
```bash
openssl x509 -in server-cert.pem -noout -text
```

3. **Certificate Paths**: Ensure all certificate files are readable by the user running Lazydocker

### Testing TLS Configuration

For testing purposes only, you can skip certificate verification:

```yaml
tls:
enable: true
caCertPath: "/path/to/ca.pem"
certPath: "/path/to/cert.pem"
keyPath: "/path/to/key.pem"
host: "any-name-here" # Ignored when insecureSkipVerify is true
insecureSkipVerify: true # INSECURE - only for testing!
```

### Common TLS Errors

- **"x509: certificate is valid for X, not Y"**: The `host` field doesn't match any name on the server's certificate
- **"certificate signed by unknown authority"**: The `caCertPath` is incorrect or the server's certificate wasn't signed by the specified CA
- **"no such host"**: Network connectivity issue with `DOCKER_HOST` (not a TLS config problem)
- **"tls: failed to verify certificate"**: General certificate validation failure - check all certificate paths and the `host` field

## Color Attributes:

For color attributes you can choose an array of attributes (with max one color attribute)
Expand Down
35 changes: 34 additions & 1 deletion pkg/commands/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
ogLog "log"
"net/http"
"os"
"os/exec"
"strings"
Expand All @@ -18,6 +19,7 @@ import (
ctxstore "github.com/docker/cli/cli/context/store"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/go-connections/tlsconfig"
"github.com/imdario/mergo"
"github.com/jesseduffield/lazydocker/pkg/commands/ssh"
"github.com/jesseduffield/lazydocker/pkg/config"
Expand Down Expand Up @@ -97,7 +99,38 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
dockerHost = dockerHostFromEnv
}

cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion(APIVersion), client.WithHost(dockerHost))
opts := []client.Opt{
client.FromEnv,
client.WithVersion(APIVersion),
client.WithHost(dockerHost),
}

if config.UserConfig.TLS.Enable {
tlsConfigOpts := tlsconfig.Options{
CAFile: config.UserConfig.TLS.CACertPath,
CertFile: config.UserConfig.TLS.CertPath,
KeyFile: config.UserConfig.TLS.KeyPath,
InsecureSkipVerify: config.UserConfig.TLS.InsecureSkipVerify,
ExclusiveRootPools: true,
}
customTLSConfig, err := tlsconfig.Client(tlsConfigOpts)
if err != nil {
ogLog.Fatalf("Failed to create custom TLS config: %v", err)
}

if config.UserConfig.TLS.Host != "" {
customTLSConfig.ServerName = config.UserConfig.TLS.Host
}
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: customTLSConfig,
},
CheckRedirect: client.CheckRedirect,
}
opts = append(opts, client.WithHTTPClient(httpClient))
}

cli, err := client.NewClientWithOpts(opts...)
if err != nil {
ogLog.Fatal(err)
}
Expand Down
40 changes: 40 additions & 0 deletions pkg/config/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ type UserConfig struct {
// hit esc or q when no confirmation panels are open
ConfirmOnQuit bool `yaml:"confirmOnQuit,omitempty"`

// TLS configuration for connecting to Docker daemon
TLS TLSConfig `yaml:"tls,omitempty"`

// Logs determines how we render/filter a container's logs
Logs LogsConfig `yaml:"logs,omitempty"`

Expand Down Expand Up @@ -346,6 +349,35 @@ type LogsConfig struct {
Tail string `yaml:"tail,omitempty"`
}

// TLSConfig holds TLS configuration for connecting to Docker daemon
type TLSConfig struct {
// Enable TLS connections to Docker daemon
Enable bool `yaml:"enable,omitempty"`

// CACertPath is the path to the Certificate Authority (CA) certificate file
// used to verify the Docker daemon's server certificate
CACertPath string `yaml:"caCertPath,omitempty"`

// CertPath is the path to the client certificate file for mutual TLS authentication
CertPath string `yaml:"certPath,omitempty"`

// KeyPath is the path to the client private key file corresponding to the client certificate
KeyPath string `yaml:"keyPath,omitempty"`

// InsecureSkipVerify controls whether a client verifies the server's certificate chain
// and host name. If InsecureSkipVerify is true, TLS accepts any certificate presented
// by the server and any host name in that certificate. This should only be used for
// testing purposes as it makes TLS susceptible to man-in-the-middle attacks.
InsecureSkipVerify bool `yaml:"insecureSkipVerify,omitempty"`

// Host specifies the hostname or IP address expected on the Docker daemon's certificate.
// This value is used for Server Name Indication (SNI) and certificate validation.
// It must match either the Common Name (CN) or one of the Subject Alternative Names (SANs)
// in the server's certificate. Do not include protocol (tcp://) or port - just the hostname/IP.
// Example: "docker.example.com" or "192.168.1.100"
Host string `yaml:"host,omitempty"`
}

// GetDefaultConfig returns the application default configuration NOTE (to
// contributors, not users): do not default a boolean to true, because false is
// the boolean zero value and this will be ignored when parsing the user's
Expand Down Expand Up @@ -474,6 +506,14 @@ func GetDefaultConfig() UserConfig {
Replacements: Replacements{
ImageNamePrefixes: map[string]string{},
},
TLS: TLSConfig{
Enable: false,
CACertPath: "",
CertPath: "",
KeyPath: "",
InsecureSkipVerify: false,
Host: "",
},
}
}

Expand Down