event-shipper 0.1.0

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,4 @@
1
+
2
+ = 0.1
3
+
4
+ * First version.
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+
2
+ Copyright (c) 2013 Kaspar Schiess
3
+
4
+ Permission is hereby granted, free of charge, to any person
5
+ obtaining a copy of this software and associated documentation
6
+ files (the "Software"), to deal in the Software without
7
+ restriction, including without limitation the rights to use,
8
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the
10
+ Software is furnished to do so, subject to the following
11
+ conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,57 @@
1
+
2
+ event_shipper reads log files and sends each line in logstash JSON format
3
+ via encrypted UDP to redis.
4
+
5
+ LINKS
6
+
7
+ logstash is awesome and here:
8
+ http://logstash.net/
9
+
10
+ this uses a CA and public/private key architecture:
11
+ https://github.com/jordansissel/lumberjack
12
+
13
+ and finally, here's woodchuck, which inspired this work:
14
+ https://github.com/danryan/woodchuck
15
+
16
+ SYNOPSIS
17
+
18
+ To run a local example installation, start redis. Then:
19
+
20
+ # Produce one log entry per second in the file 'test.log'
21
+ $ ./bin/producer 1
22
+
23
+ # Ship the log entries to localhost
24
+ $ ./bin/esshipper -c configs/test.yaml
25
+
26
+ # Receive entries and store in redis
27
+ $ ./bin/esproxy -c configs/proxy.yaml
28
+
29
+ PERFORMANCE
30
+
31
+ Good.
32
+
33
+ ENCRYPTION
34
+
35
+ Packets are encrypted using a simple AES256 symmetric encryption with a
36
+ preshared user/password tuple on the server and the client. This should be
37
+ good to keep people from reading your logs,
38
+
39
+ If someone with deep cryptographic knowledge wants to criticise my use of
40
+ algorithms, please, you're welcome! Just keep in mind that I've selected the
41
+ current method to be simple to set up.
42
+
43
+ CONTRIBUTE
44
+
45
+ This gem is hosted on bitbucket at
46
+ https://bitbucket.org/kschiess/event_shipper
47
+
48
+ Please file any issue you might have with the program there. Contributions
49
+ are welcome - you might shoot me an email before starting, just to check if
50
+ the direction you take is welcome in the code base.
51
+
52
+ LICENSE
53
+
54
+ This piece of software is under a MIT license, which means you can do pretty
55
+ much anything you want with it. Read the license, it's in the LICENSE file.
56
+
57
+ (c) 2013 Kaspar Schiess, Technology Astronauts
data/bin/esproxy ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Tails one or more log files and ships logs as events over the network
4
+ # via a custom encrypted protocol. Transport is UDP.
5
+
6
+ require 'bundler/setup'
7
+
8
+ $:.unshift File.dirname(__FILE__) + "/../lib"
9
+ require 'event_shipper/proxy'
10
+
11
+ EventShipper::Proxy.run
data/bin/esshipper ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Tails one or more log files and ships logs as events over the network
4
+ # via a custom encrypted protocol. Transport is UDP.
5
+
6
+ require 'bundler/setup'
7
+
8
+ $:.unshift File.dirname(__FILE__) + "/../lib"
9
+ require 'event_shipper/shipper'
10
+
11
+ EventShipper::Shipper.run
data/bin/producer ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # A trivial test script that produces a file called test.log and appends N
4
+ # messages per second to the file.
5
+
6
+ n = Integer(ARGV.first || 1)
7
+
8
+ sleep_seconds = (1 / Float(n))
9
+
10
+ File.open('test.log', 'a+') do |file|
11
+ file.sync = true
12
+
13
+ loop do
14
+ file.puts "A message (#{Time.now})"
15
+ sleep sleep_seconds
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+
2
+ encrypt: true
3
+ users:
4
+ - test/MyPassword
5
+
6
+ listen:
7
+ host: 127.0.0.1
8
+ port: 5050
9
+
10
+ redis:
11
+ host: localhost
12
+ port: 6379
data/configs/test.yaml ADDED
@@ -0,0 +1,10 @@
1
+
2
+ encrypt: true
3
+ user: test
4
+ password: MyPassword
5
+
6
+ target: localhost:5050
7
+
8
+ logs:
9
+ - ./test.log
10
+
File without changes
@@ -0,0 +1,41 @@
1
+
2
+ require 'yajl'
3
+ require 'addressable/uri'
4
+
5
+ module EventShipper
6
+ class Event
7
+ def initialize host, path, line
8
+ @path = path
9
+ @line = line
10
+ @host = host
11
+ @timestamp = Time.now.utc.iso8601(6)
12
+ @source = Addressable::URI.new(
13
+ :scheme => 'file',
14
+ :host => host, :path => path)
15
+ @message = line.chomp.strip
16
+ @fields = {}
17
+ @tags = []
18
+ end
19
+
20
+ def to_hash
21
+ {
22
+ '@source' => @source.to_s,
23
+ '@type' => @source.scheme,
24
+ '@tags' => @tags,
25
+ '@fields' => @fields,
26
+ '@timestamp' => @timestamp,
27
+ '@source_host' => @host,
28
+ '@source_path' => @path,
29
+ '@message' => @message
30
+ }
31
+ end
32
+
33
+ def to_json
34
+ Yajl::Encoder.encode(to_hash)
35
+ end
36
+
37
+ def to_s
38
+ to_json
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+
2
+ require 'time'
3
+ require 'socket'
4
+ require 'file-tail'
5
+
6
+ require 'event_shipper/event'
7
+
8
+ module EventShipper
9
+ class LogTailer
10
+ def initialize path, transport
11
+ @path = path
12
+ @transport = transport
13
+ @host = Socket.gethostname
14
+ end
15
+
16
+ def start
17
+ @thread = Thread.new(&method(:thread_main))
18
+ @thread.abort_on_exception = true
19
+ end
20
+
21
+ def thread_main
22
+ File.open(@path) do |log|
23
+ log.seek 0, IO::SEEK_END
24
+ log.extend File::Tail
25
+
26
+ log.tail do |line|
27
+ issue line
28
+ end
29
+ end
30
+ end
31
+ def issue line
32
+ event = Event.new @host, @path, line
33
+ @transport.send "queue/#{event.to_json}"
34
+ end
35
+
36
+ def join
37
+ @thread.join
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,42 @@
1
+
2
+ require 'redis'
3
+ require 'yaml'
4
+ require 'clamp'
5
+
6
+ module EventShipper
7
+
8
+ require_relative 'transport/encrypt'
9
+ require_relative 'transport/udp'
10
+
11
+ class Proxy < Clamp::Command
12
+
13
+ option %w(-c --config), "FILE", "Location of the configuration file."
14
+
15
+ def execute
16
+ fail "Please specify a configuration file to use on the command line. (--config)" \
17
+ unless config
18
+
19
+ configuration = YAML.load_file(config)
20
+
21
+ listen = configuration['listen']
22
+ host, port = listen['host'], listen['port']
23
+ transport = Transport::UDP.new(host, port)
24
+
25
+ if configuration['encrypt']
26
+ users = configuration['users']
27
+ userdb = Hash[users.map { |str| str.split('/') }]
28
+
29
+ transport.filter = Transport::Decrypt.new(userdb)
30
+ end
31
+
32
+ redis_config = configuration['redis']
33
+ redis = Redis.new(
34
+ host: redis_config['host'],
35
+ port: redis_config['port'])
36
+
37
+ transport.dispatch { |queue, message|
38
+ # puts message
39
+ redis.lpush queue, message }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+
2
+ require 'yaml'
3
+ require 'clamp'
4
+
5
+ module EventShipper
6
+
7
+ require 'event_shipper/transport/encrypt'
8
+ require 'event_shipper/transport/udp'
9
+ require 'event_shipper/log_tailer'
10
+
11
+ class Shipper < Clamp::Command
12
+ option %w(-c --config), "FILE", "Location of the configuration file."
13
+
14
+ def execute
15
+ fail "Please specify a configuration file to use on the command line. (--config)" \
16
+ unless config
17
+
18
+ configuration = YAML.load_file(config)
19
+
20
+ host, port = configuration['target'].split(':')
21
+ transport = Transport::UDP.new(host, port)
22
+
23
+ if configuration['encrypt']
24
+ user, password = configuration.values_at('user', 'password')
25
+ transport.filter = Transport::Encrypt.new(user, password)
26
+ end
27
+
28
+ tailers = configuration["logs"].map do |path|
29
+ tailer = LogTailer.new(path, transport)
30
+ tailer.start
31
+
32
+ tailer
33
+ end
34
+
35
+ # Block until everyone exits.
36
+ tailers.each do |tailer|
37
+ tailer.join
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,101 @@
1
+
2
+ require 'openssl'
3
+
4
+ module EventShipper; end
5
+ module EventShipper::Transport
6
+ class Encryption
7
+ def initialize password
8
+ @password = password
9
+ @salt = generate_salt
10
+
11
+ @key = generate_key(password, @salt)
12
+ end
13
+
14
+ def generate_salt
15
+ OpenSSL::Random.random_bytes(8)
16
+ end
17
+
18
+ def generate_key password, salt
19
+ if [password, salt] == @last_key_seed
20
+ return @last_key
21
+ end
22
+
23
+ iterations = 10000
24
+ key_length = 32
25
+
26
+ @last_key_seed = [password, salt]
27
+ @last_key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(
28
+ password, salt, iterations, key_length)
29
+ end
30
+
31
+ def enc str
32
+ cipher = OpenSSL::Cipher::AES256.new(:CBC)
33
+ cipher.encrypt
34
+ # cipher.random_iv
35
+ cipher.key = @key
36
+
37
+ ciphertext = cipher.update(str) + cipher.final
38
+
39
+ "Salted__#{@salt}#{ciphertext}" #OpenSSL compatible
40
+ end
41
+ def dec str
42
+ salt = str[8..15]
43
+ str = str[16..-1]
44
+
45
+ cipher = OpenSSL::Cipher::AES256.new(:CBC)
46
+ cipher.decrypt
47
+ # cipher.random_iv
48
+ cipher.key = generate_key(@password, salt)
49
+
50
+ cipher.update(str) + cipher.final
51
+ end
52
+ end
53
+
54
+ class Encrypt
55
+ def initialize user, password
56
+ @user = user
57
+ @encryption = Encryption.new(password)
58
+ end
59
+
60
+ def call string
61
+ ciphertext = @encryption.enc string
62
+ "#{@user}/#{ciphertext}"
63
+ end
64
+ end
65
+
66
+ class Decrypt
67
+ def initialize userdb
68
+ @userdb = userdb.inject({}) do |hash, (key, value)|
69
+ hash[key] = Encryption.new(value)
70
+ hash
71
+ end
72
+ end
73
+
74
+ def call str
75
+ user, _, ciphertext = str.partition('/')
76
+
77
+ if encryption = @userdb[user]
78
+ encryption.dec(ciphertext)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ if $0 == __FILE__
85
+ enc = EventShipper::Transport::Encryption.new 'password'
86
+ str = enc.enc 'A very secret text'
87
+ puts str
88
+
89
+ dec = EventShipper::Transport::Encryption.new 'password'
90
+ puts dec.dec(str)
91
+
92
+ require 'benchmark'
93
+ puts "Doing it a 1000 times"
94
+ msg = "foobar"*20
95
+ puts Benchmark.measure {
96
+ e = EventShipper::Transport::Encrypt.new('test', 'password')
97
+ d = EventShipper::Transport::Decrypt.new('test' => 'password')
98
+ 1000.times do d.call(e.call(msg)) end
99
+ }
100
+
101
+ end
@@ -0,0 +1,54 @@
1
+ require 'socket'
2
+
3
+ module EventShipper::Transport
4
+ class UDP
5
+ def initialize host, port
6
+ @host, @port = host, port
7
+ @socket = UDPSocket.new
8
+ @filter = nil
9
+
10
+ @messages_per_period = 0
11
+ @period_start = Time.now
12
+ Thread.start do
13
+ loop do
14
+ puts "total #{@messages_per_period / (Time.now - @period_start)} msgs/s"
15
+
16
+ @messages_per_period = 0
17
+ @period_start = Time.now
18
+
19
+ sleep 10
20
+ end
21
+ end
22
+ end
23
+
24
+ attr_writer :filter
25
+
26
+ def filter string
27
+ if @filter
28
+ @filter.call(string)
29
+ else
30
+ string
31
+ end
32
+ end
33
+
34
+ def send string
35
+ @messages_per_period += 1
36
+ @socket.send filter(string),
37
+ 0, # flags...
38
+ @host, @port
39
+ end
40
+
41
+ # Enters a loop, receiving messages, yielding them to the block.
42
+ #
43
+ def dispatch
44
+ @socket.bind @host, @port
45
+ loop do
46
+ datagram, source_info = @socket.recvfrom(10 * 1024)
47
+ @messages_per_period += 1
48
+
49
+ queue, _, message = filter(datagram).partition('/')
50
+ yield queue, message
51
+ end
52
+ end
53
+ end
54
+ end
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: event-shipper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kaspar Schiess
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redis
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: clamp
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: file-tail
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: addressable
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: yajl-ruby
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: ! "\n event_shipper reads log files and sends each line in logstash
95
+ JSON format \n via encrypted UDP to redis.\n "
96
+ email: kaspar.schiess@absurd.li
97
+ executables:
98
+ - esproxy
99
+ - esshipper
100
+ extensions: []
101
+ extra_rdoc_files:
102
+ - README
103
+ files:
104
+ - HISTORY
105
+ - LICENSE
106
+ - README
107
+ - lib/event_shipper/event.rb
108
+ - lib/event_shipper/log_tailer.rb
109
+ - lib/event_shipper/proxy.rb
110
+ - lib/event_shipper/shipper.rb
111
+ - lib/event_shipper/transport/encrypt.rb
112
+ - lib/event_shipper/transport/udp.rb
113
+ - lib/event_shipper.rb
114
+ - bin/esproxy
115
+ - bin/esshipper
116
+ - bin/producer
117
+ - configs/proxy.yaml
118
+ - configs/test.yaml
119
+ homepage: https://bitbucket.org/kschiess/event_shipper
120
+ licenses: []
121
+ post_install_message:
122
+ rdoc_options:
123
+ - --main
124
+ - README
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ! '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 1.8.25
142
+ signing_key:
143
+ specification_version: 3
144
+ summary: Ships log events to logstash.
145
+ test_files: []
146
+ has_rdoc: