github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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 "fmt" 8 "os" 9 "os/user" 10 "strconv" 11 "time" 12 13 "github.com/juju/errors" 14 "github.com/juju/loggo" 15 "github.com/juju/names" 16 "github.com/juju/utils" 17 18 "github.com/juju/juju/agent" 19 "github.com/juju/juju/cert" 20 apirsyslog "github.com/juju/juju/state/api/rsyslog" 21 "github.com/juju/juju/state/api/watcher" 22 "github.com/juju/juju/utils/syslog" 23 "github.com/juju/juju/worker" 24 ) 25 26 var logger = loggo.GetLogger("juju.worker.rsyslog") 27 28 var ( 29 rsyslogConfDir = "/etc/rsyslog.d" 30 logDir = agent.DefaultLogDir 31 ) 32 33 // RsyslogMode describes how to configure rsyslog. 34 type RsyslogMode int 35 36 const ( 37 RsyslogModeInvalid RsyslogMode = iota 38 // RsyslogModeForwarding is the mode in which 39 // rsyslog will be configured to forward logging 40 // to state servers. 41 RsyslogModeForwarding 42 // RsyslogModeAccumulate is the mode in which 43 // rsyslog will be configured to accumulate logging 44 // from other machines into an "all-machines.log". 45 RsyslogModeAccumulate 46 ) 47 48 // RsyslogConfigHandler implements worker.NotifyWatchHandler, watching 49 // environment configuration changes and generating new rsyslog 50 // configuration. 51 type RsyslogConfigHandler struct { 52 st *apirsyslog.State 53 mode RsyslogMode 54 syslogConfig *syslog.SyslogConfig 55 rsyslogConfPath string 56 tag string 57 // We store the syslog-port and rsyslog-ca-cert 58 // values after writing the rsyslog configuration, 59 // so we can decide whether a change has occurred. 60 syslogPort int 61 rsyslogCACert string 62 } 63 64 var _ worker.NotifyWatchHandler = (*RsyslogConfigHandler)(nil) 65 66 // NewRsyslogConfigWorker returns a worker.Worker that uses 67 // WatchForRsyslogChanges and updates rsyslog configuration based 68 // on changes. The worker will remove the configuration file 69 // on teardown. 70 func NewRsyslogConfigWorker(st *apirsyslog.State, mode RsyslogMode, tag, namespace string, stateServerAddrs []string) (worker.Worker, error) { 71 handler, err := newRsyslogConfigHandler(st, mode, tag, namespace, stateServerAddrs) 72 if err != nil { 73 return nil, err 74 } 75 logger.Debugf("starting rsyslog worker mode %v for %q %q", mode, tag, namespace) 76 return worker.NewNotifyWorker(handler), nil 77 } 78 79 func newRsyslogConfigHandler(st *apirsyslog.State, mode RsyslogMode, tag, namespace string, stateServerAddrs []string) (*RsyslogConfigHandler, error) { 80 var syslogConfig *syslog.SyslogConfig 81 if mode == RsyslogModeAccumulate { 82 syslogConfig = syslog.NewAccumulateConfig( 83 tag, logDir, 0, namespace, stateServerAddrs, 84 ) 85 } else { 86 syslogConfig = syslog.NewForwardConfig( 87 tag, logDir, 0, namespace, stateServerAddrs, 88 ) 89 } 90 91 // Historically only machine-0 includes the namespace in the log 92 // dir/file; for backwards compatibility we continue the tradition. 93 if tag != "machine-0" { 94 namespace = "" 95 } 96 kind, err := names.TagKind(tag) 97 if err != nil { 98 return nil, err 99 } 100 if kind == names.MachineTagKind { 101 if namespace == "" { 102 syslogConfig.ConfigFileName = "25-juju.conf" 103 } else { 104 syslogConfig.ConfigFileName = fmt.Sprintf("25-juju-%s.conf", namespace) 105 } 106 } else { 107 syslogConfig.ConfigFileName = fmt.Sprintf("26-juju-%s.conf", tag) 108 } 109 110 syslogConfig.ConfigDir = rsyslogConfDir 111 syslogConfig.LogDir = logDir 112 if namespace != "" { 113 syslogConfig.LogDir += "-" + namespace 114 } 115 return &RsyslogConfigHandler{ 116 st: st, 117 mode: mode, 118 syslogConfig: syslogConfig, 119 tag: tag, 120 }, nil 121 } 122 123 func (h *RsyslogConfigHandler) SetUp() (watcher.NotifyWatcher, error) { 124 if h.mode == RsyslogModeAccumulate { 125 if err := h.ensureCertificates(); err != nil { 126 return nil, errors.Annotate(err, "failed to write rsyslog certificates") 127 } 128 } 129 return h.st.WatchForRsyslogChanges(h.tag) 130 } 131 132 var restartRsyslog = syslog.Restart 133 134 func (h *RsyslogConfigHandler) TearDown() error { 135 if err := os.Remove(h.syslogConfig.ConfigFilePath()); err == nil { 136 restartRsyslog() 137 } 138 return nil 139 } 140 141 func (h *RsyslogConfigHandler) Handle() error { 142 cfg, err := h.st.GetRsyslogConfig(h.tag) 143 if err != nil { 144 return errors.Annotate(err, "cannot get environ config") 145 } 146 rsyslogCACert := cfg.CACert 147 if rsyslogCACert == "" { 148 return nil 149 } 150 151 h.syslogConfig.Port = cfg.Port 152 if h.mode == RsyslogModeForwarding { 153 if err := writeFileAtomic(h.syslogConfig.CACertPath(), []byte(rsyslogCACert), 0644, 0, 0); err != nil { 154 return errors.Annotate(err, "cannot write CA certificate") 155 } 156 } 157 data, err := h.syslogConfig.Render() 158 if err != nil { 159 return errors.Annotate(err, "failed to render rsyslog configuration file") 160 } 161 if err := writeFileAtomic(h.syslogConfig.ConfigFilePath(), []byte(data), 0644, 0, 0); err != nil { 162 return errors.Annotate(err, "failed to write rsyslog configuration file") 163 } 164 logger.Debugf("Reloading rsyslog configuration") 165 if err := restartRsyslog(); err != nil { 166 logger.Errorf("failed to reload rsyslog configuration") 167 return errors.Annotate(err, "cannot restart rsyslog") 168 } 169 // Record config values so we don't try again. 170 // Do this last so we recover from intermittent 171 // failures. 172 h.syslogPort = cfg.Port 173 h.rsyslogCACert = rsyslogCACert 174 return nil 175 } 176 177 var lookupUser = func(username string) (uid, gid int, err error) { 178 u, err := user.Lookup(username) 179 if err != nil { 180 return -1, -1, err 181 } 182 uid, err = strconv.Atoi(u.Uid) 183 if err != nil { 184 return -1, -1, err 185 } 186 gid, err = strconv.Atoi(u.Gid) 187 if err != nil { 188 return -1, -1, err 189 } 190 return uid, gid, nil 191 } 192 193 // ensureCertificates ensures that a CA certificate, 194 // server certificate, and private key exist in the log 195 // directory, and writes them if not. The CA certificate 196 // is entered into the environment configuration to be 197 // picked up by other agents. 198 func (h *RsyslogConfigHandler) ensureCertificates() error { 199 // We write ca-cert.pem last, after propagating into state. 200 // If it's there, then there's nothing to do. Otherwise, 201 // start over. 202 caCertPEM := h.syslogConfig.CACertPath() 203 if _, err := os.Stat(caCertPEM); err == nil { 204 return nil 205 } 206 207 // Files must be chowned to syslog:adm. 208 syslogUid, syslogGid, err := lookupUser("syslog") 209 if err != nil { 210 return err 211 } 212 213 // Generate a new CA and server cert/key pairs. 214 // The CA key will be discarded after the server 215 // cert has been generated. 216 expiry := time.Now().UTC().AddDate(10, 0, 0) 217 caCertPEM, caKeyPEM, err := cert.NewCA("rsyslog", expiry) 218 if err != nil { 219 return err 220 } 221 rsyslogCertPEM, rsyslogKeyPEM, err := cert.NewServer(caCertPEM, caKeyPEM, expiry, nil) 222 if err != nil { 223 return err 224 } 225 226 // Update the environment config with the CA cert, 227 // so clients can configure rsyslog. 228 if err := h.st.SetRsyslogCert(caCertPEM); err != nil { 229 return err 230 } 231 232 // Write the certificates and key. The CA certificate must be written last for idempotency. 233 for _, pair := range []struct { 234 path string 235 data string 236 }{ 237 {h.syslogConfig.ServerCertPath(), rsyslogCertPEM}, 238 {h.syslogConfig.ServerKeyPath(), rsyslogKeyPEM}, 239 {h.syslogConfig.CACertPath(), caCertPEM}, 240 } { 241 if err := writeFileAtomic(pair.path, []byte(pair.data), 0600, syslogUid, syslogGid); err != nil { 242 return err 243 } 244 } 245 return nil 246 } 247 248 func writeFileAtomic(path string, data []byte, mode os.FileMode, uid, gid int) error { 249 chmodAndChown := func(f *os.File) error { 250 if err := f.Chmod(mode); err != nil { 251 return err 252 } 253 if uid != 0 { 254 if err := f.Chown(uid, gid); err != nil { 255 return err 256 } 257 } 258 return nil 259 } 260 return utils.AtomicWriteFileAndChange(path, data, chmodAndChown) 261 }