cumulus-aws 0.11.1

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.
Files changed (173) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +12 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +29 -0
  6. data/LICENSE +202 -0
  7. data/README.md +41 -0
  8. data/autocomplete +137 -0
  9. data/bin/cumulus +658 -0
  10. data/cumulus +2 -0
  11. data/cumulus-aws.gemspec +20 -0
  12. data/lib/autoscaling/AutoScaling.rb +40 -0
  13. data/lib/autoscaling/loader/Loader.rb +56 -0
  14. data/lib/autoscaling/manager/Manager.rb +360 -0
  15. data/lib/autoscaling/models/AlarmConfig.rb +165 -0
  16. data/lib/autoscaling/models/AlarmDiff.rb +172 -0
  17. data/lib/autoscaling/models/AutoScalingDiff.rb +178 -0
  18. data/lib/autoscaling/models/GroupConfig.rb +330 -0
  19. data/lib/autoscaling/models/PolicyConfig.rb +135 -0
  20. data/lib/autoscaling/models/PolicyDiff.rb +73 -0
  21. data/lib/autoscaling/models/ScheduledActionDiff.rb +53 -0
  22. data/lib/autoscaling/models/ScheduledConfig.rb +96 -0
  23. data/lib/aws_extensions/ec2/DhcpOptions.rb +41 -0
  24. data/lib/aws_extensions/ec2/Instance.rb +29 -0
  25. data/lib/aws_extensions/ec2/NetworkAcl.rb +25 -0
  26. data/lib/aws_extensions/ec2/NetworkInterface.rb +14 -0
  27. data/lib/aws_extensions/ec2/RouteTable.rb +26 -0
  28. data/lib/aws_extensions/ec2/SecurityGroup.rb +16 -0
  29. data/lib/aws_extensions/ec2/Subnet.rb +28 -0
  30. data/lib/aws_extensions/ec2/Volume.rb +24 -0
  31. data/lib/aws_extensions/ec2/Vpc.rb +14 -0
  32. data/lib/aws_extensions/ec2/VpcEndpoint.rb +11 -0
  33. data/lib/aws_extensions/elb/BackendServerDescription.rb +12 -0
  34. data/lib/aws_extensions/elb/PolicyDescription.rb +14 -0
  35. data/lib/aws_extensions/kinesis/StreamDescription.rb +12 -0
  36. data/lib/aws_extensions/route53/AliasTarget.rb +21 -0
  37. data/lib/aws_extensions/s3/Bucket.rb +33 -0
  38. data/lib/aws_extensions/s3/BucketAcl.rb +28 -0
  39. data/lib/aws_extensions/s3/BucketCors.rb +17 -0
  40. data/lib/aws_extensions/s3/BucketLifecycle.rb +21 -0
  41. data/lib/aws_extensions/s3/BucketLogging.rb +18 -0
  42. data/lib/aws_extensions/s3/BucketNotification.rb +23 -0
  43. data/lib/aws_extensions/s3/BucketPolicy.rb +18 -0
  44. data/lib/aws_extensions/s3/BucketTagging.rb +15 -0
  45. data/lib/aws_extensions/s3/BucketVersioning.rb +14 -0
  46. data/lib/aws_extensions/s3/BucketWebsite.rb +49 -0
  47. data/lib/aws_extensions/s3/CORSRule.rb +27 -0
  48. data/lib/aws_extensions/s3/ReplicationConfiguration.rb +22 -0
  49. data/lib/cloudfront/CloudFront.rb +83 -0
  50. data/lib/cloudfront/loader/Loader.rb +31 -0
  51. data/lib/cloudfront/manager/Manager.rb +183 -0
  52. data/lib/cloudfront/models/CacheBehaviorConfig.rb +237 -0
  53. data/lib/cloudfront/models/CacheBehaviorDiff.rb +211 -0
  54. data/lib/cloudfront/models/CustomOriginConfig.rb +51 -0
  55. data/lib/cloudfront/models/CustomOriginDiff.rb +74 -0
  56. data/lib/cloudfront/models/DistributionConfig.rb +183 -0
  57. data/lib/cloudfront/models/DistributionDiff.rb +131 -0
  58. data/lib/cloudfront/models/InvalidationConfig.rb +37 -0
  59. data/lib/cloudfront/models/OriginConfig.rb +144 -0
  60. data/lib/cloudfront/models/OriginDiff.rb +86 -0
  61. data/lib/cloudfront/models/OriginSslProtocols.rb +28 -0
  62. data/lib/cloudfront/models/OriginSslProtocolsDiff.rb +39 -0
  63. data/lib/common/BaseLoader.rb +80 -0
  64. data/lib/common/manager/Manager.rb +148 -0
  65. data/lib/common/models/Diff.rb +114 -0
  66. data/lib/common/models/ListChange.rb +21 -0
  67. data/lib/common/models/TagsDiff.rb +55 -0
  68. data/lib/common/models/UTCTimeSource.rb +17 -0
  69. data/lib/conf/Configuration.rb +365 -0
  70. data/lib/ec2/EC2.rb +503 -0
  71. data/lib/ec2/IPProtocolMapping.rb +165 -0
  72. data/lib/ec2/loaders/EbsLoader.rb +19 -0
  73. data/lib/ec2/loaders/InstanceLoader.rb +32 -0
  74. data/lib/ec2/managers/EbsManager.rb +176 -0
  75. data/lib/ec2/managers/InstanceManager.rb +509 -0
  76. data/lib/ec2/models/EbsGroupConfig.rb +133 -0
  77. data/lib/ec2/models/EbsGroupDiff.rb +48 -0
  78. data/lib/ec2/models/InstanceConfig.rb +202 -0
  79. data/lib/ec2/models/InstanceDiff.rb +95 -0
  80. data/lib/elb/ELB.rb +148 -0
  81. data/lib/elb/loader/Loader.rb +65 -0
  82. data/lib/elb/manager/Manager.rb +581 -0
  83. data/lib/elb/models/AccessLogConfig.rb +82 -0
  84. data/lib/elb/models/AccessLogDiff.rb +47 -0
  85. data/lib/elb/models/HealthCheckConfig.rb +91 -0
  86. data/lib/elb/models/HealthCheckDiff.rb +50 -0
  87. data/lib/elb/models/ListenerConfig.rb +99 -0
  88. data/lib/elb/models/ListenerDiff.rb +91 -0
  89. data/lib/elb/models/LoadBalancerConfig.rb +239 -0
  90. data/lib/elb/models/LoadBalancerDiff.rb +265 -0
  91. data/lib/iam/IAM.rb +36 -0
  92. data/lib/iam/loader/Loader.rb +117 -0
  93. data/lib/iam/manager/IamGroups.rb +98 -0
  94. data/lib/iam/manager/IamResource.rb +288 -0
  95. data/lib/iam/manager/IamRoles.rb +112 -0
  96. data/lib/iam/manager/IamUsers.rb +54 -0
  97. data/lib/iam/manager/Manager.rb +29 -0
  98. data/lib/iam/migration/AssumeRoleUnifier.rb +34 -0
  99. data/lib/iam/migration/PolicyUnifier.rb +90 -0
  100. data/lib/iam/models/GroupConfig.rb +40 -0
  101. data/lib/iam/models/IamDiff.rb +132 -0
  102. data/lib/iam/models/PolicyConfig.rb +67 -0
  103. data/lib/iam/models/ResourceWithPolicy.rb +208 -0
  104. data/lib/iam/models/RoleConfig.rb +53 -0
  105. data/lib/iam/models/StatementConfig.rb +35 -0
  106. data/lib/iam/models/UserConfig.rb +21 -0
  107. data/lib/kinesis/Kinesis.rb +94 -0
  108. data/lib/kinesis/loader/Loader.rb +19 -0
  109. data/lib/kinesis/manager/Manager.rb +206 -0
  110. data/lib/kinesis/models/StreamConfig.rb +75 -0
  111. data/lib/kinesis/models/StreamDiff.rb +58 -0
  112. data/lib/lambda/Lambda.rb +41 -0
  113. data/lib/route53/loader/Loader.rb +32 -0
  114. data/lib/route53/manager/Manager.rb +241 -0
  115. data/lib/route53/models/AliasTarget.rb +86 -0
  116. data/lib/route53/models/RecordConfig.rb +178 -0
  117. data/lib/route53/models/RecordDiff.rb +140 -0
  118. data/lib/route53/models/Vpc.rb +24 -0
  119. data/lib/route53/models/ZoneConfig.rb +156 -0
  120. data/lib/route53/models/ZoneDiff.rb +118 -0
  121. data/lib/s3/S3.rb +89 -0
  122. data/lib/s3/loader/Loader.rb +66 -0
  123. data/lib/s3/manager/Manager.rb +296 -0
  124. data/lib/s3/models/BucketConfig.rb +321 -0
  125. data/lib/s3/models/BucketDiff.rb +167 -0
  126. data/lib/s3/models/GrantConfig.rb +189 -0
  127. data/lib/s3/models/GrantDiff.rb +50 -0
  128. data/lib/s3/models/LifecycleConfig.rb +142 -0
  129. data/lib/s3/models/LifecycleDiff.rb +46 -0
  130. data/lib/s3/models/LoggingConfig.rb +81 -0
  131. data/lib/s3/models/NotificationConfig.rb +157 -0
  132. data/lib/s3/models/NotificationDiff.rb +62 -0
  133. data/lib/s3/models/ReplicationConfig.rb +133 -0
  134. data/lib/s3/models/ReplicationDiff.rb +60 -0
  135. data/lib/s3/models/WebsiteConfig.rb +107 -0
  136. data/lib/security/SecurityGroups.rb +39 -0
  137. data/lib/security/loader/Loader.rb +94 -0
  138. data/lib/security/manager/Manager.rb +246 -0
  139. data/lib/security/models/RuleConfig.rb +161 -0
  140. data/lib/security/models/RuleDiff.rb +72 -0
  141. data/lib/security/models/RuleMigration.rb +127 -0
  142. data/lib/security/models/SecurityGroupConfig.rb +172 -0
  143. data/lib/security/models/SecurityGroupDiff.rb +112 -0
  144. data/lib/sns/SNS.rb +40 -0
  145. data/lib/sqs/SQS.rb +62 -0
  146. data/lib/sqs/loader/Loader.rb +34 -0
  147. data/lib/sqs/manager/Manager.rb +128 -0
  148. data/lib/sqs/models/DeadLetterConfig.rb +70 -0
  149. data/lib/sqs/models/DeadLetterDiff.rb +35 -0
  150. data/lib/sqs/models/QueueConfig.rb +115 -0
  151. data/lib/sqs/models/QueueDiff.rb +89 -0
  152. data/lib/util/Colors.rb +111 -0
  153. data/lib/util/StatusCodes.rb +51 -0
  154. data/lib/vpc/loader/Loader.rb +73 -0
  155. data/lib/vpc/manager/Manager.rb +954 -0
  156. data/lib/vpc/models/AclEntryConfig.rb +150 -0
  157. data/lib/vpc/models/AclEntryDiff.rb +54 -0
  158. data/lib/vpc/models/DhcpConfig.rb +100 -0
  159. data/lib/vpc/models/DhcpDiff.rb +90 -0
  160. data/lib/vpc/models/EndpointConfig.rb +76 -0
  161. data/lib/vpc/models/EndpointDiff.rb +69 -0
  162. data/lib/vpc/models/NetworkAclConfig.rb +87 -0
  163. data/lib/vpc/models/NetworkAclDiff.rb +116 -0
  164. data/lib/vpc/models/RouteConfig.rb +82 -0
  165. data/lib/vpc/models/RouteDiff.rb +50 -0
  166. data/lib/vpc/models/RouteTableConfig.rb +92 -0
  167. data/lib/vpc/models/RouteTableDiff.rb +101 -0
  168. data/lib/vpc/models/SubnetConfig.rb +113 -0
  169. data/lib/vpc/models/SubnetDiff.rb +78 -0
  170. data/lib/vpc/models/VpcConfig.rb +173 -0
  171. data/lib/vpc/models/VpcDiff.rb +315 -0
  172. data/rakefile.rb +8 -0
  173. metadata +245 -0
@@ -0,0 +1,39 @@
1
+ require "conf/Configuration"
2
+ require "ec2/EC2"
3
+
4
+ require "aws-sdk"
5
+
6
+ module Cumulus
7
+ module SecurityGroups
8
+ class << self
9
+ @@client = Aws::EC2::Client.new(Configuration.instance.client)
10
+
11
+ require "aws_extensions/ec2/SecurityGroup"
12
+ Aws::EC2::Types::SecurityGroup.send(:include, AwsExtensions::EC2::SecurityGroup)
13
+
14
+ def id_security_groups
15
+ @id_security_groups ||= Hash[security_groups.map { |a| [a.group_id, a] }]
16
+ end
17
+
18
+ # Public: Returns a Hash of vpc id to Hash of security group name to group
19
+ def vpc_security_groups
20
+ @vpc_security_groups ||= Hash[security_groups.map(&:vpc_id).uniq.map do |vpc_id|
21
+ [vpc_id, Hash[security_groups.select { |g| g.vpc_id == vpc_id }.map { |g| [g.group_name, g] }]]
22
+ end]
23
+ end
24
+
25
+ # Describe all security groups
26
+ def security_groups
27
+ @security_groups ||= @@client.describe_security_groups.security_groups
28
+ end
29
+
30
+ # Public: Returns a Hash of vpc id to Hash of security group id to group name
31
+ def vpc_security_group_id_names
32
+ @vpc_security_group_id_names ||= Hash[vpc_security_groups.map do |vpc_id, group_hash|
33
+ [vpc_id, Hash[group_hash.map {|_, sg| [sg.group_id, sg.group_name]}]]
34
+ end]
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,94 @@
1
+ require "common/BaseLoader"
2
+ require "conf/Configuration"
3
+ require "security/models/SecurityGroupConfig"
4
+ require "util/Colors"
5
+ require "util/StatusCodes"
6
+
7
+ module Cumulus
8
+ module SecurityGroups
9
+ # Public: Load Security Group assets
10
+ module Loader
11
+ include Common::BaseLoader
12
+
13
+ @@groups_dir = Configuration.instance.security.groups_directory
14
+ @@rules_dir = Configuration.instance.security.rules_directory
15
+ @@subnet_files = Configuration.instance.security.subnet_files
16
+
17
+ # Public: Load all the security group configurations as SecurityGroupConfig objects
18
+ #
19
+ # Returns an array of SecurityGroupConfig
20
+ def Loader.groups
21
+ # List all the directories to load groups from each vpc
22
+ vpc_dirs = Dir.entries(@@groups_dir).reject { |f| f == "." or f == ".."}.select { |f| File.directory?(File.join(@@groups_dir, f)) }
23
+
24
+ vpc_groups = vpc_dirs.map do |d|
25
+ aws_vpc = EC2::named_vpcs[d]
26
+
27
+ if aws_vpc.nil?
28
+ puts Colors.red("No VPC named #{d} exists")
29
+ exit StatusCodes::EXCEPTION
30
+ end
31
+
32
+ Common::BaseLoader.resources(File.join(@@groups_dir, d)) do |file_name, json|
33
+ name = "#{aws_vpc.name}/#{file_name}"
34
+ SecurityGroupConfig.new(name, aws_vpc.vpc_id, json)
35
+ end
36
+ end.flatten
37
+
38
+ non_vpc_groups = Common::BaseLoader.resources(@@groups_dir) do |file_name, json|
39
+ SecurityGroupConfig.new(file_name, nil, json)
40
+ end
41
+
42
+ if !EC2::supports_ec2_classic and !non_vpc_groups.empty?
43
+ puts "Ignoring Non-VPC Security Groups because your account does not support them"
44
+ non_vpc_groups = []
45
+ end
46
+
47
+ vpc_groups + non_vpc_groups
48
+ end
49
+
50
+ # Public: Load a single static rule
51
+ #
52
+ # Returns the static rule as json
53
+ def Loader.rule(rule_name)
54
+ Common::BaseLoader.resource(rule_name, @@rules_dir) { |_, json| json }
55
+ end
56
+
57
+ # Public: Get the local definition of a subnet group.
58
+ #
59
+ # name - the name of the subnet group to get
60
+ #
61
+ # Returns an array of ip addresses that is empty if there is no subnet group with that name
62
+ def Loader.subnet_group(name)
63
+ if self.subnet_groups[name].nil?
64
+ raise "Could not find subnet #{name}"
65
+ else
66
+ self.subnet_groups[name]
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ # Internal: Get the subnet group definitions
73
+ #
74
+ # Returns a hash that maps group name to an array of ips
75
+ def Loader.subnet_groups
76
+ @subnet_groups ||= self.load_subnet_groups
77
+ end
78
+
79
+ # Internal: Load the subnet group definitions
80
+ #
81
+ # Returns a hash that maps group name to an array of ips
82
+ def Loader.load_subnet_groups
83
+ @@subnet_files.reduce({}) do |sofar, f|
84
+ subnet_group = Common::BaseLoader.resource(f, "") { |_, json| json }
85
+ if subnet_group
86
+ subnet_group.merge(sofar)
87
+ else
88
+ sofar
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,246 @@
1
+ require "common/manager/Manager"
2
+ require "conf/Configuration"
3
+ require "security/loader/Loader"
4
+ require "security/models/SecurityGroupConfig"
5
+ require "security/models/SecurityGroupDiff"
6
+ require "security/SecurityGroups"
7
+ require "util/Colors"
8
+
9
+ require "aws-sdk"
10
+ require "json"
11
+
12
+ module Cumulus
13
+ module SecurityGroups
14
+ class Manager < Common::Manager
15
+ def initialize
16
+ super()
17
+ @ec2 = Aws::EC2::Client.new(Configuration.instance.client)
18
+ end
19
+
20
+ # Public: Migrate AWS Security Groups to Cumulus configuration.
21
+ def migrate
22
+ groups_dir = "#{@migration_root}/security-groups"
23
+
24
+ if !Dir.exists?(@migration_root)
25
+ Dir.mkdir(@migration_root)
26
+ end
27
+ if !Dir.exists?(groups_dir)
28
+ Dir.mkdir(groups_dir)
29
+ end
30
+
31
+ # Make the directories needed for resources that require it
32
+ aws_resources.map do |name, _|
33
+ parts = name.partition("/")
34
+ if parts.length > 1
35
+ "#{groups_dir}/#{parts.first}"
36
+ end
37
+ end.uniq.compact.each do |dir|
38
+ if !Dir.exists?(dir)
39
+ Dir.mkdir(dir)
40
+ end
41
+ end
42
+
43
+ aws_resources.each_value do |resource|
44
+ puts "Processing #{resource.vpc_group_name}..."
45
+ config = SecurityGroupConfig.new(resource.vpc_group_name, resource.vpc_id)
46
+ config.populate!(resource)
47
+
48
+ puts "Writing #{resource.vpc_group_name} configuration to file..."
49
+ File.open("#{groups_dir}/#{config.name}.json", "w") { |f| f.write(config.pretty_json) }
50
+ end
51
+
52
+ File.open("#{@migration_root}/subnets.json", "w") do |f|
53
+ f.write(JSON.pretty_generate({
54
+ "all" => ["0.0.0.0/0"]
55
+ }))
56
+ end
57
+
58
+ puts Colors.blue("IP addresses for inbound and outbound rules have been left as is in each individual security group, except in the case of 0.0.0.0/0.")
59
+ puts Colors.blue("0.0.0.0/0 has been renamed to 'all' and is referenced as such in security group definitions.")
60
+ puts Colors.blue("See subnets.json to see the definition of the 'all' subnet group.")
61
+ end
62
+
63
+ def resource_name
64
+ "Security Group"
65
+ end
66
+
67
+ def local_resources
68
+ @local_resources ||= Hash[Loader.groups.map { |local| [local.name, local] }]
69
+ end
70
+
71
+ # Hash the aws security groups using the vpc and security group name
72
+ def aws_resources
73
+ @aws_resources ||= Hash[SecurityGroups::security_groups.map { |sg| [sg.vpc_group_name, sg] }]
74
+ end
75
+
76
+ def unmanaged_diff(aws)
77
+ SecurityGroupDiff.unmanaged(aws)
78
+ end
79
+
80
+ def added_diff(local)
81
+ SecurityGroupDiff.added(local)
82
+ end
83
+
84
+ def diff_resource(local, aws)
85
+ local.diff(aws)
86
+ end
87
+
88
+ def create(local)
89
+ result = @ec2.create_security_group({
90
+ group_name: local.name.split("/").last,
91
+ description: local.description,
92
+ vpc_id: local.vpc_id,
93
+ })
94
+ security_group_id = result.group_id
95
+
96
+ update_tags(security_group_id, local.tags, {})
97
+ update_inbound(local.vpc_id, security_group_id, local.inbound, [])
98
+
99
+ allow_all_rule = RuleConfig.allow_all
100
+ allow_all_outbound = Configuration.instance.security.outbound_default_all_allowed or local.outbound.find { |g| g.hash == allow_all_rule.hash }
101
+
102
+ outbound_remove = if allow_all_outbound
103
+ []
104
+ else
105
+ [allow_all_rule]
106
+ end
107
+
108
+ outbound_add = local.outbound.reject { |g| g.hash == allow_all_rule.hash }
109
+
110
+ update_outbound(local.vpc_id, security_group_id, outbound_add, outbound_remove)
111
+ end
112
+
113
+ def update(local, diffs)
114
+ diffs_by_type = diffs.group_by(&:type)
115
+
116
+ if diffs_by_type.include?(SecurityGroupChange::DESCRIPTION)
117
+ puts "\tUnfortunately, AWS's SDK does not allow updating the description."
118
+ else
119
+ diffs.each do |diff|
120
+ case diff.type
121
+ when SecurityGroupChange::TAGS
122
+ update_tags(diff.aws.group_id, diff.tags_to_add, diff.tags_to_remove)
123
+ when SecurityGroupChange::INBOUND
124
+ update_inbound(local.vpc_id, diff.aws.group_id, diff.added_inbounds, diff.removed_inbounds)
125
+ when SecurityGroupChange::OUTBOUND
126
+ update_outbound(local.vpc_id, diff.aws.group_id, diff.added_outbounds, diff.removed_outbounds)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ # Internal: Update the tags associated with a security group.
135
+ #
136
+ # security_group_id - the id of the security group to update
137
+ # add - the tags to add (expects a hash of key value pairs)
138
+ # remove - the tags to remove (expects a hash of key value pairs)
139
+ def update_tags(security_group_id, add, remove)
140
+ if !add.empty?
141
+ puts Colors.blue("\tadding tags...")
142
+ @ec2.create_tags({
143
+ resources: [security_group_id],
144
+ tags: add.map { |k, v| { key: k, value: v } }
145
+ })
146
+ end
147
+ if !remove.empty?
148
+ puts Colors.blue("\tremoving tags...")
149
+ @ec2.delete_tags({
150
+ resources: [security_group_id],
151
+ tags: remove.map { |k, v| { key: k, value: v } }
152
+ })
153
+ end
154
+ end
155
+
156
+ # Internal: Update the inbound rules associated with a security group.
157
+ #
158
+ # vpc_id - - the id of the vpc the security groups are in
159
+ # security_group_id - the id of the security group
160
+ # add - the inbound rules to associate with the security group
161
+ # remove - the inbound rules to dissociate from the security group
162
+ def update_inbound(vpc_id, security_group_id, add, remove)
163
+ update_rules(
164
+ vpc_id,
165
+ security_group_id,
166
+ add,
167
+ remove,
168
+ {
169
+ :type => "inbound",
170
+ :add_action => @ec2.method(:authorize_security_group_ingress),
171
+ :remove_action => @ec2.method(:revoke_security_group_ingress)
172
+ }
173
+ )
174
+ end
175
+
176
+ # Internal: Update the outbound rules associated with a security group.
177
+ #
178
+ # vpc_id - - the id of the vpc the security groups are in
179
+ # security_group_id - the id of the security group
180
+ # add - the outbound rules to associate with the security group
181
+ # remove - the outbound rules to associate with the security group
182
+ #
183
+ def update_outbound(vpc_id, security_group_id, add, remove)
184
+ update_rules(
185
+ vpc_id,
186
+ security_group_id,
187
+ add,
188
+ remove,
189
+ {
190
+ :type => "outbound",
191
+ :add_action => @ec2.method(:authorize_security_group_egress),
192
+ :remove_action => @ec2.method(:revoke_security_group_egress)
193
+ }
194
+ )
195
+ end
196
+
197
+ # Internal: Update rules associated with a security group. Called by inbound
198
+ # and outbound.
199
+ #
200
+ # vpc_id - - the id of the vpc the security groups are in
201
+ # security_group_id - the id of the security group
202
+ # add - the rules to associate with the security group
203
+ # remove - the rules to remove from the security group
204
+ # options - a hash containing options about the operation to run. Should contain:
205
+ # - type - a string representing the type of rules to process
206
+ # - add_action - the client method to call to add rules
207
+ # - remove_action - the client method to call to remove rules
208
+ def update_rules(vpc_id, security_group_id, add, remove, options)
209
+ if !add.empty?
210
+ puts Colors.blue("\tadding #{options[:type]} rules...")
211
+ options[:add_action].call({
212
+ group_id: security_group_id,
213
+ ip_permissions: add.map do |added|
214
+ missing_group = added.security_groups.find do |s|
215
+ !SecurityGroups::vpc_security_groups[vpc_id].include?(s)
216
+ end
217
+ if missing_group
218
+ puts Colors.red("\t\tNo such security group: #{missing_group}. Security group not added.")
219
+ end
220
+
221
+ added.to_aws(vpc_id)
222
+ end
223
+ })
224
+ end
225
+
226
+ if !remove.empty?
227
+ puts Colors.blue("\tremoving #{options[:type]} rules...")
228
+ options[:remove_action].call({
229
+ group_id: security_group_id,
230
+ ip_permissions: remove.map do |removed|
231
+ missing_group = removed.security_groups.find do |s|
232
+ !SecurityGroups::vpc_security_groups[vpc_id].include?(s)
233
+ end
234
+ if missing_group
235
+ puts Colors.red("\t\tNo such security group: #{missing_group}. Security group not removed.")
236
+ end
237
+
238
+ removed.to_aws(vpc_id)
239
+ end
240
+ })
241
+ end
242
+
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,161 @@
1
+ require "security/loader/Loader"
2
+ require "security/SecurityGroups"
3
+
4
+ module Cumulus
5
+ module SecurityGroups
6
+ # Public: An object representing configuration for a security group rule
7
+ class RuleConfig
8
+
9
+ attr_reader :from
10
+ attr_reader :protocol
11
+ attr_reader :security_groups
12
+ attr_reader :subnets
13
+ attr_reader :to
14
+
15
+ # Public: Static method that will produce a RuleConfig from an AWS rule resource.
16
+ #
17
+ # aws - the aws resource to use
18
+ #
19
+ # Returns a RuleConfig containing the data in the AWS rule
20
+ def RuleConfig.from_aws(aws)
21
+ RuleConfig.new({
22
+ "security-groups" => aws.user_id_group_pairs.map { |security| SecurityGroups::id_security_groups[security.group_id].group_name },
23
+ "protocol" => if aws.ip_protocol == "-1" then "all" else aws.ip_protocol end,
24
+ "from-port" => if aws.ip_protocol != "icmp" and aws.from_port != -1 then aws.from_port end,
25
+ "to-port" => if aws.ip_protocol != "icmp" and aws.to_port != -1 then aws.to_port end,
26
+ "icmp-type" => if aws.ip_protocol == "icmp"
27
+ if aws.from_port != -1 then aws.from_port else "all" end
28
+ end,
29
+ "icmp-code" => if aws.ip_protocol == "icmp"
30
+ if aws.to_port != -1 then aws.to_port else "all" end
31
+ end,
32
+ "subnets" => aws.ip_ranges.map { |ip| ip.cidr_ip },
33
+ }.reject { |k, v| v.nil? })
34
+ end
35
+
36
+ # Public: Static method that will produce a RuleConfig that allows all access
37
+ #
38
+ # Returns the RuleConfig
39
+ def RuleConfig.allow_all
40
+ RuleConfig.new({
41
+ "protocol" => "all",
42
+ "subnets" => ["0.0.0.0/0"]
43
+ })
44
+ end
45
+
46
+ # Public: Static method that will produce multiple RuleConfigs, one for each port
47
+ # range.
48
+ #
49
+ # json - a hash containing the JSON configuration for the rule
50
+ #
51
+ # Returns an array of RuleConfigs
52
+ def RuleConfig.expand_ports(json)
53
+ ports = json["ports"]
54
+
55
+ if !ports.nil?
56
+ ports.map do |port|
57
+ rule_hash = json.clone
58
+
59
+ if port.is_a? String
60
+ parts = port.split("-").map(&:strip)
61
+ rule_hash["from-port"] = parts[0].to_i
62
+ rule_hash["to-port"] = parts[1].to_i
63
+ else
64
+ rule_hash["from-port"] = port
65
+ rule_hash["to-port"] = port
66
+ end
67
+
68
+ RuleConfig.new(rule_hash)
69
+ end
70
+ else
71
+ RuleConfig.new(json)
72
+ end
73
+ end
74
+
75
+ # Public: Constructor
76
+ #
77
+ # json - a hash containing the JSON configuration for the rule
78
+ def initialize(json)
79
+ @protocol = json["protocol"]
80
+
81
+ if @protocol.downcase == "icmp"
82
+ @from = json["icmp-type"]
83
+ @to = json["icmp-code"]
84
+ else
85
+ @from = json["from-port"]
86
+ @to = json["to-port"]
87
+ end
88
+
89
+ @security_groups = if !json["security-groups"].nil? then json["security-groups"] else [] end
90
+ @subnets = if !json["subnets"].nil?
91
+ json["subnets"].flat_map do |subnet|
92
+ if subnet.match(/\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}\/\d+/).nil?
93
+ Loader.subnet_group(subnet)
94
+ else
95
+ subnet
96
+ end
97
+ end.sort
98
+ else
99
+ []
100
+ end
101
+ end
102
+
103
+ # Public: Get the configuration as a hash
104
+ #
105
+ # Returns the hash
106
+ def hash
107
+ security_hashes = @security_groups.map do |security_group|
108
+ {
109
+ "security-groups" => [security_group],
110
+ "protocol" => @protocol,
111
+ "from-port" => if @protocol != "icmp" then @from end,
112
+ "to-port" => if @protocol != "icmp" then @to end,
113
+ "subnets" => [],
114
+ "icmp-type" => if @protocol == "icmp" then @from end,
115
+ "icmp-code" => if @protocol == "icmp" then @to end,
116
+ }.reject { |k, v| v.nil? }
117
+ end
118
+ subnet_hashes = @subnets.map do |subnet|
119
+ {
120
+ "security-groups" => [],
121
+ "protocol" => @protocol,
122
+ "from-port" => if @protocol != "icmp" then @from end,
123
+ "to-port" => if @protocol != "icmp" then @to end,
124
+ "subnets" => [subnet],
125
+ "icmp-type" => if @protocol == "icmp" then @from end,
126
+ "icmp-code" => if @protocol == "icmp" then @to end,
127
+ }.reject { |k, v| v.nil? }
128
+ end
129
+
130
+ security_hashes + subnet_hashes
131
+ end
132
+
133
+ # Public: Converts the RuleConfig into the format needed by AWS
134
+ # to authorize/deauthorize rules
135
+ #
136
+ # vpc_id - the id of the vpc that security group ids should be derived from
137
+ def to_aws(vpc_id)
138
+ {
139
+ ip_protocol: if @protocol == "all" then "-1" else @protocol end,
140
+ from_port: if @from == "all" then "-1" else @from end,
141
+ to_port: if @to == "all" then "-1" else @to end,
142
+ user_id_group_pairs: if !@security_groups.empty?
143
+ @security_groups.map do |sg|
144
+ {
145
+ group_id: SecurityGroups::vpc_security_group_id_names[vpc_id].key(sg)
146
+ }
147
+ end
148
+ end,
149
+ ip_ranges: if !@subnets.empty?
150
+ @subnets.map do |subnet|
151
+ {
152
+ cidr_ip: subnet
153
+ }
154
+ end
155
+ end
156
+ }
157
+ end
158
+
159
+ end
160
+ end
161
+ end