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  }