by2 1.0.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 +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +50 -0
- data/Rakefile +98 -0
- data/bin/by2 +16 -0
- data/by2.gemspec +32 -0
- data/config/database.yml.example +11 -0
- data/config/setup.sql +9 -0
- data/db/migrate/20140205014806_init_db.rb +147 -0
- data/db/schema.rb +160 -0
- data/lib/by2.rb +69 -0
- data/lib/by2/client.rb +109 -0
- data/lib/by2/config_loader.rb +34 -0
- data/lib/by2/ext/active_record.rb +105 -0
- data/lib/by2/models.rb +10 -0
- data/lib/by2/models/event.rb +50 -0
- data/lib/by2/models/icmphdr.rb +10 -0
- data/lib/by2/models/iphdr.rb +38 -0
- data/lib/by2/models/payload.rb +16 -0
- data/lib/by2/models/tcphdr.rb +30 -0
- data/lib/by2/models/udphdr.rb +30 -0
- data/lib/by2/options.rb +77 -0
- data/lib/by2/utils.rb +22 -0
- data/lib/by2/version.rb +3 -0
- data/man/by2.1 +105 -0
- data/man/by2.1.ronn +98 -0
- data/man/by2.1.txt +107 -0
- data/spec/by2/client_spec.rb +157 -0
- data/spec/by2/models/event_spec.rb +14 -0
- data/spec/by2/options_spec.rb +107 -0
- data/spec/by2/utils_spec.rb +19 -0
- data/spec/fixtures/data.yml +19 -0
- data/spec/fixtures/event.yml +36 -0
- data/spec/fixtures/icmphdr.yml +7 -0
- data/spec/fixtures/iphdr.yml +108 -0
- data/spec/fixtures/tcphdr.yml +55 -0
- data/spec/fixtures/udphdr.yml +7 -0
- data/spec/spec_helper.rb +55 -0
- metadata +235 -0
data/lib/by2.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'yaml'
|
3
|
+
require "optparse"
|
4
|
+
require 'ostruct'
|
5
|
+
require 'pg'
|
6
|
+
require 'active_record'
|
7
|
+
require 'composite_primary_keys'
|
8
|
+
|
9
|
+
require_relative 'by2/version'
|
10
|
+
require_relative 'by2/options'
|
11
|
+
require_relative 'by2/config_loader'
|
12
|
+
require_relative 'by2/utils'
|
13
|
+
require_relative 'by2/ext/active_record'
|
14
|
+
require_relative 'by2/models'
|
15
|
+
require_relative 'by2/client'
|
16
|
+
|
17
|
+
|
18
|
+
module By2
|
19
|
+
def self.root
|
20
|
+
@root ||= File.expand_path(File.join(__FILE__, '..', '..'))
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.config
|
24
|
+
@config ||= ConfigLoader.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.fixtures_dir
|
28
|
+
File.join(root, "spec", "fixtures")
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.db_connect
|
32
|
+
@conn ||= begin
|
33
|
+
conn_config = config.load("database.yml")
|
34
|
+
#ActiveSupport::Deprecation.silenced = true
|
35
|
+
ActiveRecord::Base.default_timezone = :local
|
36
|
+
ActiveRecord::Base.establish_connection(conn_config[env])
|
37
|
+
ActiveRecord::Base.connection
|
38
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT) if debug?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.env
|
43
|
+
@env ||= (ENV['BY2_ENV'] || config.load("env.yml", false) || "development")
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.debug?
|
47
|
+
@debug ||= false
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.debug=(d)
|
51
|
+
@debug = d
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.debug(msg)
|
55
|
+
$stdout.puts(msg) if debug?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
By2.db_connect
|
61
|
+
|
62
|
+
=begin
|
63
|
+
require "./lib/by2"
|
64
|
+
By2.db_connect
|
65
|
+
cli = By2::Client.new([])
|
66
|
+
ip = By2::Utils.int32_to_ip(2154959208)
|
67
|
+
|
68
|
+
q = cli.find_entries({ip: ip});
|
69
|
+
=end
|
data/lib/by2/client.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
module By2
|
2
|
+
class Client
|
3
|
+
include Models
|
4
|
+
|
5
|
+
def initialize(argv=[])
|
6
|
+
@opts = Options.parse(argv)
|
7
|
+
|
8
|
+
By2.debug = @opts.delete(:debug)
|
9
|
+
By2.db_connect
|
10
|
+
By2.debug(@opts.inspect)
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# payload: sid: 1, cid: 5
|
15
|
+
# 2149599422 => "128.32.72.190"
|
16
|
+
# 2954912804 => "176.32.100.36"
|
17
|
+
def find_records(options = {})
|
18
|
+
@opts = @opts.merge(options)
|
19
|
+
tables = %w(iphdr tcphdr udphdr icmphdr payload)
|
20
|
+
|
21
|
+
query = Event.
|
22
|
+
includes(*tables).
|
23
|
+
references(*tables).
|
24
|
+
order("event.timestamp")
|
25
|
+
|
26
|
+
query.
|
27
|
+
merge(ip_src_or_dst).
|
28
|
+
merge(port_src_or_dst).
|
29
|
+
merge(port_src).
|
30
|
+
merge(port_dst).
|
31
|
+
merge(ip_src).
|
32
|
+
merge(ip_dst).
|
33
|
+
merge(date).
|
34
|
+
merge(date_range)
|
35
|
+
end
|
36
|
+
|
37
|
+
def run
|
38
|
+
records = find_records
|
39
|
+
|
40
|
+
unless @opts[:count]
|
41
|
+
records.each { |r| $stdout.puts(terminal(r)) }
|
42
|
+
end
|
43
|
+
|
44
|
+
$stdout.puts(record_separator)
|
45
|
+
$stdout.puts(record_count(records.count))
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def record_separator
|
51
|
+
("-" * 80)
|
52
|
+
end
|
53
|
+
|
54
|
+
def record_count(n)
|
55
|
+
"Total records: #{n}\n\n"
|
56
|
+
end
|
57
|
+
|
58
|
+
def terminal(event)
|
59
|
+
e = event
|
60
|
+
("-" * 80) + "\n" \
|
61
|
+
"[#{e.timestamp}] #{e.iphdr.ipaddr_src}:#{e.sport} -> #{e.iphdr.ipaddr_dst}:#{e.dport} (#{e.transport})\n\n" \
|
62
|
+
"#{(e.payload && e.payload.to_s.strip) || "[no payload]"}\n\n"
|
63
|
+
end
|
64
|
+
|
65
|
+
def all
|
66
|
+
Event.all
|
67
|
+
end
|
68
|
+
|
69
|
+
def ip_src_or_dst
|
70
|
+
return all if @opts[:ip].nil?
|
71
|
+
Iphdr.ip_src_or_dst(@opts[:ip])
|
72
|
+
end
|
73
|
+
|
74
|
+
def port_src_or_dst
|
75
|
+
return all if @opts[:port].nil?
|
76
|
+
Tcphdr.src_or_dst_port(@opts[:port]).or(Udphdr.src_or_dst_port(@opts[:port]))
|
77
|
+
end
|
78
|
+
|
79
|
+
def port_src
|
80
|
+
return all if @opts[:src_port].nil?
|
81
|
+
Tcphdr.src_port(@opts[:src_port]).or(Udphdr.src_port(@opts[:src_port]))
|
82
|
+
end
|
83
|
+
|
84
|
+
def port_dst
|
85
|
+
return all if @opts[:dst_port].nil?
|
86
|
+
Tcphdr.dst_port(@opts[:dst_port]).or(Udphdr.dst_port(@opts[:dst_port]))
|
87
|
+
end
|
88
|
+
|
89
|
+
def ip_src
|
90
|
+
return all if @opts[:src_ip].nil?
|
91
|
+
Iphdr.ip_src(@opts[:src_ip])
|
92
|
+
end
|
93
|
+
|
94
|
+
def ip_dst
|
95
|
+
return all if @opts[:dst_ip].nil?
|
96
|
+
Iphdr.ip_dst(@opts[:dst_ip])
|
97
|
+
end
|
98
|
+
|
99
|
+
def date
|
100
|
+
return all if @opts[:date].nil?
|
101
|
+
Event.on_date(@opts[:date])
|
102
|
+
end
|
103
|
+
|
104
|
+
def date_range
|
105
|
+
return all if @opts[:start_date].nil?
|
106
|
+
Event.in_date_range(@opts[:start_date], @opts[:end_date])
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module By2
|
2
|
+
class ConfigLoader
|
3
|
+
attr_accessor :search_paths
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@search_paths ||= [app_config, user_config]
|
7
|
+
end
|
8
|
+
|
9
|
+
def load(config_file, required = false)
|
10
|
+
search_paths.each do |path|
|
11
|
+
file = File.expand_path(File.join(path, config_file))
|
12
|
+
By2.debug("Searching for #{file} in #{path}")
|
13
|
+
|
14
|
+
if File.exists?(file)
|
15
|
+
By2.debug("Loaded: #{file}")
|
16
|
+
return YAML.load_file(file)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
By2.debug("Could not load: #{config_file}")
|
21
|
+
required ? raise : nil
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def app_config
|
27
|
+
@app_config ||= File.expand_path(File.join(::By2.root, "config"))
|
28
|
+
end
|
29
|
+
|
30
|
+
def user_config
|
31
|
+
@user_config ||= File.expand_path(File.join(ENV['HOME'], ".by2"))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# At some point this should be merged into ActiveRecord or a gem should
|
2
|
+
# become available with this same functionality.
|
3
|
+
#
|
4
|
+
# Original gist: https://gist.github.com/j-mcnally/250eaaceef234dd8971b
|
5
|
+
# Github Pull Request: https://github.com/rails/rails/pull/9052
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
module Querying
|
9
|
+
delegate :or, :to => :all
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ActiveRecord
|
14
|
+
module QueryMethods
|
15
|
+
# OrChain objects act as placeholder for queries in which #or does not have any parameter.
|
16
|
+
# In this case, #or must be chained with any other relation method to return a new relation.
|
17
|
+
# It is intended to allow .or.where() and .or.named_scope.
|
18
|
+
class OrChain
|
19
|
+
def initialize(scope)
|
20
|
+
@scope = scope
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(method, *args, &block)
|
24
|
+
right_relation = @scope.klass.unscoped do
|
25
|
+
@scope.klass.send(method, *args, &block)
|
26
|
+
end
|
27
|
+
@scope.or(right_relation)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns a new relation, which is the result of filtering the current relation
|
32
|
+
# according to the conditions in the arguments, joining WHERE clauses with OR
|
33
|
+
# operand, contrary to the default behaviour that uses AND.
|
34
|
+
#
|
35
|
+
# #or accepts conditions in one of several formats. In the examples below, the resulting
|
36
|
+
# SQL is given as an illustration; the actual query generated may be different depending
|
37
|
+
# on the database adapter.
|
38
|
+
#
|
39
|
+
# === without arguments
|
40
|
+
#
|
41
|
+
# If #or is used without arguments, it returns an ActiveRecord::OrChain object that can
|
42
|
+
# be used to chain queries with any other relation method, like where:
|
43
|
+
#
|
44
|
+
# Post.where("id = 1").or.where("id = 2")
|
45
|
+
# # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2'))
|
46
|
+
#
|
47
|
+
# It can also be chained with a named scope:
|
48
|
+
#
|
49
|
+
# Post.where("id = 1").or.containing_the_letter_a
|
50
|
+
# # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'body LIKE \\'%a%\\''))
|
51
|
+
#
|
52
|
+
# === ActiveRecord::Relation
|
53
|
+
#
|
54
|
+
# When #or is used with an ActiveRecord::Relation as an argument, it merges the two
|
55
|
+
# relations, with the exception of the WHERE clauses, that are joined using the OR
|
56
|
+
# operand.
|
57
|
+
#
|
58
|
+
# Post.where("id = 1").or(Post.where("id = 2"))
|
59
|
+
# # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2'))
|
60
|
+
#
|
61
|
+
# === anything you would pass to #where
|
62
|
+
#
|
63
|
+
# #or also accepts anything that could be passed to the #where method, as
|
64
|
+
# a shortcut:
|
65
|
+
#
|
66
|
+
# Post.where("id = 1").or("id = ?", 2)
|
67
|
+
# # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2'))
|
68
|
+
#
|
69
|
+
def or(opts = :chain, *rest)
|
70
|
+
if opts == :chain
|
71
|
+
OrChain.new(self)
|
72
|
+
else
|
73
|
+
left = with_default_scope
|
74
|
+
right = (ActiveRecord::Relation === opts) ? opts : klass.unscoped.where(opts, rest)
|
75
|
+
|
76
|
+
unless left.where_values.empty? || right.where_values.empty?
|
77
|
+
left.where_values = [left.where_ast.or(right.where_ast)]
|
78
|
+
right.where_values = []
|
79
|
+
end
|
80
|
+
|
81
|
+
left = left.merge(right)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Returns an Arel AST containing only where_values
|
87
|
+
def where_ast
|
88
|
+
arel_wheres = []
|
89
|
+
|
90
|
+
where_values.each do |where|
|
91
|
+
arel_wheres << (String === where ? Arel.sql(where) : where)
|
92
|
+
end
|
93
|
+
|
94
|
+
return Arel::Nodes::And.new(arel_wheres) if arel_wheres.length >= 2
|
95
|
+
|
96
|
+
if Arel::Nodes::SqlLiteral === arel_wheres.first
|
97
|
+
Arel::Nodes::Grouping.new(arel_wheres.first)
|
98
|
+
else
|
99
|
+
arel_wheres.first
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
data/lib/by2/models.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module By2
|
2
|
+
module Models
|
3
|
+
|
4
|
+
class Event < ActiveRecord::Base
|
5
|
+
self.table_name = 'event'
|
6
|
+
self.primary_keys = :sid, :cid
|
7
|
+
|
8
|
+
has_one :payload, :foreign_key => [:sid, :cid]
|
9
|
+
has_one :tcphdr, :foreign_key => [:sid, :cid]
|
10
|
+
has_one :udphdr, :foreign_key => [:sid, :cid]
|
11
|
+
has_one :icmphdr, :foreign_key => [:sid, :cid]
|
12
|
+
has_one :iphdr, :foreign_key => [:sid, :cid]
|
13
|
+
|
14
|
+
|
15
|
+
def self.on_date(date)
|
16
|
+
where("cast(\"timestamp\" as date) = ?", date)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.in_date_range(start_date, end_date)
|
20
|
+
query = where("cast(\"timestamp\" as date) >= ?", start_date)
|
21
|
+
query = query.merge(where("cast(\"timestamp\" as date) <= ?", end_date)) if end_date
|
22
|
+
query
|
23
|
+
end
|
24
|
+
|
25
|
+
def transport
|
26
|
+
return "TCP" if tcphdr
|
27
|
+
return "UCP" if udphdr
|
28
|
+
return "ICMP" if icmphdr
|
29
|
+
|
30
|
+
"UNKNOWN"
|
31
|
+
end
|
32
|
+
|
33
|
+
def dport
|
34
|
+
tcphdr.try(:dport) || udphdr.try(:dport)
|
35
|
+
end
|
36
|
+
|
37
|
+
def sport
|
38
|
+
tcphdr.try(:sport) || udphdr.try(:sport)
|
39
|
+
end
|
40
|
+
|
41
|
+
def ip_src
|
42
|
+
iphdr.try(:ipaddr_src)
|
43
|
+
end
|
44
|
+
|
45
|
+
def ip_dst
|
46
|
+
iphdr.try(:ipaddr_dst)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module By2
|
2
|
+
module Models
|
3
|
+
|
4
|
+
class Iphdr < ActiveRecord::Base
|
5
|
+
self.table_name = 'iphdr'
|
6
|
+
self.primary_keys = :sid, :cid
|
7
|
+
|
8
|
+
belongs_to :event, :foreign_key => [:sid, :cid]
|
9
|
+
|
10
|
+
|
11
|
+
def self.ip_src_or_dst(ip)
|
12
|
+
where("iphdr.ip_dst = ? or iphdr.ip_src = ?", int32(ip), int32(ip))
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.ip_src(ip)
|
16
|
+
where("iphdr.ip_src = ?", int32(ip))
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.ip_dst(ip)
|
20
|
+
where("iphdr.ip_dst = ?", int32(ip))
|
21
|
+
end
|
22
|
+
|
23
|
+
def ipaddr_src
|
24
|
+
Utils.int32_to_ip(self.ip_src)
|
25
|
+
end
|
26
|
+
|
27
|
+
def ipaddr_dst
|
28
|
+
Utils.int32_to_ip(self.ip_dst)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def self.int32(ip)
|
34
|
+
Utils.ip_to_int32(ip)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module By2
|
2
|
+
module Models
|
3
|
+
|
4
|
+
class Payload < ActiveRecord::Base
|
5
|
+
self.table_name = 'data'
|
6
|
+
self.primary_keys = :sid, :cid
|
7
|
+
|
8
|
+
belongs_to :event, :foreign_key => [:sid, :cid]
|
9
|
+
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
Utils.hex_to_ascii(self.data_payload)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|