cfn-vpn 1.4.3 → 1.4.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54a7ba46c0bd16d5e70bf048aa90e4ea079e8dd5be0e6245afafb4730f804017
4
- data.tar.gz: 0e9b6ed2c904b6debf40777232f780d15fc12904abc3014fccf7c8b5c65049ae
3
+ metadata.gz: 7c3fd1a4b4105ebcd008d105ea5eb5dd493ef698f3468d6a28bdf8ae3b194633
4
+ data.tar.gz: e0cd2db1d7619b8ab5ac88a29a85f96e2b352edee7cf7883f7ba3f30d8ac1077
5
5
  SHA512:
6
- metadata.gz: 402f81e5c8976cca51b685220219370a056875c9885a86f989e06d021d5848f5e18f09f1022df6bf64d4e9b72e8546fa206a5fff3be3501b5db7c401d72175a0
7
- data.tar.gz: 19b44819f370cbdada8e747764c93d1366e2974cfe56436a96f087805ff898094bd3f63f082ca09f1410565a86c2b35129c2d1f64c29325f325995c654ca4cc4
6
+ metadata.gz: 821ec1eb8281f6f7ecadfb8c76d6e1ece81a49af10e05e01f78cfecbb14470f4231d61ea6b7cd5c7206001a4e363f3e648b487cb15c3cdff1c16c1ff52a0d79e
7
+ data.tar.gz: d60cdf23880b0903be48c83cbd6ee997820be1b4a149329c726eab7fac8f686a49fdd483f53b35c429c3caaa46867d05f6627022001b269eeea4e9f9feb1a242
data/Gemfile.lock CHANGED
@@ -47,7 +47,7 @@ GEM
47
47
  cfndsl (1.3.1)
48
48
  hana (~> 1.3)
49
49
  hana (1.3.7)
50
- jmespath (1.4.0)
50
+ jmespath (1.6.1)
51
51
  netaddr (2.0.4)
52
52
  rake (13.0.1)
53
53
  rubyzip (2.3.0)
data/docs/README.md CHANGED
@@ -42,4 +42,5 @@ For further information on the authentication types please visit https://docs.aw
42
42
  4. [Managing Routes](routes.md)
43
43
  5. [Stop and Start Client-VPN](scheduling.md)
44
44
  6. [Managing Sessions](sessions.md)
45
- 7. [Slack Notifications](slack-notifications.md)
45
+ 7. [Slack Notifications](slack-notifications.md)
46
+ 8. [YAML Configuration](yaml-config.md)
data/docs/modifying.md CHANGED
@@ -29,12 +29,12 @@ cfn-vpn params [name] --diff-yaml cfnvpn.[name].yaml
29
29
 
30
30
  ## Modifying
31
31
 
32
- ### With CLI Options
32
+ ### With YAML File
33
33
 
34
- to modify the VPN properties run the modify command with the desired options
34
+ cfn-vpn configuration can be managed through a YAML file using the `modify` command to apply the config changes to the vpn stack. See the [YAML docs](yaml-config.md) for config options.
35
35
 
36
36
  ```
37
- cfn-vpn modify [name] --dns-servers 10.15.0.2
37
+ cfn-vpn modify [name] --params-yaml cfnvpn.[name].yaml
38
38
  ```
39
39
 
40
40
  a cloudformation changeset is created with the desired changes and approval is asked
@@ -60,8 +60,10 @@ INFO: - Changeset UPDATE complete
60
60
  INFO: - Client VPN [endpoint-id] modified
61
61
  ```
62
62
 
63
- ### With YAML File
63
+ ### With CLI Options
64
+
65
+ to modify the VPN properties run the modify command with the desired options
64
66
 
65
67
  ```
66
- cfn-vpn modify [name] --params-yaml cfnvpn.[name].yaml
68
+ cfn-vpn modify [name] --dns-servers 10.15.0.2
67
69
  ```
data/docs/routes.md CHANGED
@@ -4,48 +4,124 @@ Management of the VPN routes can be altered using the `routes` command or by usi
4
4
 
5
5
  **Note:** The default route via subnet association cannot be modified through this command. Use the `modify` command to alter the subnet associations.
6
6
 
7
- CfnVpn can create static routes for CIDRs as well as dynamically lookup IPs for dns endpoints and continue to monitor and update the routes if the IPs change.
7
+ There are 3 different types of routes, [Static](#Static CIDR Routes), [DNS Lookup](#DNS Lookup Routes) and [Cloud](#Cloud Routes).
8
+
9
+ ## Static CIDR Routes
10
+
11
+ Static routes create a static entry in the cfnvpn route table with the CIDR provided.
12
+
13
+ #### CLI Commands
14
+
15
+ new route run the routes command along with the `--cidr` option
8
16
 
9
17
  ```sh
10
- cfn-vpn help routes
18
+ cfn-vpn routes [name] --cidr 10.151.0.0/16
11
19
  ```
12
20
 
13
- ## Dynamic DNS Routes
21
+ delete a route run the routes command along with the `--cidr` option of the route to delete and the delete option
14
22
 
15
- Dynamic DNS routes takes a dns endpoint and will query the record every 5 minutes to see if the IPs have changed and update the routes.
23
+ ```sh
24
+ cfn-vpn routes [name] --cidr 10.151.0.0/16 --delete
25
+ ```
16
26
 
17
- ### Add New
27
+ #### YAML Config
18
28
 
19
- to add a new route run the routes command along with the `--dns` option
29
+ ```yaml
30
+ routes:
31
+ - cidr: 10.151.0.0/16
32
+ desc: route to dev peered vpc
33
+ schedule: rate(5 minutes)
34
+ groups:
35
+ - devs
36
+ - ops
37
+ ```
38
+
39
+ ## DNS Lookup Routes
40
+
41
+ Dynamic DNS routes takes a dns endpoint and will query the record every 5 minutes to see if the IPs have changed and update the routes in the vpn route table.
42
+
43
+ #### CLI Commands
44
+
45
+ new route run the routes command along with the `--dns` option
20
46
 
21
47
  ```sh
22
48
  cfn-vpn routes [name] --dns example.com
23
49
  ```
24
50
 
25
- ### Delete
26
-
27
- to delete a route run the routes command along with the `--dns` option of the route to delete and the delete option
51
+ delete a route run the routes command along with the `--dns` option of the route to delete and the delete option
28
52
 
29
53
  ```sh
30
54
  cfn-vpn routes [name] --dns example.com --delete
31
55
  ```
32
56
 
33
- ## Static CIDR Routes
57
+ #### YAML Config
58
+
59
+ ```yaml
60
+ routes:
61
+ - dns: example.com
62
+ desc: my dev alb
63
+ schedule: rate(10 minutes)
64
+ groups:
65
+ - dev
66
+ ```
34
67
 
35
- ### Add New
68
+ ## Cloud Routes
36
69
 
37
- to add a new route run the routes command along with the `--cidr` option
70
+ Automatically lookup and create routes to push cloud provider IP ranges through the VPN. Cloud routes can only be configured through the yaml config.
38
71
 
39
- ```sh
40
- cfn-vpn routes [name] --cidr 10.151.0.0/16
72
+ Supported clouds:
73
+ - [AWS](#AWS)
74
+
75
+ ### AWS
76
+
77
+ Using AWS published [IP address ranges](https://docs.aws.amazon.com/general/latest/gr/aws-ip-ranges.html) cfn-vpn can lookup and add the CIDR ranges published the the vpn route table.
78
+
79
+ The list can be filtered by AWS `region` and `service`.
80
+
81
+ AWS services that can be used to filter the address ranges. **Note:** not all regions contain all services.
82
+
83
+ ```
84
+ API_GATEWAY
85
+ EBS
86
+ EC2_INSTANCE_CONNECT
87
+ CHIME_VOICECONNECTOR
88
+ CHIME_MEETINGS
89
+ CODEBUILD
90
+ CLOUDFRONT
91
+ ROUTE53_HEALTHCHECKS_PUBLISHING
92
+ AMAZON_APPFLOW
93
+ S3
94
+ CLOUD9
95
+ ROUTE53
96
+ AMAZON
97
+ KINESIS_VIDEO_STREAMS
98
+ ROUTE53_HEALTHCHECKS
99
+ GLOBALACCELERATOR
100
+ WORKSPACES_GATEWAYS
101
+ CLOUDFRONT_ORIGIN_FACING
102
+ EC2
103
+ DYNAMODB
104
+ ROUTE53_RESOLVER
105
+ AMAZON_CONNECT
41
106
  ```
42
107
 
43
- ### Delete
108
+ **Warning:** AWS publish 100's of IP address ranges and with Client VPN soft limit of 10 routes per vpn endpoint you will hit the limit without filtering the published ranges.
44
109
 
45
- to delete a route run the routes command along with the `--cidr` option of the route to delete and the delete option
110
+ #### YAML Config
46
111
 
47
- ```sh
48
- cfn-vpn routes [name] --cidr 10.151.0.0/16 --delete
112
+ ```yaml
113
+ routes:
114
+ - cloud: aws
115
+ schedule: rate(1 hour)
116
+ groups:
117
+ - ops
118
+ filters:
119
+ - name: region
120
+ values:
121
+ - ap-southeast-2
122
+ - name: service
123
+ values:
124
+ - API_GATEWAY
49
125
  ```
50
126
 
51
127
  ## Manage Authorization Groups
@@ -70,32 +146,6 @@ To delete groups from an existing route use the `--del-groups` options
70
146
  cfn-vpn routes [name] [--cidr 10.151.0.0/16] [--dns example.com] --del-groups dev
71
147
  ```
72
148
 
73
- ## Modify Command
74
-
75
- add or modify the `routes:` key in your config yaml file
76
-
77
- ```yaml
78
- routes:
79
- - cidr: 10.151.0.0/16
80
- desc: route to dev peered vpc
81
- groups:
82
- - devs
83
- - ops
84
- - cidr: 10.152.0.0/16
85
- desc: route to prod peered vpc
86
- groups:
87
- - ops
88
- - dns: example.com
89
- desc: my dev alb
90
- groups:
91
- - dev
92
- ```
93
-
94
- run the `modify` command and supply the yaml file to apply the changes
95
-
96
- ```sh
97
- cfn-vpn routes [name] --params-yaml cfnvpn.[name].yaml
98
- ```
99
149
 
100
150
  ## Route Limits
101
151
 
@@ -12,10 +12,19 @@ https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
12
12
 
13
13
  Next modify your VPN stack using the modify command and pass in url
14
14
 
15
+ **CLI**
16
+
15
17
  ```sh
16
18
  cfn-vpn modify [name] --slack-webhook-url "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
17
19
  ```
18
20
 
21
+ **YAML**
22
+
23
+ ```yaml
24
+ slack_webhook_url: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
25
+ ```
26
+
27
+
19
28
  ## Route Events
20
29
 
21
30
  - `FAILED`: general failure
@@ -0,0 +1,118 @@
1
+ # YAML Configuration File
2
+
3
+ cfn-vpn configuration can be managed through a YAML file using the `modify` command to apply the config changes to the vpn stack
4
+
5
+ ## Applying changes
6
+
7
+ run the `modify` command and supply the yaml file to apply the changes
8
+
9
+ ```sh
10
+ cfn-vpn routes [name] --params-yaml cfnvpn.[name].yaml
11
+ ```
12
+
13
+ ## Dump Current Config to YAML File
14
+
15
+ the following command will take the current config of a cfn-vpn stack and dump the contents into a YAML file named `cfnvpn.[name].yaml`
16
+
17
+ ```sh
18
+ cfn-vpn params [name] --dump
19
+ ```
20
+
21
+ ## Configuration Options
22
+
23
+ ### VPN Config
24
+
25
+ ```yaml
26
+ region: ap-southeast-2
27
+ subnet_ids:
28
+ - subnet-abc123
29
+ - subnet-def456
30
+ cidr: 10.250.0.0/16
31
+ dns_servers:
32
+ - 10.250.0.2
33
+ split_tunnel: true
34
+ protocol: udp
35
+ bucket: my-vpn-bucket
36
+ server_cert_arn: arn:aws:acm:us-east-1:000000000000:certificate/123456
37
+ ```
38
+
39
+ ### Certificate Authentication
40
+
41
+ ```yaml
42
+ type: certificate
43
+ ```
44
+
45
+ ### SAML Authentication
46
+
47
+ ```yaml
48
+ type: federated
49
+ saml_arn: arn:aws:iam::000000000000:saml-provider/VpnSamlRole
50
+ saml_self_service_arn: arn:aws:iam::000000000000:saml-provider/VpnSelfServiceSamlRole
51
+ ```
52
+
53
+ ### AWS Directory Services Authentication
54
+
55
+ ```yaml
56
+ type: active-directory
57
+ directory_id: d-a1b2c3d4e5
58
+ ```
59
+
60
+ ### Routes
61
+
62
+ **Static CIDR Route**
63
+
64
+ ```yaml
65
+ routes:
66
+ - cidr: 10.151.0.0/16
67
+ desc: route to dev peered vpc
68
+ groups:
69
+ - devs
70
+ - ops
71
+ ```
72
+
73
+ **DNS Lookup Route**
74
+
75
+ ```yaml
76
+ routes:
77
+ - dns: example.com
78
+ desc: my dev alb
79
+ schedule: rate(10 minutes)
80
+ groups:
81
+ - dev
82
+ ```
83
+
84
+ **Cloud Lookup Route**
85
+
86
+ ```yaml
87
+ routes:
88
+ - cloud: aws
89
+ schedule: rate(1 hour)
90
+ groups:
91
+ - ops
92
+ filters:
93
+ - name: region
94
+ values:
95
+ - ap-southeast-2
96
+ - name: service
97
+ values:
98
+ - API_GATEWAY
99
+ ```
100
+
101
+ ### Default Auth Groups
102
+
103
+ ```yaml
104
+ default_groups:
105
+ - group-a
106
+ ```
107
+
108
+ ### Auto Route Limit Increase
109
+
110
+ ```yaml
111
+ auto_limit_increase: true
112
+ ```
113
+
114
+ ### Slack Notifications
115
+
116
+ ```yaml
117
+ slack_webhook_url: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
118
+ ```
@@ -1,8 +1,8 @@
1
1
  import os
2
- import socket
3
2
  import boto3
4
3
  from botocore.exceptions import ClientError
5
4
  from lib.slack import Slack
5
+ from lookup import dns_lookup, cloud_lookup
6
6
  from states import *
7
7
  import logging
8
8
  from quotas import increase_quota, AUTH_RULE_TABLE_QUOTA_CODE, ROUTE_TABLE_QUOTA_CODE
@@ -26,8 +26,8 @@ def delete_route(client, vpn_endpoint, subnet, cidr):
26
26
  raise e
27
27
 
28
28
 
29
- def create_route(client, event, cidr, target_subnet):
30
- description = f"cfnvpn auto generated route for endpoint {event['Record']}."
29
+ def create_route(client, event, cidr, target_subnet, lookup_item):
30
+ description = f"cfnvpn auto generated route for lookup {lookup_item}."
31
31
  if event['Description']:
32
32
  description += f" {event['Description']}"
33
33
 
@@ -61,8 +61,8 @@ def revoke_route_auth(client, event, cidr, group = None):
61
61
  raise e
62
62
 
63
63
 
64
- def authorize_route(client, event, cidr, group = None):
65
- description = f"cfnvpn auto generated authorization for endpoint {event['Record']}."
64
+ def authorize_route(client, event, cidr, lookup_item, group = None):
65
+ description = f"cfnvpn auto generated authorization for lookup {lookup_item}."
66
66
  if event['Description']:
67
67
  description += f" {event['Description']}"
68
68
 
@@ -80,7 +80,7 @@ def authorize_route(client, event, cidr, group = None):
80
80
  client.authorize_client_vpn_ingress(**args)
81
81
 
82
82
 
83
- def get_routes(client, event):
83
+ def get_routes(client, event, lookup_item):
84
84
  paginator = client.get_paginator('describe_client_vpn_routes')
85
85
  response_iterator = paginator.paginate(
86
86
  ClientVpnEndpointId=event['ClientVpnEndpointId'],
@@ -94,10 +94,10 @@ def get_routes(client, event):
94
94
 
95
95
  return [route for page in response_iterator
96
96
  for route in page['Routes']
97
- if 'Description' in route and event['Record'] in route['Description']]
97
+ if 'Description' in route and lookup_item in route['Description']]
98
98
 
99
99
 
100
- def get_auth_rules(client, event):
100
+ def get_auth_rules(client, event, lookup_item):
101
101
  paginator = client.get_paginator('describe_client_vpn_authorization_rules')
102
102
  response_iterator = paginator.paginate(
103
103
  ClientVpnEndpointId=event['ClientVpnEndpointId']
@@ -105,7 +105,7 @@ def get_auth_rules(client, event):
105
105
 
106
106
  return [rule for page in response_iterator
107
107
  for rule in page['AuthorizationRules']
108
- if 'Description' in rule and event['Record'] in rule['Description']]
108
+ if 'Description' in rule and lookup_item in rule['Description']]
109
109
 
110
110
 
111
111
  def expired_auth_rules(auth_rules, cidrs, groups):
@@ -131,16 +131,27 @@ def handler(event,context):
131
131
 
132
132
  logger.info(f"auto route populator triggered with event : {event}")
133
133
  slack = Slack(username=SLACK_USERNAME)
134
+
135
+ lookup_type = None
134
136
 
135
- # DNS lookup on the dns record and return all IPS for the endpoint
136
- try:
137
- cidrs = [ ip + "/32" for ip in socket.gethostbyname_ex(event['Record'])[-1]]
138
- logger.info(f"resolved endpoint {event['Record']} to {cidrs}")
139
- except socket.gaierror as e:
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)
137
+ if 'Record' in event:
138
+ cidrs = dns_lookup(event['Record'])
139
+ lookup_item = event['Record']
140
+ if cidrs is None:
141
+ slack.post_event(message=f"failed to resolve record {event['Record']}", state=RESOLVE_FAILED)
142
+ logger.info(f"dns lookup found {len(cidrs)} IP's")
143
+ elif 'Cloud' in event:
144
+ cidrs = cloud_lookup(event['Cloud'], event.get('Filters', []))
145
+ lookup_item = f"cloud:{event['Cloud']}"
146
+ logger.info(f"cloud {event['Cloud']} lookup found {len(cidrs)} IP address ranges")
147
+ else:
148
+ logger.error("one of [ Record | Cloud ] not found in event")
142
149
  return 'KO'
143
-
150
+
151
+ if not cidrs:
152
+ logger.error(f"no cidrs found")
153
+ return 'KO'
154
+
144
155
  client = boto3.client('ec2')
145
156
 
146
157
  # describe vpn and check if subnets are associated with the vpn
@@ -150,17 +161,17 @@ def handler(event,context):
150
161
 
151
162
  if not response['ClientVpnEndpoints']:
152
163
  logger.error(f"endpoint not found")
153
- slack.post_event(message=f"failed create routes for {event['Record']}", state=FAILED, error="endpoint not found")
164
+ slack.post_error(lookup_item, FAILED, "endpoint not found")
154
165
  return 'KO'
155
166
 
156
167
  endpoint = response['ClientVpnEndpoints'][0]
157
168
  if endpoint['Status'] == 'pending-associate':
158
169
  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")
170
+ slack.post_error(lookup_item, FAILED, "vpn is in a stopped state")
160
171
  return 'KO'
161
172
 
162
- routes = get_routes(client, event)
163
- auth_rules = get_auth_rules(client, event)
173
+ routes = get_routes(client, event, lookup_item)
174
+ auth_rules = get_auth_rules(client, event, lookup_item)
164
175
 
165
176
  auto_limit_increase = os.environ.get('AUTO_LIMIT_INCREASE')
166
177
  route_limit_increase_required = False
@@ -171,28 +182,20 @@ def handler(event,context):
171
182
  for subnet in event['TargetSubnets']:
172
183
  if not any(route['DestinationCidr'] == cidr and route['TargetSubnet'] == subnet for route in routes):
173
184
  try:
174
- create_route(client, event, cidr, subnet)
185
+ create_route(client, event, cidr, subnet, lookup_item)
175
186
  except ClientError as e:
176
187
  if e.response['Error']['Code'] == 'ClientVpnRouteLimitExceeded':
177
188
  route_limit_increase_required = True
178
189
  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
- )
190
+ slack.post_error(lookup_item, ROUTE_LIMIT_EXCEEDED, "vpn route table has reached the route limit")
184
191
  elif e.response['Error']['Code'] == 'InvalidClientVpnActiveAssociationNotFound':
185
192
  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
- )
193
+ slack.post_error(lookup_item, SUBNET_NOT_ASSOCIATED, "no subnets are associated with the vpn")
191
194
  else:
192
195
  logger.error("encountered a unexpected client error when creating a route", exc_info=True)
193
196
  else:
194
197
  slack.post_event(
195
- message=f"created new route {cidr} ({event['Record']}) to target subnet {subnet}",
198
+ message=f"created new route {cidr} ({lookup_item}) to target subnet {subnet}",
196
199
  state=NEW_ROUTE
197
200
  )
198
201
 
@@ -211,18 +214,18 @@ def handler(event,context):
211
214
  new_groups = [group for group in event['Groups'] if group not in existing_groups]
212
215
 
213
216
  for group in new_groups:
214
- authorize_route(client, event, cidr, group)
217
+ authorize_route(client, event, cidr, lookup_item, group)
215
218
 
216
219
  # create an allow all rule
217
220
  elif 'Groups' not in event and not cidr_auth_rules:
218
- authorize_route(client, event, cidr)
221
+ authorize_route(client, event, cidr, lookup_item)
219
222
 
220
223
  except ClientError as e:
221
224
  if e.response['Error']['Code'] == 'ClientVpnAuthorizationRuleLimitExceeded':
222
225
  auth_rules_limit_increase_required = True
223
226
  logger.error("vpn has reached the authorization rule limit", exc_info=True)
224
227
  slack.post_event(
225
- message=f"unable add to authorization rule for route {cidr} from {event['Record']}",
228
+ message=f"unable add to authorization rule for route {cidr} from lookup {lookup_item}",
226
229
  state=AUTH_RULE_LIMIT_EXCEEDED,
227
230
  error="vpn has reached the authorization rule limit"
228
231
  )
@@ -248,13 +251,13 @@ def handler(event,context):
248
251
 
249
252
  # remove expired auth rules
250
253
  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']}")
254
+ logger.info(f"removing expired auth rule {rule['DestinationCidr']} for lookup {lookup_item}")
252
255
  revoke_route_auth(client, event, rule['DestinationCidr'])
253
256
 
254
257
  # remove expired routes
255
258
  for route in expired_routes(routes, cidrs):
256
- logger.info(f"removing expired route {route['DestinationCidr']} for endpoint {event['Record']}")
259
+ logger.info(f"removing expired route {route['DestinationCidr']} for lookup {lookup_item}")
257
260
  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)
261
+ slack.post_event(message=f"removed expired route {route['DestinationCidr']} for lookup {lookup_item}", state=EXPIRED_ROUTE)
259
262
 
260
263
  return 'OK'
@@ -0,0 +1,58 @@
1
+ import json
2
+ import socket
3
+ from urllib.request import Request, urlopen
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+ logger.setLevel(logging.INFO)
8
+
9
+ AWS_URL = 'https://ip-ranges.amazonaws.com/ip-ranges.json'
10
+
11
+ def dns_lookup(record):
12
+ try:
13
+ cidrs = [ ip + "/32" for ip in socket.gethostbyname_ex(record)[-1]]
14
+ logger.info(f"resolved endpoint {record} to {cidrs}")
15
+ except socket.gaierror as e:
16
+ logger.error(f"failed to resolve record {record}", exc_info=True)
17
+ return None
18
+
19
+ return cidrs
20
+
21
+ def cloud_lookup(cloud, filters):
22
+ if cloud == 'aws':
23
+ return aws_lookup(filters)
24
+ else:
25
+ logger.error(f"unsupported cloud lookup : {cloud}")
26
+ return None
27
+
28
+ def http_request(endpoint):
29
+ request = Request(endpoint, headers={'User-Agent': 'Mozilla/5.0'})
30
+ response = urlopen(request)
31
+ return response.read().decode('utf-8')
32
+
33
+ def get_filter(filters, key):
34
+ return next((filter['values'] for filter in filters if filter['name'] == key), None)
35
+
36
+ def aws_lookup(filters):
37
+ response = http_request(AWS_URL)
38
+ data = json.loads(response)
39
+ regions = get_filter(filters, 'region')
40
+ services = get_filter(filters, 'service')
41
+
42
+ cidrs = []
43
+ for prefix in data['prefixes']:
44
+ if regions and services:
45
+ if prefix['region'] in regions and prefix['service'] in services:
46
+ cidrs.append(prefix['ip_prefix'])
47
+ elif regions:
48
+ if prefix['region'] in regions:
49
+ cidrs.append(prefix['ip_prefix'])
50
+ elif services:
51
+ if prefix['service'] in services:
52
+ cidrs.append(prefix['ip_prefix'])
53
+ else:
54
+ cidrs.append(prefix['ip_prefix'])
55
+
56
+ return cidrs
57
+
58
+
@@ -12,6 +12,9 @@ class Slack:
12
12
  self.username = username
13
13
  self.slack_url = os.environ.get('SLACK_URL')
14
14
 
15
+ def post_error(self, lookup_item, state, error):
16
+ self.post_event(message=f"failed create routes for lookup {lookup_item}", state=state, error=error)
17
+
15
18
  def post_event(self, message, state, error=None, support_case=None):
16
19
  """Posts event to slack using an incoming webhook
17
20
  Parameters
@@ -127,22 +127,23 @@ module CfnVpn
127
127
  output(:InternetRoute, config[:internet_route])
128
128
  end
129
129
 
130
- dns_routes = config[:routes].select {|route| route.has_key?(:dns)}
131
- cidr_routes = config[:routes].select {|route| route.has_key?(:cidr)}
130
+ create_auto_route_populator = config[:routes].detect { |route| route.has_key?(:dns) || route.has_key?(:cloud)}
132
131
 
133
- if dns_routes.any?
132
+ if create_auto_route_populator
134
133
  auto_route_populator(name, config)
134
+ end
135
135
 
136
- dns_routes.each do |route|
137
- # to aide in the migration from single to HA routes if the vpn is HA
138
- if route[:subnets]
139
- target_subnets = route[:subnets]
140
- elsif config[:subnet_ids].include?(route[:subnet])
141
- target_subnets = config[:subnet_ids]
142
- else
143
- target_subnets = [*route[:subnet]]
144
- end
136
+ config[:routes].each do |route|
137
+
138
+ if route[:subnets]
139
+ target_subnets = route[:subnets]
140
+ elsif config[:subnet_ids].include?(route[:subnet])
141
+ target_subnets = config[:subnet_ids]
142
+ else
143
+ target_subnets = [*route[:subnet]]
144
+ end
145
145
 
146
+ if route.has_key?(:dns)
146
147
  input = {
147
148
  Record: route[:dns],
148
149
  ClientVpnEndpointId: "${ClientVpnEndpoint}",
@@ -150,7 +151,7 @@ module CfnVpn
150
151
  Description: route[:desc]
151
152
  }
152
153
 
153
- if route[:groups].any?
154
+ if !route[:groups].nil? && route[:groups].any?
154
155
  input[:Groups] = route[:groups]
155
156
  end
156
157
 
@@ -159,29 +160,48 @@ module CfnVpn
159
160
  DependsOn network_assoc_dependson if network_assoc_dependson.any?
160
161
  State 'ENABLED'
161
162
  Description "cfnvpn auto route populator schedule for #{route[:dns]}"
162
- ScheduleExpression "rate(5 minutes)"
163
+ ScheduleExpression route.fetch(:schedule, 'rate(5 minutes)')
163
164
  Targets([
164
165
  {
165
166
  Arn: FnGetAtt(:CfnVpnAutoRoutePopulator, :Arn),
166
- Id: "auto-route-populator",
167
+ Id: "dns-auto-route-populator",
167
168
  Input: FnSub(input.to_json)
168
169
  }
169
170
  ])
170
171
  }
171
- end
172
- end
173
-
174
- if cidr_routes.any?
175
- cidr_routes.each do |route|
176
- # to aide in the migration from single to HA routes if the vpn is HA
177
- if route[:subnets]
178
- target_subnets = route[:subnets]
179
- elsif config[:subnet_ids].include?(route[:subnet])
180
- target_subnets = config[:subnet_ids]
181
- else
182
- target_subnets = [*route[:subnet]]
172
+
173
+ elsif route.has_key?(:cloud)
174
+ input = {
175
+ Cloud: route[:cloud],
176
+ ClientVpnEndpointId: "${ClientVpnEndpoint}",
177
+ TargetSubnets: target_subnets,
178
+ Description: route[:desc]
179
+ }
180
+
181
+ if !route[:groups].nil? && route[:groups].any?
182
+ input[:Groups] = route[:groups]
183
183
  end
184
184
 
185
+ if !route[:filters].nil? && route[:filters].any?
186
+ input[:Filters] = route[:filters]
187
+ end
188
+
189
+ Events_Rule(:"CfnVpnAutoRoutePopulatorEvent#{route[:cloud].resource_safe}"[0..255]) {
190
+ Condition(:EnableSubnetAssociation)
191
+ DependsOn network_assoc_dependson if network_assoc_dependson.any?
192
+ State 'ENABLED'
193
+ Description "cfnvpn auto route populator schedule for cloud #{route[:cloud]} lookup"
194
+ ScheduleExpression route.fetch(:schedule, 'rate(5 minutes)')
195
+ Targets([
196
+ {
197
+ Arn: FnGetAtt(:CfnVpnAutoRoutePopulator, :Arn),
198
+ Id: "cloud-auto-route-populator",
199
+ Input: FnSub(input.to_json)
200
+ }
201
+ ])
202
+ }
203
+
204
+ elsif route.has_key?(:cidr)
185
205
  target_subnets.each do |subnet|
186
206
  EC2_ClientVpnRoute(:"#{route[:cidr].resource_safe}VpnRouteTo#{subnet.resource_safe}"[0..255]) {
187
207
  Description "cfnvpn static route for #{route[:cidr]}. #{route[:desc]}".strip
@@ -208,6 +228,7 @@ module CfnVpn
208
228
  TargetNetworkCidr route[:cidr]
209
229
  }
210
230
  end
231
+
211
232
  end
212
233
  end
213
234
 
@@ -336,6 +357,7 @@ module CfnVpn
336
357
  func: 'auto_route_populator',
337
358
  files: [
338
359
  'auto_route_populator/app.py',
360
+ 'auto_route_populator/lookup.py',
339
361
  'auto_route_populator/quotas.py',
340
362
  'lib/slack.py',
341
363
  'auto_route_populator/states.py'
@@ -1,4 +1,4 @@
1
1
  module CfnVpn
2
- VERSION = "1.4.3".freeze
2
+ VERSION = "1.4.4".freeze
3
3
  CHANGE_SET_VERSION = VERSION.gsub('.', '-').freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfn-vpn
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.3
4
+ version: 1.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Guslington
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-22 00:00:00.000000000 Z
11
+ date: 2022-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -248,6 +248,7 @@ files:
248
248
  - docs/scheduling.md
249
249
  - docs/sessions.md
250
250
  - docs/slack-notifications.md
251
+ - docs/yaml-config.md
251
252
  - exe/cfn-vpn
252
253
  - lib/cfnvpn.rb
253
254
  - lib/cfnvpn/acm.rb
@@ -274,6 +275,7 @@ files:
274
275
  - lib/cfnvpn/templates/helper.rb
275
276
  - lib/cfnvpn/templates/lambdas.rb
276
277
  - lib/cfnvpn/templates/lambdas/auto_route_populator/app.py
278
+ - lib/cfnvpn/templates/lambdas/auto_route_populator/lookup.py
277
279
  - lib/cfnvpn/templates/lambdas/auto_route_populator/quotas.py
278
280
  - lib/cfnvpn/templates/lambdas/auto_route_populator/states.py
279
281
  - lib/cfnvpn/templates/lambdas/lib/slack.py