logstash-filter-cipher 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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YTVhMTMwYjM4Zjk5ZDc5YWRjNjcyNDRmZDhiNmQwYzBiODNlYWM5Yw==
5
+ data.tar.gz: !binary |-
6
+ MGZkMzNjOTk5ZGFlMzdjMDhlNjU4NzhlZThlM2UwMWE3YWEwNTYwYw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MGZjNTBkMzE4MDc5NzE0ODA4NTczMTc5Y2I1MWZkYzdmMjVmNDg4MDBkZjJl
10
+ ZjNkYWU2NDYwNzRlNzJiMjNiZTViNDY1YjdmMWE5YTJmMTQ1NzNiZjVhNTJm
11
+ MDk2MjdiNGJmMDhiMDE3MmVmYjYyYTEwNzg3Yzc2NzgxMTY1MzY=
12
+ data.tar.gz: !binary |-
13
+ MGMwZTkxMDYwNmQwNjc5MDFiZTM2ZDZkMTgxMWM5MzM5NWJiZGIyMjk5ZTQ2
14
+ MjdjZGI3YTNhZmI0NTRhNjIxMWFmYzEzMWY3NGRkYmE1MzA0NDZmMzljNjJk
15
+ NmM5NDI3YTFkMzA2ZTBjNDc3OTIzMTQ5ODJiOGQ4ZjliY2U3MDg=
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ Gemfile.lock
3
+ .bundle
4
+ vendor
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+ gem 'rake'
3
+ gem 'gem_publisher'
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ @files=[]
2
+
3
+ task :default do
4
+ system("rake -T")
5
+ end
6
+
@@ -0,0 +1,146 @@
1
+ # encoding: utf-8
2
+ require "logstash/filters/base"
3
+ require "logstash/namespace"
4
+
5
+
6
+ # This filter parses a source and apply a cipher or decipher before
7
+ # storing it in the target.
8
+ #
9
+ class LogStash::Filters::Cipher < LogStash::Filters::Base
10
+ config_name "cipher"
11
+ milestone 1
12
+
13
+ # The field to perform filter
14
+ #
15
+ # Example, to use the @message field (default) :
16
+ #
17
+ # filter { cipher { source => "message" } }
18
+ config :source, :validate => :string, :default => "message"
19
+
20
+ # The name of the container to put the result
21
+ #
22
+ # Example, to place the result into crypt :
23
+ #
24
+ # filter { cipher { target => "crypt" } }
25
+ config :target, :validate => :string, :default => "message"
26
+
27
+ # Do we have to perform a base64 decode or encode?
28
+ #
29
+ # If we are decrypting, base64 decode will be done before.
30
+ # If we are encrypting, base64 will be done after.
31
+ #
32
+ config :base64, :validate => :boolean, :default => true
33
+
34
+ # The key to use
35
+ config :key, :validate => :string
36
+
37
+ # The key size to pad
38
+ #
39
+ # It depends of the cipher algorythm.I your key don't need
40
+ # padding, don't set this parameter
41
+ #
42
+ # Example, for AES-256, we must have 32 char long key
43
+ # filter { cipher { key_size => 32 }
44
+ #
45
+ config :key_size, :validate => :number, :default => 32
46
+
47
+ # The character used to pad the key
48
+ config :key_pad, :default => "\0"
49
+
50
+ # The cipher algorythm
51
+ #
52
+ # A list of supported algorithms can be obtained by
53
+ #
54
+ # puts OpenSSL::Cipher.ciphers
55
+ config :algorithm, :validate => :string, :required => true
56
+
57
+ # Encrypting or decrypting some data
58
+ #
59
+ # Valid values are encrypt or decrypt
60
+ config :mode, :validate => :string, :required => true
61
+
62
+ # Cypher padding to use. Enables or disables padding.
63
+ #
64
+ # By default encryption operations are padded using standard block padding
65
+ # and the padding is checked and removed when decrypting. If the pad
66
+ # parameter is zero then no padding is performed, the total amount of data
67
+ # encrypted or decrypted must then be a multiple of the block size or an
68
+ # error will occur.
69
+ #
70
+ # See EVP_CIPHER_CTX_set_padding for further information.
71
+ #
72
+ # We are using Openssl jRuby which uses default padding to PKCS5Padding
73
+ # If you want to change it, set this parameter. If you want to disable
74
+ # it, Set this parameter to 0
75
+ # filter { cipher { padding => 0 }}
76
+ config :cipher_padding, :validate => :string
77
+
78
+ # The initialization vector to use
79
+ #
80
+ # The cipher modes CBC, CFB, OFB and CTR all need an "initialization
81
+ # vector", or short, IV. ECB mode is the only mode that does not require
82
+ # an IV, but there is almost no legitimate use case for this mode
83
+ # because of the fact that it does not sufficiently hide plaintext patterns.
84
+ config :iv, :validate => :string
85
+
86
+ def register
87
+ require 'base64' if @base64
88
+ init_cipher
89
+ end # def register
90
+
91
+
92
+ def filter(event)
93
+ return unless filter?(event)
94
+
95
+
96
+ #If decrypt or encrypt fails, we keep it it intact.
97
+ begin
98
+ #@logger.debug("Event to filter", :event => event)
99
+ data = event[@source]
100
+ if @mode == "decrypt"
101
+ data = Base64.decode64(data) if @base64 == true
102
+ end
103
+ result = @cipher.update(data) + @cipher.final
104
+ if @mode == "encrypt"
105
+ data = Base64.encode64(data) if @base64 == true
106
+ end
107
+ rescue => e
108
+ @logger.warn("Exception catch on cipher filter", :event => event, :error => e)
109
+ else
110
+ event[@target]= result
111
+ #Is it necessary to add 'if !result.nil?' ? exception have been already catched.
112
+ #In doubt, I keep it.
113
+ filter_matched(event) if !result.nil?
114
+ #Too much bad result can be a problem, reinit cipher prevent this.
115
+ init_cipher
116
+ end
117
+ end # def filter
118
+
119
+ def init_cipher
120
+
121
+ @cipher = OpenSSL::Cipher.new(@algorithm)
122
+ if @mode == "encrypt"
123
+ @cipher.encrypt
124
+ elsif @mode == "decrypt"
125
+ @cipher.decrypt
126
+ else
127
+ @logger.error("Invalid cipher mode. Valid values are \"encrypt\" or \"decrypt\"", :mode => @mode)
128
+ raise "Bad configuration, aborting."
129
+ end
130
+
131
+ if @key.length != @key_size
132
+ @logger.debug("key length is " + @key.length.to_s + ", padding it to " + @key_size.to_s + " with '" + @key_pad.to_s + "'")
133
+ @key = @key[0,@key_size].ljust(@key_size,@key_pad)
134
+ end
135
+
136
+ @cipher.key = @key
137
+
138
+ @cipher.iv = @iv if @iv
139
+
140
+ @cipher.padding = @cipher_padding if @cipher_padding
141
+
142
+ @logger.debug("Cipher initialisation done", :mode => @mode, :key => @key, :iv => @iv, :cipher_padding => @cipher_padding)
143
+ end # def init_cipher
144
+
145
+
146
+ end # class LogStash::Filters::Cipher
@@ -0,0 +1,26 @@
1
+ Gem::Specification.new do |s|
2
+
3
+ s.name = 'logstash-filter-cipher'
4
+ s.version = '0.1.0'
5
+ s.licenses = ['Apache License (2.0)']
6
+ s.summary = "This filter parses a source and apply a cipher or decipher before storing it in the target"
7
+ s.description = "This filter parses a source and apply a cipher or decipher before storing it in the target"
8
+ s.authors = ["Elasticsearch"]
9
+ s.email = 'richard.pijnenburg@elasticsearch.com'
10
+ s.homepage = "http://logstash.net/"
11
+ s.require_paths = ["lib"]
12
+
13
+ # Files
14
+ s.files = `git ls-files`.split($\)+::Dir.glob('vendor/*')
15
+
16
+ # Tests
17
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
+
19
+ # Special flag to let us know this is actually a logstash plugin
20
+ s.metadata = { "logstash_plugin" => "true", "group" => "filter" }
21
+
22
+ # Gem dependencies
23
+ s.add_runtime_dependency 'logstash', '>= 1.4.0', '< 2.0.0'
24
+
25
+ end
26
+
@@ -0,0 +1,9 @@
1
+ require "gem_publisher"
2
+
3
+ desc "Publish gem to RubyGems.org"
4
+ task :publish_gem do |t|
5
+ gem_file = Dir.glob(File.expand_path('../*.gemspec',File.dirname(__FILE__))).first
6
+ gem = GemPublisher.publish_if_updated(gem_file, :rubygems)
7
+ puts "Published #{gem}" if gem
8
+ end
9
+
@@ -0,0 +1,169 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "digest/sha1"
4
+
5
+ def vendor(*args)
6
+ return File.join("vendor", *args)
7
+ end
8
+
9
+ directory "vendor/" => ["vendor"] do |task, args|
10
+ mkdir task.name
11
+ end
12
+
13
+ def fetch(url, sha1, output)
14
+
15
+ puts "Downloading #{url}"
16
+ actual_sha1 = download(url, output)
17
+
18
+ if actual_sha1 != sha1
19
+ fail "SHA1 does not match (expected '#{sha1}' but got '#{actual_sha1}')"
20
+ end
21
+ end # def fetch
22
+
23
+ def file_fetch(url, sha1)
24
+ filename = File.basename( URI(url).path )
25
+ output = "vendor/#{filename}"
26
+ task output => [ "vendor/" ] do
27
+ begin
28
+ actual_sha1 = file_sha1(output)
29
+ if actual_sha1 != sha1
30
+ fetch(url, sha1, output)
31
+ end
32
+ rescue Errno::ENOENT
33
+ fetch(url, sha1, output)
34
+ end
35
+ end.invoke
36
+
37
+ return output
38
+ end
39
+
40
+ def file_sha1(path)
41
+ digest = Digest::SHA1.new
42
+ fd = File.new(path, "r")
43
+ while true
44
+ begin
45
+ digest << fd.sysread(16384)
46
+ rescue EOFError
47
+ break
48
+ end
49
+ end
50
+ return digest.hexdigest
51
+ ensure
52
+ fd.close if fd
53
+ end
54
+
55
+ def download(url, output)
56
+ uri = URI(url)
57
+ digest = Digest::SHA1.new
58
+ tmp = "#{output}.tmp"
59
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == "https")) do |http|
60
+ request = Net::HTTP::Get.new(uri.path)
61
+ http.request(request) do |response|
62
+ fail "HTTP fetch failed for #{url}. #{response}" if [200, 301].include?(response.code)
63
+ size = (response["content-length"].to_i || -1).to_f
64
+ count = 0
65
+ File.open(tmp, "w") do |fd|
66
+ response.read_body do |chunk|
67
+ fd.write(chunk)
68
+ digest << chunk
69
+ if size > 0 && $stdout.tty?
70
+ count += chunk.bytesize
71
+ $stdout.write(sprintf("\r%0.2f%%", count/size * 100))
72
+ end
73
+ end
74
+ end
75
+ $stdout.write("\r \r") if $stdout.tty?
76
+ end
77
+ end
78
+
79
+ File.rename(tmp, output)
80
+
81
+ return digest.hexdigest
82
+ rescue SocketError => e
83
+ puts "Failure while downloading #{url}: #{e}"
84
+ raise
85
+ ensure
86
+ File.unlink(tmp) if File.exist?(tmp)
87
+ end # def download
88
+
89
+ def untar(tarball, &block)
90
+ require "archive/tar/minitar"
91
+ tgz = Zlib::GzipReader.new(File.open(tarball))
92
+ # Pull out typesdb
93
+ tar = Archive::Tar::Minitar::Input.open(tgz)
94
+ tar.each do |entry|
95
+ path = block.call(entry)
96
+ next if path.nil?
97
+ parent = File.dirname(path)
98
+
99
+ mkdir_p parent unless File.directory?(parent)
100
+
101
+ # Skip this file if the output file is the same size
102
+ if entry.directory?
103
+ mkdir path unless File.directory?(path)
104
+ else
105
+ entry_mode = entry.instance_eval { @mode } & 0777
106
+ if File.exists?(path)
107
+ stat = File.stat(path)
108
+ # TODO(sissel): Submit a patch to archive-tar-minitar upstream to
109
+ # expose headers in the entry.
110
+ entry_size = entry.instance_eval { @size }
111
+ # If file sizes are same, skip writing.
112
+ next if stat.size == entry_size && (stat.mode & 0777) == entry_mode
113
+ end
114
+ puts "Extracting #{entry.full_name} from #{tarball} #{entry_mode.to_s(8)}"
115
+ File.open(path, "w") do |fd|
116
+ # eof? check lets us skip empty files. Necessary because the API provided by
117
+ # Archive::Tar::Minitar::Reader::EntryStream only mostly acts like an
118
+ # IO object. Something about empty files in this EntryStream causes
119
+ # IO.copy_stream to throw "can't convert nil into String" on JRuby
120
+ # TODO(sissel): File a bug about this.
121
+ while !entry.eof?
122
+ chunk = entry.read(16384)
123
+ fd.write(chunk)
124
+ end
125
+ #IO.copy_stream(entry, fd)
126
+ end
127
+ File.chmod(entry_mode, path)
128
+ end
129
+ end
130
+ tar.close
131
+ File.unlink(tarball) if File.file?(tarball)
132
+ end # def untar
133
+
134
+ def ungz(file)
135
+
136
+ outpath = file.gsub('.gz', '')
137
+ tgz = Zlib::GzipReader.new(File.open(file))
138
+ begin
139
+ File.open(outpath, "w") do |out|
140
+ IO::copy_stream(tgz, out)
141
+ end
142
+ File.unlink(file)
143
+ rescue
144
+ File.unlink(outpath) if File.file?(outpath)
145
+ raise
146
+ end
147
+ tgz.close
148
+ end
149
+
150
+ desc "Process any vendor files required for this plugin"
151
+ task "vendor" do |task, args|
152
+
153
+ @files.each do |file|
154
+ download = file_fetch(file['url'], file['sha1'])
155
+ if download =~ /.tar.gz/
156
+ prefix = download.gsub('.tar.gz', '').gsub('vendor/', '')
157
+ untar(download) do |entry|
158
+ if !file['files'].nil?
159
+ next unless file['files'].include?(entry.full_name.gsub(prefix, ''))
160
+ out = entry.full_name.split("/").last
161
+ end
162
+ File.join('vendor', out)
163
+ end
164
+ elsif download =~ /.gz/
165
+ ungz(download)
166
+ end
167
+ end
168
+
169
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+ require 'logstash/filters/cipher'
3
+
4
+ describe LogStash::Filters::Cipher do
5
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-filter-cipher
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Elasticsearch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logstash
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.4.0
20
+ - - <
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.4.0
30
+ - - <
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.0
33
+ description: This filter parses a source and apply a cipher or decipher before storing
34
+ it in the target
35
+ email: richard.pijnenburg@elasticsearch.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - .gitignore
41
+ - Gemfile
42
+ - Rakefile
43
+ - lib/logstash/filters/cipher.rb
44
+ - logstash-filter-cipher.gemspec
45
+ - rakelib/publish.rake
46
+ - rakelib/vendor.rake
47
+ - spec/filters/cipher_spec.rb
48
+ homepage: http://logstash.net/
49
+ licenses:
50
+ - Apache License (2.0)
51
+ metadata:
52
+ logstash_plugin: 'true'
53
+ group: filter
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 2.4.1
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: This filter parses a source and apply a cipher or decipher before storing
74
+ it in the target
75
+ test_files:
76
+ - spec/filters/cipher_spec.rb