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 +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
|