github.com/containerd/nerdctl@v1.7.7/pkg/logging/syslog_logger.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package logging
    18  
    19  import (
    20  	"crypto/tls"
    21  	"errors"
    22  	"fmt"
    23  	"net"
    24  	"net/url"
    25  	"os"
    26  	"strconv"
    27  	"strings"
    28  	"sync"
    29  
    30  	"github.com/containerd/log"
    31  	"github.com/docker/go-connections/tlsconfig"
    32  	syslog "github.com/yuchanns/srslog"
    33  
    34  	"github.com/containerd/containerd/runtime/v2/logging"
    35  	"github.com/containerd/nerdctl/pkg/strutil"
    36  )
    37  
    38  const (
    39  	syslogAddress       = "syslog-address"
    40  	syslogFacility      = "syslog-facility"
    41  	syslogTLSCaCert     = "syslog-tls-ca-cert"
    42  	syslogTLSCert       = "syslog-tls-cert"
    43  	syslogTLSKey        = "syslog-tls-key"
    44  	syslogTLSSkipVerify = "syslog-tls-skip-verify"
    45  	syslogFormat        = "syslog-format"
    46  )
    47  
    48  var syslogOpts = []string{
    49  	syslogAddress,
    50  	syslogFacility,
    51  	syslogTLSCaCert,
    52  	syslogTLSCert,
    53  	syslogTLSKey,
    54  	syslogTLSSkipVerify,
    55  	syslogFormat,
    56  	Tag,
    57  }
    58  
    59  var syslogFacilities = map[string]syslog.Priority{
    60  	"kern":     syslog.LOG_KERN,
    61  	"user":     syslog.LOG_USER,
    62  	"mail":     syslog.LOG_MAIL,
    63  	"daemon":   syslog.LOG_DAEMON,
    64  	"auth":     syslog.LOG_AUTH,
    65  	"syslog":   syslog.LOG_SYSLOG,
    66  	"lpr":      syslog.LOG_LPR,
    67  	"news":     syslog.LOG_NEWS,
    68  	"uucp":     syslog.LOG_UUCP,
    69  	"cron":     syslog.LOG_CRON,
    70  	"authpriv": syslog.LOG_AUTHPRIV,
    71  	"ftp":      syslog.LOG_FTP,
    72  	"local0":   syslog.LOG_LOCAL0,
    73  	"local1":   syslog.LOG_LOCAL1,
    74  	"local2":   syslog.LOG_LOCAL2,
    75  	"local3":   syslog.LOG_LOCAL3,
    76  	"local4":   syslog.LOG_LOCAL4,
    77  	"local5":   syslog.LOG_LOCAL5,
    78  	"local6":   syslog.LOG_LOCAL6,
    79  	"local7":   syslog.LOG_LOCAL7,
    80  }
    81  
    82  const (
    83  	syslogSecureProto = "tcp+tls"
    84  	syslogDefaultPort = "514"
    85  
    86  	syslogFormatRFC3164      = "rfc3164"
    87  	syslogFormatRFC5424      = "rfc5424"
    88  	syslogFormatRFC5424Micro = "rfc5424micro"
    89  )
    90  
    91  func SyslogOptsValidate(logOptMap map[string]string) error {
    92  	for key := range logOptMap {
    93  		if !strutil.InStringSlice(syslogOpts, key) {
    94  			log.L.Warnf("log-opt %s is ignored for syslog log driver", key)
    95  		}
    96  	}
    97  	proto, _, err := parseSyslogAddress(logOptMap[syslogAddress])
    98  	if err != nil {
    99  		return err
   100  	}
   101  	if _, err := parseSyslogFacility(logOptMap[syslogFacility]); err != nil {
   102  		return err
   103  	}
   104  	if _, _, err := parseSyslogLogFormat(logOptMap[syslogFormat], proto); err != nil {
   105  		return err
   106  	}
   107  	if proto == syslogSecureProto {
   108  		if _, tlsErr := parseTLSConfig(logOptMap); tlsErr != nil {
   109  			return tlsErr
   110  		}
   111  	}
   112  	return nil
   113  }
   114  
   115  type SyslogLogger struct {
   116  	Opts   map[string]string
   117  	logger *syslog.Writer
   118  }
   119  
   120  func (sy *SyslogLogger) Init(dataStore string, ns string, id string) error {
   121  	return nil
   122  }
   123  
   124  func (sy *SyslogLogger) PreProcess(dataStore string, config *logging.Config) error {
   125  	logger, err := parseSyslog(config.ID, sy.Opts)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	sy.logger = logger
   130  	return nil
   131  }
   132  
   133  func (sy *SyslogLogger) Process(stdout <-chan string, stderr <-chan string) error {
   134  	var wg sync.WaitGroup
   135  	wg.Add(2)
   136  	fn := func(dataChan <-chan string, logFn func(msg string) error) {
   137  		defer wg.Done()
   138  		for log := range dataChan {
   139  			logFn(log)
   140  		}
   141  	}
   142  	go fn(stdout, sy.logger.Info)
   143  	go fn(stderr, sy.logger.Err)
   144  	wg.Wait()
   145  	return nil
   146  }
   147  
   148  func (sy *SyslogLogger) PostProcess() error {
   149  	defer sy.logger.Close()
   150  	return nil
   151  }
   152  
   153  func parseSyslog(containerID string, config map[string]string) (*syslog.Writer, error) {
   154  	tag := containerID[:12]
   155  	if cfgTag, ok := config[Tag]; ok {
   156  		tag = cfgTag
   157  	}
   158  	proto, address, err := parseSyslogAddress(config[syslogAddress])
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	facility, err := parseSyslogFacility(config[syslogFacility])
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	syslogFormatter, syslogFramer, err := parseSyslogLogFormat(config[syslogFormat], proto)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	var logger *syslog.Writer
   171  	if proto == syslogSecureProto {
   172  		tlsConfig, tlsErr := parseTLSConfig(config)
   173  		if tlsErr != nil {
   174  			return nil, tlsErr
   175  		}
   176  		logger, err = syslog.DialWithTLSConfig(proto, address, facility, tag, tlsConfig)
   177  	} else {
   178  		logger, err = syslog.Dial(proto, address, facility, tag)
   179  	}
   180  
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	logger.SetFormatter(syslogFormatter)
   186  	logger.SetFramer(syslogFramer)
   187  
   188  	return logger, nil
   189  }
   190  
   191  func parseSyslogAddress(address string) (string, string, error) {
   192  	if address == "" {
   193  		// Docker-compatible: fallback to `unix:///dev/log`,
   194  		// `unix:///var/run/syslog` or `unix:///var/run/log`. We do nothing
   195  		// with the empty address, just leave it here and the srslog will
   196  		// handle the fallback.
   197  		return "", "", nil
   198  	}
   199  	addr, err := url.Parse(address)
   200  	if err != nil {
   201  		return "", "", err
   202  	}
   203  
   204  	// unix and unixgram socket validation
   205  	if addr.Scheme == "unix" || addr.Scheme == "unixgram" {
   206  		if _, err := os.Stat(addr.Path); err != nil {
   207  			return "", "", err
   208  		}
   209  		return addr.Scheme, addr.Path, nil
   210  	}
   211  	if addr.Scheme != "udp" && addr.Scheme != "tcp" && addr.Scheme != syslogSecureProto {
   212  		return "", "", fmt.Errorf("unsupported scheme: '%s'", addr.Scheme)
   213  	}
   214  
   215  	// here we process tcp|udp
   216  	host := addr.Host
   217  	if _, _, err := net.SplitHostPort(host); err != nil {
   218  		if !strings.Contains(err.Error(), "missing port in address") {
   219  			return "", "", err
   220  		}
   221  		host = net.JoinHostPort(host, syslogDefaultPort)
   222  	}
   223  
   224  	return addr.Scheme, host, nil
   225  }
   226  
   227  func parseSyslogFacility(facility string) (syslog.Priority, error) {
   228  	if facility == "" {
   229  		return syslog.LOG_DAEMON, nil
   230  	}
   231  
   232  	if syslogFacility, valid := syslogFacilities[facility]; valid {
   233  		return syslogFacility, nil
   234  	}
   235  
   236  	fInt, err := strconv.Atoi(facility)
   237  	if err == nil && 0 <= fInt && fInt <= 23 {
   238  		return syslog.Priority(fInt << 3), nil
   239  	}
   240  
   241  	return syslog.Priority(0), errors.New("invalid syslog facility")
   242  }
   243  
   244  func parseTLSConfig(cfg map[string]string) (*tls.Config, error) {
   245  	_, skipVerify := cfg[syslogTLSSkipVerify]
   246  
   247  	opts := tlsconfig.Options{
   248  		CAFile:             cfg[syslogTLSCaCert],
   249  		CertFile:           cfg[syslogTLSCert],
   250  		KeyFile:            cfg[syslogTLSKey],
   251  		InsecureSkipVerify: skipVerify,
   252  	}
   253  
   254  	return tlsconfig.Client(opts)
   255  }
   256  
   257  func parseSyslogLogFormat(logFormat, proto string) (syslog.Formatter, syslog.Framer, error) {
   258  	switch logFormat {
   259  	case "":
   260  		return syslog.UnixFormatter, syslog.DefaultFramer, nil
   261  	case syslogFormatRFC3164:
   262  		return syslog.RFC3164Formatter, syslog.DefaultFramer, nil
   263  	case syslogFormatRFC5424:
   264  		if proto == syslogSecureProto {
   265  			return syslog.RFC5424FormatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil
   266  		}
   267  		return syslog.RFC5424FormatterWithAppNameAsTag, syslog.DefaultFramer, nil
   268  	case syslogFormatRFC5424Micro:
   269  		if proto == syslogSecureProto {
   270  			return syslog.RFC5424MicroFormatterWithAppNameAsTag, syslog.RFC5425MessageLengthFramer, nil
   271  		}
   272  		return syslog.RFC5424MicroFormatterWithAppNameAsTag, syslog.DefaultFramer, nil
   273  	default:
   274  		return nil, nil, errors.New("invalid syslog format")
   275  	}
   276  }