cfn-vpn 1.2.0 → 1.3.4

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,48 @@
1
+ require 'aws-sdk-s3'
2
+ require 'fileutils'
3
+ require 'securerandom'
4
+
5
+ module CfnVpn
6
+ class S3Bucket
7
+
8
+ def initialize(region, name)
9
+ @client = Aws::S3::Client.new(region: region)
10
+ @name = name
11
+ end
12
+
13
+ def generate_bucket_name
14
+ return "cfnvpn-#{@name}-#{SecureRandom.hex}"
15
+ end
16
+
17
+ def create_bucket(bucket)
18
+ @client.create_bucket({
19
+ bucket: bucket,
20
+ acl: 'private'
21
+ })
22
+
23
+ @client.put_public_access_block({
24
+ bucket: bucket,
25
+ public_access_block_configuration: {
26
+ block_public_acls: true,
27
+ ignore_public_acls: true,
28
+ block_public_policy: true,
29
+ restrict_public_buckets: true,
30
+ }
31
+ })
32
+
33
+ @client.put_bucket_encryption({
34
+ bucket: bucket,
35
+ server_side_encryption_configuration: {
36
+ rules: [
37
+ {
38
+ apply_server_side_encryption_by_default: {
39
+ sse_algorithm: "AES256"
40
+ }
41
+ }
42
+ ]
43
+ }
44
+ })
45
+ end
46
+
47
+ end
48
+ end
data/lib/cfnvpn/string.rb CHANGED
@@ -11,6 +11,10 @@ class String
11
11
  self.gsub(/[^a-zA-Z0-9]/, "").capitalize
12
12
  end
13
13
 
14
+ def event_id_safe
15
+ self.gsub('*', 'wildcard').gsub(/[^\.\-_A-Za-z0-9]+/, "").downcase
16
+ end
17
+
14
18
  def colorize(color_code)
15
19
  "\e[#{color_code}m#{self}\e[0m"
16
20
  end
@@ -0,0 +1,175 @@
1
+ import socket
2
+ import boto3
3
+ from botocore.exceptions import ClientError
4
+ import logging
5
+
6
+ logger = logging.getLogger()
7
+ logger.setLevel(logging.INFO)
8
+
9
+
10
+ def delete_route(client, vpn_endpoint, subnet, cidr):
11
+ client.delete_client_vpn_route(
12
+ ClientVpnEndpointId=vpn_endpoint,
13
+ TargetVpcSubnetId=subnet,
14
+ DestinationCidrBlock=cidr,
15
+ )
16
+
17
+
18
+ def create_route(client, event, cidr):
19
+ client.create_client_vpn_route(
20
+ ClientVpnEndpointId=event['ClientVpnEndpointId'],
21
+ DestinationCidrBlock=cidr,
22
+ TargetVpcSubnetId=event['TargetSubnet'],
23
+ Description=f"cfnvpn auto generated route for endpoint {event['Record']}. {event['Description']}"
24
+ )
25
+
26
+
27
+ def revoke_route_auth(client, event, cidr, group = None):
28
+ args = {
29
+ 'ClientVpnEndpointId': event['ClientVpnEndpointId'],
30
+ 'TargetNetworkCidr': cidr
31
+ }
32
+
33
+ if group is None:
34
+ args['RevokeAllGroups'] = True
35
+ else:
36
+ args['AccessGroupId'] = group
37
+
38
+ client.revoke_client_vpn_ingress(**args)
39
+
40
+
41
+ def authorize_route(client, event, cidr, group = None):
42
+ args = {
43
+ 'ClientVpnEndpointId': event['ClientVpnEndpointId'],
44
+ 'TargetNetworkCidr': cidr,
45
+ 'Description': f"cfnvpn auto generated authorization for endpoint {event['Record']}. {event['Description']}"
46
+ }
47
+
48
+ if group is None:
49
+ args['AuthorizeAllGroups'] = True
50
+ else:
51
+ args['AccessGroupId'] = group
52
+
53
+ client.authorize_client_vpn_ingress(**args)
54
+
55
+
56
+ def get_routes(client, event):
57
+ response = client.describe_client_vpn_routes(
58
+ ClientVpnEndpointId=event['ClientVpnEndpointId'],
59
+ Filters=[
60
+ {
61
+ 'Name': 'origin',
62
+ 'Values': ['add-route']
63
+ }
64
+ ]
65
+ )
66
+
67
+ routes = [route for route in response['Routes'] if event['Record'] in route['Description']]
68
+ logger.info(f"found {len(routes)} exisiting routes for {event['Record']}")
69
+ return routes
70
+
71
+
72
+ def get_rules(client, vpn_endpoint, cidr):
73
+ response = client.describe_client_vpn_authorization_rules(
74
+ ClientVpnEndpointId=vpn_endpoint,
75
+ Filters=[
76
+ {
77
+ 'Name': 'destination-cidr',
78
+ 'Values': [cidr]
79
+ }
80
+ ]
81
+ )
82
+ return response['AuthorizationRules']
83
+
84
+
85
+ def handler(event,context):
86
+
87
+ # DNS lookup on the dns record and return all IPS for the endpoint
88
+ try:
89
+ cidrs = [ ip + "/32" for ip in socket.gethostbyname_ex(event['Record'])[-1]]
90
+ logger.info(f"resolved endpoint {event['Record']} to {cidrs}")
91
+ except socket.gaierror as e:
92
+ logger.exception(f"failed to resolve record {event['Record']}")
93
+ return 'KO'
94
+
95
+ client = boto3.client('ec2')
96
+ routes = get_routes(client, event)
97
+
98
+ for cidr in cidrs:
99
+ route = next((route for route in routes if route['DestinationCidr'] == cidr), None)
100
+
101
+ # if there are no existing routes for the endpoint cidr create a new route
102
+ if route is None:
103
+ try:
104
+ create_route(client, event, cidr)
105
+ if 'Groups' in event:
106
+ for group in event['Groups']:
107
+ authorize_route(client, event, cidr, group)
108
+ else:
109
+ authorize_route(client, event, cidr)
110
+ except ClientError as e:
111
+ if e.response['Error']['Code'] == 'InvalidClientVpnDuplicateRoute':
112
+ logger.error(f"route for CIDR {cidr} already exists with a different endpoint")
113
+ continue
114
+ raise e
115
+
116
+ # if the route already exists
117
+ else:
118
+
119
+ logger.info(f"route for cidr {cidr} is already in place")
120
+
121
+ # if the target subnet has changed in the payload, recreate the routes to use the new subnet
122
+ if route['TargetSubnet'] != event['TargetSubnet']:
123
+ logger.info(f"target subnet for route for {cidr} has changed, recreating the route")
124
+ delete_route(client, event['ClientVpnEndpointId'], route['TargetSubnet'], cidr)
125
+ create_route(client, event, cidr)
126
+
127
+ logger.info(f"checking authorization rules for the route")
128
+
129
+ # check the rules match the payload
130
+ rules = get_rules(client, event['ClientVpnEndpointId'], cidr)
131
+ existing_groups = [rule['GroupId'] for rule in rules]
132
+ if 'Groups' in event:
133
+ # remove expired rules not defined in the payload anymore
134
+ expired_rules = [rule for rule in rules if rule['GroupId'] not in event['Groups']]
135
+ for rule in expired_rules:
136
+ logger.info(f"removing expired authorization rule for group {rule['GroupId']} for route {cidr}")
137
+ revoke_route_auth(client, event, cidr, rule['GroupId'])
138
+ # add new rules defined in the payload
139
+ new_rules = [group for group in event['Groups'] if group not in existing_groups]
140
+ for group in new_rules:
141
+ logger.info(f"creating new authorization rule for group {rule['GroupId']} for route {cidr}")
142
+ authorize_route(client, event, cidr, group)
143
+ else:
144
+ # if amount of rules for the cidr is greater than 1 when no groups are specified in the payload
145
+ # we'll assume that all groups have been removed from the payload so we'll remove all existing rules and add a rule for allow all
146
+ if len(rules) > 1:
147
+ logger.info(f"creating an allow all rule for route {cidr}")
148
+ revoke_route_auth(client, event, cidr)
149
+ authorize_route(client, event, cidr)
150
+
151
+
152
+
153
+
154
+ # clean up any expired routes when the ips for an endpoint change
155
+ expired_routes = [route for route in routes if route['DestinationCidr'] not in cidrs]
156
+ for route in expired_routes:
157
+ logger.info(f"removing expired route {route['DestinationCidr']} for endpoint {event['Record']}")
158
+
159
+ try:
160
+ revoke_route_auth(client, event, route['DestinationCidr'])
161
+ except ClientError as e:
162
+ if e.response['Error']['Code'] == 'InvalidClientVpnEndpointAuthorizationRuleNotFound':
163
+ pass
164
+ else:
165
+ raise e
166
+
167
+ try:
168
+ delete_route(client, event['ClientVpnEndpointId'], route['TargetSubnet'], route['DestinationCidr'])
169
+ except ClientError as e:
170
+ if e.response['Error']['Code'] == 'InvalidClientVpnRouteNotFound':
171
+ pass
172
+ else:
173
+ raise e
174
+
175
+ return 'OK'
@@ -0,0 +1,36 @@
1
+ import boto3
2
+ import logging
3
+
4
+ logger = logging.getLogger()
5
+ logger.setLevel(logging.INFO)
6
+
7
+ def handler(event, context):
8
+
9
+ logger.info(f"updating cfn-vpn stack {event['StackName']} parameter AssociateSubnets with value {event['AssociateSubnets']}")
10
+
11
+ if event['AssociateSubnets'] == 'false':
12
+ logger.info(f"terminating current vpn sessions to {event['ClientVpnEndpointId']}")
13
+ ec2 = boto3.client('ec2')
14
+ resp = ec2.describe_client_vpn_connections(ClientVpnEndpointId=event['ClientVpnEndpointId'])
15
+ for conn in resp['Connections']:
16
+ if conn['Status']['Code'] == 'active':
17
+ ec2.terminate_client_vpn_connections(
18
+ ClientVpnEndpointId=event['ClientVpnEndpointId'],
19
+ ConnectionId=conn['ConnectionId']
20
+ )
21
+ logger.info(f"terminated session {conn['ConnectionId']}")
22
+
23
+ client = boto3.client('cloudformation')
24
+ logger.info(client.update_stack(
25
+ StackName=event['StackName'],
26
+ UsePreviousTemplate=True,
27
+ Capabilities=['CAPABILITY_IAM'],
28
+ Parameters=[
29
+ {
30
+ 'ParameterKey': 'AssociateSubnets',
31
+ 'ParameterValue': event['AssociateSubnets']
32
+ }
33
+ ]
34
+ ))
35
+
36
+ return 'OK'
@@ -0,0 +1,35 @@
1
+ require 'zip'
2
+ require 'securerandom'
3
+ require 'aws-sdk-s3'
4
+ require 'cfnvpn/log'
5
+
6
+ module CfnVpn
7
+ module Templates
8
+ class Lambdas
9
+
10
+ def self.package_lambda(name:, bucket:, func:, files:)
11
+ lambdas_dir = File.join(File.dirname(File.expand_path(__FILE__)), 'lambdas')
12
+ FileUtils.mkdir_p(lambdas_dir)
13
+
14
+ CfnVpn::Log.logger.debug "zipping lambda function #{func}"
15
+ zipfile_name = "#{func}-#{SecureRandom.hex}.zip"
16
+ zipfile_path = "#{CfnVpn.cfnvpn_path}/#{name}/lambdas"
17
+ FileUtils.mkdir_p(zipfile_path)
18
+ Zip::File.open("#{zipfile_path}/#{zipfile_name}", Zip::File::CREATE) do |zipfile|
19
+ files.each do |file|
20
+ zipfile.add(file, File.join("#{lambdas_dir}/#{func}", file))
21
+ end
22
+ end
23
+
24
+ bucket = Aws::S3::Bucket.new(bucket)
25
+ object = bucket.object("cfnvpn/lambdas/#{name}/#{zipfile_name}")
26
+ CfnVpn::Log.logger.debug "uploading #{zipfile_name} to s3://#{bucket}/#{object.key}"
27
+ object.upload_file("#{zipfile_path}/#{zipfile_name}")
28
+
29
+ return object.key
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+
@@ -1,5 +1,6 @@
1
1
  require 'cfndsl'
2
2
  require 'cfnvpn/templates/helper'
3
+ require 'cfnvpn/templates/lambdas'
3
4
 
4
5
  module CfnVpn
5
6
  module Templates
@@ -32,7 +33,7 @@ module CfnVpn
32
33
  {
33
34
  FederatedAuthentication: {
34
35
  SAMLProviderArn: config[:saml_arn],
35
- SelfServiceSAMLProviderArn: config[:saml_arn]
36
+ SelfServiceSAMLProviderArn: config[:saml_self_service_arn].nil? ? config[:saml_arn] : config[:saml_self_service_arn]
36
37
  },
37
38
  Type: 'federated-authentication'
38
39
  }
@@ -69,6 +70,7 @@ module CfnVpn
69
70
  SplitTunnel config[:split_tunnel]
70
71
  }
71
72
 
73
+ network_assoc_dependson = []
72
74
  config[:subnet_ids].each_with_index do |subnet, index|
73
75
  suffix = index == 0 ? "" : "For#{subnet.resource_safe}"
74
76
 
@@ -78,74 +80,112 @@ module CfnVpn
78
80
  SubnetId subnet
79
81
  }
80
82
 
81
- if config[:default_groups].any?
82
- config[:default_groups].each do |group|
83
- EC2_ClientVpnAuthorizationRule(:"TargetNetworkAuthorizationRule#{suffix}#{group.resource_safe}"[0..255]) {
84
- Condition(:EnableSubnetAssociation)
85
- DependsOn "ClientVpnTargetNetworkAssociation#{suffix}"
86
- Description FnSub("#{name} client-vpn auth rule for subnet association")
87
- AccessGroupId group
88
- ClientVpnEndpointId Ref(:ClientVpnEndpoint)
89
- TargetNetworkCidr CfnVpn::Templates::Helper.get_auth_cidr(config[:region], subnet)
90
- }
91
- end
92
- else
93
- EC2_ClientVpnAuthorizationRule(:"TargetNetworkAuthorizationRule#{suffix}") {
83
+ network_assoc_dependson << "ClientVpnTargetNetworkAssociation#{suffix}"
84
+ end
85
+
86
+ if config[:default_groups].any?
87
+ config[:default_groups].each do |group|
88
+ EC2_ClientVpnAuthorizationRule(:"TargetNetworkAuthorizationRule#{group.resource_safe}"[0..255]) {
94
89
  Condition(:EnableSubnetAssociation)
95
- DependsOn "ClientVpnTargetNetworkAssociation#{suffix}"
90
+ DependsOn network_assoc_dependson if network_assoc_dependson.any?
96
91
  Description FnSub("#{name} client-vpn auth rule for subnet association")
97
- AuthorizeAllGroups true
92
+ AccessGroupId group
98
93
  ClientVpnEndpointId Ref(:ClientVpnEndpoint)
99
- TargetNetworkCidr CfnVpn::Templates::Helper.get_auth_cidr(config[:region], subnet)
94
+ TargetNetworkCidr CfnVpn::Templates::Helper.get_auth_cidr(config[:region], config[:subnet_ids].first)
100
95
  }
101
96
  end
97
+ else
98
+ EC2_ClientVpnAuthorizationRule(:"TargetNetworkAuthorizationRule") {
99
+ Condition(:EnableSubnetAssociation)
100
+ DependsOn network_assoc_dependson if network_assoc_dependson.any?
101
+ Description FnSub("#{name} client-vpn auth rule for subnet association")
102
+ AuthorizeAllGroups true
103
+ ClientVpnEndpointId Ref(:ClientVpnEndpoint)
104
+ TargetNetworkCidr CfnVpn::Templates::Helper.get_auth_cidr(config[:region], config[:subnet_ids].first)
105
+ }
106
+ end
102
107
 
103
- if subnet == config[:internet_route]
104
- EC2_ClientVpnRoute(:RouteToInternet) {
105
- Condition(:EnableSubnetAssociation)
106
- DependsOn "ClientVpnTargetNetworkAssociation#{suffix}"
107
- Description 'Route to the internet'
108
- ClientVpnEndpointId Ref(:ClientVpnEndpoint)
109
- DestinationCidrBlock '0.0.0.0/0'
110
- TargetVpcSubnetId config[:internet_route]
108
+ if !config[:internet_route].nil?
109
+ EC2_ClientVpnRoute(:RouteToInternet) {
110
+ Condition(:EnableSubnetAssociation)
111
+ DependsOn network_assoc_dependson if network_assoc_dependson.any?
112
+ Description "Route to the internet through subnet #{config[:internet_route]}"
113
+ ClientVpnEndpointId Ref(:ClientVpnEndpoint)
114
+ DestinationCidrBlock '0.0.0.0/0'
115
+ TargetVpcSubnetId config[:internet_route]
116
+ }
117
+
118
+ EC2_ClientVpnAuthorizationRule(:RouteToInternetAuthorizationRule) {
119
+ Condition(:EnableSubnetAssociation)
120
+ DependsOn network_assoc_dependson if network_assoc_dependson.any?
121
+ Description "Internet route authorization from subnet #{config[:internet_route]}"
122
+ AuthorizeAllGroups true
123
+ ClientVpnEndpointId Ref(:ClientVpnEndpoint)
124
+ TargetNetworkCidr '0.0.0.0/0'
125
+ }
126
+
127
+ output(:InternetRoute, config[:internet_route])
128
+ end
129
+
130
+ dns_routes = config[:routes].select {|route| route.has_key?(:dns)}
131
+ cidr_routes = config[:routes].select {|route| route.has_key?(:cidr)}
132
+
133
+ if dns_routes.any?
134
+ auto_route_populator(name, config[:bucket])
135
+
136
+ dns_routes.each do |route|
137
+ input = {
138
+ Record: route[:dns],
139
+ ClientVpnEndpointId: "${ClientVpnEndpoint}",
140
+ TargetSubnet: route[:subnet],
141
+ Description: route[:desc]
111
142
  }
112
-
113
- EC2_ClientVpnAuthorizationRule(:RouteToInternetAuthorizationRule) {
114
- Condition(:EnableSubnetAssociation)
115
- DependsOn "ClientVpnTargetNetworkAssociation#{suffix}"
116
- Description 'Route to the internet'
117
- AuthorizeAllGroups true
118
- ClientVpnEndpointId Ref(:ClientVpnEndpoint)
119
- TargetNetworkCidr '0.0.0.0/0'
143
+
144
+ if route[:groups].any?
145
+ input[:Groups] = route[:groups]
146
+ end
147
+
148
+ Events_Rule(:"CfnVpnAutoRoutePopulatorEvent#{route[:dns].resource_safe}"[0..255]) {
149
+ State 'ENABLED'
150
+ Description "cfnvpn auto route populator schedule for #{route[:dns]}"
151
+ ScheduleExpression "rate(5 minutes)"
152
+ Targets([
153
+ {
154
+ Arn: FnGetAtt(:CfnVpnAutoRoutePopulator, :Arn),
155
+ Id: "cfnvpnautoroutepopulator#{route[:dns].event_id_safe}",
156
+ Input: FnSub(input.to_json)
157
+ }
158
+ ])
120
159
  }
121
-
122
- output(:InternetRoute, config[:internet_route])
123
160
  end
124
161
  end
125
162
 
126
- config[:routes].each do |route|
127
- EC2_ClientVpnRoute(:"#{route[:cidr].resource_safe}VpnRoute") {
128
- Description route[:desc]
129
- ClientVpnEndpointId Ref(:ClientVpnEndpoint)
130
- DestinationCidrBlock route[:cidr]
131
- TargetVpcSubnetId route[:subnet]
132
- }
133
- if route[:groups].any?
134
- route[:groups].each do |group|
135
- EC2_ClientVpnAuthorizationRule(:"#{route[:cidr].resource_safe}AuthorizationRule#{group.resource_safe}"[0..255]) {
136
- Description route[:desc]
137
- AccessGroupId group
163
+ if cidr_routes.any?
164
+ cidr_routes.each do |route|
165
+ EC2_ClientVpnRoute(:"#{route[:cidr].resource_safe}VpnRoute") {
166
+ Description "cfnvpn static route for #{route[:cidr]}. #{route[:desc]}".strip
167
+ ClientVpnEndpointId Ref(:ClientVpnEndpoint)
168
+ DestinationCidrBlock route[:cidr]
169
+ TargetVpcSubnetId route[:subnet]
170
+ }
171
+
172
+ if route[:groups].any?
173
+ route[:groups].each do |group|
174
+ EC2_ClientVpnAuthorizationRule(:"#{route[:cidr].resource_safe}AuthorizationRule#{group.resource_safe}"[0..255]) {
175
+ Description "cfnvpn static authorization rule for group #{group} to route #{route[:cidr]}. #{route[:desc]}".strip
176
+ AccessGroupId group
177
+ ClientVpnEndpointId Ref(:ClientVpnEndpoint)
178
+ TargetNetworkCidr route[:cidr]
179
+ }
180
+ end
181
+ else
182
+ EC2_ClientVpnAuthorizationRule(:"#{route[:cidr].resource_safe}AllowAllAuthorizationRule") {
183
+ Description "cfnvpn static allow all authorization rule to route #{route[:cidr]}. #{route[:desc]}".strip
184
+ AuthorizeAllGroups true
138
185
  ClientVpnEndpointId Ref(:ClientVpnEndpoint)
139
186
  TargetNetworkCidr route[:cidr]
140
187
  }
141
188
  end
142
- else
143
- EC2_ClientVpnAuthorizationRule(:"#{route[:cidr].resource_safe}AllowAllAuthorizationRule") {
144
- Description route[:desc]
145
- AuthorizeAllGroups true
146
- ClientVpnEndpointId Ref(:ClientVpnEndpoint)
147
- TargetNetworkCidr route[:cidr]
148
- }
149
189
  end
150
190
  end
151
191
 
@@ -156,13 +196,13 @@ module CfnVpn
156
196
  Type 'String'
157
197
  Value config.to_json
158
198
  Tags({
159
- Name: "#{name}-cfnvpn-config",
199
+ Name: "#{name}-cfnvpn-config",
160
200
  Environment: 'cfnvpn'
161
201
  })
162
202
  }
163
203
 
164
204
  if config[:start] || config[:stop]
165
- scheduler(name, config[:start], config[:stop])
205
+ scheduler(name, config[:start], config[:stop], config[:bucket])
166
206
  output(:Start, config[:start]) if config[:start]
167
207
  output(:Stop, config[:stop]) if config[:stop]
168
208
  end
@@ -188,7 +228,92 @@ module CfnVpn
188
228
  Output(name) { Value value }
189
229
  end
190
230
 
191
- def scheduler(name, start, stop)
231
+ def auto_route_populator(name, bucket)
232
+ IAM_Role(:CfnVpnAutoRoutePopulatorRole) {
233
+ AssumeRolePolicyDocument({
234
+ Version: '2012-10-17',
235
+ Statement: [{
236
+ Effect: 'Allow',
237
+ Principal: { Service: [ 'lambda.amazonaws.com' ] },
238
+ Action: [ 'sts:AssumeRole' ]
239
+ }]
240
+ })
241
+ Path '/cfnvpn/'
242
+ Policies([
243
+ {
244
+ PolicyName: 'client-vpn',
245
+ PolicyDocument: {
246
+ Version: '2012-10-17',
247
+ Statement: [{
248
+ Effect: 'Allow',
249
+ Action: [
250
+ 'ec2:AuthorizeClientVpnIngress',
251
+ 'ec2:RevokeClientVpnIngress',
252
+ 'ec2:DescribeClientVpnAuthorizationRules',
253
+ 'ec2:DescribeClientVpnEndpoints',
254
+ 'ec2:DescribeClientVpnRoutes',
255
+ 'ec2:CreateClientVpnRoute',
256
+ 'ec2:DeleteClientVpnRoute'
257
+ ],
258
+ Resource: '*'
259
+ }]
260
+ }
261
+ },
262
+ {
263
+ PolicyName: 'logging',
264
+ PolicyDocument: {
265
+ Version: '2012-10-17',
266
+ Statement: [{
267
+ Effect: 'Allow',
268
+ Action: [
269
+ 'logs:DescribeLogGroups',
270
+ 'logs:CreateLogGroup',
271
+ 'logs:CreateLogStream',
272
+ 'logs:DescribeLogStreams',
273
+ 'logs:PutLogEvents'
274
+ ],
275
+ Resource: '*'
276
+ }]
277
+ }
278
+ }
279
+ ])
280
+ Tags([
281
+ { Key: 'Name', Value: "#{name}-cfnvpn-auto-route-populator-role" },
282
+ { Key: 'Environment', Value: 'cfnvpn' }
283
+ ])
284
+ }
285
+
286
+ s3_key = CfnVpn::Templates::Lambdas.package_lambda(name: name, bucket: bucket, func: 'auto_route_populator', files: ['app.py'])
287
+
288
+ Lambda_Function(:CfnVpnAutoRoutePopulator) {
289
+ Runtime 'python3.8'
290
+ Role FnGetAtt(:CfnVpnAutoRoutePopulatorRole, :Arn)
291
+ MemorySize '128'
292
+ Handler 'app.handler'
293
+ Timeout 60
294
+ Code({
295
+ S3Bucket: bucket,
296
+ S3Key: s3_key
297
+ })
298
+ Tags([
299
+ { Key: 'Name', Value: "#{name}-cfnvpn-auto-route-populator" },
300
+ { Key: 'Environment', Value: 'cfnvpn' }
301
+ ])
302
+ }
303
+
304
+ Logs_LogGroup(:CfnVpnAutoRoutePopulatorLogGroup) {
305
+ LogGroupName FnSub("/aws/lambda/${CfnVpnAutoRoutePopulator}")
306
+ RetentionInDays 30
307
+ }
308
+
309
+ Lambda_Permission(:CfnVpnAutoRoutePopulatorFunctionPermissions) {
310
+ FunctionName Ref(:CfnVpnAutoRoutePopulator)
311
+ Action 'lambda:InvokeFunction'
312
+ Principal 'events.amazonaws.com'
313
+ }
314
+ end
315
+
316
+ def scheduler(name, start, stop, bucket)
192
317
  IAM_Role(:ClientVpnSchedulerRole) {
193
318
  AssumeRolePolicyDocument({
194
319
  Version: '2012-10-17',
@@ -228,7 +353,10 @@ module CfnVpn
228
353
  'ec2:DescribeClientVpnAuthorizationRules',
229
354
  'ec2:DescribeClientVpnEndpoints',
230
355
  'ec2:DescribeClientVpnConnections',
231
- 'ec2:TerminateClientVpnConnections'
356
+ 'ec2:TerminateClientVpnConnections',
357
+ 'ec2:DescribeClientVpnRoutes',
358
+ 'ec2:CreateClientVpnRoute',
359
+ 'ec2:DeleteClientVpnRoute'
232
360
  ],
233
361
  Resource: '*'
234
362
  }]
@@ -258,46 +386,17 @@ module CfnVpn
258
386
  ])
259
387
  }
260
388
 
389
+ s3_key = CfnVpn::Templates::Lambdas.package_lambda(name: name, bucket: bucket, func: 'scheduler', files: ['app.py'])
390
+
261
391
  Lambda_Function(:ClientVpnSchedulerFunction) {
262
- Runtime 'python3.7'
392
+ Runtime 'python3.8'
263
393
  Role FnGetAtt(:ClientVpnSchedulerRole, :Arn)
264
394
  MemorySize '128'
265
- Handler 'index.handler'
395
+ Handler 'app.handler'
396
+ Timeout 60
266
397
  Code({
267
- ZipFile: <<~EOS
268
- import boto3
269
-
270
- def handler(event, context):
271
-
272
- print(f"updating cfn-vpn stack {event['StackName']} parameter AssociateSubnets with value {event['AssociateSubnets']}")
273
-
274
- if event['AssociateSubnets'] == 'false':
275
- print(f"terminating current vpn sessions to {event['ClientVpnEndpointId']}")
276
- ec2 = boto3.client('ec2')
277
- resp = ec2.describe_client_vpn_connections(ClientVpnEndpointId=event['ClientVpnEndpointId'])
278
- for conn in resp['Connections']:
279
- if conn['Status']['Code'] == 'active':
280
- ec2.terminate_client_vpn_connections(
281
- ClientVpnEndpointId=event['ClientVpnEndpointId'],
282
- ConnectionId=conn['ConnectionId']
283
- )
284
- print(f"terminated session {conn['ConnectionId']}")
285
-
286
- client = boto3.client('cloudformation')
287
- print(client.update_stack(
288
- StackName=event['StackName'],
289
- UsePreviousTemplate=True,
290
- Capabilities=['CAPABILITY_IAM'],
291
- Parameters=[
292
- {
293
- 'ParameterKey': 'AssociateSubnets',
294
- 'ParameterValue': event['AssociateSubnets']
295
- }
296
- ]
297
- ))
298
-
299
- return 'OK'
300
- EOS
398
+ S3Bucket: bucket,
399
+ S3Key: s3_key
301
400
  })
302
401
  Tags([
303
402
  { Key: 'Name', Value: "#{name}-cfnvpn-scheduler-function" },
@@ -1,4 +1,4 @@
1
1
  module CfnVpn
2
- VERSION = "1.2.0".freeze
2
+ VERSION = "1.3.4".freeze
3
3
  CHANGE_SET_VERSION = VERSION.gsub('.', '-').freeze
4
4
  end