From 125470f1106373ff42cf03ae28467055a8186de5 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Wed, 18 Dec 2019 17:28:04 +0300 Subject: [PATCH] Support SSH connection to Docker --- docs/content/providers/docker.md | 27 +++++++++++- go.mod | 5 ++- go.sum | 8 ++++ pkg/provider/docker/docker.go | 76 +++++++++++++++++++++++--------- 4 files changed, 92 insertions(+), 24 deletions(-) diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index 5310a78e4..0fbbf17d2 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -135,7 +135,7 @@ You can specify which Docker API Endpoint to use with the directive [`endpoint`] ??? success "Solutions" - Expose the Docker socket over TCP, instead of the default Unix socket file. + Expose the Docker socket over TCP or SSH, instead of the default Unix socket file. It allows different implementation levels of the [AAA (Authentication, Authorization, Accounting) concepts](https://en.wikipedia.org/wiki/AAA_(computer_security)), depending on your security assessment: - Authentication with Client Certificates as described in ["Protect the Docker daemon socket."](https://docs.docker.com/engine/security/https/) @@ -145,6 +145,7 @@ You can specify which Docker API Endpoint to use with the directive [`endpoint`] - Accounting at container level, by exposing the socket on a another container than Traefik's. With Swarm mode, it allows scheduling of Traefik on worker nodes, with only the "socket exposer" container on the manager nodes. - Accounting at kernel level, by enforcing kernel calls with mechanisms like [SELinux](https://en.wikipedia.org/wiki/Security-Enhanced_Linux), to only allows an identified set of actions for Traefik's process (or the "socket exposer" process). + - SSH public key authentication (SSH is supported with Docker > 18.09) ??? info "More Resources and Examples" - ["Paranoid about mounting /var/run/docker.sock?"](https://medium.com/@containeroo/traefik-2-0-paranoid-about-mounting-var-run-docker-sock-22da9cb3e78c) @@ -273,6 +274,30 @@ See the sections [Docker API Access](#docker-api-access) and [Docker Swarm API A # ... ``` +??? example "Using SSH" + + Using Docker 18.09+ you can connect Traefik to daemon using SSH + We specify the SSH host and user in Traefik's configuration file. + Note that is server requires public keys for authentication you must have those accessible for user who runs Traefik. + + ```toml tab="File (TOML)" + [providers.docker] + endpoint = "ssh://traefik@192.168.2.5:2022" + # ... + ``` + + ```yaml tab="File (YAML)" + providers: + docker: + endpoint: "ssh://traefik@192.168.2.5:2022" + # ... + ``` + + ```bash tab="CLI" + --providers.docker.endpoint=ssh://traefik@192.168.2.5:2022 + # ... + ``` + ### `useBindPortIP` _Optional, Default=false_ diff --git a/go.mod b/go.mod index 0c5106cd9..d632e68aa 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/davecgh/go-spew v1.1.1 - github.com/docker/cli v0.0.0-20190711175710-5b38d82aa076 // indirect + github.com/docker/cli v0.0.0-20190711175710-5b38d82aa076 github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/docker v0.0.0-00010101000000-000000000000 github.com/docker/docker-credential-helpers v0.6.3 // indirect @@ -43,6 +43,7 @@ require ( github.com/go-acme/lego/v3 v3.2.0 github.com/go-check/check v0.0.0-00010101000000-000000000000 github.com/go-kit/kit v0.9.0 + github.com/gogo/protobuf v1.3.0 // indirect github.com/golang/protobuf v1.3.2 github.com/google/go-github/v28 v28.1.1 github.com/googleapis/gnostic v0.1.0 // indirect @@ -75,6 +76,7 @@ require ( github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 github.com/rancher/go-rancher-metadata v0.0.0-00010101000000-000000000000 github.com/sirupsen/logrus v1.4.2 + github.com/spf13/pflag v1.0.3 // indirect github.com/stretchr/testify v1.4.0 github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 github.com/tinylib/msgp v1.0.2 // indirect @@ -92,6 +94,7 @@ require ( golang.org/x/net v0.0.0-20191204025024-5ee1b9f4859a golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e // indirect golang.org/x/time v0.0.0-20191024005414-555d28b269f0 + google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69 // indirect google.golang.org/grpc v1.22.1 gopkg.in/DataDog/dd-trace-go.v1 v1.19.0 gopkg.in/fsnotify.v1 v1.4.7 diff --git a/go.sum b/go.sum index c389ecf61..46e8088d3 100644 --- a/go.sum +++ b/go.sum @@ -235,6 +235,8 @@ github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -379,6 +381,7 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181 h1:TrxPzApUukas24OMMVDUMlCs1XCExJtnGaDEiIAR4oQ= github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= @@ -571,6 +574,8 @@ github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= @@ -749,6 +754,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -782,6 +788,8 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69 h1:4rNOqY4ULrKzS6twXa619uQgI7h9PaVd4ZhjFQ7C5zs= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= diff --git a/pkg/provider/docker/docker.go b/pkg/provider/docker/docker.go index 534ee47c7..9372a565b 100644 --- a/pkg/provider/docker/docker.go +++ b/pkg/provider/docker/docker.go @@ -19,6 +19,7 @@ import ( "github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/types" "github.com/containous/traefik/v2/pkg/version" + "github.com/docker/cli/cli/connhelper" dockertypes "github.com/docker/docker/api/types" dockercontainertypes "github.com/docker/docker/api/types/container" eventtypes "github.com/docker/docker/api/types/events" @@ -108,46 +109,77 @@ type networkData struct { } func (p *Provider) createClient() (client.APIClient, error) { - var httpClient *http.Client + opts, err := p.getClientOpts() + if err != nil { + return nil, err + } + + httpHeaders := map[string]string{ + "User-Agent": "Traefik " + version.Version, + } + opts = append(opts, client.WithHTTPHeaders(httpHeaders)) + + apiVersion := DockerAPIVersion + if p.SwarmMode { + apiVersion = SwarmAPIVersion + } + opts = append(opts, client.WithVersion(apiVersion)) + + return client.NewClientWithOpts(opts...) +} + +func (p *Provider) getClientOpts() ([]client.Opt, error) { + helper, err := connhelper.GetConnectionHelper(p.Endpoint) + if err != nil { + return nil, err + } + + // SSH + if helper != nil { + // https://github.com/docker/cli/blob/ebca1413117a3fcb81c89d6be226dcec74e5289f/cli/context/docker/load.go#L112-L123 + + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: helper.Dialer, + }, + } + + return []client.Opt{ + client.WithHTTPClient(httpClient), + client.WithHost(helper.Host), // To avoid 400 Bad Request: malformed Host header daemon error + client.WithDialContext(helper.Dialer), + }, nil + } + + opts := []client.Opt{ + client.WithHost(p.Endpoint), + } if p.TLS != nil { ctx := log.With(context.Background(), log.Str(log.ProviderName, "docker")) + conf, err := p.TLS.CreateTLSConfig(ctx) if err != nil { return nil, err } - tr := &http.Transport{ - TLSClientConfig: conf, - } hostURL, err := client.ParseHostURL(p.Endpoint) if err != nil { return nil, err } + + tr := &http.Transport{ + TLSClientConfig: conf, + } + if err := sockets.ConfigureTransport(tr, hostURL.Scheme, hostURL.Host); err != nil { return nil, err } - httpClient = &http.Client{ - Transport: tr, - } + opts = append(opts, client.WithHTTPClient(&http.Client{Transport: tr})) } - httpHeaders := map[string]string{ - "User-Agent": "Traefik " + version.Version, - } - - apiVersion := DockerAPIVersion - if p.SwarmMode { - apiVersion = SwarmAPIVersion - } - - return client.NewClientWithOpts( - client.WithHost(p.Endpoint), - client.WithVersion(apiVersion), - client.WithHTTPClient(httpClient), - client.WithHTTPHeaders(httpHeaders), - ) + return opts, nil } // Provide allows the docker provider to provide configurations to traefik using the given configuration channel.