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 }