cfn-vpn 1.3.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,26 +1,41 @@
1
+ import os
1
2
  import socket
2
3
  import boto3
3
4
  from botocore.exceptions import ClientError
5
+ from lib.slack import Slack
6
+ from states import *
4
7
  import logging
8
+ from quotas import increase_quota, AUTH_RULE_TABLE_QUOTA_CODE, ROUTE_TABLE_QUOTA_CODE
5
9
 
6
- logger = logging.getLogger()
10
+ logger = logging.getLogger(__name__)
7
11
  logger.setLevel(logging.INFO)
8
12
 
13
+ SLACK_USERNAME = 'CfnVpn Route Table Event'
9
14
 
10
15
  def delete_route(client, vpn_endpoint, subnet, cidr):
16
+ try:
11
17
  client.delete_client_vpn_route(
12
18
  ClientVpnEndpointId=vpn_endpoint,
13
19
  TargetVpcSubnetId=subnet,
14
20
  DestinationCidrBlock=cidr,
15
21
  )
22
+ except ClientError as e:
23
+ if e.response['Error']['Code'] == 'InvalidClientVpnEndpointAuthorizationRuleNotFound':
24
+ logger.info(f"route not found when deleting", exc_info=True)
25
+ else:
26
+ raise e
16
27
 
17
28
 
18
- def create_route(client, event, cidr):
29
+ def create_route(client, event, cidr, target_subnet):
30
+ description = f"cfnvpn auto generated route for endpoint {event['Record']}."
31
+ if event['Description']:
32
+ description += f" {event['Description']}"
33
+
19
34
  client.create_client_vpn_route(
20
35
  ClientVpnEndpointId=event['ClientVpnEndpointId'],
21
36
  DestinationCidrBlock=cidr,
22
- TargetVpcSubnetId=event['TargetSubnet'],
23
- Description=f"cfnvpn auto generated route for endpoint {event['Record']}. {event['Description']}"
37
+ TargetVpcSubnetId=target_subnet,
38
+ Description=description
24
39
  )
25
40
 
26
41
 
@@ -34,15 +49,27 @@ def revoke_route_auth(client, event, cidr, group = None):
34
49
  args['RevokeAllGroups'] = True
35
50
  else:
36
51
  args['AccessGroupId'] = group
37
-
38
- client.revoke_client_vpn_ingress(**args)
52
+
53
+ try:
54
+ client.revoke_client_vpn_ingress(**args)
55
+ except ClientError as e:
56
+ if e.response['Error']['Code'] == 'ConcurrentMutationLimitExceeded':
57
+ logger.warn(f"revoking auth is being rate limited", exc_info=True)
58
+ elif e.response['Error']['Code'] == 'InvalidClientVpnEndpointAuthorizationRuleNotFound':
59
+ logger.info(f"rule not found when revoking", exc_info=True)
60
+ else:
61
+ raise e
39
62
 
40
63
 
41
64
  def authorize_route(client, event, cidr, group = None):
65
+ description = f"cfnvpn auto generated authorization for endpoint {event['Record']}."
66
+ if event['Description']:
67
+ description += f" {event['Description']}"
68
+
42
69
  args = {
43
70
  'ClientVpnEndpointId': event['ClientVpnEndpointId'],
44
71
  'TargetNetworkCidr': cidr,
45
- 'Description': f"cfnvpn auto generated authorization for endpoint {event['Record']}. {event['Description']}"
72
+ 'Description': description
46
73
  }
47
74
 
48
75
  if group is None:
@@ -54,7 +81,8 @@ def authorize_route(client, event, cidr, group = None):
54
81
 
55
82
 
56
83
  def get_routes(client, event):
57
- response = client.describe_client_vpn_routes(
84
+ paginator = client.get_paginator('describe_client_vpn_routes')
85
+ response_iterator = paginator.paginate(
58
86
  ClientVpnEndpointId=event['ClientVpnEndpointId'],
59
87
  Filters=[
60
88
  {
@@ -63,113 +91,170 @@ def get_routes(client, event):
63
91
  }
64
92
  ]
65
93
  )
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
94
+
95
+ return [route for page in response_iterator
96
+ for route in page['Routes']
97
+ if 'Description' in route and event['Record'] in route['Description']]
70
98
 
71
99
 
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
- ]
100
+ def get_auth_rules(client, event):
101
+ paginator = client.get_paginator('describe_client_vpn_authorization_rules')
102
+ response_iterator = paginator.paginate(
103
+ ClientVpnEndpointId=event['ClientVpnEndpointId']
81
104
  )
82
- return response['AuthorizationRules']
105
+
106
+ return [rule for page in response_iterator
107
+ for rule in page['AuthorizationRules']
108
+ if 'Description' in rule and event['Record'] in rule['Description']]
109
+
110
+
111
+ def expired_auth_rules(auth_rules, cidrs, groups):
112
+ for rule in auth_rules:
113
+ # if there is a rule for the record with an old cidr
114
+ if rule['DestinationCidr'] not in cidrs:
115
+ yield rule
116
+ # if there is a rule for a group that is no longer in the event
117
+ if groups and rule['GroupId'] not in groups:
118
+ yield rule
119
+ # if there is a rule for allow all but groups are in the event
120
+ if groups and rule['AccessAll']:
121
+ yield rule
122
+
123
+
124
+ def expired_routes(routes, cidrs):
125
+ for route in routes:
126
+ if route['DestinationCidr'] not in cidrs:
127
+ yield route
83
128
 
84
129
 
85
130
  def handler(event,context):
131
+
132
+ logger.info(f"auto route populator triggered with event : {event}")
133
+ slack = Slack(username=SLACK_USERNAME)
86
134
 
87
135
  # DNS lookup on the dns record and return all IPS for the endpoint
88
136
  try:
89
137
  cidrs = [ ip + "/32" for ip in socket.gethostbyname_ex(event['Record'])[-1]]
90
138
  logger.info(f"resolved endpoint {event['Record']} to {cidrs}")
91
139
  except socket.gaierror as e:
92
- logger.exception(f"failed to resolve record {event['Record']}")
140
+ logger.error(f"failed to resolve record {event['Record']}", exc_info=True)
141
+ slack.post_event(message=f"failed to resolve record {event['Record']}", state=RESOLVE_FAILED, error=e)
93
142
  return 'KO'
94
143
 
95
144
  client = boto3.client('ec2')
145
+
146
+ # describe vpn and check if subnets are associated with the vpn
147
+ response = client.describe_client_vpn_endpoints(
148
+ ClientVpnEndpointIds=[event['ClientVpnEndpointId']]
149
+ )
150
+
151
+ if not response['ClientVpnEndpoints']:
152
+ logger.error(f"endpoint not found")
153
+ slack.post_event(message=f"failed create routes for {event['Record']}", state=FAILED, error="endpoint not found")
154
+ return 'KO'
155
+
156
+ endpoint = response['ClientVpnEndpoints'][0]
157
+ if endpoint['Status'] == 'pending-associate':
158
+ logger.error(f"no subnets associated with endpoint")
159
+ slack.post_event(message=f"failed create routes for {event['Record']}", state=FAILED, error="vpn is in a stopped state")
160
+ return 'KO'
161
+
96
162
  routes = get_routes(client, event)
163
+ auth_rules = get_auth_rules(client, event)
164
+
165
+ auto_limit_increase = os.environ.get('AUTO_LIMIT_INCREASE')
166
+ route_limit_increase_required = False
167
+ auth_rules_limit_increase_required = False
97
168
 
98
169
  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)
170
+ # create route if doesn't exist
171
+ for subnet in event['TargetSubnets']:
172
+ if not any(route['DestinationCidr'] == cidr and route['TargetSubnet'] == subnet for route in routes):
173
+ try:
174
+ create_route(client, event, cidr, subnet)
175
+ except ClientError as e:
176
+ if e.response['Error']['Code'] == 'ClientVpnRouteLimitExceeded':
177
+ route_limit_increase_required = True
178
+ logger.error("vpn route table has reached the route limit", exc_info=True)
179
+ slack.post_event(
180
+ message=f"unable to create route {cidr} from {event['Record']}",
181
+ state=ROUTE_LIMIT_EXCEEDED,
182
+ error="vpn route table has reached the route limit"
183
+ )
184
+ elif e.response['Error']['Code'] == 'InvalidClientVpnActiveAssociationNotFound':
185
+ logger.warn("no subnets are associated with the vpn", exc_info=True)
186
+ slack.post_event(
187
+ message=f"unable to create the route {cidr} from {event['Record']}",
188
+ state=SUBNET_NOT_ASSOCIATED,
189
+ error="no subnets are associated with the vpn"
190
+ )
191
+ else:
192
+ logger.error("encountered a unexpected client error when creating a route", exc_info=True)
108
193
  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")
194
+ slack.post_event(
195
+ message=f"created new route {cidr} ({event['Record']}) to target subnet {subnet}",
196
+ state=NEW_ROUTE
197
+ )
198
+
199
+ # remove route if target subnet has changed
200
+ for route in routes:
201
+ if route['DestinationCidr'] == cidr and route['TargetSubnet'] not in event['TargetSubnets']:
124
202
  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]
203
+
204
+ # collect all rules that matches the current cidr
205
+ cidr_auth_rules = [rule for rule in auth_rules if rule['DestinationCidr'] == cidr]
206
+
207
+ try:
208
+ # create rules for newly added groups
132
209
  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}")
210
+ existing_groups = list(set(rule['GroupId'] for rule in cidr_auth_rules))
211
+ new_groups = [group for group in event['Groups'] if group not in existing_groups]
212
+
213
+ for group in new_groups:
142
214
  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
215
 
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'])
216
+ # create an allow all rule
217
+ elif 'Groups' not in event and not cidr_auth_rules:
218
+ authorize_route(client, event, cidr)
219
+
169
220
  except ClientError as e:
170
- if e.response['Error']['Code'] == 'InvalidClientVpnRouteNotFound':
171
- pass
172
- else:
173
- raise e
221
+ if e.response['Error']['Code'] == 'ClientVpnAuthorizationRuleLimitExceeded':
222
+ auth_rules_limit_increase_required = True
223
+ logger.error("vpn has reached the authorization rule limit", exc_info=True)
224
+ slack.post_event(
225
+ message=f"unable add to authorization rule for route {cidr} from {event['Record']}",
226
+ state=AUTH_RULE_LIMIT_EXCEEDED,
227
+ error="vpn has reached the authorization rule limit"
228
+ )
229
+ continue
230
+ else:
231
+ logger.error("encountered a unexpected client error when creating an auth rule", exc_info=True)
232
+
233
+ # request route limit increase
234
+ if route_limit_increase_required and auto_limit_increase:
235
+ case_id = increase_quota(10, ROUTE_TABLE_QUOTA_CODE, event['ClientVpnEndpointId'])
236
+ if case_id is not None:
237
+ slack.post_event(message=f"requested an increase for the routes per vpn service quota", state=QUOTA_INCREASE_REQUEST, support_case=case_id)
238
+ else:
239
+ logger.info(f"routes per vpn service quota increase request pending")
240
+
241
+ # request auth rule limit increase
242
+ if auth_rules_limit_increase_required and auto_limit_increase:
243
+ case_id = increase_quota(20, AUTH_RULE_TABLE_QUOTA_CODE, event['ClientVpnEndpointId'])
244
+ if case_id is not None:
245
+ slack.post_event(message=f"requested an increase for the authorization rules per vpn service quota", state=QUOTA_INCREASE_REQUEST, support_case=case_id)
246
+ else:
247
+ logger.info(f"authorization rules per vpn service quota increase request pending")
248
+
249
+ # remove expired auth rules
250
+ for rule in expired_auth_rules(auth_rules, cidrs, event.get('Groups', [])):
251
+ logger.info(f"removing expired auth rule {rule['DestinationCidr']} for endpoint {event['Record']}")
252
+ revoke_route_auth(client, event, route['DestinationCidr'])
253
+
254
+ # remove expired routes
255
+ for route in expired_routes(routes, cidrs):
256
+ logger.info(f"removing expired route {route['DestinationCidr']} for endpoint {event['Record']}")
257
+ delete_route(client, event['ClientVpnEndpointId'], route['TargetSubnet'], route['DestinationCidr'])
258
+ slack.post_event(message=f"removed expired route {route['DestinationCidr']} for endpoint {event['Record']}", state=EXPIRED_ROUTE)
174
259
 
175
260
  return 'OK'
@@ -0,0 +1,37 @@
1
+ import boto3
2
+
3
+ ROUTE_TABLE_QUOTA_CODE = 'L-401D78F7'
4
+ AUTH_RULE_TABLE_QUOTA_CODE = 'L-9A1BC94B'
5
+ EC2_SERVICE_CODE = 'ec2'
6
+ IN_PROGRESS = ['PENDING', 'CASE_OPENED']
7
+
8
+ def get_route_count(endpoint) -> int:
9
+ client = boto3.client('ec2')
10
+ response = client.describe_client_vpn_routes(
11
+ ClientVpnEndpointId=endpoint,
12
+ )
13
+ return len(response['Routes'])
14
+
15
+ def quota_request_open(quota_code) -> bool:
16
+ client = boto3.client('service-quotas')
17
+ response = client.list_requested_service_quota_change_history_by_quota(
18
+ ServiceCode=EC2_SERVICE_CODE,
19
+ QuotaCode=quota_code
20
+ )
21
+ # Status='PENDING'|'CASE_OPENED'|'APPROVED'|'DENIED'|'CASE_CLOSED'
22
+ return any(req['status'] in IN_PROGRESS for req in response['RequestedQuotas'])
23
+
24
+ def increase_quota(increase_value, quota_code, endpoint) -> str:
25
+ if quota_request_open(quota_code):
26
+ return None
27
+
28
+ current_route_count = get_route_count(endpoint)
29
+ desired_value = current_route_count + increase_value
30
+
31
+ client = boto3.client('service-quotas')
32
+ response = client.request_service_quota_increase(
33
+ ServiceCode=EC2_SERVICE_CODE,
34
+ QuotaCode=quota_code,
35
+ DesiredValue=desired_value
36
+ )
37
+ return response['CaseId']
@@ -0,0 +1,21 @@
1
+ """
2
+ states:
3
+
4
+ FAILED: general failure
5
+ NEW_ROUTE: new route added to route table
6
+ EXPIRED_ROUTE: cidr is no longer associated with DNS entry and is removed from the route table
7
+ ROUTE_LIMIT_EXCEEDED: no new routes can be added to the route table due to aws route table limit
8
+ AUTH_RULE_LIMIT_EXCEEDED: no new authorization rules can be added to the rule list due to aws auth rule limit
9
+ RESOLVE_FAILED: failed to resolve the provided dns entry
10
+ SUBNET_NOT_ASSOCIATED: no subnets are associated with the client vpn
11
+ QUOTA_INCREASE_REQUEST: automatic quota increase made
12
+ """
13
+
14
+ FAILED = 'FAILED'
15
+ NEW_ROUTE = 'NEW_ROUTE'
16
+ EXPIRED_ROUTE = 'EXPIRED_ROUTE'
17
+ ROUTE_LIMIT_EXCEEDED = 'ROUTE_LIMIT_EXCEEDED'
18
+ AUTH_RULE_LIMIT_EXCEEDED = 'AUTH_RULE_LIMIT_EXCEEDED'
19
+ RESOLVE_FAILED = 'RESOLVE_FAILED'
20
+ SUBNET_NOT_ASSOCIATED = 'SUBNET_NOT_ASSOCIATED'
21
+ QUOTA_INCREASE_REQUEST = 'QUOTA_INCREASE_REQUEST'
@@ -0,0 +1,66 @@
1
+ import os
2
+ import json
3
+ import logging
4
+ import urllib
5
+
6
+ logger = logging.getLogger(__name__)
7
+ logger.setLevel(logging.INFO)
8
+
9
+ class Slack:
10
+
11
+ def __init__(self, username):
12
+ self.username = username
13
+ self.slack_url = os.environ.get('SLACK_URL')
14
+
15
+ def post_event(self, message, state, error=None, support_case=None):
16
+ """Posts event to slack using an incoming webhook
17
+ Parameters
18
+ ----------
19
+ message: str
20
+ message to post to slack
21
+ state: str
22
+ the state of the event
23
+ error: str
24
+ error message to add to the message
25
+ support_case: str
26
+ displays a aws console link to the support case in the message
27
+ """
28
+
29
+ if not self.slack_url.startswith('https://hooks.slack.com'):
30
+ return
31
+
32
+ if 'FAILED' in state or 'LIMIT_EXCEEDED' in state:
33
+ colour = '#ad0614'
34
+ elif 'NOT_ASSOCIATED' in state:
35
+ colour = '#d4b126'
36
+ else:
37
+ colour = '#3ead3e'
38
+
39
+ text = f'Message: {message}\nState: {state}'
40
+
41
+ if error:
42
+ text += f'\nError: {error}'
43
+
44
+ if support_case:
45
+ text += f'\nSupport Case: <https://console.aws.amazon.com/support/cases#/{support_case}|{support_case}>'
46
+
47
+ payload = {
48
+ 'username': self.username,
49
+ 'attachments': [
50
+ {
51
+ 'color': colour,
52
+ 'text': text,
53
+ 'mrkdwn_in': ['text','pretext']
54
+ }
55
+ ]
56
+ }
57
+
58
+ try:
59
+ urllib.request.urlopen(urllib.request.Request(
60
+ self.slack_url,
61
+ headers={'Content-Type': 'application/json'},
62
+ data=json.dumps(payload).encode('utf-8')
63
+ ))
64
+ except urllib.error.HTTPError as e:
65
+ logger.error(f"failed to post slack notification. REASON: {e.reason} CODE: {e.code}", exc_info=True)
66
+
@@ -1,36 +1,54 @@
1
1
  import boto3
2
2
  import logging
3
+ from lib.slack import Slack
4
+ from states import *
3
5
 
4
6
  logger = logging.getLogger()
5
7
  logger.setLevel(logging.INFO)
6
8
 
9
+ SLACK_USERNAME = 'CfnVpn Scheduler'
10
+
7
11
  def handler(event, context):
8
12
 
9
13
  logger.info(f"updating cfn-vpn stack {event['StackName']} parameter AssociateSubnets with value {event['AssociateSubnets']}")
14
+ slack = Slack(username=SLACK_USERNAME)
15
+
16
+ try:
17
+ if event['AssociateSubnets'] == 'false':
18
+ logger.info(f"terminating current vpn sessions to {event['ClientVpnEndpointId']}")
19
+ ec2 = boto3.client('ec2')
20
+ resp = ec2.describe_client_vpn_connections(ClientVpnEndpointId=event['ClientVpnEndpointId'])
21
+ for conn in resp['Connections']:
22
+ if conn['Status']['Code'] == 'active':
23
+ ec2.terminate_client_vpn_connections(
24
+ ClientVpnEndpointId=event['ClientVpnEndpointId'],
25
+ ConnectionId=conn['ConnectionId']
26
+ )
27
+ logger.info(f"terminated session {conn['ConnectionId']}")
28
+
29
+ client = boto3.client('cloudformation')
30
+ logger.info(client.update_stack(
31
+ StackName=event['StackName'],
32
+ UsePreviousTemplate=True,
33
+ Capabilities=['CAPABILITY_IAM'],
34
+ Parameters=[
35
+ {
36
+ 'ParameterKey': 'AssociateSubnets',
37
+ 'ParameterValue': event['AssociateSubnets']
38
+ }
39
+ ]
40
+ ))
41
+ except Exception as ex:
42
+ logger.error(f"failed to start/stop client vpn", exc_info=True)
43
+ if event['AssociateSubnets'] == 'true':
44
+ slack.post_event(message=f"failed to associate subnets with the client vpn", state=START_FAILED, error=ex)
45
+ else:
46
+ slack.post_event(message=f"failed to disassociate subnets with the client vpn", state=STOP_FAILED, error=ex)
47
+ return 'KO'
10
48
 
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
- ))
49
+ if event['AssociateSubnets'] == 'true':
50
+ slack.post_event(message=f"successfully associated subnets with the client vpn", state=START_IN_PROGRESS)
51
+ else:
52
+ slack.post_event(message=f"successfully disassociated subnets with the client vpn", state=STOP_IN_PROGRESS)
35
53
 
36
54
  return 'OK'
@@ -0,0 +1,13 @@
1
+ """
2
+ states:
3
+
4
+ START_IN_PROGRESS: associating subnets with the Client VPN
5
+ STOP_IN_PROGRESS: disassociating subnets with the Client VPN
6
+ START_FAILED: failed to associated subnets with the Client VPN
7
+ STOP_FAILED: failed to disassociated subnets with the Client VPN
8
+ """
9
+
10
+ START_IN_PROGRESS = 'START_IN_PROGRESS'
11
+ STOP_IN_PROGRESS = 'STOP_IN_PROGRESS'
12
+ START_FAILED = 'START_FAILED'
13
+ STOP_FAILED = 'STOP_FAILED'
@@ -17,7 +17,16 @@ module CfnVpn
17
17
  FileUtils.mkdir_p(zipfile_path)
18
18
  Zip::File.open("#{zipfile_path}/#{zipfile_name}", Zip::File::CREATE) do |zipfile|
19
19
  files.each do |file|
20
- zipfile.add(file, File.join("#{lambdas_dir}/#{func}", file))
20
+ file_path = lambdas_dir
21
+
22
+ # this is to allow for shared library methods in a different lambda directory
23
+ # so the files in the function lambda is in the root of the zip
24
+ if file.include? func
25
+ file_path = "#{file_path}/#{func}"
26
+ file.gsub!("#{func}/", "")
27
+ end
28
+
29
+ zipfile.add(file, File.join(file_path, file))
21
30
  end
22
31
  end
23
32