mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00

This fixes an index out-of-bounds panic caused when using the embed package and the zap logger. When a TLS handshake error is logged, the slice for cert ip addresses is allocated with capacity but no length, so subsequent index access causes the panic, and doesn't surface the TLS handshake error to the user. Fixes #10932
292 lines
8.7 KiB
Go
292 lines
8.7 KiB
Go
// Copyright 2018 The etcd Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package embed
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"reflect"
|
|
"sync"
|
|
|
|
"go.etcd.io/etcd/pkg/logutil"
|
|
|
|
"github.com/coreos/pkg/capnslog"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/grpclog"
|
|
)
|
|
|
|
// GetLogger returns the logger.
|
|
func (cfg Config) GetLogger() *zap.Logger {
|
|
cfg.loggerMu.RLock()
|
|
l := cfg.logger
|
|
cfg.loggerMu.RUnlock()
|
|
return l
|
|
}
|
|
|
|
// for testing
|
|
var grpcLogOnce = new(sync.Once)
|
|
|
|
// setupLogging initializes etcd logging.
|
|
// Must be called after flag parsing or finishing configuring embed.Config.
|
|
func (cfg *Config) setupLogging() error {
|
|
// handle "DeprecatedLogOutput" in v3.4
|
|
// TODO: remove "DeprecatedLogOutput" in v3.5
|
|
len1 := len(cfg.DeprecatedLogOutput)
|
|
len2 := len(cfg.LogOutputs)
|
|
if len1 != len2 {
|
|
switch {
|
|
case len1 > len2: // deprecate "log-output" flag is used
|
|
fmt.Fprintln(os.Stderr, "'--log-output' flag has been deprecated! Please use '--log-outputs'!")
|
|
cfg.LogOutputs = cfg.DeprecatedLogOutput
|
|
case len1 < len2: // "--log-outputs" flag has been set with multiple writers
|
|
cfg.DeprecatedLogOutput = []string{}
|
|
}
|
|
} else {
|
|
if len1 > 1 {
|
|
return errors.New("both '--log-output' and '--log-outputs' are set; only set '--log-outputs'")
|
|
}
|
|
if len1 < 1 {
|
|
return errors.New("either '--log-output' or '--log-outputs' flag must be set")
|
|
}
|
|
if reflect.DeepEqual(cfg.DeprecatedLogOutput, cfg.LogOutputs) && cfg.DeprecatedLogOutput[0] != DefaultLogOutput {
|
|
return fmt.Errorf("'--log-output=%q' and '--log-outputs=%q' are incompatible; only set --log-outputs", cfg.DeprecatedLogOutput, cfg.LogOutputs)
|
|
}
|
|
if !reflect.DeepEqual(cfg.DeprecatedLogOutput, []string{DefaultLogOutput}) {
|
|
fmt.Fprintf(os.Stderr, "Deprecated '--log-output' flag is set to %q\n", cfg.DeprecatedLogOutput)
|
|
fmt.Fprintln(os.Stderr, "Please use '--log-outputs' flag")
|
|
}
|
|
}
|
|
|
|
switch cfg.Logger {
|
|
case "capnslog": // TODO: deprecate this in v3.5
|
|
cfg.ClientTLSInfo.HandshakeFailure = logTLSHandshakeFailure
|
|
cfg.PeerTLSInfo.HandshakeFailure = logTLSHandshakeFailure
|
|
|
|
if cfg.Debug {
|
|
capnslog.SetGlobalLogLevel(capnslog.DEBUG)
|
|
grpc.EnableTracing = true
|
|
// enable info, warning, error
|
|
grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stderr, os.Stderr, os.Stderr))
|
|
} else {
|
|
capnslog.SetGlobalLogLevel(capnslog.INFO)
|
|
// only discard info
|
|
grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, os.Stderr, os.Stderr))
|
|
}
|
|
|
|
// TODO: deprecate with "capnslog"
|
|
if cfg.LogPkgLevels != "" {
|
|
repoLog := capnslog.MustRepoLogger("go.etcd.io/etcd")
|
|
settings, err := repoLog.ParseLogLevelConfig(cfg.LogPkgLevels)
|
|
if err != nil {
|
|
plog.Warningf("couldn't parse log level string: %s, continuing with default levels", err.Error())
|
|
return nil
|
|
}
|
|
repoLog.SetLogLevel(settings)
|
|
}
|
|
|
|
if len(cfg.LogOutputs) != 1 {
|
|
return fmt.Errorf("--logger=capnslog supports only 1 value in '--log-outputs', got %q", cfg.LogOutputs)
|
|
}
|
|
// capnslog initially SetFormatter(NewDefaultFormatter(os.Stderr))
|
|
// where NewDefaultFormatter returns NewJournaldFormatter when syscall.Getppid() == 1
|
|
// specify 'stdout' or 'stderr' to skip journald logging even when running under systemd
|
|
output := cfg.LogOutputs[0]
|
|
switch output {
|
|
case StdErrLogOutput:
|
|
capnslog.SetFormatter(capnslog.NewPrettyFormatter(os.Stderr, cfg.Debug))
|
|
case StdOutLogOutput:
|
|
capnslog.SetFormatter(capnslog.NewPrettyFormatter(os.Stdout, cfg.Debug))
|
|
case DefaultLogOutput:
|
|
default:
|
|
return fmt.Errorf("unknown log-output %q (only supports %q, %q, %q)", output, DefaultLogOutput, StdErrLogOutput, StdOutLogOutput)
|
|
}
|
|
|
|
case "zap":
|
|
if len(cfg.LogOutputs) == 0 {
|
|
cfg.LogOutputs = []string{DefaultLogOutput}
|
|
}
|
|
if len(cfg.LogOutputs) > 1 {
|
|
for _, v := range cfg.LogOutputs {
|
|
if v == DefaultLogOutput {
|
|
return fmt.Errorf("multi logoutput for %q is not supported yet", DefaultLogOutput)
|
|
}
|
|
}
|
|
}
|
|
|
|
outputPaths, errOutputPaths := make([]string, 0), make([]string, 0)
|
|
isJournal := false
|
|
for _, v := range cfg.LogOutputs {
|
|
switch v {
|
|
case DefaultLogOutput:
|
|
outputPaths = append(outputPaths, StdErrLogOutput)
|
|
errOutputPaths = append(errOutputPaths, StdErrLogOutput)
|
|
|
|
case JournalLogOutput:
|
|
isJournal = true
|
|
|
|
case StdErrLogOutput:
|
|
outputPaths = append(outputPaths, StdErrLogOutput)
|
|
errOutputPaths = append(errOutputPaths, StdErrLogOutput)
|
|
|
|
case StdOutLogOutput:
|
|
outputPaths = append(outputPaths, StdOutLogOutput)
|
|
errOutputPaths = append(errOutputPaths, StdOutLogOutput)
|
|
|
|
default:
|
|
outputPaths = append(outputPaths, v)
|
|
errOutputPaths = append(errOutputPaths, v)
|
|
}
|
|
}
|
|
|
|
if !isJournal {
|
|
copied := logutil.AddOutputPaths(logutil.DefaultZapLoggerConfig, outputPaths, errOutputPaths)
|
|
|
|
if cfg.Debug {
|
|
copied.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
|
|
grpc.EnableTracing = true
|
|
}
|
|
if cfg.ZapLoggerBuilder == nil {
|
|
cfg.ZapLoggerBuilder = func(c *Config) error {
|
|
var err error
|
|
c.logger, err = copied.Build()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.loggerMu.Lock()
|
|
defer c.loggerMu.Unlock()
|
|
c.loggerConfig = &copied
|
|
c.loggerCore = nil
|
|
c.loggerWriteSyncer = nil
|
|
grpcLogOnce.Do(func() {
|
|
// debug true, enable info, warning, error
|
|
// debug false, only discard info
|
|
var gl grpclog.LoggerV2
|
|
gl, err = logutil.NewGRPCLoggerV2(copied)
|
|
if err == nil {
|
|
grpclog.SetLoggerV2(gl)
|
|
}
|
|
})
|
|
return nil
|
|
}
|
|
}
|
|
} else {
|
|
if len(cfg.LogOutputs) > 1 {
|
|
for _, v := range cfg.LogOutputs {
|
|
if v != DefaultLogOutput {
|
|
return fmt.Errorf("running with systemd/journal but other '--log-outputs' values (%q) are configured with 'default'; override 'default' value with something else", cfg.LogOutputs)
|
|
}
|
|
}
|
|
}
|
|
|
|
// use stderr as fallback
|
|
syncer, lerr := getJournalWriteSyncer()
|
|
if lerr != nil {
|
|
return lerr
|
|
}
|
|
|
|
lvl := zap.NewAtomicLevelAt(zap.InfoLevel)
|
|
if cfg.Debug {
|
|
lvl = zap.NewAtomicLevelAt(zap.DebugLevel)
|
|
grpc.EnableTracing = true
|
|
}
|
|
|
|
// WARN: do not change field names in encoder config
|
|
// journald logging writer assumes field names of "level" and "caller"
|
|
cr := zapcore.NewCore(
|
|
zapcore.NewJSONEncoder(logutil.DefaultZapLoggerConfig.EncoderConfig),
|
|
syncer,
|
|
lvl,
|
|
)
|
|
if cfg.ZapLoggerBuilder == nil {
|
|
cfg.ZapLoggerBuilder = func(c *Config) error {
|
|
c.logger = zap.New(cr, zap.AddCaller(), zap.ErrorOutput(syncer))
|
|
c.loggerMu.Lock()
|
|
defer c.loggerMu.Unlock()
|
|
c.loggerConfig = nil
|
|
c.loggerCore = cr
|
|
c.loggerWriteSyncer = syncer
|
|
|
|
grpcLogOnce.Do(func() {
|
|
grpclog.SetLoggerV2(logutil.NewGRPCLoggerV2FromZapCore(cr, syncer))
|
|
})
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
err := cfg.ZapLoggerBuilder(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
logTLSHandshakeFailure := func(conn *tls.Conn, err error) {
|
|
state := conn.ConnectionState()
|
|
remoteAddr := conn.RemoteAddr().String()
|
|
serverName := state.ServerName
|
|
if len(state.PeerCertificates) > 0 {
|
|
cert := state.PeerCertificates[0]
|
|
ips := make([]string, len(cert.IPAddresses))
|
|
for i := range cert.IPAddresses {
|
|
ips[i] = cert.IPAddresses[i].String()
|
|
}
|
|
cfg.logger.Warn(
|
|
"rejected connection",
|
|
zap.String("remote-addr", remoteAddr),
|
|
zap.String("server-name", serverName),
|
|
zap.Strings("ip-addresses", ips),
|
|
zap.Strings("dns-names", cert.DNSNames),
|
|
zap.Error(err),
|
|
)
|
|
} else {
|
|
cfg.logger.Warn(
|
|
"rejected connection",
|
|
zap.String("remote-addr", remoteAddr),
|
|
zap.String("server-name", serverName),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
}
|
|
cfg.ClientTLSInfo.HandshakeFailure = logTLSHandshakeFailure
|
|
cfg.PeerTLSInfo.HandshakeFailure = logTLSHandshakeFailure
|
|
|
|
default:
|
|
return fmt.Errorf("unknown logger option %q", cfg.Logger)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewZapCoreLoggerBuilder generates a zap core logger builder.
|
|
func NewZapCoreLoggerBuilder(lg *zap.Logger, cr zapcore.Core, syncer zapcore.WriteSyncer) func(*Config) error {
|
|
return func(cfg *Config) error {
|
|
cfg.loggerMu.Lock()
|
|
defer cfg.loggerMu.Unlock()
|
|
cfg.logger = lg
|
|
cfg.loggerConfig = nil
|
|
cfg.loggerCore = cr
|
|
cfg.loggerWriteSyncer = syncer
|
|
|
|
grpcLogOnce.Do(func() {
|
|
grpclog.SetLoggerV2(logutil.NewGRPCLoggerV2FromZapCore(cr, syncer))
|
|
})
|
|
return nil
|
|
}
|
|
}
|