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.
@@ -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
@@ -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
@@ -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,14 @@
1
+ class Exclusions
2
+ attr_reader :patterns
3
+
4
+ def initialize(patterns)
5
+ @patterns = patterns.map {|p| /#{p}/} unless patterns.nil?
6
+ end
7
+
8
+ def match(str)
9
+ return false if patterns.nil?
10
+ patterns.any? { |p|
11
+ p.match(str)
12
+ }
13
+ end
14
+ end
@@ -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
@@ -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