github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/utils/syslog/config.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package syslog
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"path/filepath"
    11  	"strings"
    12  	"text/template"
    13  )
    14  
    15  // tagOffset represents the substring start value for the tag to return
    16  // the logfileName value from the syslogtag.  Substrings in syslog are
    17  // indexed from 1, hence the + 1.
    18  const tagOffset = len("juju-") + 1
    19  
    20  // The rsyslog conf for state server nodes.
    21  // Messages are gathered from other nodes and accumulated in an all-machines.log file.
    22  //
    23  // The apparmor profile is quite strict about where rsyslog can write files.
    24  // Instead of poking with the profile, the local provider now logs to
    25  // {{logDir}}-{{user}}-{{env name}}/all-machines.log, and a symlink is made
    26  // in the local provider log dir to point to that file. By
    27  // default rsyslog creates files with 0644, but in the ubuntu package, the
    28  // setting is changed to 0640. Using a new action directive (new as in
    29  // not-legacy), we can specify the file create mode so it doesn't use
    30  // the default.
    31  //
    32  // I would dearly love to write the filtering action as follows to avoid setting
    33  // and resetting the global $FileCreateMode, but alas, precise doesn't support it
    34  //
    35  // if $syslogtag startswith "juju{{namespace}}-" then
    36  //   action(type="omfile"
    37  //          File="{{logDir}}{{namespace}}/all-machines.log"
    38  //          Template="JujuLogFormat{{namespace}}"
    39  //          FileCreateMode="0644")
    40  // & stop
    41  //
    42  // Instead we need to mess with the global FileCreateMode.  We set it back
    43  // to the ubuntu default after defining our rule.
    44  const stateServerRsyslogTemplate = `
    45  $ModLoad imuxsock
    46  $ModLoad imfile
    47  
    48  # Messages received from remote rsyslog machines have messages prefixed with a space,
    49  # so add one in for local messages too if needed.
    50  $template JujuLogFormat{{namespace}},"%syslogtag:{{tagStart}}:$%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n"
    51  
    52  $template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%"
    53  
    54  {{range $i, $stateServerIP := stateServerHosts}}
    55  # start: Forwarding rule for {{$stateServerIP}}
    56  $ActionQueueType LinkedList
    57  $ActionQueueFileName {{logfileName}}{{namespace}}_{{$i}}
    58  $ActionResumeRetryCount -1
    59  $ActionQueueSaveOnShutdown on
    60  $DefaultNetstreamDriver gtls
    61  $DefaultNetstreamDriverCAFile {{tlsCACertPath}}
    62  $ActionSendStreamDriverAuthMode anon
    63  $ActionSendStreamDriverMode 1 # run driver in TLS-only mode
    64  
    65  :syslogtag, startswith, "juju{{namespace}}-" @@{{$stateServerIP}}:{{portNumber}};LongTagForwardFormat
    66  # end: Forwarding rule for {{$stateServerIP}}
    67  {{end}}
    68  :syslogtag, startswith, "juju{{namespace}}-" stop
    69  
    70  $FileCreateMode 0600
    71  
    72  # Maximum size for the log on this outchannel is 512MB
    73  # The command to execute when an outchannel as reached its size limit cannot accept any arguments
    74  # that is why we have created the helper script for executing logrotate.
    75  $outchannel logRotation,{{logDir}}/all-machines.log,536870912,{{logrotateHelperPath}}
    76  
    77  $RuleSet remote
    78  $FileCreateMode 0600
    79  :syslogtag, startswith, "juju{{namespace}}-" :omfile:$logRotation;JujuLogFormat{{namespace}}
    80  :syslogtag, startswith, "juju{{namespace}}-" stop
    81  $FileCreateMode 0600
    82  
    83  $InputFilePersistStateInterval 50
    84  $InputFilePollInterval 5
    85  $InputFileName {{logfilePath}}
    86  $InputFileTag juju{{namespace}}-{{logfileName}}:
    87  $InputFileStateFile {{logfileName}}{{namespace}}
    88  $InputRunFileMonitor
    89  
    90  $ModLoad imtcp
    91  $DefaultNetstreamDriver gtls
    92  $DefaultNetstreamDriverCAFile {{tlsCACertPath}}
    93  $DefaultNetstreamDriverCertFile {{tlsCertPath}}
    94  $DefaultNetstreamDriverKeyFile {{tlsKeyPath}}
    95  $InputTCPServerStreamDriverAuthMode anon
    96  $InputTCPServerStreamDriverMode 1 # run driver in TLS-only mode
    97  $InputTCPMaxSessions 10000 # default is 200, all agents connect to all rsyslog daemons
    98  
    99  $InputTCPServerBindRuleset remote
   100  $InputTCPServerRun {{portNumber}}
   101  
   102  # switch back to default ruleset for further rules
   103  $RuleSet RSYSLOG_DefaultRuleset
   104  `
   105  
   106  // The rsyslog conf for non-state server nodes.
   107  // Messages are forwarded to the state server node.
   108  //
   109  // Each forwarding rule must be repeated in full for every state server and
   110  // each rule must use a unique ActionQueueFileName
   111  // See: http://www.rsyslog.com/doc/rsyslog_reliable_forwarding.html
   112  const nodeRsyslogTemplate = `
   113  $ModLoad imuxsock
   114  $ModLoad imfile
   115  
   116  $InputFilePersistStateInterval 50
   117  $InputFilePollInterval 5
   118  $InputFileName {{logfilePath}}
   119  $InputFileTag juju{{namespace}}-{{logfileName}}:
   120  $InputFileStateFile {{logfileName}}{{namespace}}
   121  $InputRunFileMonitor
   122  {{range $i, $stateServerIP := stateServerHosts}}
   123  # start: Forwarding rule for {{$stateServerIP}}
   124  $ActionQueueType LinkedList
   125  $ActionQueueFileName {{logfileName}}{{namespace}}_{{$i}}
   126  $ActionResumeRetryCount -1
   127  $ActionQueueSaveOnShutdown on
   128  $DefaultNetstreamDriver gtls
   129  $DefaultNetstreamDriverCAFile {{tlsCACertPath}}
   130  $ActionSendStreamDriverAuthMode anon
   131  $ActionSendStreamDriverMode 1 # run driver in TLS-only mode
   132  
   133  $template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%"
   134  :syslogtag, startswith, "juju{{namespace}}-" @@{{$stateServerIP}}:{{portNumber}};LongTagForwardFormat
   135  # end: Forwarding rule for {{$stateServerIP}}
   136  {{end}}
   137  & ~
   138  `
   139  
   140  // The logrotate conf for state serve nodes.
   141  // default size is 512MB, ensuring that the log + one rotation
   142  // will never take up more than 1GB of space.
   143  //
   144  // The size of the logrotate configuration is low for
   145  // a very specific reason, see config comment.
   146  const logrotateConf = `
   147  {{.LogDir}}/all-machines.log {
   148      # rsyslogd informs logrotate when to rotate.
   149      # The size specified here must be less than or equal to the log size
   150      # when rsyslogd informs logrotate, or logrotate will take no action.
   151      # The size value is otherwise unimportant.
   152      size 1K
   153      # maximum of one old file
   154      rotate 1
   155      # counting old files starts at 1 rather than 0
   156      start 1
   157  	# ensure new file is created with the correct permissions
   158      create 600
   159  	# reload rsyslog after rotation so it will use the new file
   160      postrotate
   161        service rsyslog reload
   162      endscript
   163  }
   164  `
   165  
   166  var logrotateConfTemplate = template.Must(template.New("logrotate.conf").Parse(logrotateConf))
   167  
   168  // The logrotate helper script for state server nodes.
   169  // We specify a state file to ensure we have the proper permissions.
   170  const logrotateHelper = `
   171  /usr/sbin/logrotate -s {{.LogDir}}/logrotate.state {{.LogrotateConfPath}}
   172  `
   173  
   174  var logrotateHelperTemplate = template.Must(template.New("logrotate.run").Parse(logrotateHelper))
   175  
   176  // nodeRsyslogTemplateTLSHeader is prepended to
   177  // nodeRsyslogTemplate if TLS is to be used.
   178  const nodeRsyslogTemplateTLSHeader = `
   179  `
   180  
   181  const (
   182  	defaultConfigDir               = "/etc/rsyslog.d"
   183  	defaultCACertFileName          = "ca-cert.pem"
   184  	defaultServerCertFileName      = "rsyslog-cert.pem"
   185  	defaultServerKeyFileName       = "rsyslog-key.pem"
   186  	defaultLogrotateConfFileName   = "logrotate.conf"
   187  	defaultLogrotateHelperFileName = "logrotate.run"
   188  )
   189  
   190  // SyslogConfigRenderer instances are used to generate a rsyslog conf file.
   191  type SyslogConfigRenderer interface {
   192  	Render() ([]byte, error)
   193  }
   194  
   195  // SyslogConfig provides a means to configure and generate rsyslog conf files for
   196  // the state server nodes and unit nodes.
   197  // rsyslog is configured to tail the specified log file.
   198  type SyslogConfig struct {
   199  	// the template representing the config file contents.
   200  	configTemplate string
   201  	// the directory where the config file is written.
   202  	ConfigDir string
   203  	// the config file name.
   204  	ConfigFileName string
   205  	// the name of the log file to tail.
   206  	LogFileName string
   207  	// the name of the logrotate configuration file.
   208  	LogrotateConfFileName string
   209  	// the name of the script that executes the logrotate command.
   210  	LogrotateHelperFileName string
   211  	// the addresses of the state server to which messages should be forwarded.
   212  	StateServerAddresses []string
   213  	// CA certificate file name.
   214  	CACertFileName string
   215  	// Server certificate file name.
   216  	ServerCertFileName string
   217  	// Server private key file name.
   218  	ServerKeyFileName string
   219  	// the port number for the listener
   220  	Port int
   221  	// the directory for the logfiles
   222  	LogDir string
   223  	// namespace is used when there are multiple environments on one machine
   224  	Namespace string
   225  }
   226  
   227  // NewForwardConfig creates a SyslogConfig instance used on unit nodes to forward log entries
   228  // to the state server nodes.
   229  func NewForwardConfig(logFile, logDir string, port int, namespace string, stateServerAddresses []string) *SyslogConfig {
   230  	conf := &SyslogConfig{
   231  		configTemplate:       nodeRsyslogTemplate,
   232  		StateServerAddresses: stateServerAddresses,
   233  		LogFileName:          logFile,
   234  		Port:                 port,
   235  		LogDir:               logDir,
   236  	}
   237  	if namespace != "" {
   238  		conf.Namespace = "-" + namespace
   239  	}
   240  	return conf
   241  }
   242  
   243  // NewAccumulateConfig creates a SyslogConfig instance used to accumulate log entries from the
   244  // various unit nodes.
   245  func NewAccumulateConfig(logFile, logDir string, port int, namespace string, stateServerAddresses []string) *SyslogConfig {
   246  	conf := &SyslogConfig{
   247  		configTemplate:       stateServerRsyslogTemplate,
   248  		LogFileName:          logFile,
   249  		Port:                 port,
   250  		LogDir:               logDir,
   251  		StateServerAddresses: stateServerAddresses,
   252  	}
   253  	if namespace != "" {
   254  		conf.Namespace = "-" + namespace
   255  	}
   256  	return conf
   257  }
   258  
   259  func either(a, b string) string {
   260  	if a != "" {
   261  		return a
   262  	}
   263  	return b
   264  }
   265  
   266  func (slConfig *SyslogConfig) ConfigFilePath() string {
   267  	dir := either(slConfig.ConfigDir, defaultConfigDir)
   268  	return filepath.Join(dir, slConfig.ConfigFileName)
   269  }
   270  
   271  func (slConfig *SyslogConfig) CACertPath() string {
   272  	filename := either(slConfig.CACertFileName, defaultCACertFileName)
   273  	return filepath.Join(slConfig.LogDir, filename)
   274  }
   275  
   276  func (slConfig *SyslogConfig) ServerCertPath() string {
   277  	filename := either(slConfig.ServerCertFileName, defaultServerCertFileName)
   278  	return filepath.Join(slConfig.LogDir, filename)
   279  }
   280  
   281  func (slConfig *SyslogConfig) ServerKeyPath() string {
   282  	filename := either(slConfig.ServerCertFileName, defaultServerKeyFileName)
   283  	return filepath.Join(slConfig.LogDir, filename)
   284  }
   285  
   286  // LogrotateConfPath returns the the the entire logrotate.conf path including filename.
   287  func (slConfig *SyslogConfig) LogrotateConfPath() string {
   288  	filename := either(slConfig.LogrotateConfFileName, defaultLogrotateConfFileName)
   289  	return filepath.Join(slConfig.LogDir, filename)
   290  }
   291  
   292  // LogrotateHelperPath returns the entire logrotate.helper path including filename.
   293  func (slConfig *SyslogConfig) LogrotateHelperPath() string {
   294  	filename := either(slConfig.LogrotateHelperFileName, defaultLogrotateHelperFileName)
   295  	return filepath.Join(slConfig.LogDir, filename)
   296  }
   297  
   298  // LogrotateConfFile returns a ready to write to disk byte array of the logrotate.conf file.
   299  func (slConfig *SyslogConfig) LogrotateConfFile() ([]byte, error) {
   300  	return slConfig.logrotateRender(logrotateConfTemplate)
   301  }
   302  
   303  // LogrotateHelperFile returns a ready to write to disk byte array of the logrotate.helper file.
   304  func (slConfig *SyslogConfig) LogrotateHelperFile() ([]byte, error) {
   305  	return slConfig.logrotateRender(logrotateHelperTemplate)
   306  }
   307  
   308  func (slConfig *SyslogConfig) logrotateRender(t *template.Template) ([]byte, error) {
   309  	var buffer bytes.Buffer
   310  	if err := t.Execute(&buffer, slConfig); err != nil {
   311  		return nil, err
   312  	}
   313  	return buffer.Bytes(), nil
   314  }
   315  
   316  // Render generates the rsyslog config.
   317  func (slConfig *SyslogConfig) Render() ([]byte, error) {
   318  	var stateServerHosts = func() []string {
   319  		var hosts []string
   320  		for _, addr := range slConfig.StateServerAddresses {
   321  			parts := strings.Split(addr, ":")
   322  			hosts = append(hosts, parts[0])
   323  		}
   324  		return hosts
   325  	}
   326  
   327  	var logFilePath = func() string {
   328  		return fmt.Sprintf("%s/%s.log", slConfig.LogDir, slConfig.LogFileName)
   329  	}
   330  
   331  	t := template.New("syslogConfig")
   332  	t.Funcs(template.FuncMap{
   333  		"logfileName":         func() string { return slConfig.LogFileName },
   334  		"stateServerHosts":    stateServerHosts,
   335  		"logfilePath":         logFilePath,
   336  		"portNumber":          func() int { return slConfig.Port },
   337  		"logDir":              func() string { return slConfig.LogDir },
   338  		"namespace":           func() string { return slConfig.Namespace },
   339  		"tagStart":            func() int { return tagOffset + len(slConfig.Namespace) },
   340  		"tlsCACertPath":       slConfig.CACertPath,
   341  		"tlsCertPath":         slConfig.ServerCertPath,
   342  		"tlsKeyPath":          slConfig.ServerKeyPath,
   343  		"logrotateHelperPath": slConfig.LogrotateHelperPath,
   344  	})
   345  
   346  	// Process the rsyslog config template and echo to the conf file.
   347  	p, err := t.Parse(slConfig.configTemplate)
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  	var confBuf bytes.Buffer
   352  	if err := p.Execute(&confBuf, nil); err != nil {
   353  		return nil, err
   354  	}
   355  	return confBuf.Bytes(), nil
   356  }
   357  
   358  // Write generates and writes the rsyslog config.
   359  func (slConfig *SyslogConfig) Write() error {
   360  	data, err := slConfig.Render()
   361  	if err != nil {
   362  		return err
   363  	}
   364  	err = ioutil.WriteFile(slConfig.ConfigFilePath(), data, 0644)
   365  	return err
   366  }