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.
- checksums.yaml +4 -4
- data/Gemfile.lock +21 -19
- data/cfn-vpn.gemspec +1 -0
- data/docs/README.md +2 -2
- data/docs/getting-started.md +52 -26
- data/docs/routes.md +34 -20
- data/lib/cfnvpn/actions/init.rb +17 -11
- data/lib/cfnvpn/actions/modify.rb +20 -0
- data/lib/cfnvpn/actions/routes.rb +60 -8
- data/lib/cfnvpn/clientvpn.rb +18 -0
- data/lib/cfnvpn/s3.rb +30 -0
- data/lib/cfnvpn/s3_bucket.rb +48 -0
- data/lib/cfnvpn/string.rb +4 -0
- data/lib/cfnvpn/templates/lambdas/auto_route_populator/app.py +175 -0
- data/lib/cfnvpn/templates/lambdas/scheduler/app.py +36 -0
- data/lib/cfnvpn/templates/lambdas.rb +35 -0
- data/lib/cfnvpn/templates/vpn.rb +193 -94
- data/lib/cfnvpn/version.rb +1 -1
- metadata +21 -3
@@ -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
@@ -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
|
+
|
data/lib/cfnvpn/templates/vpn.rb
CHANGED
@@ -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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
90
|
+
DependsOn network_assoc_dependson if network_assoc_dependson.any?
|
96
91
|
Description FnSub("#{name} client-vpn auth rule for subnet association")
|
97
|
-
|
92
|
+
AccessGroupId group
|
98
93
|
ClientVpnEndpointId Ref(:ClientVpnEndpoint)
|
99
|
-
TargetNetworkCidr CfnVpn::Templates::Helper.get_auth_cidr(config[:region],
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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:
|
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
|
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.
|
392
|
+
Runtime 'python3.8'
|
263
393
|
Role FnGetAtt(:ClientVpnSchedulerRole, :Arn)
|
264
394
|
MemorySize '128'
|
265
|
-
Handler '
|
395
|
+
Handler 'app.handler'
|
396
|
+
Timeout 60
|
266
397
|
Code({
|
267
|
-
|
268
|
-
|
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" },
|
data/lib/cfnvpn/version.rb
CHANGED