github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/daemon/logger/syslog/syslog.go (about)

     1  // Package syslog provides the logdriver for forwarding server logs to syslog endpoints.
     2  package syslog // import "github.com/docker/docker/daemon/logger/syslog"
     3  
     4  import (
     5  	"crypto/tls"
     6  	"errors"
     7  	"fmt"
     8  	"net"
     9  	"net/url"
    10  	"os"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	syslog "github.com/RackSec/srslog"
    16  	"github.com/docker/docker/daemon/logger"
    17  	"github.com/docker/docker/daemon/logger/loggerutils"
    18  	"github.com/docker/go-connections/tlsconfig"
    19  )
    20  
    21  const (
    22  	name        = "syslog"
    23  	secureProto = "tcp+tls"
    24  	defaultPort = "514"
    25  )
    26  
    27  var facilities = map[string]syslog.Priority{
    28  	"kern":     syslog.LOG_KERN,
    29  	"user":     syslog.LOG_USER,
    30  	"mail":     syslog.LOG_MAIL,
    31  	"daemon":   syslog.LOG_DAEMON,
    32  	"auth":     syslog.LOG_AUTH,
    33  	"syslog":   syslog.LOG_SYSLOG,
    34  	"lpr":      syslog.LOG_LPR,
    35  	"news":     syslog.LOG_NEWS,
    36  	"uucp":     syslog.LOG_UUCP,
    37  	"cron":     syslog.LOG_CRON,
    38  	"authpriv": syslog.LOG_AUTHPRIV,
    39  	"ftp":      syslog.LOG_FTP,
    40  	"local0":   syslog.LOG_LOCAL0,
    41  	"local1":   syslog.LOG_LOCAL1,
    42  	"local2":   syslog.LOG_LOCAL2,
    43  	"local3":   syslog.LOG_LOCAL3,
    44  	"local4":   syslog.LOG_LOCAL4,
    45  	"local5":   syslog.LOG_LOCAL5,
    46  	"local6":   syslog.LOG_LOCAL6,
    47  	"local7":   syslog.LOG_LOCAL7,
    48  }
    49  
    50  type syslogger struct {
    51  	writer *syslog.Writer
    52  }
    53  
    54  func init() {
    55  	if err := logger.RegisterLogDriver(name, New); err != nil {
    56  		panic(err)
    57  	}
    58  	if err := logger.RegisterLogOptValidator(name, ValidateLogOpt); err != nil {
    59  		panic(err)
    60  	}
    61  }
    62  
    63  // rsyslog uses appname part of syslog message to fill in an %syslogtag% template
    64  // attribute in rsyslog.conf. In order to be backward compatible to rfc3164
    65  // tag will be also used as an appname
    66  func rfc5424formatterWithAppNameAsTag(p syslog.Priority, hostname, tag, content string) string {
    67  	timestamp := time.Now().Format(time.RFC3339)
    68  	pid := os.Getpid()
    69  	msg := fmt.Sprintf("<%d>%d %s %s %s %d %s - %s",
    70  		p, 1, timestamp, hostname, tag, pid, tag, content)
    71  	return msg
    72  }
    73  
    74  // The timestamp field in rfc5424 is derived from rfc3339. Whereas rfc3339 makes allowances
    75  // for multiple syntaxes, there are further restrictions in rfc5424, i.e., the maximum
    76  // resolution is limited to "TIME-SECFRAC" which is 6 (microsecond resolution)
    77  func rfc5424microformatterWithAppNameAsTag(p syslog.Priority, hostname, tag, content string) string {
    78  	timestamp := time.Now().Format("2006-01-02T15:04:05.000000Z07:00")
    79  	pid := os.Getpid()
    80  	msg := fmt.Sprintf("<%d>%d %s %s %s %d %s - %s",
    81  		p, 1, timestamp, hostname, tag, pid, tag, content)
    82  	return msg
    83  }
    84  
    85  // New creates a syslog logger using the configuration passed in on
    86  // the context. Supported context configuration variables are
    87  // syslog-address, syslog-facility, syslog-format.
    88  func New(info logger.Info) (logger.Logger, error) {
    89  	tag, err := loggerutils.ParseLogTag(info, loggerutils.DefaultTemplate)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	proto, address, err := parseAddress(info.Config["syslog-address"])
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	facility, err := parseFacility(info.Config["syslog-facility"])
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	syslogFormatter, syslogFramer, err := parseLogFormat(info.Config["syslog-format"], proto)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	var log *syslog.Writer
   110  	if proto == secureProto {
   111  		tlsConfig, tlsErr := parseTLSConfig(info.Config)
   112  		if tlsErr != nil {
   113  			return nil, tlsErr
   114  		}
   115  		log, err = syslog.DialWithTLSConfig(proto, address, facility, tag, tlsConfig)
   116  	} else {
   117  		log, err = syslog.Dial(proto, address, facility, tag)
   118  	}
   119  
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	log.SetFormatter(syslogFormatter)
   125  	log.SetFramer(syslogFramer)
   126  
   127  	return &syslogger{
   128  		writer: log,
   129  	}, nil
   130  }
   131  
   132  func (s *syslogger) Log(msg *logger.Message) error {
   133  	if len(msg.Line) == 0 {
   134  		return nil
   135  	}
   136  
   137  	line := string(msg.Line)
   138  	source := msg.Source
   139  	logger.PutMessage(msg)
   140  	if source == "stderr" {
   141  		return s.writer.Err(line)
   142  	}
   143  	return s.writer.Info(line)
   144  }
   145  
   146  func (s *syslogger) Close() error {
   147  	return s.writer.Close()
   148  }
   149  
   150  func (s *syslogger) Name() string {
   151  	return name
   152  }
   153  
   154  func parseAddress(address string) (string, string, error) {
   155  	if address == "" {
   156  		return "", "", nil
   157  	}
   158  	addr, err := url.Parse(address)
   159  	if err != nil {
   160  		return "", "", err
   161  	}
   162  
   163  	// unix and unixgram socket validation
   164  	if addr.Scheme == "unix" || addr.Scheme == "unixgram" {
   165  		if _, err := os.Stat(addr.Path); err != nil {
   166  			return "", "", err
   167  		}
   168  		return addr.Scheme, addr.Path, nil
   169  	}
   170  	if addr.Scheme != "udp" && addr.Scheme != "tcp" && addr.Scheme != secureProto {
   171  		return "", "", fmt.Errorf("unsupported scheme: '%s'", addr.Scheme)
   172  	}
   173  
   174  	// here we process tcp|udp
   175  	host := addr.Host
   176  	if _, _, err := net.SplitHostPort(host); err != nil {
   177  		if !strings.Contains(err.Error(), "missing port in address") {
   178  			return "", "", err
   179  		}
   180  		host = net.JoinHostPort(host, defaultPort)
   181  	}
   182  
   183  	return addr.Scheme, host, nil
   184  }
   185  
   186  // ValidateLogOpt looks for syslog specific log options
   187  // syslog-address, syslog-facility.
   188  func ValidateLogOpt(cfg map[string]string) error {
   189  	for key := range cfg {
   190  		switch key {
   191  		case "env":
   192  		case "env-regex":
   193  		case "labels":
   194  		case "labels-regex":
   195  		case "syslog-address":
   196  		case "syslog-facility":
   197  		case "syslog-tls-ca-cert":
   198  		case "syslog-tls-cert":
   199  		case "syslog-tls-key":
   200  		case "syslog-tls-skip-verify":
   201  		case "tag":
   202  		case "syslog-format":
   203  		default:
   204  			return fmt.Errorf("unknown log opt '%s' for syslog log driver", key)
   205  		}
   206  	}
   207  	if _, _, err := parseAddress(cfg["syslog-address"]); err != nil {
   208  		return err
   209  	}
   210  	if _, err := parseFacility(cfg["syslog-facility"]); err != nil {
   211  		return err
   212  	}
   213  	if _, _, err := parseLogFormat(cfg["syslog-format"], ""); err != nil {
   214  		return err
   215  	}
   216  	return nil
   217  }
   218  
   219  func parseFacility(facility string) (syslog.Priority, error) {
   220  	if facility == "" {
   221  		return syslog.LOG_DAEMON, nil
   222  	}
   223  
   224  	if syslogFacility, valid := facilities[facility]; valid {
   225  		return syslogFacility, nil
   226  	}
   227  
   228  	fInt, err := strconv.Atoi(facility)
   229  	if err == nil && 0 <= fInt && fInt <= 23 {
   230  		return syslog.Priority(fInt << 3), nil
   231  	}
   232  
   233  	return syslog.Priority(0), errors.New("invalid syslog facility")
   234  }
   235  
   236  func parseTLSConfig(cfg map[string]string) (*tls.Config, error) {
   237  	_, skipVerify := cfg["syslog-tls-skip-verify"]
   238  
   239  	opts := tlsconfig.Options{
   240  		CAFile:             cfg["syslog-tls-ca-cert"],
   241  		CertFile:           cfg["syslog-tls-cert"],
   242  		KeyFile:            cfg["syslog-tls-key"],
   243  		InsecureSkipVerify: skipVerify,
   244  	}
   245  
   246  	return tlsconfig.Client(opts)
   247  }
   248  
   249  func parseLogFormat(logFormat, proto string) (syslog.Formatter, syslog.Framer, error) {
   250  	switch logFormat {
   251  	case "":
   252  		return syslog.UnixFormatter, syslog.DefaultFramer, nil
   253  	case "rfc3164":
   254  		return syslog.RFC3164Formatter, syslog.DefaultFramer, nil
   255  	case "rfc5424":
   256  		if proto == secureProto {
   257  			return rfc5424formatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil
   258  		}
   259  		return rfc5424formatterWithAppNameAsTag, syslog.DefaultFramer, nil
   260  	case "rfc5424micro":
   261  		if proto == secureProto {
   262  			return rfc5424microformatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil
   263  		}
   264  		return rfc5424microformatterWithAppNameAsTag, syslog.DefaultFramer, nil
   265  	default:
   266  		return nil, nil, errors.New("Invalid syslog format")
   267  	}
   268  }