aws_security_viz 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 +7 -0
- data/.gitignore +33 -0
- data/.travis.yml +6 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +126 -0
- data/LICENSE.md +21 -0
- data/README.md +71 -0
- data/Rakefile +2 -0
- data/Vagrantfile +17 -0
- data/aws_security_viz.gemspec +34 -0
- data/config/boot.rb +4 -0
- data/exe/aws_security_viz +21 -0
- data/images/sample.png +0 -0
- data/lib/aws_config.rb +22 -0
- data/lib/aws_security_viz.rb +44 -0
- data/lib/color_picker.rb +18 -0
- data/lib/debug/parse_log.rb +26 -0
- data/lib/debug_graph.rb +24 -0
- data/lib/ec2/ip_permission.rb +33 -0
- data/lib/ec2/security_groups.rb +77 -0
- data/lib/ec2/traffic.rb +32 -0
- data/lib/exclusions.rb +14 -0
- data/lib/export/html/view.html +36 -0
- data/lib/graph.rb +40 -0
- data/lib/provider/ec2.rb +87 -0
- data/lib/provider/json.rb +92 -0
- data/lib/version.rb +3 -0
- data/opts.yml.sample +10 -0
- data/spec/integration/dummy.dot +49 -0
- data/spec/integration/dummy.json +64 -0
- data/spec/integration/visualize_aws_spec.rb +21 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/matchers/graph.rb +8 -0
- data/spec/visualize_aws_spec.rb +132 -0
- metadata +210 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative '../graph.rb'
|
2
|
+
require 'organic_hash'
|
3
|
+
|
4
|
+
@oh = OrganicHash.new
|
5
|
+
|
6
|
+
def h(s)
|
7
|
+
@oh.hash(s)
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def debug
|
12
|
+
g = Graph.new
|
13
|
+
File.readlines('debug-output.log').map do |l|
|
14
|
+
type, left, right = l.split(/\W+/)
|
15
|
+
if type=="node"
|
16
|
+
g.add_node(h(left))
|
17
|
+
elsif type=="edge"
|
18
|
+
g.add_edge(h(left), h(right), {})
|
19
|
+
end
|
20
|
+
end
|
21
|
+
g.output(:svg => 'test.svg', :use => 'sfdp')
|
22
|
+
end
|
23
|
+
|
24
|
+
if __FILE__ == $0
|
25
|
+
debug
|
26
|
+
end
|
data/lib/debug_graph.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
class DebugGraph
|
4
|
+
def initialize
|
5
|
+
@g = Graph.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_node(name)
|
9
|
+
@g.add_node(h(name)) if name
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_edge(from, to, opts)
|
13
|
+
@g.add_edge(h(from), h(to), opts.update(label: h(opts[:label])))
|
14
|
+
end
|
15
|
+
|
16
|
+
def output(opts)
|
17
|
+
@g.output(opts)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def h(msg)
|
22
|
+
Digest::SHA256.hexdigest msg
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'traffic.rb'
|
2
|
+
|
3
|
+
class IpPermission
|
4
|
+
def initialize(group, ip, ingress, exclusions)
|
5
|
+
@group = group
|
6
|
+
@ip = ip
|
7
|
+
@ingress = ingress
|
8
|
+
@exclusions = exclusions
|
9
|
+
end
|
10
|
+
|
11
|
+
def traffic
|
12
|
+
cidr_traffic + group_traffic
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def port_range
|
17
|
+
@ip.protocol == '-1' ? '*' : [@ip.from, @ip.to].uniq.join('-') + '/' + @ip.protocol
|
18
|
+
end
|
19
|
+
|
20
|
+
def cidr_traffic
|
21
|
+
@ip.ip_ranges.collect { |range|
|
22
|
+
Traffic.new(@ingress, range.cidr_ip, @group.name, port_range)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def group_traffic
|
27
|
+
@ip.groups
|
28
|
+
.select { |gp| !@exclusions.match(gp.name)}
|
29
|
+
.collect { |gp|
|
30
|
+
Traffic.new(@ingress, gp.name, @group.name, port_range)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'forwardable'
|
3
|
+
require_relative 'ip_permission.rb'
|
4
|
+
|
5
|
+
class SecurityGroups
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(provider, config)
|
9
|
+
@groups = provider.security_groups
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def each(&block)
|
14
|
+
groups = @groups.select { |sg| !@config.exclusions.match(sg.name) }
|
15
|
+
groups.each { |group|
|
16
|
+
if block_given?
|
17
|
+
block.call SecurityGroup.new(@groups, group, @config)
|
18
|
+
else
|
19
|
+
yield SecurityGroup.new(@groups, group, @config)
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def size
|
25
|
+
@groups.size
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class SecurityGroup
|
30
|
+
extend Forwardable
|
31
|
+
|
32
|
+
def_delegator :@group, :name
|
33
|
+
|
34
|
+
def initialize(all_groups, group, config)
|
35
|
+
@all_groups = all_groups
|
36
|
+
@group = group
|
37
|
+
@config = config
|
38
|
+
end
|
39
|
+
|
40
|
+
def permissions
|
41
|
+
ingress_permissions = @group.ip_permissions.collect { |ip|
|
42
|
+
IpPermission.new(@group, ip, true, @config.exclusions)
|
43
|
+
}
|
44
|
+
egress_permissions = @group.ip_permissions_egress.collect { |ip|
|
45
|
+
IpPermission.new(@group, ip, false, @config.exclusions)
|
46
|
+
}
|
47
|
+
ingress_permissions + egress_permissions
|
48
|
+
end
|
49
|
+
|
50
|
+
def traffic
|
51
|
+
all_traffic = permissions.collect { |permission|
|
52
|
+
permission.traffic
|
53
|
+
}.flatten.uniq
|
54
|
+
CidrGroupMapping.new(@all_groups, @config.groups).map(all_traffic)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class CidrGroupMapping
|
59
|
+
def initialize(all_groups, user_groups)
|
60
|
+
@all_groups = all_groups
|
61
|
+
@user_groups = user_groups
|
62
|
+
end
|
63
|
+
|
64
|
+
def map(all_traffic)
|
65
|
+
traffic = all_traffic.collect { |traffic|
|
66
|
+
traffic.copy(mapping(traffic.from), mapping(traffic.to))
|
67
|
+
}
|
68
|
+
traffic.uniq.group_by {|t| [t.from, t.to, t.ingress]}.collect {|k,v| Traffic.grouped(v)}.uniq
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def mapping(val)
|
73
|
+
group = @all_groups.find { |g| g.group_id == val }
|
74
|
+
name = group.nil? ? val : group.name
|
75
|
+
@user_groups[name] ? @user_groups[name] : name
|
76
|
+
end
|
77
|
+
end
|
data/lib/ec2/traffic.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class Traffic
|
2
|
+
attr_accessor :from, :to, :port_range, :ingress
|
3
|
+
|
4
|
+
def initialize(ingress, from, to, port_range)
|
5
|
+
@ingress = ingress
|
6
|
+
@from = from
|
7
|
+
@to = to
|
8
|
+
@port_range = port_range
|
9
|
+
end
|
10
|
+
|
11
|
+
def copy(from, to)
|
12
|
+
Traffic.new(@ingress, from, to, @port_range)
|
13
|
+
end
|
14
|
+
|
15
|
+
def eql?(other)
|
16
|
+
if @ingress == other.ingress
|
17
|
+
@from == other.from && @to == other.to && @port_range == other.port_range
|
18
|
+
else
|
19
|
+
@from == other.to && @to == other.from && @port_range == other.port_range
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def hash
|
24
|
+
@from.hash + @to.hash + @port_range.hash
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.grouped(traffic_list)
|
28
|
+
t = traffic_list.first
|
29
|
+
port_range = traffic_list.collect(&:port_range).uniq.join(',')
|
30
|
+
Traffic.new(t.ingress, t.from, t.to, port_range)
|
31
|
+
end
|
32
|
+
end
|
data/lib/exclusions.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.min.css">
|
4
|
+
<link rel="stylesheet" href="https://cdn.rawgit.com/mountainstorm/jquery.graphviz.svg/master/css/graphviz.svg.css">
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<div id="graph" style=""></div>
|
8
|
+
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
|
9
|
+
<script type="text/javascript" src="https://cdn.rawgit.com/jquery/jquery-mousewheel/master/jquery.mousewheel.min.js"></script>
|
10
|
+
<script type="text/javascript" src="https://cdn.rawgit.com/jquery/jquery-color/master/jquery.color.js"></script>
|
11
|
+
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js"></script>
|
12
|
+
<script type="text/javascript" src="https://cdn.rawgit.com/mountainstorm/jquery.graphviz.svg/master/js/jquery.graphviz.svg.js"></script>
|
13
|
+
<script type="text/javascript">
|
14
|
+
$(document).ready(function(){
|
15
|
+
$("#graph").graphviz({
|
16
|
+
url: "demo.svg",
|
17
|
+
ready: function() {
|
18
|
+
var gv = this
|
19
|
+
gv.nodes().click(function () {
|
20
|
+
var $set = $()
|
21
|
+
$set.push(this)
|
22
|
+
$set = $set.add(gv.linkedFrom(this, true))
|
23
|
+
gv.highlight($set, true)
|
24
|
+
gv.bringToFront($set)
|
25
|
+
})
|
26
|
+
$(document).keydown(function (evt) {
|
27
|
+
if (evt.keyCode == 27) {
|
28
|
+
gv.highlight()
|
29
|
+
}
|
30
|
+
})
|
31
|
+
}
|
32
|
+
});
|
33
|
+
});
|
34
|
+
</script>
|
35
|
+
</body>
|
36
|
+
</html>
|
data/lib/graph.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'graphviz'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
class Graph
|
5
|
+
def initialize
|
6
|
+
@g = GraphViz::new('G', :type => 'strict digraph') { |g|
|
7
|
+
g[:overlap] = :false
|
8
|
+
g[:splines] = :true
|
9
|
+
g[:sep] = 1
|
10
|
+
g[:concentrate] = :true
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_node(name)
|
15
|
+
log("node: #{name}")
|
16
|
+
@g.add_node(name) if name
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_node(name, &block)
|
20
|
+
@g.get_node(name, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_edge(from, to, opts)
|
24
|
+
log("edge: #{from} -> #{to}")
|
25
|
+
@g.add_edge(from, to, opts)
|
26
|
+
end
|
27
|
+
|
28
|
+
def each_edge(&block)
|
29
|
+
@g.each_edge(&block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def output(opts)
|
33
|
+
log("output: #{opts}")
|
34
|
+
@g.output(opts)
|
35
|
+
end
|
36
|
+
|
37
|
+
def log(msg)
|
38
|
+
puts msg if ENV["DEBUG"]
|
39
|
+
end
|
40
|
+
end
|
data/lib/provider/ec2.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'fog'
|
2
|
+
|
3
|
+
class Ec2Provider
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@compute = Fog::Compute.new(:provider => 'AWS', :aws_access_key_id => options[:access_key], :aws_secret_access_key => options[:secret_key], :region => options[:region])
|
7
|
+
end
|
8
|
+
|
9
|
+
def security_groups
|
10
|
+
@compute.security_groups.collect { |sg|
|
11
|
+
Ec2::SecurityGroup.new(sg)
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Ec2
|
17
|
+
class SecurityGroup
|
18
|
+
extend Forwardable
|
19
|
+
def_delegators :@sg, :name, :group_id
|
20
|
+
def initialize(sg)
|
21
|
+
@sg = sg
|
22
|
+
end
|
23
|
+
|
24
|
+
def ip_permissions
|
25
|
+
@sg.ip_permissions.collect { |ip|
|
26
|
+
Ec2::IpPermission.new(ip)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def ip_permissions_egress
|
31
|
+
@sg.ip_permissions_egress.collect { |ip|
|
32
|
+
Ec2::IpPermission.new(ip)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class IpPermission
|
38
|
+
def initialize(ip)
|
39
|
+
@ip = ip
|
40
|
+
end
|
41
|
+
|
42
|
+
def protocol
|
43
|
+
@ip['ipProtocol']
|
44
|
+
end
|
45
|
+
|
46
|
+
def from
|
47
|
+
@ip['fromPort']
|
48
|
+
end
|
49
|
+
|
50
|
+
def to
|
51
|
+
@ip['toPort']
|
52
|
+
end
|
53
|
+
|
54
|
+
def ip_ranges
|
55
|
+
@ip['ipRanges'].collect {|gp|
|
56
|
+
Ec2::IpPermissionRange.new(gp)
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def groups
|
61
|
+
@ip['groups'].collect {|gp|
|
62
|
+
Ec2::IpPermissionGroup.new(gp)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class IpPermissionRange
|
68
|
+
def initialize(range)
|
69
|
+
@range = range
|
70
|
+
end
|
71
|
+
|
72
|
+
def cidr_ip
|
73
|
+
@range['cidrIp']
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class IpPermissionGroup
|
78
|
+
def initialize(gp)
|
79
|
+
@gp = gp
|
80
|
+
end
|
81
|
+
|
82
|
+
def name
|
83
|
+
@gp['groupName'] || @gp['groupId']
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class JsonProvider
|
4
|
+
def initialize(options)
|
5
|
+
@groups = JSON.parse(File.read(options[:source_file]))['SecurityGroups']
|
6
|
+
end
|
7
|
+
|
8
|
+
def security_groups
|
9
|
+
@groups.collect { |sg|
|
10
|
+
Json::SecurityGroup.new(sg)
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Json
|
16
|
+
class SecurityGroup
|
17
|
+
def initialize(sg)
|
18
|
+
@sg = sg
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
@sg['GroupName']
|
23
|
+
end
|
24
|
+
|
25
|
+
def ip_permissions
|
26
|
+
@sg['IpPermissions'].collect { |ip|
|
27
|
+
Json::IpPermission.new(ip)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def group_id
|
32
|
+
@sg['GroupId']
|
33
|
+
end
|
34
|
+
|
35
|
+
def ip_permissions_egress
|
36
|
+
@sg['IpPermissionsEgress'].collect { |ip|
|
37
|
+
Json::IpPermission.new(ip)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class IpPermission
|
43
|
+
def initialize(ip)
|
44
|
+
@ip = ip
|
45
|
+
end
|
46
|
+
|
47
|
+
def protocol
|
48
|
+
@ip['IpProtocol']
|
49
|
+
end
|
50
|
+
|
51
|
+
def from
|
52
|
+
@ip['FromPort']
|
53
|
+
end
|
54
|
+
|
55
|
+
def to
|
56
|
+
@ip['ToPort']
|
57
|
+
end
|
58
|
+
|
59
|
+
def ip_ranges
|
60
|
+
@ip['IpRanges'].collect { |gp|
|
61
|
+
Json::IpPermissionRange.new(gp)
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def groups
|
66
|
+
@ip['UserIdGroupPairs'].collect { |pair|
|
67
|
+
Json::IpPermissionGroup.new(pair)
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
class IpPermissionRange
|
74
|
+
def initialize(range)
|
75
|
+
@range = range
|
76
|
+
end
|
77
|
+
|
78
|
+
def cidr_ip
|
79
|
+
@range['CidrIp']
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class IpPermissionGroup
|
84
|
+
def initialize(gp)
|
85
|
+
@gp = gp
|
86
|
+
end
|
87
|
+
|
88
|
+
def name
|
89
|
+
@gp['GroupName'] || @gp['GroupId']
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|