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  }