syslog-shipper 1.0.20120102102042 → 1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/HISTORY ADDED
@@ -0,0 +1,8 @@
1
+ 1.1
2
+
3
+ Added TLS support
4
+ Can supply custom CA cert, bypass peer checking, or accept an unknown certificate
5
+ Ping mode
6
+ When used with verbose, helps debug TLS issues
7
+ Config file
8
+ Use -f to supply a YAML config file that maps to the command line flags
data/LICENSE ADDED
@@ -0,0 +1,2 @@
1
+ This software is licensed under the BSD license as here:
2
+ http://www.opensource.org/licenses/bsd-license.php
data/README CHANGED
@@ -1,9 +1,21 @@
1
1
  Ship logs to a syslog server
2
2
  ----------------------------
3
3
 
4
- Usage: bin/syslog-shipper [options] -s HOST:PORT <path_or_glob> [path_or_glob2] [...]
5
- If a path begins with '+' each line is sent unmodified to the syslog server.
6
- Otherwise, this tool will prefix each line read from the file with a syslog
7
- header.
8
-
9
- For example: syslog-shipper -s somehost:514 +/var/log/messages /var/log/apache2/access.log
4
+ [syslog-shipper (master *+$)]$ bin/syslog-shipper -h
5
+ Usage: bin/syslog-shipper [options] -s HOST:PORT <path_or_glob> [path_or_glob2] [...]
6
+ If a path begins with '+' each line is sent unmodified to the syslog server.
7
+ Otherwise, this tool will prefix each line read from the file with a syslog
8
+ header.
9
+
10
+ For example: bin/syslog-shipper -s somehost:514 +/var/log/messages /var/log/apache2/access.syslog
11
+ --check-interval, -i <i>: How frequently, in seconds, to check the glob patternsfor new files (default: 5)
12
+ --exclude, -x <s+>: A pattern to ignore. Wildcard/globs accepted. Can be specified multiple times
13
+ --server, -s <s>: What syslog server to ship to (uses TCP)
14
+ --verbose, -v: Verbose mode
15
+ --ping, -p: Try to connect and quit immediately after
16
+ --ca-cert, -c <s>: Custom certificate used to verify TLS certificates (implies --tls)
17
+ --skip-peer-check, -k: When connecting with TLS, do not prompt the user to verify the peer (not recommended, implies --tls)
18
+ --tls, -t: Connect via tls
19
+ --config-file, -f <s>: YAML config file (command line options override values in this file)
20
+ --version, -e: Print version and exit
21
+ --help, -h: Show this message
@@ -1,57 +1,53 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env ruby -rubygems
2
2
 
3
- require "rubygems"
4
- require "optparse"
5
3
  require File.expand_path('../../lib/syslog_shipper', __FILE__)
4
+ require 'trollop'
5
+ require 'yaml'
6
6
 
7
7
  def main(args)
8
- globcheck_interval = 5
9
- exclude_patterns = []
10
- hostarg = nil
11
- verbose = false
12
-
13
- opts = OptionParser.new do |opts|
14
- opts.banner = [
15
- "Usage: #{$0} [options] -s HOST:PORT <path_or_glob> [path_or_glob2] [...]",
16
- " If a path begins with '+' each line is sent unmodified to the syslog server.",
17
- " Otherwise, this tool will prefix each line read from the file with a syslog",
18
- " header. ",
19
- "",
20
- " For example: #{$0} -s somehost:514 +/var/log/messages /var/log/apache2/access.log",
21
- ].join("\n")
22
-
23
- opts.on("-i SECONDS", "--check-interval SECONDS",
24
- "How frequently, in seconds, to check the glob patterns" \
25
- "for new files") do |x|
26
- globcheck_interval = x.to_f
27
- end # -i SECONDS
28
-
29
- opts.on("-x EXCLUDE", "--exclude EXCLUDE",
30
- "A pattern to ignore. Wildcard/globs accepted." \
31
- " Can be specified multiple times") do |pattern|
32
- exclude_patterns << pattern_to_regexp(pattern)
33
- end
8
+ options = Trollop::options do
9
+ version "syslog-shipper 1.0"
10
+ banner <<-EOS
11
+ Usage: #{$0} [options] -s HOST:PORT <path_or_glob> [path_or_glob2] [...]
12
+ If a path begins with '+' each line is sent unmodified to the syslog server.
13
+ Otherwise, this tool will prefix each line read from the file with a syslog
14
+ header.
15
+
16
+ For example: #{$0} -s somehost:514 +/var/log/messages /var/log/apache2/access.syslog
17
+ EOS
18
+
19
+ opt :check_interval, "How frequently, in seconds, to check the glob patterns" \
20
+ "for new files", :default => 5, :short => :i
21
+ opt :exclude, "A pattern to ignore. Wildcard/globs accepted." \
22
+ " Can be specified multiple times", :short => :x, :type => :strings, :multi => true
23
+ opt :server, "What syslog server to ship to (uses TCP)", :type => :string
24
+ opt :verbose, "Verbose mode"
25
+ opt :ping, "Try to connect and quit immediately after"
26
+ opt :ca_cert, "Custom certificate used to verify TLS certificates (implies --tls)", :type => :string
27
+ opt :skip_peer_check, "When connecting with TLS, do not prompt the user to verify the peer (not recommended, implies --tls)"
28
+ opt :tls, "Connect via tls"
29
+ opt :config_file, "YAML config file (command line options override values in this file)", :type => :string, :short => :f
30
+ end
34
31
 
35
- opts.on("-s HOST:PORT", "--server HOST:PORT",
36
- "What syslog server to ship to (uses TCP)") do |arg|
37
- hostarg = arg
38
- end
32
+ files = ARGV || []
39
33
 
40
- opts.on("-v", "--verbose", "verbose (outputs each log line as seen)") do |arg|
41
- verbose = true
42
- end
34
+ if options[:config_file]
35
+ config = Hash[YAML::load(open(options[:config_file])).map { |k, v| [k.to_sym, v] }]
36
+ # prefer command line values, but take them from the config file
37
+ options.merge!(config) {|k, right_v, left_v| right_v.nil? ? left_v : right_v }
38
+ files += options[:files]
39
+ end
43
40
 
44
- opts.on("-c CERT_PATH", "--ca-cert CERT_PATH", "Certificate authority PEM file to use") do |arg|
45
- SyslogShipper::Client.ca_cert = arg
46
- end
47
- end # OptionParser
41
+ Trollop.die "You must supply files to watch" if files.empty?
42
+ Trollop.die "You must supply the host:port to connect to" if options.server.nil?
48
43
 
49
- opts.parse!(args)
44
+ host, port = options[:server].split(":")
45
+ port = 514 if port == nil
46
+ exluded_patterns = options[:exclude] ? options[:exclude].map{|glob| pattern_to_regexp(glob)} : []
50
47
 
51
- if args.length == 0 or hostarg == nil
52
- puts opts.banner
53
- return 1
54
- end
48
+ options[:tls] = true if (options[:tls] || options[:skip_peer_check] || options[:ca_cert])
49
+
50
+ puts options if options[:verbose]
55
51
 
56
52
  EventMachine.run do
57
53
  Signal.trap("INT") do
@@ -61,26 +57,28 @@ def main(args)
61
57
  end
62
58
  end
63
59
 
64
- host, port = hostarg.split(":")
65
- port = 514 if port == nil
66
-
67
- connection = EventMachine::connect(host, port, SyslogShipper::TlsWrapper)
68
-
69
- args.each do |path|
70
- if path.start_with?("+")
60
+ files.uniq.each do |path|
61
+ if path =~ /\A\+/
71
62
  raw = true
72
63
  path = path[1..-1]
73
64
  else
74
65
  raw = false
75
66
  end
67
+
68
+ connection = if options[:tls]
69
+ EventMachine::connect(host, port, SyslogShipper::TlsWrapper, options[:ca_cert], options[:tls], options[:skip_peer_check], options[:verbose])
70
+ else
71
+ EventMachine::connect(host, port)
72
+ end
73
+
76
74
  EventMachine::FileGlobWatchTail.new(path, SyslogShipper::Client,
77
- interval = globcheck_interval,
78
- exclude = exclude_patterns,
79
- start_pos = -1,
80
- connection = connection,
81
- raw = raw,
82
- verbose = verbose
83
- )
75
+ options[:check_interval],
76
+ exclude = exluded_patterns,
77
+ {:startpos => -1,
78
+ :connection => connection,
79
+ :raw => raw,
80
+ :ping => options[:ping],
81
+ :verbose => options[:verbose]})
84
82
  end # args.each
85
83
  end # EventMachine.run
86
84
  end # def main
@@ -2,29 +2,27 @@ require "socket"
2
2
 
3
3
  module SyslogShipper
4
4
  class Client < EventMachine::FileTail
5
- class << self
6
- attr_accessor :ca_cert
7
- end
8
-
9
- def initialize(path, startpos=-1, connection=nil, raw=false, verbose=false)
10
- super(path, startpos)
5
+ def initialize path, options = {:startpos => -1}
6
+ super path, options[:startpos]
11
7
  @buffer = BufferedTokenizer.new
12
8
  @hostname = Socket.gethostname
13
- @connection = connection
14
- @raw = raw
15
- @verbose = verbose
9
+ @connection = options[:connection]
10
+ @raw = options[:raw]
11
+ @ping = options[:ping]
12
+ @verbose = options[:verbose]
16
13
  end
17
14
 
18
15
  def receive_data(data)
19
16
  @buffer.extract(data).each do |line|
20
- line = if @raw
21
- "#{line}\n"
22
- else
23
- "#{Time.now.strftime("%b %d %H:%M:%S")} #{@hostname} #{path}: #{line}\n"
17
+ if @ping
18
+ puts 'connection successful'
19
+ exit
20
+ end
21
+
22
+ if message = build_message(line)
23
+ puts message if @verbose
24
+ send_data message
24
25
  end
25
-
26
- print line if @verbose
27
- send_data(line)
28
26
  end
29
27
  end
30
28
 
@@ -33,5 +31,16 @@ module SyslogShipper
33
31
  def send_data line
34
32
  @connection.send_data line
35
33
  end
34
+
35
+ def build_message line
36
+ # don't send anything if there is no data
37
+ return if line && line.gsub(/\s/, '').empty?
38
+
39
+ if @raw
40
+ "#{line}\n"
41
+ else
42
+ "#{Time.now.strftime("%b %d %H:%M:%S")} #{@hostname} #{path}: #{line}\n"
43
+ end
44
+ end
36
45
  end
37
46
  end
@@ -1,25 +1,69 @@
1
1
  require 'openssl'
2
+ require 'thread'
2
3
 
3
4
  module SyslogShipper::TlsWrapper
5
+ class << self
6
+ attr_accessor :verified
7
+ end
8
+
9
+ def initialize(ca_cert = nil, with_tls = false, bypass_peer_check = false, verbose = false)
10
+ @ca_cert = ca_cert
11
+ @with_tls = true
12
+ @bypass_peer_check = bypass_peer_check
13
+ @verbose = verbose
14
+ end
15
+
4
16
  def post_init
5
- start_tls(:verify_peer => true)
17
+ puts 'post init' if @verbose
18
+
19
+ start_tls :verify_peer => @with_tls
6
20
  end
7
21
 
8
22
  def connection_completed
9
-
23
+ puts 'connection completed' if @verbose
10
24
  end
11
25
 
12
26
  def ssl_verify_peer cert
13
- ca_cert = OpenSSL::X509::Certificate.new File.read(SyslogShipper::Client.ca_cert)
14
- server_cert = OpenSSL::X509::Certificate.new cert
15
- server_cert.verify ca_cert.public_key
27
+ puts 'verifying peer' if @verbose
28
+ unless defined?(@@verified)
29
+ return true if @bypass_peer_check
30
+
31
+ server_cert = OpenSSL::X509::Certificate.new cert
32
+ verified = false
33
+
34
+ if @ca_cert
35
+ ca_cert = read_ca_cert
36
+ verified = server_cert.verify(ca_cert.public_key)
37
+ end
38
+
39
+ unless verified
40
+ puts server_cert.inspect
41
+ print "The server certificate is not recognized, would you still like to connect? (Y/N) "
42
+ answer = STDIN.gets.chomp
43
+ unless ['y', 'yes'].include?(answer.downcase)
44
+ raise OpenSSL::X509::CertificateError.new("Couldn't verify peer")
45
+ end
46
+ end
47
+
48
+ @@verified = verified
49
+
50
+ puts 'verified peer' if @verbose
51
+ end
52
+
53
+ true
16
54
  end
17
55
 
18
56
  def ssl_handshake_completed
19
- $server_handshake_completed = true
57
+ puts 'ssl handshake completed' if @verbose
20
58
  end
21
59
 
22
60
  def unbind
61
+ puts 'connection unbound!' if @verbose
62
+ end
63
+
64
+ private
23
65
 
66
+ def read_ca_cert
67
+ OpenSSL::X509::Certificate.new(File.read(@ca_cert))
24
68
  end
25
69
  end
@@ -6,8 +6,19 @@ describe SyslogShipper::Client do
6
6
 
7
7
  end
8
8
 
9
- context "sending data" do
10
- it "sends over TLS"
9
+ it 'builds a message'
10
+ it 'prints the message if in verbose mode'
11
+ it 'sends data'
12
+ end
13
+
14
+ describe '#build_message' do
15
+ context 'raw mode' do
16
+ it 'does not modify the message'
17
+ end
18
+
19
+ context 'non-raw mode' do
20
+ it 'prepends the timestamp and host'
11
21
  end
22
+
12
23
  end
13
24
  end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ class MyTlsModule
4
+ include SyslogShipper::TlsWrapper
5
+ end
6
+
7
+ describe SyslogShipper::TlsWrapper do
8
+
9
+ let(:subject) {MyTlsModule.new}
10
+ let(:server_cert) {double}
11
+ let(:ca_cert) {double}
12
+
13
+
14
+ before(:each) do
15
+ MyTlsModule.class_variable_set(:@@verified, false)
16
+ subject.stub(:read_ca_cert).and_return(ca_cert)
17
+ ca_cert.stub(:public_key)
18
+ server_cert.stub(:public_key)
19
+ OpenSSL::X509::Certificate.stub(:new).and_return(server_cert)
20
+ end
21
+
22
+ describe "#ssl_verify_peer" do
23
+ context 'using a valid CA certificate' do
24
+ before(:each) do
25
+ subject.instance_variable_set(:@ca_cert, double)
26
+ server_cert.stub(:verify).and_return true
27
+ end
28
+
29
+ it 'verifies the peer' do
30
+ subject.ssl_verify_peer("").should be_true
31
+ end
32
+ end
33
+
34
+ context 'connecting to an unverified peer' do
35
+ before(:each) do
36
+ server_cert.stub(:verify).and_return false
37
+ end
38
+
39
+ it 'should connect if the bypass peer checking flag is set' do
40
+ subject.instance_variable_set(:@bypass_peer_check, true)
41
+ subject.ssl_verify_peer("").should be_true
42
+ end
43
+
44
+ it 'should connect if the user answers yes to the command prompt' do
45
+ pending("previous tests keeping state...")
46
+ STDIN.should_receive(:gets).and_return("yes")
47
+ subject.ssl_verify_peer("").should be_true
48
+ end
49
+
50
+ it 'should not connect to an untrusted peer' do
51
+ pending("previous tests keeping state...")
52
+ STDIN.stub(:gets).and_return("no")
53
+ lambda {
54
+ subject.ssl_verify_peer("")
55
+ }.should raise_error(OpenSSL::X509::CertificateError)
56
+ end
57
+ end
58
+ end
59
+ end
metadata CHANGED
@@ -1,82 +1,78 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: syslog-shipper
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 1
7
- - 0
8
- - 20120102102042
9
- version: 1.0.20120102102042
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.1'
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
12
- - Jordan Sissel
7
+ authors:
8
+ - Neil Matatall
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2012-01-02 00:00:00 -08:00
18
- default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-01-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: eventmachine-tail
16
+ requirement: &70190739730420 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
22
23
  prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
+ version_requirements: *70190739730420
25
+ - !ruby/object:Gem::Dependency
26
+ name: trollop
27
+ requirement: &70190739729940 !ruby/object:Gem::Requirement
24
28
  none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- segments:
29
- - 0
30
- version: "0"
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
31
33
  type: :runtime
32
- version_requirements: *id001
34
+ prerelease: false
35
+ version_requirements: *70190739729940
33
36
  description: Ship logs from files to a remote syslog server over TCP
34
- email: jls@semicomplete.com
35
- executables:
37
+ email: neil@matatall.com
38
+ executables:
36
39
  - syslog-shipper
37
40
  extensions: []
38
-
39
41
  extra_rdoc_files: []
40
-
41
- files:
42
+ files:
42
43
  - README
43
- - lib/syslog_shipper/tls_wrapper.rb
44
+ - LICENSE
45
+ - HISTORY
44
46
  - lib/syslog_shipper/client.rb
47
+ - lib/syslog_shipper/tls_wrapper.rb
45
48
  - lib/syslog_shipper.rb
46
49
  - bin/syslog-shipper
47
- - spec/spec_helper.rb
48
50
  - spec/client_spec.rb
49
- has_rdoc: true
50
- homepage: https://github.com/jordansissel/syslog-shipper
51
+ - spec/spec_helper.rb
52
+ - spec/tls_wrapper_spec.rb
53
+ homepage: https://github.com/oreoshake/syslog-shipper
51
54
  licenses: []
52
-
53
55
  post_install_message:
54
56
  rdoc_options: []
55
-
56
- require_paths:
57
+ require_paths:
57
58
  - lib
58
- required_ruby_version: !ruby/object:Gem::Requirement
59
+ required_ruby_version: !ruby/object:Gem::Requirement
59
60
  none: false
60
- requirements:
61
- - - ">="
62
- - !ruby/object:Gem::Version
63
- segments:
64
- - 0
65
- version: "0"
66
- required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
66
  none: false
68
- requirements:
69
- - - ">="
70
- - !ruby/object:Gem::Version
71
- segments:
72
- - 0
73
- version: "0"
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
74
71
  requirements: []
75
-
76
72
  rubyforge_project:
77
- rubygems_version: 1.3.7
73
+ rubygems_version: 1.8.10
78
74
  signing_key:
79
75
  specification_version: 3
80
- summary: syslog-shipper - a tool for streaming logs from files to a remote syslog server
76
+ summary: syslog-shipper - a tool for streaming logs from files to a remote syslog
77
+ server
81
78
  test_files: []
82
-