Merge pull request #41285 from cpuguy83/no_more_pwns
Sterner warnings and deprecation notice for unauthenticated tcp access
This commit is contained in:
commit
ccc1233f37
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -380,8 +381,16 @@ func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
|
||||||
conf.Debug = opts.Debug
|
conf.Debug = opts.Debug
|
||||||
conf.Hosts = opts.Hosts
|
conf.Hosts = opts.Hosts
|
||||||
conf.LogLevel = opts.LogLevel
|
conf.LogLevel = opts.LogLevel
|
||||||
conf.TLS = opts.TLS
|
|
||||||
conf.TLSVerify = opts.TLSVerify
|
if opts.flags.Changed(FlagTLS) {
|
||||||
|
conf.TLS = &opts.TLS
|
||||||
|
}
|
||||||
|
if opts.flags.Changed(FlagTLSVerify) {
|
||||||
|
conf.TLSVerify = &opts.TLSVerify
|
||||||
|
v := true
|
||||||
|
conf.TLS = &v
|
||||||
|
}
|
||||||
|
|
||||||
conf.CommonTLSOptions = config.CommonTLSOptions{}
|
conf.CommonTLSOptions = config.CommonTLSOptions{}
|
||||||
|
|
||||||
if opts.TLSOptions != nil {
|
if opts.TLSOptions != nil {
|
||||||
|
@ -409,6 +418,7 @@ func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
|
||||||
return nil, errors.Wrapf(err, "unable to configure the Docker daemon with file %s", opts.configFile)
|
return nil, errors.Wrapf(err, "unable to configure the Docker daemon with file %s", opts.configFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the merged configuration can be nil if the config file didn't exist.
|
// the merged configuration can be nil if the config file didn't exist.
|
||||||
// leave the current configuration as it is if when that happens.
|
// leave the current configuration as it is if when that happens.
|
||||||
if c != nil {
|
if c != nil {
|
||||||
|
@ -434,7 +444,12 @@ func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
|
||||||
// Regardless of whether the user sets it to true or false, if they
|
// Regardless of whether the user sets it to true or false, if they
|
||||||
// specify TLSVerify at all then we need to turn on TLS
|
// specify TLSVerify at all then we need to turn on TLS
|
||||||
if conf.IsValueSet(FlagTLSVerify) {
|
if conf.IsValueSet(FlagTLSVerify) {
|
||||||
conf.TLS = true
|
v := true
|
||||||
|
conf.TLS = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.TLSVerify == nil && conf.TLS != nil {
|
||||||
|
conf.TLSVerify = conf.TLS
|
||||||
}
|
}
|
||||||
|
|
||||||
return conf, nil
|
return conf, nil
|
||||||
|
@ -548,7 +563,7 @@ func newAPIServerConfig(cli *DaemonCli) (*apiserver.Config, error) {
|
||||||
CorsHeaders: cli.Config.CorsHeaders,
|
CorsHeaders: cli.Config.CorsHeaders,
|
||||||
}
|
}
|
||||||
|
|
||||||
if cli.Config.TLS {
|
if cli.Config.TLS != nil && *cli.Config.TLS {
|
||||||
tlsOptions := tlsconfig.Options{
|
tlsOptions := tlsconfig.Options{
|
||||||
CAFile: cli.Config.CommonTLSOptions.CAFile,
|
CAFile: cli.Config.CommonTLSOptions.CAFile,
|
||||||
CertFile: cli.Config.CommonTLSOptions.CertFile,
|
CertFile: cli.Config.CommonTLSOptions.CertFile,
|
||||||
|
@ -556,7 +571,7 @@ func newAPIServerConfig(cli *DaemonCli) (*apiserver.Config, error) {
|
||||||
ExclusiveRootPools: true,
|
ExclusiveRootPools: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if cli.Config.TLSVerify {
|
if cli.Config.TLSVerify == nil || *cli.Config.TLSVerify {
|
||||||
// server requires and verifies client's certificate
|
// server requires and verifies client's certificate
|
||||||
tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert
|
tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
}
|
}
|
||||||
|
@ -574,13 +589,43 @@ func newAPIServerConfig(cli *DaemonCli) (*apiserver.Config, error) {
|
||||||
return serverConfig, nil
|
return serverConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkTLSAuthOK checks basically for an explicitly disabled TLS/TLSVerify
|
||||||
|
// Going forward we do not want to support a scenario where dockerd listens
|
||||||
|
// on TCP without either TLS client auth (or an explicit opt-in to disable it)
|
||||||
|
func checkTLSAuthOK(c *config.Config) bool {
|
||||||
|
if c.TLS == nil {
|
||||||
|
// Either TLS is enabled by default, in which case TLS verification should be enabled by default, or explicitly disabled
|
||||||
|
// Or TLS is disabled by default... in any of these cases, we can just take the default value as to how to proceed
|
||||||
|
return DefaultTLSValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !*c.TLS {
|
||||||
|
// TLS is explicitly disabled, which is supported
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.TLSVerify == nil {
|
||||||
|
// this actually shouldn't happen since we set TLSVerify on the config object anyway
|
||||||
|
// But in case it does get here, be cautious and assume this is not supported.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either TLSVerify is explicitly enabled or disabled, both cases are supported
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config) ([]string, error) {
|
func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config) ([]string, error) {
|
||||||
var hosts []string
|
var hosts []string
|
||||||
seen := make(map[string]struct{}, len(cli.Config.Hosts))
|
seen := make(map[string]struct{}, len(cli.Config.Hosts))
|
||||||
|
|
||||||
|
useTLS := DefaultTLSValue
|
||||||
|
if cli.Config.TLS != nil {
|
||||||
|
useTLS = *cli.Config.TLS
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < len(cli.Config.Hosts); i++ {
|
for i := 0; i < len(cli.Config.Hosts); i++ {
|
||||||
var err error
|
var err error
|
||||||
if cli.Config.Hosts[i], err = dopts.ParseHost(cli.Config.TLS, honorXDG, cli.Config.Hosts[i]); err != nil {
|
if cli.Config.Hosts[i], err = dopts.ParseHost(useTLS, honorXDG, cli.Config.Hosts[i]); err != nil {
|
||||||
return nil, errors.Wrapf(err, "error parsing -H %s", cli.Config.Hosts[i])
|
return nil, errors.Wrapf(err, "error parsing -H %s", cli.Config.Hosts[i])
|
||||||
}
|
}
|
||||||
if _, ok := seen[cli.Config.Hosts[i]]; ok {
|
if _, ok := seen[cli.Config.Hosts[i]]; ok {
|
||||||
|
@ -598,8 +643,43 @@ func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config) ([]string, er
|
||||||
addr := protoAddrParts[1]
|
addr := protoAddrParts[1]
|
||||||
|
|
||||||
// It's a bad idea to bind to TCP without tlsverify.
|
// It's a bad idea to bind to TCP without tlsverify.
|
||||||
if proto == "tcp" && (serverConfig.TLSConfig == nil || serverConfig.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert) {
|
authEnabled := serverConfig.TLSConfig != nil && serverConfig.TLSConfig.ClientAuth == tls.RequireAndVerifyClientCert
|
||||||
logrus.Warn("[!] DON'T BIND ON ANY IP ADDRESS WITHOUT setting --tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING [!]")
|
if proto == "tcp" && !authEnabled {
|
||||||
|
logrus.WithField("host", protoAddr).Warn("Binding to IP address without --tlsverify is insecure and gives root access on this machine to everyone who has access to your network.")
|
||||||
|
logrus.WithField("host", protoAddr).Warn("Binding to an IP address, even on localhost, can also give access to scripts run in a browser. Be safe out there!")
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
// If TLSVerify is explicitly set to false we'll take that as "Please let me shoot myself in the foot"
|
||||||
|
// We do not want to continue to support a default mode where tls verification is disabled, so we do some extra warnings here and eventually remove support
|
||||||
|
if !checkTLSAuthOK(cli.Config) {
|
||||||
|
ipAddr, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error parsing tcp address")
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcut all this extra stuff for literal "localhost"
|
||||||
|
// -H supports specifying hostnames, since we want to bypass this on loopback interfaces we'll look it up here.
|
||||||
|
if ipAddr != "localhost" {
|
||||||
|
ip := net.ParseIP(ipAddr)
|
||||||
|
if ip == nil {
|
||||||
|
ipA, err := net.ResolveIPAddr("ip", ipAddr)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).WithField("host", ipAddr).Error("Error looking up specified host address")
|
||||||
|
}
|
||||||
|
if ipA != nil {
|
||||||
|
ip = ipA.IP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ip == nil || !ip.IsLoopback() {
|
||||||
|
logrus.WithField("host", protoAddr).Warn("Binding to an IP address without --tlsverify is deprecated. Startup is intentionally being slowed down to show this message")
|
||||||
|
logrus.WithField("host", protoAddr).Warn("Please consider generating tls certificates with client validation to prevent exposing unauthenticated root access to your network")
|
||||||
|
logrus.WithField("host", protoAddr).Warnf("You can override this by explicitly specifying '--%s=false' or '--%s=false'", FlagTLS, FlagTLSVerify)
|
||||||
|
logrus.WithField("host", protoAddr).Warnf("Support for listening on TCP without authentication or explicit intent to run without authentication will be removed in the next release")
|
||||||
|
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig)
|
ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -112,7 +112,7 @@ func TestLoadDaemonCliConfigWithTLSVerify(t *testing.T) {
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, loadedConfig != nil)
|
assert.Assert(t, loadedConfig != nil)
|
||||||
assert.Check(t, is.Equal(loadedConfig.TLS, true))
|
assert.Check(t, is.Equal(*loadedConfig.TLS, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadDaemonCliConfigWithExplicitTLSVerifyFalse(t *testing.T) {
|
func TestLoadDaemonCliConfigWithExplicitTLSVerifyFalse(t *testing.T) {
|
||||||
|
@ -125,7 +125,7 @@ func TestLoadDaemonCliConfigWithExplicitTLSVerifyFalse(t *testing.T) {
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, loadedConfig != nil)
|
assert.Assert(t, loadedConfig != nil)
|
||||||
assert.Check(t, loadedConfig.TLS)
|
assert.Check(t, *loadedConfig.TLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadDaemonCliConfigWithoutTLSVerify(t *testing.T) {
|
func TestLoadDaemonCliConfigWithoutTLSVerify(t *testing.T) {
|
||||||
|
@ -138,7 +138,7 @@ func TestLoadDaemonCliConfigWithoutTLSVerify(t *testing.T) {
|
||||||
loadedConfig, err := loadDaemonCliConfig(opts)
|
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Assert(t, loadedConfig != nil)
|
assert.Assert(t, loadedConfig != nil)
|
||||||
assert.Check(t, !loadedConfig.TLS)
|
assert.Check(t, loadedConfig.TLS == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadDaemonCliConfigWithLogLevel(t *testing.T) {
|
func TestLoadDaemonCliConfigWithLogLevel(t *testing.T) {
|
||||||
|
|
|
@ -20,6 +20,10 @@ const (
|
||||||
DefaultCertFile = "cert.pem"
|
DefaultCertFile = "cert.pem"
|
||||||
// FlagTLSVerify is the flag name for the TLS verification option
|
// FlagTLSVerify is the flag name for the TLS verification option
|
||||||
FlagTLSVerify = "tlsverify"
|
FlagTLSVerify = "tlsverify"
|
||||||
|
// FlagTLS is the flag name for the TLS option
|
||||||
|
FlagTLS = "tls"
|
||||||
|
// DefaultTLSValue is the default value used for setting the tls option for tcp connections
|
||||||
|
DefaultTLSValue = false
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -56,8 +60,8 @@ func (o *daemonOptions) InstallFlags(flags *pflag.FlagSet) {
|
||||||
|
|
||||||
flags.BoolVarP(&o.Debug, "debug", "D", false, "Enable debug mode")
|
flags.BoolVarP(&o.Debug, "debug", "D", false, "Enable debug mode")
|
||||||
flags.StringVarP(&o.LogLevel, "log-level", "l", "info", `Set the logging level ("debug"|"info"|"warn"|"error"|"fatal")`)
|
flags.StringVarP(&o.LogLevel, "log-level", "l", "info", `Set the logging level ("debug"|"info"|"warn"|"error"|"fatal")`)
|
||||||
flags.BoolVar(&o.TLS, "tls", false, "Use TLS; implied by --tlsverify")
|
flags.BoolVar(&o.TLS, FlagTLS, DefaultTLSValue, "Use TLS; implied by --tlsverify")
|
||||||
flags.BoolVar(&o.TLSVerify, FlagTLSVerify, dockerTLSVerify, "Use TLS and verify the remote")
|
flags.BoolVar(&o.TLSVerify, FlagTLSVerify, dockerTLSVerify || DefaultTLSValue, "Use TLS and verify the remote")
|
||||||
|
|
||||||
// TODO use flag flags.String("identity"}, "i", "", "Path to libtrust key file")
|
// TODO use flag flags.String("identity"}, "i", "", "Path to libtrust key file")
|
||||||
|
|
||||||
|
@ -86,6 +90,11 @@ func (o *daemonOptions) SetDefaultOptions(flags *pflag.FlagSet) {
|
||||||
o.TLS = true
|
o.TLS = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.TLS && !flags.Changed(FlagTLSVerify) {
|
||||||
|
// Enable tls verification unless explicitly disabled
|
||||||
|
o.TLSVerify = true
|
||||||
|
}
|
||||||
|
|
||||||
if !o.TLS {
|
if !o.TLS {
|
||||||
o.TLSOptions = nil
|
o.TLSOptions = nil
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -205,8 +205,8 @@ type CommonConfig struct {
|
||||||
Debug bool `json:"debug,omitempty"`
|
Debug bool `json:"debug,omitempty"`
|
||||||
Hosts []string `json:"hosts,omitempty"`
|
Hosts []string `json:"hosts,omitempty"`
|
||||||
LogLevel string `json:"log-level,omitempty"`
|
LogLevel string `json:"log-level,omitempty"`
|
||||||
TLS bool `json:"tls,omitempty"`
|
TLS *bool `json:"tls,omitempty"`
|
||||||
TLSVerify bool `json:"tlsverify,omitempty"`
|
TLSVerify *bool `json:"tlsverify,omitempty"`
|
||||||
|
|
||||||
// Embedded structs that allow config
|
// Embedded structs that allow config
|
||||||
// deserialization without the full struct.
|
// deserialization without the full struct.
|
||||||
|
|
|
@ -219,11 +219,11 @@ func (daemon *Daemon) fillAPIInfo(v *types.Info) {
|
||||||
if proto != "tcp" {
|
if proto != "tcp" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !cfg.TLS {
|
if cfg.TLS == nil || !*cfg.TLS {
|
||||||
v.Warnings = append(v.Warnings, fmt.Sprintf("WARNING: API is accessible on http://%s without encryption.%s", addr, warn))
|
v.Warnings = append(v.Warnings, fmt.Sprintf("WARNING: API is accessible on http://%s without encryption.%s", addr, warn))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !cfg.TLSVerify {
|
if cfg.TLSVerify == nil || !*cfg.TLSVerify {
|
||||||
v.Warnings = append(v.Warnings, fmt.Sprintf("WARNING: API is accessible on https://%s without TLS client verification.%s", addr, warn))
|
v.Warnings = append(v.Warnings, fmt.Sprintf("WARNING: API is accessible on https://%s without TLS client verification.%s", addr, warn))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -528,21 +528,26 @@ func (s *DockerDaemonSuite) TestDaemonFlagDebugLogLevelFatal(c *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerDaemonSuite) TestDaemonAllocatesListeningPort(c *testing.T) {
|
func (s *DockerDaemonSuite) TestDaemonAllocatesListeningPort(c *testing.T) {
|
||||||
listeningPorts := [][]string{
|
type listener struct {
|
||||||
|
daemon string
|
||||||
|
client string
|
||||||
|
port string
|
||||||
|
}
|
||||||
|
listeningPorts := []listener{
|
||||||
{"0.0.0.0", "0.0.0.0", "5678"},
|
{"0.0.0.0", "0.0.0.0", "5678"},
|
||||||
{"127.0.0.1", "127.0.0.1", "1234"},
|
{"127.0.0.1", "127.0.0.1", "1234"},
|
||||||
{"localhost", "127.0.0.1", "1235"},
|
{"localhost", "127.0.0.1", "1235"},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdArgs := make([]string, 0, len(listeningPorts)*2)
|
cmdArgs := make([]string, 0, len(listeningPorts)*2)
|
||||||
for _, hostDirective := range listeningPorts {
|
for _, l := range listeningPorts {
|
||||||
cmdArgs = append(cmdArgs, "--host", fmt.Sprintf("tcp://%s:%s", hostDirective[0], hostDirective[2]))
|
cmdArgs = append(cmdArgs, "--tls=false", "--host", fmt.Sprintf("tcp://%s:%s", l.daemon, l.port))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.d.StartWithBusybox(c, cmdArgs...)
|
s.d.StartWithBusybox(c, cmdArgs...)
|
||||||
|
|
||||||
for _, hostDirective := range listeningPorts {
|
for _, l := range listeningPorts {
|
||||||
output, err := s.d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", hostDirective[1], hostDirective[2]), "busybox", "true")
|
output, err := s.d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", l.client, l.port), "busybox", "true")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.Fatalf("Container should not start, expected port already allocated error: %q", output)
|
c.Fatalf("Container should not start, expected port already allocated error: %q", output)
|
||||||
} else if !strings.Contains(output, "port is already allocated") {
|
} else if !strings.Contains(output, "port is already allocated") {
|
||||||
|
|
Loading…
Reference in New Issue