cfn-vpn 1.4.3 → 1.4.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 +1 -1
- data/docs/README.md +2 -1
- data/docs/modifying.md +7 -5
- data/docs/routes.md +94 -44
- data/docs/slack-notifications.md +9 -0
- data/docs/yaml-config.md +118 -0
- data/lib/cfnvpn/templates/lambdas/auto_route_populator/app.py +42 -39
- data/lib/cfnvpn/templates/lambdas/auto_route_populator/lookup.py +58 -0
- data/lib/cfnvpn/templates/lambdas/lib/slack.py +3 -0
- data/lib/cfnvpn/templates/vpn.rb +49 -27
- data/lib/cfnvpn/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c3fd1a4b4105ebcd008d105ea5eb5dd493ef698f3468d6a28bdf8ae3b194633
|
4
|
+
data.tar.gz: e0cd2db1d7619b8ab5ac88a29a85f96e2b352edee7cf7883f7ba3f30d8ac1077
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 821ec1eb8281f6f7ecadfb8c76d6e1ece81a49af10e05e01f78cfecbb14470f4231d61ea6b7cd5c7206001a4e363f3e648b487cb15c3cdff1c16c1ff52a0d79e
|
7
|
+
data.tar.gz: d60cdf23880b0903be48c83cbd6ee997820be1b4a149329c726eab7fac8f686a49fdd483f53b35c429c3caaa46867d05f6627022001b269eeea4e9f9feb1a242
|
data/Gemfile.lock
CHANGED
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
|
32
|
+
### With YAML File
|
33
33
|
|
34
|
-
|
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] --
|
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
|
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] --
|
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
|
-
|
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
|
18
|
+
cfn-vpn routes [name] --cidr 10.151.0.0/16
|
11
19
|
```
|
12
20
|
|
13
|
-
|
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
|
-
|
23
|
+
```sh
|
24
|
+
cfn-vpn routes [name] --cidr 10.151.0.0/16 --delete
|
25
|
+
```
|
16
26
|
|
17
|
-
|
27
|
+
#### YAML Config
|
18
28
|
|
19
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
68
|
+
## Cloud Routes
|
36
69
|
|
37
|
-
to
|
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
|
-
|
40
|
-
|
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
|
-
|
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
|
-
|
110
|
+
#### YAML Config
|
46
111
|
|
47
|
-
```
|
48
|
-
|
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
|
|
data/docs/slack-notifications.md
CHANGED
@@ -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
|
data/docs/yaml-config.md
ADDED
@@ -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
|
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
|
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
|
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
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
logger.
|
141
|
-
|
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.
|
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.
|
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.
|
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.
|
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} ({
|
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 {
|
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
|
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
|
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
|
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
|
data/lib/cfnvpn/templates/vpn.rb
CHANGED
@@ -127,22 +127,23 @@ module CfnVpn
|
|
127
127
|
output(:InternetRoute, config[:internet_route])
|
128
128
|
end
|
129
129
|
|
130
|
-
|
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
|
132
|
+
if create_auto_route_populator
|
134
133
|
auto_route_populator(name, config)
|
134
|
+
end
|
135
135
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
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
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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'
|
data/lib/cfnvpn/version.rb
CHANGED
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.
|
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:
|
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
|