fog-bouncer 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ module Fog
2
+ module Bouncer
3
+ module IPPermissions
4
+ def self.from(protocols, options = {})
5
+ permissions = []
6
+
7
+ protocols.each do |protocol|
8
+ next if (options[:remote_only] && protocol.local?) ||
9
+ (options[:local_only] && protocol.remote?)
10
+
11
+ source = protocol.source
12
+ permission = permissions.find { |permission| permission["IpProtocol"] == protocol.type && permission["FromPort"] == protocol.from && permission["ToPort"] == protocol.to }
13
+
14
+ if permission.nil?
15
+ permission = { "Groups" => [], "IpRanges" => [], "IpProtocol" => protocol.type, "FromPort" => protocol.from, "ToPort" => protocol.to }
16
+ permissions << permission
17
+ end
18
+
19
+ if source.is_a?(Fog::Bouncer::Sources::CIDR)
20
+ permission["IpRanges"] << { "CidrIp" => source.range }
21
+ else
22
+ permission["Groups"] << { "UserId" => source.user_id, "GroupName" => source.name }
23
+ end
24
+ end
25
+
26
+ permissions
27
+ end
28
+
29
+ def self.to(group, permissions)
30
+ permissions.each do |permission|
31
+ remote_sources = []
32
+ remote_sources = remote_sources | permission["groups"].collect { |group| "#{group["groupName"]}@#{group["userId"]}" }
33
+ remote_sources = remote_sources | permission["ipRanges"].collect { |range| range["cidrIp"] }
34
+ remote_sources.each do |remote_source|
35
+ source = group.sources.find { |s| s.match(remote_source) }
36
+
37
+ if source.nil?
38
+ source = Sources.for(remote_source, group)
39
+ group.sources << source
40
+ end
41
+
42
+ source.remote = true
43
+
44
+ protocol = source.add_protocol(permission["ipProtocol"], Range.new(permission["fromPort"], permission["toPort"]))
45
+ protocol.remote = true
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,115 @@
1
+ module Fog
2
+ module Bouncer
3
+ class Protocol
4
+ attr_reader :from, :local, :source, :to
5
+ attr_writer :local, :remote
6
+
7
+ def self.range(port)
8
+ if port.is_a?(Range)
9
+ [port.begin, port.end]
10
+ else
11
+ [port, port]
12
+ end
13
+ end
14
+
15
+ def initialize(port, source)
16
+ @from, @to = Protocol.range(port)
17
+ @source = source
18
+ validate
19
+ end
20
+
21
+ def local
22
+ @local ||= false
23
+ end
24
+
25
+ def local?
26
+ !!local
27
+ end
28
+
29
+ def match(type, port)
30
+ type.to_s == self.type && Protocol.range(port) == [from, to]
31
+ end
32
+
33
+ def remote
34
+ @remote ||= false
35
+ end
36
+
37
+ def remote?
38
+ !!remote
39
+ end
40
+
41
+ def type
42
+ @type ||= self.class.to_s.gsub("Fog::Bouncer::Protocols::", "").downcase
43
+ end
44
+
45
+ def ==(other)
46
+ type == other.type &&
47
+ from == other.from &&
48
+ to == other.to
49
+ end
50
+
51
+ def <=>(other)
52
+ [from, to] <=> [other.from, other.to]
53
+ end
54
+
55
+ def inspect
56
+ "<#{self.class.name} @from=#{from.inspect} @to=#{to.inspect} @local=#{local} @remote=#{remote}>"
57
+ end
58
+
59
+ def to_log
60
+ { source: source.source, protocol: type, from: from, to: to }
61
+ end
62
+ end
63
+
64
+ module Protocols
65
+ class InvalidICMPType < StandardError; end
66
+ class InvalidPort < StandardError; end
67
+
68
+ class ICMP < Protocol
69
+ ICMP_MAPPING = {
70
+ all: -1,
71
+ ping: 8..0
72
+ }
73
+
74
+ ICMP_TYPE_RANGE = (-1..255)
75
+
76
+ def initialize(port, source)
77
+ if port.is_a?(Symbol) && range = ICMP_MAPPING[port]
78
+ port = range
79
+ end
80
+ super
81
+ end
82
+
83
+ def match(type, port)
84
+ if port.is_a?(Symbol) && range = ICMP_MAPPING[port]
85
+ type.to_s == self.type && Protocol.range(range) == [from, to]
86
+ else
87
+ super
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def validate
94
+ raise InvalidICMPType.new("Must be between and including -1 and 255.") unless ICMP_TYPE_RANGE.include?(from)
95
+ end
96
+ end
97
+
98
+ class TCP < Protocol
99
+ private
100
+
101
+ def validate
102
+ raise InvalidPort.new("Invalid port #{from}. Must be between and including 0 and 65535.") unless (0..65535).include?(from)
103
+ end
104
+ end
105
+
106
+ class UDP < Protocol
107
+ private
108
+
109
+ def validate
110
+ raise InvalidPort.new("Invalid port #{from}. Must be between and including 0 and 65535.") unless (0..65535).include?(from)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,88 @@
1
+ module Fog
2
+ module Bouncer
3
+ class DefinitionNotFound < StandardError; end
4
+ class SourceBlockRequired < StandardError; end
5
+ class Security
6
+ attr_reader :name, :description
7
+
8
+ def initialize(name, &block)
9
+ @name = name
10
+ @definitions = {}
11
+ @using = []
12
+ instance_eval(&block)
13
+ apply_definitions
14
+ end
15
+
16
+ def accounts
17
+ @accounts ||= { 'amazon-elb' => 'amazon-elb', 'self' => Fog::Bouncer.aws_account_id }
18
+ end
19
+
20
+ def define(name, source, &block)
21
+ raise SourceBlockRequired unless block_given?
22
+ @definitions[name] = { source: source, block: block }
23
+ end
24
+
25
+ def definitions(name)
26
+ @definitions[name] || raise(DefinitionNotFound.new("No definition found for #{name}."))
27
+ end
28
+
29
+ def extra_remote_groups
30
+ groups.select { |group| !group.local? && group.remote? }
31
+ end
32
+
33
+ def groups
34
+ @groups ||= []
35
+ end
36
+
37
+ def import_remote_groups
38
+ Fog::Bouncer.fog.security_groups.each do |remote_group|
39
+ group = group(remote_group.name, remote_group.description)
40
+ group.remote = remote_group
41
+ IPPermissions.to(group, remote_group.ip_permissions) if remote_group.ip_permissions
42
+ end
43
+ end
44
+
45
+ def missing_remote_groups
46
+ groups.select { |group| group.local? && !group.remote? }
47
+ end
48
+
49
+ def sync
50
+ GroupManager.new(self).synchronize
51
+ end
52
+
53
+ def use(name)
54
+ @using << definitions(name)
55
+ end
56
+
57
+ def clear_remote
58
+ GroupManager.new(self).clear
59
+ end
60
+
61
+ private
62
+
63
+ def account(name, account_id)
64
+ accounts[name] = account_id
65
+ end
66
+
67
+ def apply_definitions
68
+ return if @using.empty?
69
+
70
+ @using.each do |definition|
71
+ @groups.each do |group|
72
+ group.add_source(definition[:source], &definition[:block])
73
+ end
74
+ end
75
+ end
76
+
77
+ def group(name, description, &block)
78
+ group = groups.find { |group| group.name == name }
79
+ if group.nil?
80
+ group = Group.new(name, description, self, &block)
81
+ groups << group
82
+ end
83
+
84
+ group
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,87 @@
1
+ module Fog
2
+ module Bouncer
3
+ class Source
4
+ attr_reader :group, :source
5
+ attr_writer :local, :remote
6
+
7
+ def initialize(source, group, &block)
8
+ @source = source
9
+ @group = group
10
+ if block_given?
11
+ @local = true
12
+ instance_eval(&block)
13
+ end
14
+ end
15
+
16
+ def extras
17
+ protocols.select { |protocol| !protocol.local? }
18
+ end
19
+
20
+ def add_protocol(type, port)
21
+ protocol = protocols.find { |p| p.match(type, port) }
22
+ if protocol.nil?
23
+ protocol = case type.to_sym
24
+ when :icmp
25
+ Fog::Bouncer::Protocols::ICMP.new(port, self)
26
+ when :tcp
27
+ Fog::Bouncer::Protocols::TCP.new(port, self)
28
+ when :udp
29
+ Fog::Bouncer::Protocols::UDP.new(port, self)
30
+ end
31
+
32
+ protocols << protocol
33
+ end
34
+
35
+ protocol
36
+ end
37
+
38
+ def local
39
+ @local ||= false
40
+ end
41
+
42
+ def local?
43
+ !!local
44
+ end
45
+
46
+ def missing
47
+ protocols.select { |protocol| protocol.local? && !protocol.remote? }
48
+ end
49
+
50
+ def protocols
51
+ @protocols ||= []
52
+ end
53
+
54
+ def remote
55
+ @remote ||= false
56
+ end
57
+
58
+ def remote?
59
+ !!remote
60
+ end
61
+
62
+ def ==(other)
63
+ source == other.source &&
64
+ group == other.group &&
65
+ protocols.sort! == other.protocols.sort!
66
+ end
67
+
68
+ def inspect
69
+ "<#{self.class.name} @source=#{source.inspect} @local=#{local} @remote=#{remote} @protocols=#{protocols.inspect}>"
70
+ end
71
+
72
+ private
73
+
74
+ def icmp(*ports)
75
+ ports.each { |port| p = add_protocol(:icmp, port); p.local = true }
76
+ end
77
+
78
+ def tcp(*ports)
79
+ ports.each { |port| p = add_protocol(:tcp, port); p.local = true }
80
+ end
81
+
82
+ def udp(*ports)
83
+ ports.each { |port| p = add_protocol(:udp, port); p.local = true }
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,59 @@
1
+ module Fog
2
+ module Bouncer
3
+ class SourceManager
4
+ def self.log(data, &block)
5
+ Fog::Bouncer.log({source_manager: true}.merge(data), &block)
6
+ end
7
+
8
+ def log(data, &block)
9
+ self.class.log({group_name: @group.name}.merge(data), &block)
10
+ end
11
+
12
+ def initialize(group)
13
+ @group = group
14
+ end
15
+
16
+ def synchronize
17
+ log(synchronize: true) do
18
+ create_missing_source_permissions
19
+ remove_extra_source_permissions
20
+ @group.sources.each { |s| s.remote = true } unless Fog::Bouncer.pretending?
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def create_missing_source_permissions
27
+ if missing_source_permissions.any?
28
+ @group.remote.connection.authorize_security_group_ingress(@group.name, "IpPermissions" => IPPermissions.from(missing_source_permissions, :local_only => true)) unless Fog::Bouncer.pretending?
29
+ missing_source_permissions.each do |protocol|
30
+ log({authorized: true}.merge(protocol.to_log))
31
+ protocol.remote = true unless Fog::Bouncer.pretending?
32
+ end
33
+ end
34
+ end
35
+
36
+ def missing_source_permissions
37
+ @group.sources.map do |source|
38
+ source.protocols.select { |p| p.local? && !p.remote? }
39
+ end.flatten.compact
40
+ end
41
+
42
+ def remove_extra_source_permissions
43
+ if extra_source_permissions.any?
44
+ @group.remote.connection.revoke_security_group_ingress(@group.name, "IpPermissions" => IPPermissions.from(extra_source_permissions, :remote_only => true)) unless Fog::Bouncer.pretending?
45
+ extra_source_permissions.each do |protocol|
46
+ log({revoked: true}.merge(protocol.to_log))
47
+ protocol.source.protocols.delete_if { |p| p == protocol } unless Fog::Bouncer.pretending?
48
+ end
49
+ end
50
+ end
51
+
52
+ def extra_source_permissions
53
+ @group.sources.map do |source|
54
+ source.protocols.select { |p| !p.local? && p.remote? }
55
+ end.flatten.compact
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,61 @@
1
+ require "fog/bouncer/source"
2
+ require "ipaddress"
3
+
4
+ module Fog
5
+ module Bouncer
6
+ module Sources
7
+ def self.for(source, group, &block)
8
+ if source =~ /^\d+\.\d+\.\d+.\d+\/\d+$/
9
+ CIDR.new(source, group, &block)
10
+ else
11
+ Group.new(source, group, &block)
12
+ end
13
+ end
14
+
15
+ class CIDR < Fog::Bouncer::Source
16
+ def initialize(source, group, &block)
17
+ source = IPAddress::IPv4.new(source).to_string
18
+ super
19
+ end
20
+
21
+ def match(source)
22
+ range == source
23
+ end
24
+
25
+ def range
26
+ @source
27
+ end
28
+ end
29
+
30
+ class Group < Fog::Bouncer::Source
31
+ attr_reader :name, :user_alias, :user_id
32
+
33
+ def initialize(source, group, &block)
34
+ super
35
+ case source
36
+ when /^(.+)@(.+)$/
37
+ @name = $1
38
+ @user_alias = $2
39
+ if @user_alias[/^\d+$/]
40
+ @user_id = @user_alias
41
+ if account = group.security.accounts.find { |key, id| id == @user_id }
42
+ @user_alias = account[0]
43
+ end
44
+ end
45
+ else
46
+ @name = source
47
+ @user_alias = 'self'
48
+ end
49
+ end
50
+
51
+ def match(source)
52
+ "#{name}@#{user_id}" == source || name == source
53
+ end
54
+
55
+ def user_id
56
+ @user_id ||= group.security.accounts[user_alias]
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end