fog-bouncer 0.0.6

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,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