chef-provisioning-aws 0.4.0 → 0.5.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 +4 -4
- data/Rakefile +2 -0
- data/lib/chef/provider/aws_auto_scaling_group.rb +30 -41
- data/lib/chef/provider/aws_dhcp_options.rb +70 -0
- data/lib/chef/provider/aws_ebs_volume.rb +182 -34
- data/lib/chef/provider/aws_eip_address.rb +63 -60
- data/lib/chef/provider/aws_key_pair.rb +18 -27
- data/lib/chef/provider/aws_launch_configuration.rb +50 -0
- data/lib/chef/provider/aws_route_table.rb +122 -0
- data/lib/chef/provider/aws_s3_bucket.rb +42 -49
- data/lib/chef/provider/aws_security_group.rb +252 -59
- data/lib/chef/provider/aws_sns_topic.rb +10 -26
- data/lib/chef/provider/aws_sqs_queue.rb +16 -38
- data/lib/chef/provider/aws_subnet.rb +85 -32
- data/lib/chef/provider/aws_vpc.rb +163 -23
- data/lib/chef/provisioning/aws_driver.rb +18 -1
- data/lib/chef/provisioning/aws_driver/aws_provider.rb +206 -0
- data/lib/chef/provisioning/aws_driver/aws_resource.rb +186 -0
- data/lib/chef/provisioning/aws_driver/aws_resource_with_entry.rb +114 -0
- data/lib/chef/provisioning/aws_driver/driver.rb +317 -255
- data/lib/chef/provisioning/aws_driver/resources.rb +8 -5
- data/lib/chef/provisioning/aws_driver/super_lwrp.rb +45 -0
- data/lib/chef/provisioning/aws_driver/version.rb +1 -1
- data/lib/chef/resource/aws_auto_scaling_group.rb +15 -13
- data/lib/chef/resource/aws_dhcp_options.rb +57 -0
- data/lib/chef/resource/aws_ebs_volume.rb +20 -22
- data/lib/chef/resource/aws_eip_address.rb +50 -25
- data/lib/chef/resource/aws_image.rb +20 -0
- data/lib/chef/resource/aws_instance.rb +20 -0
- data/lib/chef/resource/aws_internet_gateway.rb +16 -0
- data/lib/chef/resource/aws_key_pair.rb +6 -10
- data/lib/chef/resource/aws_launch_configuration.rb +15 -0
- data/lib/chef/resource/aws_load_balancer.rb +16 -0
- data/lib/chef/resource/aws_network_interface.rb +16 -0
- data/lib/chef/resource/aws_route_table.rb +76 -0
- data/lib/chef/resource/aws_s3_bucket.rb +8 -18
- data/lib/chef/resource/aws_security_group.rb +49 -19
- data/lib/chef/resource/aws_sns_topic.rb +14 -15
- data/lib/chef/resource/aws_sqs_queue.rb +16 -14
- data/lib/chef/resource/aws_subnet.rb +87 -17
- data/lib/chef/resource/aws_vpc.rb +137 -15
- data/spec/integration/aws_security_group_spec.rb +55 -0
- data/spec/spec_helper.rb +8 -2
- data/spec/support/aws_support.rb +211 -0
- metadata +33 -10
- data/lib/chef/provider/aws_launch_config.rb +0 -43
- data/lib/chef/provider/aws_provider.rb +0 -22
- data/lib/chef/provisioning/aws_driver/aws_profile.rb +0 -73
- data/lib/chef/resource/aws_launch_config.rb +0 -14
- data/lib/chef/resource/aws_resource.rb +0 -10
- data/spec/chef_zero_rspec_helper.rb +0 -8
- data/spec/unit/provider/aws_subnet_spec.rb +0 -67
- data/spec/unit/resource/aws_subnet_spec.rb +0 -23
@@ -1,29 +1,24 @@
|
|
1
1
|
require 'chef/provider/lwrp_base'
|
2
|
-
require 'chef/provisioning/aws_driver'
|
3
|
-
require 'chef/provider/aws_provider'
|
2
|
+
require 'chef/provisioning/aws_driver/aws_provider'
|
4
3
|
require 'aws-sdk-v1'
|
5
4
|
|
6
5
|
|
7
|
-
class Chef::Provider::AwsKeyPair < Chef::
|
8
|
-
|
9
|
-
use_inline_resources
|
10
|
-
|
11
|
-
def whyrun_supported?
|
12
|
-
true
|
13
|
-
end
|
6
|
+
class Chef::Provider::AwsKeyPair < Chef::Provisioning::AWSDriver::AWSProvider
|
14
7
|
|
15
8
|
action :create do
|
16
9
|
create_key(:create)
|
17
10
|
end
|
18
11
|
|
19
|
-
action :
|
12
|
+
action :destroy do
|
20
13
|
if current_resource_exists?
|
21
|
-
|
14
|
+
converge_by "delete AWS key pair #{new_resource.name} on region #{region}" do
|
15
|
+
driver.ec2.key_pairs[new_resource.name].delete
|
16
|
+
end
|
22
17
|
end
|
23
18
|
end
|
24
19
|
|
25
20
|
def key_description
|
26
|
-
"#{new_resource.name} on #{
|
21
|
+
"#{new_resource.name} on #{driver.driver_url}"
|
27
22
|
end
|
28
23
|
|
29
24
|
@@use_pkcs8 = nil # For Ruby 1.9 and below, PKCS can be run
|
@@ -79,8 +74,8 @@ class Chef::Provider::AwsKeyPair < Chef::Provider::AwsProvider
|
|
79
74
|
if !new_fingerprints.any? { |f| compare_public_key f }
|
80
75
|
if new_resource.allow_overwrite
|
81
76
|
converge_by "update #{key_description} to match local key at #{new_resource.private_key_path}" do
|
82
|
-
|
83
|
-
|
77
|
+
driver.ec2.key_pairs[new_resource.name].delete
|
78
|
+
driver.ec2.key_pairs.import(new_resource.name, Cheffish::KeyFormatter.encode(desired_key, :format => :openssh))
|
84
79
|
end
|
85
80
|
else
|
86
81
|
raise "#{key_description} with fingerprint #{@current_fingerprint} does not match local key fingerprint(s) #{new_fingerprints}, and allow_overwrite is false!"
|
@@ -92,12 +87,12 @@ class Chef::Provider::AwsKeyPair < Chef::Provider::AwsProvider
|
|
92
87
|
|
93
88
|
# Create key
|
94
89
|
converge_by "create #{key_description} from local key at #{new_resource.private_key_path}" do
|
95
|
-
|
90
|
+
driver.ec2.key_pairs.import(new_resource.name, Cheffish::KeyFormatter.encode(desired_key, :format => :openssh))
|
96
91
|
end
|
97
92
|
end
|
98
93
|
end
|
99
94
|
|
100
|
-
def
|
95
|
+
def driver
|
101
96
|
run_context.chef_provisioning.driver_for(new_resource.driver)
|
102
97
|
end
|
103
98
|
|
@@ -135,15 +130,11 @@ class Chef::Provider::AwsKeyPair < Chef::Provider::AwsProvider
|
|
135
130
|
end
|
136
131
|
|
137
132
|
def existing_keypair
|
138
|
-
@existing_keypair ||=
|
139
|
-
new_driver.ec2.key_pairs[fqn]
|
140
|
-
rescue
|
141
|
-
nil
|
142
|
-
end
|
133
|
+
@existing_keypair ||= new_resource.aws_object
|
143
134
|
end
|
144
135
|
|
145
136
|
def current_resource_exists?
|
146
|
-
@current_resource.action != [ :
|
137
|
+
@current_resource.action != [ :destroy ]
|
147
138
|
end
|
148
139
|
|
149
140
|
def compare_public_key(new)
|
@@ -160,9 +151,9 @@ class Chef::Provider::AwsKeyPair < Chef::Provider::AwsProvider
|
|
160
151
|
private_key_path = new_resource.private_key_path || new_resource.name
|
161
152
|
if private_key_path.is_a?(Symbol)
|
162
153
|
private_key_path
|
163
|
-
elsif Pathname.new(private_key_path).relative? &&
|
154
|
+
elsif Pathname.new(private_key_path).relative? && driver.config[:private_key_write_path]
|
164
155
|
@should_create_directory = true
|
165
|
-
::File.join(
|
156
|
+
::File.join(driver.config[:private_key_write_path], private_key_path)
|
166
157
|
else
|
167
158
|
private_key_path
|
168
159
|
end
|
@@ -175,11 +166,11 @@ class Chef::Provider::AwsKeyPair < Chef::Provider::AwsProvider
|
|
175
166
|
def load_current_resource
|
176
167
|
@current_resource = Chef::Resource::AwsKeyPair.new(new_resource.name, run_context)
|
177
168
|
|
178
|
-
current_key_pair =
|
179
|
-
if current_key_pair
|
169
|
+
current_key_pair = new_resource.aws_object
|
170
|
+
if current_key_pair
|
180
171
|
@current_fingerprint = current_key_pair ? current_key_pair.fingerprint : nil
|
181
172
|
else
|
182
|
-
current_resource.action :
|
173
|
+
current_resource.action :destroy
|
183
174
|
end
|
184
175
|
|
185
176
|
if new_private_key_path && ::File.exist?(new_private_key_path)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'chef/provisioning/aws_driver/aws_provider'
|
2
|
+
require 'chef/resource/aws_image'
|
3
|
+
|
4
|
+
class Chef::Provider::AwsLaunchConfiguration < Chef::Provisioning::AWSDriver::AWSProvider
|
5
|
+
protected
|
6
|
+
|
7
|
+
def create_aws_object
|
8
|
+
image = Chef::Resource::AwsImage.get_aws_object_id(new_resource.image, resource: new_resource)
|
9
|
+
instance_type = new_resource.instance_type || new_resource.driver.default_instance_type
|
10
|
+
options = AWSResource.lookup_options(new_resource.options || options, resource: new_resource)
|
11
|
+
|
12
|
+
converge_by "Creating new Launch Configuration #{new_resource.name} in #{region}" do
|
13
|
+
new_resource.driver.auto_scaling.launch_configurations.create(
|
14
|
+
new_resource.name,
|
15
|
+
image,
|
16
|
+
instance_type,
|
17
|
+
options
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def update_aws_object(launch_configuration)
|
23
|
+
if new_resource.image
|
24
|
+
image = Chef::Resource::AwsImage.get_aws_object_id(new_resource.image, resource: new_resource)
|
25
|
+
if image != launch_configuration.image_id
|
26
|
+
raise "#{new_resource.to_s}.image = #{new_resource.image} (#{image.id}), but actual launch configuration has image set to #{launch_configuration.image_id}. Cannot be modified!"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
if new_resource.instance_type
|
30
|
+
if new_resource.instance_type != launch_configuration.instance_type
|
31
|
+
raise "#{new_resource.to_s}.instance_type = #{new_resource.instance_type}, but actual launch configuration has instance_type set to #{launch_configuration.instance_type}. Cannot be modified!"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
# TODO compare options
|
35
|
+
end
|
36
|
+
|
37
|
+
def destroy_aws_object(launch_configuration)
|
38
|
+
converge_by "delete Launch Configuration #{new_resource.name} in #{region}" do
|
39
|
+
# TODO add a timeout here.
|
40
|
+
# TODO is InUse really a status guaranteed to go away??
|
41
|
+
begin
|
42
|
+
launch_configuration.delete
|
43
|
+
rescue AWS::AutoScaling::Errors::ResourceInUse
|
44
|
+
sleep 5
|
45
|
+
retry
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'chef/provisioning/aws_driver/aws_provider'
|
2
|
+
|
3
|
+
class Chef::Provider::AwsRouteTable < Chef::Provisioning::AWSDriver::AWSProvider
|
4
|
+
|
5
|
+
def action_create
|
6
|
+
route_table = super
|
7
|
+
|
8
|
+
if !new_resource.routes.nil?
|
9
|
+
update_routes(vpc, route_table)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def create_aws_object
|
16
|
+
options = {}
|
17
|
+
options[:vpc] = new_resource.vpc
|
18
|
+
options = AWSResource.lookup_options(options, resource: new_resource)
|
19
|
+
self.vpc = Chef::Resource::AwsVpc.get_aws_object(options[:vpc], resource: new_resource)
|
20
|
+
|
21
|
+
converge_by "create new route table #{new_resource.name} in VPC #{new_resource.vpc} (#{vpc.id}) and region #{region}" do
|
22
|
+
route_table = new_resource.driver.ec2.route_tables.create(options)
|
23
|
+
route_table.tags['Name'] = new_resource.name
|
24
|
+
route_table
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_aws_object(route_table)
|
29
|
+
self.vpc = route_table.vpc
|
30
|
+
|
31
|
+
if new_resource.vpc
|
32
|
+
desired_vpc = Chef::Resource::AwsVpc.get_aws_object(new_resource.vpc, resource: new_resource)
|
33
|
+
if vpc != desired_vpc
|
34
|
+
raise "VPC of route table #{new_resource.name} (#{route_table.id}) is #{route_table.vpc.id}, but desired vpc is #{new_resource.vpc}! Moving (or rather, recreating) a route table is not yet supported."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def destroy_aws_object(route_table)
|
40
|
+
converge_by "delete route table #{new_resource.name} (#{route_table.id}) in #{region}" do
|
41
|
+
route_table.delete
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_accessor :vpc
|
48
|
+
|
49
|
+
def update_routes(vpc, route_table)
|
50
|
+
# Collect current routes
|
51
|
+
current_routes = {}
|
52
|
+
route_table.routes.each do |route|
|
53
|
+
# Ignore the automatic local route
|
54
|
+
next if route.target.id == 'local'
|
55
|
+
current_routes[route.destination_cidr_block] = route
|
56
|
+
end
|
57
|
+
|
58
|
+
# Add or replace routes from `routes`
|
59
|
+
new_resource.routes.each do |destination_cidr_block, route_target|
|
60
|
+
options = get_route_target(vpc, route_target)
|
61
|
+
target = options.values.first
|
62
|
+
# If we already have a route to that CIDR block, replace it.
|
63
|
+
if current_routes[destination_cidr_block]
|
64
|
+
current_route = current_routes.delete(destination_cidr_block)
|
65
|
+
if current_route.target != target
|
66
|
+
action_handler.perform_action "reroute #{destination_cidr_block} to #{route_target} (#{target.id}) instead of #{current_route.target.id}" do
|
67
|
+
current_route.replace(options)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
else
|
71
|
+
action_handler.perform_action "route #{destination_cidr_block} to #{route_target} (#{target.id})" do
|
72
|
+
route_table.create_route(destination_cidr_block, options)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Delete anything that's left (that wasn't replaced)
|
78
|
+
current_routes.values.each do |current_route|
|
79
|
+
action_handler.perform_action "remove route sending #{current_route.destination_cidr_block} to #{current_route.target.id}" do
|
80
|
+
current_route.delete
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def get_route_target(vpc, route_target)
|
86
|
+
case route_target
|
87
|
+
when :internet_gateway
|
88
|
+
route_target = { internet_gateway: vpc.internet_gateway }
|
89
|
+
if !route_target[:internet_gateway]
|
90
|
+
raise "VPC #{new_resource.vpc} (#{vpc.id}) does not have an internet gateway to route to! Use `internet_gateway true` on the VPC itself to create one."
|
91
|
+
end
|
92
|
+
when /^igw-[A-Fa-f0-9]{8}$/, Chef::Resource::AwsInternetGateway, AWS::EC2::InternetGateway
|
93
|
+
route_target = { internet_gateway: route_target }
|
94
|
+
when /^eni-[A-Fa-f0-9]{8}$/, Chef::Resource::AwsNetworkInterface, AWS::EC2::NetworkInterface
|
95
|
+
route_target = { network_interface: route_target }
|
96
|
+
when String, Chef::Resource::AwsInstance
|
97
|
+
route_target = { instance: route_target }
|
98
|
+
when Chef::Resource::Machine
|
99
|
+
route_target = { instance: route_target.name }
|
100
|
+
when AWS::EC2::Instance
|
101
|
+
route_target = { instance: route_target.id }
|
102
|
+
when Hash
|
103
|
+
if route_target.size != 1
|
104
|
+
raise "Route target #{route_target} must have exactly one key, either :internet_gateway, :instance or :network_interface!"
|
105
|
+
end
|
106
|
+
route_target = route_target.dup
|
107
|
+
else
|
108
|
+
raise "Unrecognized route destination #{route_target.inspect}"
|
109
|
+
end
|
110
|
+
route_target.each do |name, value|
|
111
|
+
case name
|
112
|
+
when :instance
|
113
|
+
route_target[name] = Chef::Resource::AwsInstance.get_aws_object(value, resource: new_resource)
|
114
|
+
when :network_interface
|
115
|
+
route_target[name] = Chef::Resource::AwsNetworkInterface.get_aws_object(value, resource: new_resource)
|
116
|
+
when :internet_gateway
|
117
|
+
route_target[name] = Chef::Resource::AwsInternetGateway.get_aws_object(value, resource: new_resource)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
route_target
|
121
|
+
end
|
122
|
+
end
|
@@ -1,81 +1,74 @@
|
|
1
|
-
require 'chef/
|
1
|
+
require 'chef/provisioning/aws_driver/aws_provider'
|
2
2
|
require 'date'
|
3
3
|
|
4
|
-
class Chef::Provider::AwsS3Bucket < Chef::
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
if modifies_website_configuration?
|
14
|
-
if new_resource.enable_website_hosting
|
15
|
-
converge_by "Setting website configuration for bucket #{fqn}" do
|
16
|
-
existing_bucket.website_configuration = AWS::S3::WebsiteConfiguration.new(
|
4
|
+
class Chef::Provider::AwsS3Bucket < Chef::Provisioning::AWSDriver::AWSProvider
|
5
|
+
def action_create
|
6
|
+
bucket = super
|
7
|
+
|
8
|
+
if new_resource.enable_website_hosting
|
9
|
+
if !bucket.website?
|
10
|
+
converge_by "Enabling website configuration for bucket #{new_resource.name}" do
|
11
|
+
bucket.website_configuration = AWS::S3::WebsiteConfiguration.new(
|
17
12
|
new_resource.website_options)
|
18
13
|
end
|
19
|
-
|
20
|
-
converge_by "
|
21
|
-
|
14
|
+
elsif modifies_website_configuration?(bucket)
|
15
|
+
converge_by "Reconfiguring website configuration for bucket #{new_resource.name} to #{new_resource.website_options}" do
|
16
|
+
bucket.website_configuration = AWS::S3::WebsiteConfiguration.new(
|
17
|
+
new_resource.website_options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
else
|
21
|
+
if bucket.website?
|
22
|
+
converge_by "Disabling website configuration for bucket #{new_resource.name}" do
|
23
|
+
bucket.website_configuration = nil
|
22
24
|
end
|
23
25
|
end
|
24
26
|
end
|
25
|
-
new_resource.endpoint "#{fqn}.s3-website-#{s3_website_endpoint_region}.amazonaws.com"
|
26
|
-
new_resource.save
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
protected
|
30
|
+
|
31
|
+
def create_aws_object
|
32
|
+
converge_by "create new S3 bucket #{new_resource.name}" do
|
33
|
+
bucket = new_resource.driver.s3.buckets.create(new_resource.name)
|
34
|
+
bucket.tags['Name'] = new_resource.name
|
35
|
+
bucket
|
34
36
|
end
|
37
|
+
end
|
35
38
|
|
36
|
-
|
39
|
+
def update_aws_object(bucket)
|
37
40
|
end
|
38
41
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
+
def destroy_aws_object(bucket)
|
43
|
+
converge_by "delete S3 bucket #{new_resource.name}" do
|
44
|
+
bucket.delete
|
45
|
+
end
|
42
46
|
end
|
43
47
|
|
44
|
-
|
48
|
+
private
|
49
|
+
|
50
|
+
def modifies_website_configuration?(aws_object)
|
45
51
|
# This is incomplete, routing rules have many optional values, so its
|
46
52
|
# possible aws will put in default values for those which won't be in
|
47
53
|
# the requested config.
|
48
|
-
new_web_config = new_resource.website_options
|
49
|
-
current_web_config = current_website_configuration
|
54
|
+
new_web_config = new_resource.website_options || {}
|
50
55
|
|
51
|
-
|
52
|
-
(current_web_config[:index_document] != new_web_config.fetch(:index_document, {}) ||
|
53
|
-
current_web_config[:error_document] != new_web_config.fetch(:error_document, {}) ||
|
54
|
-
current_web_config[:routing_rules] != new_web_config.fetch(:routing_rules, []))
|
55
|
-
end
|
56
|
+
current_web_config = (aws_object.website_configuration || {}).to_hash
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
else
|
61
|
-
{}
|
62
|
-
end
|
58
|
+
(current_web_config[:index_document] != new_web_config.fetch(:index_document, {}) ||
|
59
|
+
current_web_config[:error_document] != new_web_config.fetch(:error_document, {}) ||
|
60
|
+
current_web_config[:routing_rules] != new_web_config.fetch(:routing_rules, []))
|
63
61
|
end
|
64
62
|
|
65
63
|
def s3_website_endpoint_region
|
66
64
|
# ¯\_(ツ)_/¯
|
67
|
-
case
|
65
|
+
case aws_object.location_constraint
|
68
66
|
when nil, 'US'
|
69
67
|
'us-east-1'
|
70
68
|
when 'EU'
|
71
69
|
'eu-west-1'
|
72
70
|
else
|
73
|
-
|
71
|
+
aws_object.location_constraint
|
74
72
|
end
|
75
73
|
end
|
76
|
-
|
77
|
-
# Fully qualified bucket name (i.e resource_region unless otherwise specified)
|
78
|
-
def id
|
79
|
-
new_resource.bucket_name || new_resource.name
|
80
|
-
end
|
81
74
|
end
|
@@ -1,89 +1,282 @@
|
|
1
|
-
require 'chef/
|
1
|
+
require 'chef/provisioning/aws_driver/aws_provider'
|
2
2
|
require 'date'
|
3
|
+
require 'ipaddr'
|
4
|
+
require 'set'
|
3
5
|
|
4
|
-
class Chef::Provider::AwsSecurityGroup < Chef::
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
6
|
+
class Chef::Provider::AwsSecurityGroup < Chef::Provisioning::AWSDriver::AWSProvider
|
7
|
+
|
8
|
+
def action_create
|
9
|
+
sg = super
|
10
|
+
|
11
|
+
apply_rules(sg)
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def create_aws_object
|
17
|
+
converge_by "Creating new SG #{new_resource.name} in #{region}" do
|
18
|
+
options = { description: new_resource.description }
|
19
|
+
options[:vpc] = new_resource.vpc if new_resource.vpc
|
20
|
+
options = AWSResource.lookup_options(options, resource: new_resource)
|
21
|
+
Chef::Log.debug("VPC: #{options[:vpc]}")
|
22
|
+
|
23
|
+
sg = new_resource.driver.ec2.security_groups.create(new_resource.name, options)
|
24
|
+
end
|
25
|
+
end
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
def update_aws_object(sg)
|
28
|
+
if !new_resource.description.nil? && new_resource.description != sg.description
|
29
|
+
raise "Security Group descriptions cannot be changed after being created! Desired description for #{new_resource.name} (#{sg.id}) was \"#{new_resource.description}\" and actual description is \"#{sg.description}\""
|
30
|
+
end
|
31
|
+
if !new_resource.vpc.nil?
|
32
|
+
desired_vpc = Chef::Resource::AwsVpc.get_aws_object_id(new_resource.vpc, resource: new_resource)
|
33
|
+
if desired_vpc != sg.vpc_id
|
34
|
+
raise "Security Group VPC cannot be changed after being created! Desired VPC for #{new_resource.name} (#{sg.id}) was #{new_resource.vpc} (#{desired_vpc}) and actual VPC is #{sg.vpc_id}"
|
27
35
|
end
|
28
36
|
end
|
37
|
+
apply_rules(sg)
|
38
|
+
end
|
29
39
|
|
30
|
-
|
31
|
-
|
40
|
+
def destroy_aws_object(sg)
|
41
|
+
converge_by "Deleting SG #{new_resource.name} in #{region}" do
|
42
|
+
sg.delete
|
43
|
+
end
|
32
44
|
end
|
33
45
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
46
|
+
private
|
47
|
+
|
48
|
+
def apply_rules(sg)
|
49
|
+
vpc = sg.vpc
|
50
|
+
if !new_resource.outbound_rules.nil?
|
51
|
+
update_outbound_rules(sg, vpc)
|
39
52
|
end
|
40
53
|
|
41
|
-
new_resource.
|
54
|
+
if !new_resource.inbound_rules.nil?
|
55
|
+
update_inbound_rules(sg, vpc)
|
56
|
+
end
|
42
57
|
end
|
43
58
|
|
44
|
-
|
45
|
-
|
46
|
-
#
|
47
|
-
|
59
|
+
def update_inbound_rules(sg, vpc)
|
60
|
+
#
|
61
|
+
# Get desired rules
|
62
|
+
#
|
63
|
+
desired_rules = {}
|
64
|
+
|
65
|
+
case new_resource.inbound_rules
|
66
|
+
when Hash
|
67
|
+
new_resource.inbound_rules.each do |sources_spec, port_spec|
|
68
|
+
add_rule(desired_rules, get_port_ranges(port_spec), get_actors(vpc, sources_spec))
|
69
|
+
end
|
70
|
+
|
71
|
+
when Array
|
72
|
+
# [ { port: X, protocol: Y, sources: [ ... ]}]
|
48
73
|
new_resource.inbound_rules.each do |rule|
|
49
|
-
|
50
|
-
|
51
|
-
security_group.authorize_ingress(rule[:protocol], rule[:ports], *rule[:sources])
|
52
|
-
end
|
53
|
-
rescue AWS::EC2::Errors::InvalidPermission::Duplicate
|
54
|
-
Chef::Log.debug 'Duplicate rule, ignoring.'
|
55
|
-
end
|
74
|
+
port_ranges = get_port_ranges(port_range: rule[:port], protocol: rule[:protocol])
|
75
|
+
add_rule(desired_rules, port_ranges, get_actors(vpc, rule[:sources]))
|
56
76
|
end
|
77
|
+
|
78
|
+
else
|
79
|
+
raise ArgumentError, "inbound_rules must be a Hash or Array (was #{new_resource.inbound_rules.inspect})"
|
57
80
|
end
|
58
81
|
|
59
|
-
#
|
60
|
-
|
82
|
+
#
|
83
|
+
# Actually update the rules (remove, add)
|
84
|
+
#
|
85
|
+
update_rules(desired_rules, sg.ip_permissions_list,
|
86
|
+
|
87
|
+
authorize: proc do |port_range, protocol, actors|
|
88
|
+
names = actors.map { |a| a.is_a?(Hash) ? a[:group_id] : a }
|
89
|
+
converge_by "authorize #{names.join(', ')} to send traffic to group #{new_resource.name} (#{sg.id}) on port_range #{port_range} with protocol #{protocol}" do
|
90
|
+
sg.authorize_ingress(protocol, port_range, *actors)
|
91
|
+
end
|
92
|
+
end,
|
93
|
+
|
94
|
+
revoke: proc do |port_range, protocol, actors|
|
95
|
+
names = actors.map { |a| a.is_a?(Hash) ? a[:group_id] : a }
|
96
|
+
converge_by "revoke the ability of #{names.join(', ')} to send traffic to group #{new_resource.name} (#{sg.id}) on port_range #{port_range} with protocol #{protocol}" do
|
97
|
+
sg.revoke_ingress(protocol, port_range, *actors)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
def update_outbound_rules(sg, vpc)
|
104
|
+
#
|
105
|
+
# Get desired rules
|
106
|
+
#
|
107
|
+
desired_rules = {}
|
108
|
+
|
109
|
+
case new_resource.outbound_rules
|
110
|
+
when Hash
|
111
|
+
new_resource.outbound_rules.each do |port_spec, sources_spec|
|
112
|
+
add_rule(desired_rules, get_port_ranges(port_spec), get_actors(vpc, sources_spec))
|
113
|
+
end
|
114
|
+
|
115
|
+
when Array
|
116
|
+
# [ { port: X, protocol: Y, sources: [ ... ]}]
|
61
117
|
new_resource.outbound_rules.each do |rule|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
118
|
+
port_ranges = get_port_ranges(port_range: rule[:port], protocol: rule[:protocol])
|
119
|
+
add_rule(desired_rules, port_ranges, get_actors(vpc, rule[:destinations]))
|
120
|
+
end
|
121
|
+
|
122
|
+
else
|
123
|
+
raise ArgumentError, "outbound_rules must be a Hash or Array (was #{new_resource.outbound_rules.inspect})"
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
# Actually update the rules (remove, add)
|
128
|
+
#
|
129
|
+
update_rules(desired_rules, sg.ip_permissions_list_egress,
|
130
|
+
|
131
|
+
authorize: proc do |port_range, protocol, actors|
|
132
|
+
names = actors.map { |a| a.is_a?(Hash) ? a[:group_id] : a }
|
133
|
+
converge_by "authorize group #{new_resource.name} (#{sg.id}) to send traffic to #{names.join(', ')} on port_range #{port_range} with protocol #{protocol}" do
|
134
|
+
sg.authorize_egress(*actors, ports: port_range, protocol: protocol)
|
135
|
+
end
|
136
|
+
end,
|
137
|
+
|
138
|
+
revoke: proc do |port_range, protocol, actors|
|
139
|
+
names = actors.map { |a| a.is_a?(Hash) ? a[:group_id] : a }
|
140
|
+
converge_by "revoke the ability of group #{new_resource.name} (#{sg.id}) to send traffic to #{names.join(', ')} on port_range #{port_range} with protocol #{protocol}" do
|
141
|
+
sg.revoke_egress(*actors, ports: port_range, protocol: protocol)
|
68
142
|
end
|
69
143
|
end
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
def update_rules(desired_rules, actual_rules_list, authorize: nil, revoke: nil)
|
148
|
+
actual_rules = {}
|
149
|
+
actual_rules_list.each do |rule|
|
150
|
+
port_range = {
|
151
|
+
port_range: rule[:from_port] ? rule[:from_port]..rule[:to_port] : nil,
|
152
|
+
protocol: rule[:ip_protocol].to_s.to_sym
|
153
|
+
}
|
154
|
+
add_rule(actual_rules, [ port_range ], rule[:groups]) if rule[:groups]
|
155
|
+
add_rule(actual_rules, [ port_range ], rule[:ip_ranges].map { |r| r[:cidr_ip] }) if rule[:ip_ranges]
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# Get the list of permissions to add and remove
|
160
|
+
#
|
161
|
+
actual_rules.each do |port_range, actors|
|
162
|
+
if desired_rules[port_range]
|
163
|
+
intersection = actors & desired_rules[port_range]
|
164
|
+
# Anything unhandled in desired_rules will be added
|
165
|
+
desired_rules[port_range] -= intersection
|
166
|
+
# Anything unhandled in actual_rules will be removed
|
167
|
+
actual_rules[port_range] -= intersection
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# Add any new rules
|
173
|
+
#
|
174
|
+
desired_rules.each do |port_range, actors|
|
175
|
+
unless actors.empty?
|
176
|
+
authorize.call(port_range[:port_range], port_range[:protocol], actors)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# Remove any rules no longer in effect
|
182
|
+
#
|
183
|
+
actual_rules.each do |port_range, actors|
|
184
|
+
unless actors.empty?
|
185
|
+
revoke.call(port_range[:port_range], port_range[:protocol], actors)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def add_rule(rules, port_ranges, actors)
|
191
|
+
unless actors.empty?
|
192
|
+
port_ranges.each do |port_range|
|
193
|
+
rules[port_range] ||= Set.new
|
194
|
+
rules[port_range] += actors
|
195
|
+
end
|
70
196
|
end
|
71
197
|
end
|
72
198
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
|
199
|
+
def get_port_ranges(port_spec)
|
200
|
+
case port_spec
|
201
|
+
when Integer
|
202
|
+
[ { port_range: port_spec..port_spec, protocol: :tcp } ]
|
203
|
+
when Range
|
204
|
+
[ { port_range: port_spec, protocol: :tcp } ]
|
205
|
+
when Array
|
206
|
+
port_spec.map { |p| get_port_ranges(p) }.flatten
|
207
|
+
when Hash
|
208
|
+
if port_spec[:protocol]
|
209
|
+
[ { port_range: port_spec[:port_range] || port_spec[:port], protocol: port_spec[:protocol].to_s.to_sym } ]
|
77
210
|
else
|
78
|
-
|
211
|
+
get_port_ranges(port_spec[:port_range] || port_spec[:port])
|
79
212
|
end
|
80
|
-
|
81
|
-
|
213
|
+
# The to_s.to_sym dance is because if you specify a protocol number, AWS symbolifies it,
|
214
|
+
# but 26.to_sym doesn't work (so we have to to_s it first).
|
215
|
+
when nil
|
216
|
+
[ { port_range: nil, protocol: :any } ]
|
82
217
|
end
|
83
218
|
end
|
84
219
|
|
85
|
-
|
86
|
-
|
220
|
+
#
|
221
|
+
# Turns an actor_spec into a uniform array, containing CIDRs, AWS::EC2::LoadBalancers and AWS::EC2::SecurityGroups.
|
222
|
+
#
|
223
|
+
def get_actors(vpc, actor_spec)
|
224
|
+
result = case actor_spec
|
225
|
+
|
226
|
+
# An array is always considered a list of actors. Each one may follow any supported format.
|
227
|
+
when Array
|
228
|
+
actor_spec.map { |a| get_actors(vpc, a) }
|
229
|
+
|
230
|
+
# Hashes come in several forms:
|
231
|
+
when Hash
|
232
|
+
# The default AWS Ruby SDK form with :user_id, :group_id and :group_name forms
|
233
|
+
if actor_spec.keys.all? { |key| [ :user_id, :group_id, :group_name ].include?(key) }
|
234
|
+
if actor_spec.has_key?(:group_name)
|
235
|
+
actor_spec[:group_id] ||= vpc.security_groups.filter('group-name', actor_spec[:group_name]).first.id
|
236
|
+
end
|
237
|
+
actor_spec[:user_id] ||= new_resource.driver.account_id
|
238
|
+
{ user_id: actor_spec[:user_id], group_id: actor_spec[:group_id] }
|
239
|
+
|
240
|
+
# load_balancer: <load balancer name>
|
241
|
+
elsif actor_spec.keys == [ :load_balancer ]
|
242
|
+
lb = Chef::Resource::AwsLoadBalancer.get_aws_object(actor_spec.values.first, resource: new_resource)
|
243
|
+
get_actors(vpc, lb)
|
244
|
+
|
245
|
+
# security_group: <security group name>
|
246
|
+
elsif actor_spec.keys == [ :security_group ]
|
247
|
+
Chef::Resource::AwsSecurityGroup.get_aws_object(actor_spec[:security_group], resource: new_resource)
|
248
|
+
|
249
|
+
else
|
250
|
+
raise "Unable to reference security group with spec #{actor_spec}"
|
251
|
+
end
|
252
|
+
|
253
|
+
# If a load balancer is specified, grab it and then get its automatic security group
|
254
|
+
when /^elb-[a-fA-F0-9]{8}$/, AWS::ELB::LoadBalancer, Chef::Resource::AwsLoadBalancer
|
255
|
+
lb = Chef::Resource::AwsLoadBalancer.get_aws_object(actor_spec, resource: new_resource)
|
256
|
+
get_actors(vpc, lb.source_security_group)
|
257
|
+
|
258
|
+
# If a security group is specified, grab it
|
259
|
+
when /^sg-[a-fA-F0-9]{8}$/, AWS::EC2::SecurityGroup, Chef::Resource::AwsSecurityGroup
|
260
|
+
Chef::Resource::AwsSecurityGroup.get_aws_object(actor_spec, resource: new_resource)
|
261
|
+
|
262
|
+
# If an IP addresses / CIDR are passed, return it verbatim; otherwise, assume it's the
|
263
|
+
# name of a security group.
|
264
|
+
when String
|
265
|
+
begin
|
266
|
+
IPAddr.new(actor_spec)
|
267
|
+
# Add /32 to the end of raw IP addresses
|
268
|
+
actor_spec =~ /\// ? actor_spec : "#{actor_spec}/32"
|
269
|
+
rescue
|
270
|
+
Chef::Resource::AwsSecurityGroup.get_aws_object(actor_spec, resource: new_resource)
|
271
|
+
end
|
272
|
+
|
273
|
+
else
|
274
|
+
raise "Unexpected actor #{actor_spec} in rules list"
|
275
|
+
end
|
276
|
+
|
277
|
+
result = { user_id: result.owner_id, group_id: result.id } if result.is_a?(AWS::EC2::SecurityGroup)
|
278
|
+
|
279
|
+
[ result ].flatten
|
87
280
|
end
|
88
281
|
|
89
282
|
end
|