github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/worker/rsyslog/worker.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package rsyslog 5 6 import ( 7 "crypto/tls" 8 "crypto/x509" 9 "fmt" 10 "io" 11 "net" 12 "os" 13 "os/user" 14 "runtime" 15 "strconv" 16 "time" 17 18 "github.com/juju/errors" 19 "github.com/juju/loggo" 20 "github.com/juju/names" 21 rsyslog "github.com/juju/syslog" 22 "github.com/juju/utils" 23 24 "github.com/juju/juju/agent" 25 apirsyslog "github.com/juju/juju/api/rsyslog" 26 "github.com/juju/juju/api/watcher" 27 "github.com/juju/juju/cert" 28 "github.com/juju/juju/utils/syslog" 29 "github.com/juju/juju/version" 30 "github.com/juju/juju/worker" 31 ) 32 33 var logger = loggo.GetLogger("juju.worker.rsyslog") 34 35 var ( 36 rsyslogConfDir = "/etc/rsyslog.d" 37 logDir = agent.DefaultLogDir 38 syslogTargets = []*rsyslog.Writer{} 39 ) 40 41 // RsyslogMode describes how to configure rsyslog. 42 type RsyslogMode int 43 44 const ( 45 RsyslogModeInvalid RsyslogMode = iota 46 // RsyslogModeForwarding is the mode in which 47 // rsyslog will be configured to forward logging 48 // to state servers. 49 RsyslogModeForwarding 50 // RsyslogModeAccumulate is the mode in which 51 // rsyslog will be configured to accumulate logging 52 // from other machines into an "all-machines.log". 53 RsyslogModeAccumulate 54 ) 55 56 // RsyslogConfigHandler implements worker.NotifyWatchHandler, watching 57 // environment configuration changes and generating new rsyslog 58 // configuration. 59 type RsyslogConfigHandler struct { 60 st *apirsyslog.State 61 mode RsyslogMode 62 syslogConfig *syslog.SyslogConfig 63 rsyslogConfPath string 64 tag names.Tag 65 // We store the syslog-port and rsyslog-ca-cert 66 // values after writing the rsyslog configuration, 67 // so we can decide whether a change has occurred. 68 syslogPort int 69 rsyslogCACert string 70 rsyslogCAKey string 71 } 72 73 // certPair holds the path and contents for a certificate. 74 type certPair struct { 75 path string 76 data string 77 } 78 79 var _ worker.NotifyWatchHandler = (*RsyslogConfigHandler)(nil) 80 81 func syslogUser() string { 82 var user string 83 switch version.Current.OS { 84 case version.CentOS: 85 user = "root" 86 default: 87 user = "syslog" 88 } 89 90 return user 91 } 92 93 // NewRsyslogConfigWorker returns a worker.Worker that uses 94 // WatchForRsyslogChanges and updates rsyslog configuration based 95 // on changes. The worker will remove the configuration file 96 // on teardown. 97 func NewRsyslogConfigWorker(st *apirsyslog.State, mode RsyslogMode, tag names.Tag, namespace string, stateServerAddrs []string) (worker.Worker, error) { 98 if version.Current.OS == version.Windows && mode == RsyslogModeAccumulate { 99 return worker.NewNoOpWorker(), nil 100 } 101 handler, err := newRsyslogConfigHandler(st, mode, tag, namespace, stateServerAddrs) 102 if err != nil { 103 return nil, err 104 } 105 logger.Debugf("starting rsyslog worker mode %v for %q %q", mode, tag, namespace) 106 return worker.NewNotifyWorker(handler), nil 107 } 108 109 func newRsyslogConfigHandler(st *apirsyslog.State, mode RsyslogMode, tag names.Tag, namespace string, stateServerAddrs []string) (*RsyslogConfigHandler, error) { 110 var syslogConfig *syslog.SyslogConfig 111 if mode == RsyslogModeAccumulate { 112 syslogConfig = syslog.NewAccumulateConfig(tag.String(), logDir, 0, namespace, stateServerAddrs) 113 } else { 114 syslogConfig = syslog.NewForwardConfig(tag.String(), logDir, 0, namespace, stateServerAddrs) 115 } 116 117 // Historically only machine-0 includes the namespace in the log 118 // dir/file; for backwards compatibility we continue the tradition. 119 if tag != names.NewMachineTag("0") { 120 namespace = "" 121 } 122 switch tag := tag.(type) { 123 case names.MachineTag: 124 if namespace == "" { 125 syslogConfig.ConfigFileName = "25-juju.conf" 126 } else { 127 syslogConfig.ConfigFileName = fmt.Sprintf("25-juju-%s.conf", namespace) 128 } 129 default: 130 syslogConfig.ConfigFileName = fmt.Sprintf("26-juju-%s.conf", tag) 131 } 132 133 syslogConfig.ConfigDir = rsyslogConfDir 134 syslogConfig.LogDir = logDir 135 if namespace != "" { 136 syslogConfig.LogDir += "-" + namespace 137 } 138 return &RsyslogConfigHandler{ 139 st: st, 140 mode: mode, 141 syslogConfig: syslogConfig, 142 tag: tag, 143 }, nil 144 } 145 146 func (h *RsyslogConfigHandler) SetUp() (watcher.NotifyWatcher, error) { 147 if h.mode == RsyslogModeAccumulate { 148 if err := h.ensureCertificates(); err != nil { 149 return nil, errors.Annotate(err, "failed to write rsyslog certificates") 150 } 151 152 if err := h.ensureLogrotate(); err != nil { 153 return nil, errors.Annotate(err, "failed to write rsyslog logrotate scripts") 154 } 155 156 } 157 return h.st.WatchForRsyslogChanges(h.tag.String()) 158 } 159 160 var restartRsyslog = syslog.Restart 161 var dialSyslog = rsyslog.Dial 162 163 func (h *RsyslogConfigHandler) TearDown() error { 164 if err := os.Remove(h.syslogConfig.ConfigFilePath()); err == nil { 165 restartRsyslog() 166 } 167 return nil 168 } 169 170 // composeTLS generates a new client certificate for connecting to the rsyslog server. 171 // We explicitly set the ServerName field, this ensures that even if we are connecting 172 // via an IP address and are using an old certificate (pre 1.20.9), we can still 173 // successfully connect. 174 func (h *RsyslogConfigHandler) composeTLS(caCert string) (*tls.Config, error) { 175 cert := x509.NewCertPool() 176 ok := cert.AppendCertsFromPEM([]byte(caCert)) 177 if !ok { 178 return nil, errors.Errorf("Failed to parse rsyslog root certificate") 179 } 180 return &tls.Config{ 181 RootCAs: cert, 182 ServerName: "juju-rsyslog", 183 }, nil 184 } 185 186 func (h *RsyslogConfigHandler) replaceRemoteLogger(caCert string) error { 187 tlsConf, err := h.composeTLS(caCert) 188 if err != nil { 189 return err 190 } 191 192 var newLoggers []*rsyslog.Writer 193 var wrapLoggers []io.Writer 194 for _, j := range h.syslogConfig.StateServerAddresses { 195 host, _, err := net.SplitHostPort(j) 196 if err != nil { 197 // No port was found 198 host = j 199 } 200 target := fmt.Sprintf("%s:%d", host, h.syslogConfig.Port) 201 logTag := "juju" + h.syslogConfig.Namespace + "-" + h.tag.String() 202 logger.Debugf("making syslog connection for %q to %s", logTag, target) 203 writer, err := dialSyslog("tcp", target, rsyslog.LOG_DEBUG, logTag, tlsConf) 204 if err != nil { 205 return err 206 } 207 wrapLoggers = append(wrapLoggers, writer) 208 newLoggers = append(newLoggers, writer) 209 } 210 wapper := io.MultiWriter(wrapLoggers...) 211 writer := loggo.NewSimpleWriter(wapper, &loggo.DefaultFormatter{}) 212 213 loggo.RemoveWriter("syslog") 214 err = loggo.RegisterWriter("syslog", writer, loggo.TRACE) 215 if err != nil { 216 return err 217 } 218 219 // Close old targets 220 for _, j := range syslogTargets { 221 if err := j.Close(); err != nil { 222 logger.Warningf("Failed to close syslog writer: %s", err) 223 } 224 } 225 // record new targets 226 syslogTargets = newLoggers 227 return nil 228 } 229 230 func (h *RsyslogConfigHandler) Handle(_ <-chan struct{}) error { 231 cfg, err := h.st.GetRsyslogConfig(h.tag.String()) 232 if err != nil { 233 return errors.Annotate(err, "cannot get environ config") 234 } 235 236 rsyslogCACert := cfg.CACert 237 if rsyslogCACert == "" { 238 return nil 239 } 240 241 rsyslogCAKey := cfg.CAKey 242 if rsyslogCAKey == "" { 243 return nil 244 } 245 246 h.syslogConfig.Port = cfg.Port 247 if h.mode == RsyslogModeForwarding { 248 if err := writeFileAtomic(h.syslogConfig.CACertPath(), []byte(rsyslogCACert), 0644, 0, 0); err != nil { 249 return errors.Annotate(err, "cannot write CA certificate") 250 } 251 if err := h.replaceRemoteLogger(rsyslogCACert); err != nil { 252 return err 253 } 254 } else { 255 rsyslogCertPEM, rsyslogKeyPEM, err := h.rsyslogServerCerts(rsyslogCACert, rsyslogCAKey) 256 if err != nil { 257 return errors.Trace(err) 258 } 259 260 if err := writeCertificates([]certPair{ 261 {h.syslogConfig.ServerCertPath(), rsyslogCertPEM}, 262 {h.syslogConfig.ServerKeyPath(), rsyslogKeyPEM}, 263 {h.syslogConfig.CACertPath(), rsyslogCACert}, 264 }); err != nil { 265 return errors.Trace(err) 266 } 267 268 data, err := h.syslogConfig.Render() 269 if err != nil { 270 return errors.Annotate(err, "failed to render rsyslog configuration file") 271 } 272 if err := writeFileAtomic(h.syslogConfig.ConfigFilePath(), []byte(data), 0644, 0, 0); err != nil { 273 return errors.Annotate(err, "failed to write rsyslog configuration file") 274 } 275 logger.Debugf("Reloading rsyslog configuration") 276 if err := restartRsyslog(); err != nil { 277 logger.Errorf("failed to reload rsyslog configuration") 278 return errors.Annotate(err, "cannot restart rsyslog") 279 } 280 } 281 // Record config values so we don't try again. 282 // Do this last so we recover from intermittent 283 // failures. 284 h.syslogPort = cfg.Port 285 h.rsyslogCACert = rsyslogCACert 286 h.rsyslogCAKey = rsyslogCAKey 287 return nil 288 } 289 290 var lookupUser = func(username string) (uid, gid int, err error) { 291 u, err := user.Lookup(username) 292 if err != nil { 293 return -1, -1, err 294 } 295 uid, err = strconv.Atoi(u.Uid) 296 if err != nil { 297 return -1, -1, err 298 } 299 gid, err = strconv.Atoi(u.Gid) 300 if err != nil { 301 return -1, -1, err 302 } 303 return uid, gid, nil 304 } 305 306 func localIPS() ([]string, error) { 307 var ips []string 308 addrs, err := net.InterfaceAddrs() 309 if err != nil { 310 return nil, err 311 } 312 for _, j := range addrs { 313 ip, _, err := net.ParseCIDR(j.String()) 314 if err != nil { 315 return nil, err 316 } 317 if ip.IsLoopback() { 318 continue 319 } 320 ips = append(ips, ip.String()) 321 } 322 return ips, nil 323 } 324 325 func (h *RsyslogConfigHandler) rsyslogHosts() ([]string, error) { 326 var hosts []string 327 cfg, err := h.st.GetRsyslogConfig(h.tag.String()) 328 if err != nil { 329 return nil, err 330 } 331 for _, j := range cfg.HostPorts { 332 if j.Value != "" { 333 hosts = append(hosts, j.Address.Value) 334 } 335 } 336 337 // Explicitly add the '*' wildcard host. This will ensure that rsyslog 338 // clients will always be able to connect even if their hostnames and/or IPAddresses 339 // are changed. This also ensures we can continue to use SSL for our rsyslog connections 340 // and we can avoid having to use the skipVerify flag. 341 hosts = append(hosts, "*") 342 343 return hosts, nil 344 } 345 346 // ensureCertificates ensures that a CA certificate, 347 // server certificate, and private key exist in the log 348 // directory, and writes them if not. The CA certificate 349 // is entered into the environment configuration to be 350 // picked up by other agents. 351 func (h *RsyslogConfigHandler) ensureCertificates() error { 352 // We write ca-cert.pem last, after propagating into state. 353 // If it's there, then there's nothing to do. Otherwise, 354 // start over. 355 caCertPEM := h.syslogConfig.CACertPath() 356 if _, err := os.Stat(caCertPEM); err == nil { 357 return nil 358 } 359 360 // Generate a new CA and server cert/key pairs. 361 // The CA key will be discarded after the server 362 // cert has been generated. 363 expiry := time.Now().UTC().AddDate(10, 0, 0) 364 caCertPEM, caKeyPEM, err := cert.NewCA("rsyslog", expiry) 365 if err != nil { 366 return err 367 } 368 369 // Update the environment config with the CA cert, 370 // so clients can configure rsyslog. 371 if err := h.st.SetRsyslogCert(caCertPEM, caKeyPEM); err != nil { 372 return err 373 } 374 375 rsyslogCertPEM, rsyslogKeyPEM, err := h.rsyslogServerCerts(caCertPEM, caKeyPEM) 376 if err != nil { 377 return err 378 } 379 380 if err := writeCertificates([]certPair{ 381 {h.syslogConfig.ServerCertPath(), rsyslogCertPEM}, 382 {h.syslogConfig.ServerKeyPath(), rsyslogKeyPEM}, 383 {h.syslogConfig.CACertPath(), caCertPEM}, 384 }); err != nil { 385 return errors.Trace(err) 386 } 387 388 return nil 389 } 390 391 // writeCertificates persists any certPair to disk. If any 392 // of the write attempts fail it will return an error immediately. 393 // It is up to the caller to ensure the order of pairs represents 394 // a suitable order in the case of failue. 395 func writeCertificates(pairs []certPair) error { 396 // Files must be chowned to syslog:adm. 397 syslogUid, syslogGid, err := lookupUser(syslogUser()) 398 if err != nil { 399 return err 400 } 401 402 for _, pair := range pairs { 403 if err := writeFileAtomic(pair.path, []byte(pair.data), 0600, syslogUid, syslogGid); err != nil { 404 return err 405 } 406 } 407 return nil 408 } 409 410 // rsyslogServerCerts generates new certificates for RsyslogConfigHandler 411 // using the provider caCert and caKey. This is used during the setup of the 412 // rsyslog worker as well as when handling any changes to the rsyslog configuration, 413 // usually adding and removing of state machines through ensure-availability. 414 func (h *RsyslogConfigHandler) rsyslogServerCerts(caCert, caKey string) (string, string, error) { 415 if caCert == "" { 416 return "", "", errors.New("CACert is not set") 417 } 418 if caKey == "" { 419 return "", "", errors.New("CAKey is not set") 420 } 421 422 expiry := time.Now().UTC().AddDate(10, 0, 0) 423 // Add rsyslog servers in the subjectAltName so we can 424 // successfully validate when connectiong via SSL 425 hosts, err := h.rsyslogHosts() 426 if err != nil { 427 return "", "", err 428 } 429 // Add local IPs to SAN. When connecting via IP address, 430 // the client will validate the server against any IP in 431 // the subjectAltName. We add all local ips to make sure 432 // this does not cause an error 433 ips, err := localIPS() 434 if err != nil { 435 return "", "", err 436 } 437 hosts = append(hosts, ips...) 438 return cert.NewServer(caCert, caKey, expiry, hosts) 439 } 440 441 // ensureLogrotate ensures that the logrotate 442 // configuration file and logrotate helper script 443 // exist in the log directory and creates them if they do not. 444 func (h *RsyslogConfigHandler) ensureLogrotate() error { 445 // Files must be chowned to syslog 446 syslogUid, syslogGid, err := lookupUser(syslogUser()) 447 if err != nil { 448 return err 449 } 450 451 logrotateConfPath := h.syslogConfig.LogrotateConfPath() 452 // check for the logrotate conf 453 if _, err := os.Stat(logrotateConfPath); os.IsNotExist(err) { 454 logrotateConfFile, err := h.syslogConfig.LogrotateConfFile() 455 if err != nil { 456 return err 457 } 458 // create the logrotate conf 459 if err := writeFileAtomic(logrotateConfPath, logrotateConfFile, 0600, syslogUid, syslogGid); err != nil { 460 return err 461 } 462 } else { 463 return err 464 } 465 466 logrotateHelperPath := h.syslogConfig.LogrotateHelperPath() 467 // check for the logrotate helper 468 if _, err := os.Stat(logrotateHelperPath); os.IsNotExist(err) { 469 logrotateHelperFile, err := h.syslogConfig.LogrotateHelperFile() 470 if err != nil { 471 return err 472 } 473 // create the logrotate helper 474 if err := writeFileAtomic(logrotateHelperPath, logrotateHelperFile, 0700, syslogUid, syslogGid); err != nil { 475 return err 476 } 477 } else { 478 return err 479 } 480 481 return nil 482 } 483 484 func writeFileAtomic(path string, data []byte, mode os.FileMode, uid, gid int) error { 485 chmodAndChown := func(f *os.File) error { 486 // f.Chmod() and f.Chown() are not implemented on Windows 487 // There is currently no good way of doing file permission 488 // management for Windows, directly from Go. The behavior of os.Chmod() 489 // is different from its linux implementation. 490 if runtime.GOOS == "windows" { 491 return nil 492 } 493 if err := f.Chmod(mode); err != nil { 494 return err 495 } 496 if uid != 0 { 497 if err := f.Chown(uid, gid); err != nil { 498 return err 499 } 500 } 501 return nil 502 } 503 return utils.AtomicWriteFileAndChange(path, data, chmodAndChown) 504 }