chef-provisioning-aws 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|