Sterner warnings for unathenticated tcp
People keep doing this and getting pwned because they accidentally left it exposed to the internet. The warning about doing this has been there forever. This introduces a sleep after warning. To disable the extra sleep users must explicitly specify `--tls=false` or `--tlsverify=false` Warning also specifies this sleep will be removed in the next release where the flag will be required if running unauthenticated. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
This commit is contained in:
parent
2513da195e
commit
5f5285a6e2
6 changed files with 116 additions and 22 deletions
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
@ -380,8 +381,16 @@ func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
|
|||
conf.Debug = opts.Debug
|
||||
conf.Hosts = opts.Hosts
|
||||
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{}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// the merged configuration can be nil if the config file didn't exist.
|
||||
// leave the current configuration as it is if when that happens.
|
||||
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
|
||||
// specify TLSVerify at all then we need to turn on TLS
|
||||
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
|
||||
|
@ -548,7 +563,7 @@ func newAPIServerConfig(cli *DaemonCli) (*apiserver.Config, error) {
|
|||
CorsHeaders: cli.Config.CorsHeaders,
|
||||
}
|
||||
|
||||
if cli.Config.TLS {
|
||||
if cli.Config.TLS != nil && *cli.Config.TLS {
|
||||
tlsOptions := tlsconfig.Options{
|
||||
CAFile: cli.Config.CommonTLSOptions.CAFile,
|
||||
CertFile: cli.Config.CommonTLSOptions.CertFile,
|
||||
|
@ -556,7 +571,7 @@ func newAPIServerConfig(cli *DaemonCli) (*apiserver.Config, error) {
|
|||
ExclusiveRootPools: true,
|
||||
}
|
||||
|
||||
if cli.Config.TLSVerify {
|
||||
if cli.Config.TLSVerify == nil || *cli.Config.TLSVerify {
|
||||
// server requires and verifies client's certificate
|
||||
tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
@ -574,13 +589,43 @@ func newAPIServerConfig(cli *DaemonCli) (*apiserver.Config, error) {
|
|||
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) {
|
||||
var hosts []string
|
||||
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++ {
|
||||
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])
|
||||
}
|
||||
if _, ok := seen[cli.Config.Hosts[i]]; ok {
|
||||
|
@ -598,8 +643,43 @@ func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config) ([]string, er
|
|||
addr := protoAddrParts[1]
|
||||
|
||||
// It's a bad idea to bind to TCP without tlsverify.
|
||||
if proto == "tcp" && (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 [!]")
|
||||
authEnabled := serverConfig.TLSConfig != nil && serverConfig.TLSConfig.ClientAuth == tls.RequireAndVerifyClientCert
|
||||
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)
|
||||
if err != nil {
|
||||
|
|
|
@ -112,7 +112,7 @@ func TestLoadDaemonCliConfigWithTLSVerify(t *testing.T) {
|
|||
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||
assert.NilError(t, err)
|
||||
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) {
|
||||
|
@ -125,7 +125,7 @@ func TestLoadDaemonCliConfigWithExplicitTLSVerifyFalse(t *testing.T) {
|
|||
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, loadedConfig != nil)
|
||||
assert.Check(t, loadedConfig.TLS)
|
||||
assert.Check(t, *loadedConfig.TLS)
|
||||
}
|
||||
|
||||
func TestLoadDaemonCliConfigWithoutTLSVerify(t *testing.T) {
|
||||
|
@ -138,7 +138,7 @@ func TestLoadDaemonCliConfigWithoutTLSVerify(t *testing.T) {
|
|||
loadedConfig, err := loadDaemonCliConfig(opts)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, loadedConfig != nil)
|
||||
assert.Check(t, !loadedConfig.TLS)
|
||||
assert.Check(t, loadedConfig.TLS == nil)
|
||||
}
|
||||
|
||||
func TestLoadDaemonCliConfigWithLogLevel(t *testing.T) {
|
||||
|
|
|
@ -20,6 +20,10 @@ const (
|
|||
DefaultCertFile = "cert.pem"
|
||||
// FlagTLSVerify is the flag name for the TLS verification option
|
||||
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 (
|
||||
|
@ -56,8 +60,8 @@ func (o *daemonOptions) InstallFlags(flags *pflag.FlagSet) {
|
|||
|
||||
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.BoolVar(&o.TLS, "tls", false, "Use TLS; implied by --tlsverify")
|
||||
flags.BoolVar(&o.TLSVerify, FlagTLSVerify, dockerTLSVerify, "Use TLS and verify the remote")
|
||||
flags.BoolVar(&o.TLS, FlagTLS, DefaultTLSValue, "Use TLS; implied by --tlsverify")
|
||||
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")
|
||||
|
||||
|
@ -86,6 +90,11 @@ func (o *daemonOptions) SetDefaultOptions(flags *pflag.FlagSet) {
|
|||
o.TLS = true
|
||||
}
|
||||
|
||||
if o.TLS && !flags.Changed(FlagTLSVerify) {
|
||||
// Enable tls verification unless explicitly disabled
|
||||
o.TLSVerify = true
|
||||
}
|
||||
|
||||
if !o.TLS {
|
||||
o.TLSOptions = nil
|
||||
} else {
|
||||
|
|
|
@ -205,8 +205,8 @@ type CommonConfig struct {
|
|||
Debug bool `json:"debug,omitempty"`
|
||||
Hosts []string `json:"hosts,omitempty"`
|
||||
LogLevel string `json:"log-level,omitempty"`
|
||||
TLS bool `json:"tls,omitempty"`
|
||||
TLSVerify bool `json:"tlsverify,omitempty"`
|
||||
TLS *bool `json:"tls,omitempty"`
|
||||
TLSVerify *bool `json:"tlsverify,omitempty"`
|
||||
|
||||
// Embedded structs that allow config
|
||||
// deserialization without the full struct.
|
||||
|
|
|
@ -219,11 +219,11 @@ func (daemon *Daemon) fillAPIInfo(v *types.Info) {
|
|||
if proto != "tcp" {
|
||||
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))
|
||||
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))
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -528,21 +528,26 @@ func (s *DockerDaemonSuite) TestDaemonFlagDebugLogLevelFatal(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"},
|
||||
{"127.0.0.1", "127.0.0.1", "1234"},
|
||||
{"localhost", "127.0.0.1", "1235"},
|
||||
}
|
||||
|
||||
cmdArgs := make([]string, 0, len(listeningPorts)*2)
|
||||
for _, hostDirective := range listeningPorts {
|
||||
cmdArgs = append(cmdArgs, "--host", fmt.Sprintf("tcp://%s:%s", hostDirective[0], hostDirective[2]))
|
||||
for _, l := range listeningPorts {
|
||||
cmdArgs = append(cmdArgs, "--tls=false", "--host", fmt.Sprintf("tcp://%s:%s", l.daemon, l.port))
|
||||
}
|
||||
|
||||
s.d.StartWithBusybox(c, cmdArgs...)
|
||||
|
||||
for _, hostDirective := range listeningPorts {
|
||||
output, err := s.d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", hostDirective[1], hostDirective[2]), "busybox", "true")
|
||||
for _, l := range listeningPorts {
|
||||
output, err := s.d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", l.client, l.port), "busybox", "true")
|
||||
if err == nil {
|
||||
c.Fatalf("Container should not start, expected port already allocated error: %q", output)
|
||||
} else if !strings.Contains(output, "port is already allocated") {
|
||||
|
|
Loading…
Reference in a new issue