cfn-vpn 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +21 -19
- data/cfn-vpn.gemspec +1 -0
- data/docs/README.md +2 -2
- data/docs/routes.md +34 -20
- data/lib/cfnvpn/actions/init.rb +15 -11
- data/lib/cfnvpn/actions/modify.rb +20 -0
- data/lib/cfnvpn/actions/routes.rb +59 -7
- data/lib/cfnvpn/clientvpn.rb +18 -0
- data/lib/cfnvpn/s3.rb +30 -0
- data/lib/cfnvpn/s3_bucket.rb +48 -0
- data/lib/cfnvpn/string.rb +4 -0
- data/lib/cfnvpn/templates/lambdas.rb +35 -0
- data/lib/cfnvpn/templates/lambdas/auto_route_populator/app.py +175 -0
- data/lib/cfnvpn/templates/lambdas/scheduler/app.py +36 -0
- data/lib/cfnvpn/templates/vpn.rb +188 -92
- data/lib/cfnvpn/version.rb +1 -1
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e435821426e5e0e4cf69244013bf0ac1cd2ae030a024fb7b12827543f81c87af
|
4
|
+
data.tar.gz: 9c8f7968839a339966f5eb8aa9680c625764c326f84946eb8bcf08d6da818cd1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 305a236f13203b9ffd4d5d68fc6a87ff912700d3947420b02fa477b787f4c57768e9d4485c8a22bba354547449b3d39ef57811ee43a1d9093623ecad24b690ee
|
7
|
+
data.tar.gz: 82f8ba462728afb71fc18e886c20fee0d9a5ea201a071262b6ead3bd83549f4c5eb58495c0603ee1567c94da90d8a3290b9d37fcf05312e1922678db529dcb15
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cfn-vpn (1.
|
4
|
+
cfn-vpn (1.2.0)
|
5
5
|
aws-sdk-acm (~> 1, < 2)
|
6
6
|
aws-sdk-cloudformation (~> 1, < 2)
|
7
7
|
aws-sdk-ec2 (~> 1.95, < 2)
|
@@ -9,46 +9,48 @@ PATH
|
|
9
9
|
aws-sdk-ssm (~> 1, < 2)
|
10
10
|
cfndsl (~> 1, < 2)
|
11
11
|
netaddr (= 2.0.4)
|
12
|
+
rubyzip (~> 2.3)
|
12
13
|
terminal-table (~> 1, < 2)
|
13
14
|
thor (~> 0.20)
|
14
15
|
|
15
16
|
GEM
|
16
17
|
remote: https://rubygems.org/
|
17
18
|
specs:
|
18
|
-
aws-eventstream (1.1.
|
19
|
-
aws-partitions (1.
|
20
|
-
aws-sdk-acm (1.
|
21
|
-
aws-sdk-core (~> 3, >= 3.
|
19
|
+
aws-eventstream (1.1.1)
|
20
|
+
aws-partitions (1.432.0)
|
21
|
+
aws-sdk-acm (1.39.0)
|
22
|
+
aws-sdk-core (~> 3, >= 3.112.0)
|
22
23
|
aws-sigv4 (~> 1.1)
|
23
|
-
aws-sdk-cloudformation (1.
|
24
|
-
aws-sdk-core (~> 3, >= 3.
|
24
|
+
aws-sdk-cloudformation (1.49.0)
|
25
|
+
aws-sdk-core (~> 3, >= 3.112.0)
|
25
26
|
aws-sigv4 (~> 1.1)
|
26
|
-
aws-sdk-core (3.
|
27
|
+
aws-sdk-core (3.113.0)
|
27
28
|
aws-eventstream (~> 1, >= 1.0.2)
|
28
29
|
aws-partitions (~> 1, >= 1.239.0)
|
29
30
|
aws-sigv4 (~> 1.1)
|
30
31
|
jmespath (~> 1.0)
|
31
|
-
aws-sdk-ec2 (1.
|
32
|
+
aws-sdk-ec2 (1.220.0)
|
32
33
|
aws-sdk-core (~> 3, >= 3.109.0)
|
33
34
|
aws-sigv4 (~> 1.1)
|
34
|
-
aws-sdk-kms (1.
|
35
|
-
aws-sdk-core (~> 3, >= 3.
|
35
|
+
aws-sdk-kms (1.43.0)
|
36
|
+
aws-sdk-core (~> 3, >= 3.112.0)
|
36
37
|
aws-sigv4 (~> 1.1)
|
37
|
-
aws-sdk-s3 (1.
|
38
|
-
aws-sdk-core (~> 3, >= 3.
|
38
|
+
aws-sdk-s3 (1.91.0)
|
39
|
+
aws-sdk-core (~> 3, >= 3.112.0)
|
39
40
|
aws-sdk-kms (~> 1)
|
40
41
|
aws-sigv4 (~> 1.1)
|
41
|
-
aws-sdk-ssm (1.
|
42
|
-
aws-sdk-core (~> 3, >= 3.
|
42
|
+
aws-sdk-ssm (1.104.0)
|
43
|
+
aws-sdk-core (~> 3, >= 3.112.0)
|
43
44
|
aws-sigv4 (~> 1.1)
|
44
|
-
aws-sigv4 (1.2.
|
45
|
+
aws-sigv4 (1.2.3)
|
45
46
|
aws-eventstream (~> 1, >= 1.0.2)
|
46
|
-
cfndsl (1.
|
47
|
+
cfndsl (1.3.1)
|
47
48
|
hana (~> 1.3)
|
48
|
-
hana (1.3.
|
49
|
+
hana (1.3.7)
|
49
50
|
jmespath (1.4.0)
|
50
51
|
netaddr (2.0.4)
|
51
52
|
rake (13.0.1)
|
53
|
+
rubyzip (2.3.0)
|
52
54
|
terminal-table (1.8.0)
|
53
55
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
54
56
|
thor (0.20.3)
|
@@ -63,4 +65,4 @@ DEPENDENCIES
|
|
63
65
|
rake (~> 13.0)
|
64
66
|
|
65
67
|
BUNDLED WITH
|
66
|
-
2.
|
68
|
+
2.1.4
|
data/cfn-vpn.gemspec
CHANGED
@@ -37,6 +37,7 @@ Gem::Specification.new do |spec|
|
|
37
37
|
spec.add_dependency "terminal-table", '~> 1', '<2'
|
38
38
|
spec.add_dependency 'cfndsl', '~> 1', '<2'
|
39
39
|
spec.add_dependency 'netaddr', '2.0.4'
|
40
|
+
spec.add_dependency 'rubyzip', '~> 2.3'
|
40
41
|
spec.add_runtime_dependency 'aws-sdk-ec2', '~> 1.95', '<2'
|
41
42
|
spec.add_runtime_dependency 'aws-sdk-acm', '~> 1', '<2'
|
42
43
|
spec.add_runtime_dependency 'aws-sdk-s3', '~> 1', '<2'
|
data/docs/README.md
CHANGED
@@ -31,7 +31,7 @@ dig @10.0.0.2 google.com
|
|
31
31
|
|
32
32
|
## Authentication Types
|
33
33
|
|
34
|
-
`cfn-vpn` supports certificate and
|
34
|
+
`cfn-vpn` supports certificate, federated and active directory type authentication for AWS Client-VPN.
|
35
35
|
For further information on the authentication types please visit https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/client-authentication.html
|
36
36
|
|
37
37
|
## CfnVpn Documentation
|
@@ -41,4 +41,4 @@ For further information on the authentication types please visit https://docs.aw
|
|
41
41
|
3. [Managing Certificate Users](certificate-users.md)
|
42
42
|
4. [Managing Routes](routes.md)
|
43
43
|
5. [Stop and Start Client-VPN](scheduling.md)
|
44
|
-
6. [Managing Sessions](sessions.md)
|
44
|
+
6. [Managing Sessions](sessions.md)
|
data/docs/routes.md
CHANGED
@@ -4,24 +4,34 @@ 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
|
+
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.
|
8
8
|
|
9
|
+
```sh
|
10
|
+
cfn-vpn help routes
|
11
|
+
```
|
12
|
+
|
13
|
+
## Dynamic DNS Routes
|
14
|
+
|
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.
|
16
|
+
|
17
|
+
### Add New
|
18
|
+
|
19
|
+
to add a new route run the routes command along with the `--dns` option
|
20
|
+
|
21
|
+
```sh
|
22
|
+
cfn-vpn routes [name] --dns example.com
|
9
23
|
```
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
[--groups=one two three] # override all authorised groups on thr route
|
18
|
-
[--add-groups=one two three] # add authorised groups to an existing route
|
19
|
-
[--del-groups=one two three] # remove authorised groups from an existing route
|
20
|
-
[--delete], [--no-delete] # delete the route from the client vpn
|
21
|
-
|
22
|
-
List, add or delete client vpn routes
|
24
|
+
|
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
|
28
|
+
|
29
|
+
```sh
|
30
|
+
cfn-vpn routes [name] --dns example.com --delete
|
23
31
|
```
|
24
32
|
|
33
|
+
## Static CIDR Routes
|
34
|
+
|
25
35
|
### Add New
|
26
36
|
|
27
37
|
to add a new route run the routes command along with the `--cidr` option
|
@@ -32,32 +42,32 @@ cfn-vpn routes [name] --cidr 10.151.0.0/16
|
|
32
42
|
|
33
43
|
### Delete
|
34
44
|
|
35
|
-
to delete a
|
45
|
+
to delete a route run the routes command along with the `--cidr` option of the route to delete and the delete option
|
36
46
|
|
37
47
|
```sh
|
38
48
|
cfn-vpn routes [name] --cidr 10.151.0.0/16 --delete
|
39
49
|
```
|
40
50
|
|
41
|
-
|
51
|
+
## Manage Authorization Groups
|
42
52
|
|
43
|
-
When using federated authentication groups can be used to control access to certain routes. These can be managed on the routes by providing the `--groups [list of groups]` along with a space delimited list of groups to the `routes` command.
|
53
|
+
When using federated or active directory authentication groups can be used to control access to certain routes. These can be managed on the routes by providing the `--groups [list of groups]` along with a space delimited list of groups to the `routes` command. This is available for both DNS and CIDR routes
|
44
54
|
|
45
55
|
To add groups to a new route or to override all groups on an exiting route use the `--groups` options
|
46
56
|
|
47
57
|
```sh
|
48
|
-
cfn-vpn routes [name] --cidr 10.151.0.0/16 --groups devs ops
|
58
|
+
cfn-vpn routes [name] [--cidr 10.151.0.0/16] [--dns example.com] --groups devs ops
|
49
59
|
```
|
50
60
|
|
51
61
|
To add groups to an existing route use the `--add-groups` options
|
52
62
|
|
53
63
|
```sh
|
54
|
-
cfn-vpn routes [name] --cidr 10.151.0.0/16 --add-groups admin
|
64
|
+
cfn-vpn routes [name] [--cidr 10.151.0.0/16] [--dns example.com] --add-groups admin
|
55
65
|
```
|
56
66
|
|
57
67
|
To delete groups from an existing route use the `--del-groups` options
|
58
68
|
|
59
69
|
```sh
|
60
|
-
cfn-vpn routes [name] --cidr 10.151.0.0/16 --del-groups dev
|
70
|
+
cfn-vpn routes [name] [--cidr 10.151.0.0/16] [--dns example.com] --del-groups dev
|
61
71
|
```
|
62
72
|
|
63
73
|
## Modify Command
|
@@ -75,6 +85,10 @@ routes:
|
|
75
85
|
desc: route to prod peered vpc
|
76
86
|
groups:
|
77
87
|
- ops
|
88
|
+
- cidr: example.com
|
89
|
+
desc: my dev alb
|
90
|
+
groups:
|
91
|
+
- dev
|
78
92
|
```
|
79
93
|
|
80
94
|
run the `modify` command and supply the yaml file to apply the changes
|
data/lib/cfnvpn/actions/init.rb
CHANGED
@@ -6,6 +6,7 @@ require 'cfnvpn/compiler'
|
|
6
6
|
require 'cfnvpn/log'
|
7
7
|
require 'cfnvpn/clientvpn'
|
8
8
|
require 'cfnvpn/globals'
|
9
|
+
require 'cfnvpn/s3_bucket'
|
9
10
|
|
10
11
|
module CfnVpn::Actions
|
11
12
|
class Init < Thor::Group
|
@@ -20,7 +21,7 @@ module CfnVpn::Actions
|
|
20
21
|
class_option :server_cn, required: true, desc: 'server certificate common name'
|
21
22
|
class_option :client_cn, desc: 'client certificate common name'
|
22
23
|
class_option :easyrsa_local, type: :boolean, default: false, desc: 'run the easyrsa executable from your local rather than from docker'
|
23
|
-
class_option :bucket, desc: 's3 bucket'
|
24
|
+
class_option :bucket, desc: 's3 bucket, if not set one will be generated for you'
|
24
25
|
|
25
26
|
class_option :subnet_ids, required: true, type: :array, desc: 'subnet id to associate your vpn with'
|
26
27
|
class_option :default_groups, default: [], type: :array, desc: 'groups to allow through the subnet associations when using federated auth'
|
@@ -68,6 +69,18 @@ module CfnVpn::Actions
|
|
68
69
|
}
|
69
70
|
end
|
70
71
|
|
72
|
+
def create_bucket_if_bucket_not_set
|
73
|
+
if !@options['bucket']
|
74
|
+
CfnVpn::Log.logger.info "creating s3 bucket"
|
75
|
+
bucket = CfnVpn::S3Bucket.new(@options['region'], @name)
|
76
|
+
bucket_name = bucket.generate_bucket_name
|
77
|
+
bucket.create_bucket(bucket_name)
|
78
|
+
@config[:bucket] = bucket_name
|
79
|
+
else
|
80
|
+
@config[:bucket] = @options['bucket']
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
71
84
|
def set_type
|
72
85
|
if @options['saml_arn']
|
73
86
|
@config[:type] = 'federated'
|
@@ -82,15 +95,6 @@ module CfnVpn::Actions
|
|
82
95
|
CfnVpn::Log.logger.info "initialising #{@config[:type]} client vpn"
|
83
96
|
end
|
84
97
|
|
85
|
-
def conditional_options_check
|
86
|
-
if @config[:type] == 'certificate'
|
87
|
-
if !@options['bucket']
|
88
|
-
CfnVpn::Log.logger.error "--bucket option must be specified if creating a client vpn with certificate based authentication"
|
89
|
-
exit 1
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
98
|
def stack_exist
|
95
99
|
@deployer = CfnVpn::Deployer.new(@options['region'],@name)
|
96
100
|
if @deployer.does_cf_stack_exist()
|
@@ -114,7 +118,7 @@ module CfnVpn::Actions
|
|
114
118
|
# we only need the server certificate to ACM if it is a SAML federated client vpn
|
115
119
|
@config[:client_cert_arn] = cert.upload_certificates(@options['region'],@client_cn,'client')
|
116
120
|
# and only need to upload the certs to s3 if using certificate authenitcation
|
117
|
-
s3 = CfnVpn::S3.new(@options['region'],@
|
121
|
+
s3 = CfnVpn::S3.new(@options['region'],@config[:bucket],@name)
|
118
122
|
s3.store_object("#{@build_dir}/certificates/ca.tar.gz")
|
119
123
|
end
|
120
124
|
end
|
@@ -8,6 +8,7 @@ require 'cfnvpn/log'
|
|
8
8
|
require 'cfnvpn/clientvpn'
|
9
9
|
require 'cfnvpn/globals'
|
10
10
|
require 'cfnvpn/config'
|
11
|
+
require 'cfnvpn/s3_bucket'
|
11
12
|
|
12
13
|
module CfnVpn::Actions
|
13
14
|
class Modify < Thor::Group
|
@@ -38,6 +39,8 @@ module CfnVpn::Actions
|
|
38
39
|
|
39
40
|
class_option :param_yaml, type: :string, desc: 'pass in cfnvpn params through YAML file'
|
40
41
|
|
42
|
+
class_option :bucket, desc: 's3 bucket'
|
43
|
+
|
41
44
|
def self.source_root
|
42
45
|
File.dirname(__FILE__)
|
43
46
|
end
|
@@ -95,6 +98,23 @@ module CfnVpn::Actions
|
|
95
98
|
CfnVpn::Log.logger.debug "Modified config:\n#{@config}"
|
96
99
|
end
|
97
100
|
|
101
|
+
def create_bucket_if_bucket_not_set
|
102
|
+
if !@options['bucket'] && !@config.has_key?(:bucket)
|
103
|
+
if yes? "no s3 bucket supplied in the command or found in the config, select (Y) to generate a new one ot select (N) and re run teh command with the --bucket flag to import the existing bucket."
|
104
|
+
CfnVpn::Log.logger.info "creating s3 bucket"
|
105
|
+
bucket = CfnVpn::S3Bucket.new(@options['region'], @name)
|
106
|
+
bucket_name = bucket.generate_bucket_name
|
107
|
+
bucket.create_bucket(bucket_name)
|
108
|
+
@config[:bucket] = bucket_name
|
109
|
+
else
|
110
|
+
CfnVpn::Log.logger.info "rerun cfn-vpn modify #{name} command with the --bucket [BUCKET] flag"
|
111
|
+
exit 1
|
112
|
+
end
|
113
|
+
elsif @options['bucket']
|
114
|
+
@config[:bucket] = @options['bucket']
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
98
118
|
def deploy_vpn
|
99
119
|
compiler = CfnVpn::Compiler.new(@name, @config)
|
100
120
|
template_body = compiler.compile
|
@@ -13,6 +13,7 @@ module CfnVpn::Actions
|
|
13
13
|
class_option :verbose, desc: 'set log level to debug', type: :boolean
|
14
14
|
|
15
15
|
class_option :cidr, desc: 'cidr range'
|
16
|
+
class_option :dns, desc: 'dns record to auto lookup ip'
|
16
17
|
class_option :subnet, desc: 'the target vpc subnet to route through, if none is supplied the default subnet is used'
|
17
18
|
class_option :desc, desc: 'description of the route'
|
18
19
|
|
@@ -32,26 +33,50 @@ module CfnVpn::Actions
|
|
32
33
|
|
33
34
|
def set_config
|
34
35
|
@config = CfnVpn::Config.get_config(@options[:region], @name)
|
35
|
-
|
36
|
+
|
37
|
+
if @options[:cidr] && @options[:dns]
|
38
|
+
CfnVpn::Log.logger.error "only one of --dns or --cidr can be set"
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
if @options[:dns]
|
43
|
+
if @options[:dns].include?("*")
|
44
|
+
CfnVpn::Log.logger.error("wild card DNS resolution is not supported, use a record that will be resolved by the wild card instead")
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
@route = @config[:routes].detect {|route| route[:dns] == @options[:dns]}
|
48
|
+
elsif @options[:cidr]
|
49
|
+
@route = @config[:routes].detect {|route| route[:cidr] == @options[:cidr]}
|
50
|
+
end
|
36
51
|
end
|
37
52
|
|
38
53
|
def set_route
|
39
54
|
@skip_update = false
|
55
|
+
@dns_route_cleanup = nil
|
40
56
|
if @route && @options[:delete]
|
41
|
-
|
42
|
-
|
57
|
+
if @options[:dns]
|
58
|
+
CfnVpn::Log.logger.info "deleting auto lookup route for endpoint #{@options[:dns]}"
|
59
|
+
@config[:routes].reject! {|route| route[:dns] == @options[:dns]}
|
60
|
+
@dns_route_cleanup = @options[:dns]
|
61
|
+
elsif @options[:cidr]
|
62
|
+
CfnVpn::Log.logger.info "deleting route #{@options[:cidr]}"
|
63
|
+
@config[:routes].reject! {|route| route[:cidr] == @options[:cidr]}
|
64
|
+
end
|
43
65
|
elsif @route
|
44
|
-
CfnVpn::Log.logger.info "
|
66
|
+
CfnVpn::Log.logger.info "existing route for #{@options[:cidr] ? @options[:cidr] : @options[:dns]} found"
|
45
67
|
if @options[:groups]
|
68
|
+
CfnVpn::Log.logger.info "replacing groups #{@route[:groups]} with new #{@options[:groups]} for route authorization rule"
|
46
69
|
@route[:groups] = @options[:groups]
|
47
70
|
end
|
48
71
|
|
49
72
|
if @options[:add_groups]
|
73
|
+
CfnVpn::Log.logger.info "adding new group(s) #{@options[:add_groups]} to route authorization rule"
|
50
74
|
@route[:groups].concat(@options[:add_groups]).uniq!
|
51
75
|
end
|
52
76
|
|
53
77
|
if @options[:del_groups]
|
54
|
-
|
78
|
+
CfnVpn::Log.logger.info "removing new group(s) #{@options[:del_groups]} to route authorization rule"
|
79
|
+
@route[:groups].reject! {|group| @options[:del_groups].include? group}
|
55
80
|
end
|
56
81
|
|
57
82
|
if @options[:desc]
|
@@ -65,7 +90,15 @@ module CfnVpn::Actions
|
|
65
90
|
CfnVpn::Log.logger.info "adding new route for #{@options[:cidr]}"
|
66
91
|
@config[:routes] << {
|
67
92
|
cidr: @options[:cidr],
|
68
|
-
desc: @options.fetch(:desc, "
|
93
|
+
desc: @options.fetch(:desc, ""),
|
94
|
+
subnet: @options.fetch(:subnet, @config[:subnet_ids].first),
|
95
|
+
groups: @options.fetch(@options[:groups], []) + @options.fetch(@options[:add_groups], [])
|
96
|
+
}
|
97
|
+
elsif !@route && @options[:dns]
|
98
|
+
CfnVpn::Log.logger.info "adding new route lookup for dns record #{@options[:dns]}"
|
99
|
+
@config[:routes] << {
|
100
|
+
dns: @options[:dns],
|
101
|
+
desc: @options.fetch(:desc, ""),
|
69
102
|
subnet: @options.fetch(:subnet, @config[:subnet_ids].first),
|
70
103
|
groups: @options.fetch(@options[:groups], []) + @options.fetch(@options[:add_groups], [])
|
71
104
|
}
|
@@ -76,6 +109,13 @@ module CfnVpn::Actions
|
|
76
109
|
CfnVpn::Log.logger.debug "CONFIG: #{@config}"
|
77
110
|
end
|
78
111
|
|
112
|
+
def create_bucket_if_bucket_not_set
|
113
|
+
if !@config.has_key?(:bucket)
|
114
|
+
CfnVpn::Log.logger.error "no bucket found in the config, run the cfn-vpn modify #{name} command to add a bucket"
|
115
|
+
exit 1
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
79
119
|
def deploy_vpn
|
80
120
|
unless @skip_update
|
81
121
|
compiler = CfnVpn::Compiler.new(@name, @config)
|
@@ -123,8 +163,20 @@ module CfnVpn::Actions
|
|
123
163
|
end
|
124
164
|
end
|
125
165
|
|
126
|
-
def
|
166
|
+
def cleanup_dns_routes
|
127
167
|
@vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
|
168
|
+
unless @dns_route_cleanup.nil?
|
169
|
+
routes = @vpn.get_routes()
|
170
|
+
CfnVpn::Log.logger.info("Cleaning up expired routes for #{@dns_route_cleanup}")
|
171
|
+
expired_routes = routes.select {|route| route.description.include?(@dns_route_cleanup) }
|
172
|
+
expired_routes.each do |route|
|
173
|
+
@vpn.delete_route(route.destination_cidr, route.target_subnet)
|
174
|
+
@vpn.revoke_auth(route.destination_cidr)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def get_routes
|
128
180
|
@endpoint = @vpn.get_endpoint_id()
|
129
181
|
@routes = @vpn.get_routes()
|
130
182
|
end
|
data/lib/cfnvpn/clientvpn.rb
CHANGED
@@ -9,6 +9,7 @@ module CfnVpn
|
|
9
9
|
def initialize(name,region)
|
10
10
|
@client = Aws::EC2::Client.new(region: region)
|
11
11
|
@name = name
|
12
|
+
@endpoint_id = self.get_endpoint_id()
|
12
13
|
end
|
13
14
|
|
14
15
|
def get_endpoint()
|
@@ -116,5 +117,22 @@ module CfnVpn
|
|
116
117
|
return associations
|
117
118
|
end
|
118
119
|
|
120
|
+
def delete_route(cidr, subnet)
|
121
|
+
@client.delete_client_vpn_route({
|
122
|
+
client_vpn_endpoint_id: @endpoint_id,
|
123
|
+
target_vpc_subnet_id: subnet,
|
124
|
+
destination_cidr_block: cidr
|
125
|
+
})
|
126
|
+
end
|
127
|
+
|
128
|
+
def revoke_auth(cidr)
|
129
|
+
endpoint_id = get_endpoint_id()
|
130
|
+
@client.revoke_client_vpn_ingress({
|
131
|
+
client_vpn_endpoint_id: @endpoint_id,
|
132
|
+
target_network_cidr: cidr,
|
133
|
+
revoke_all_groups: true
|
134
|
+
})
|
135
|
+
end
|
136
|
+
|
119
137
|
end
|
120
138
|
end
|
data/lib/cfnvpn/s3.rb
CHANGED
@@ -63,5 +63,35 @@ module CfnVpn
|
|
63
63
|
})
|
64
64
|
end
|
65
65
|
|
66
|
+
def create_bucket
|
67
|
+
@client.create_bucket({
|
68
|
+
bucket: bucket,
|
69
|
+
acl: 'private'
|
70
|
+
})
|
71
|
+
|
72
|
+
@client.put_public_access_block({
|
73
|
+
bucket: bucket,
|
74
|
+
public_access_block_configuration: {
|
75
|
+
block_public_acls: true,
|
76
|
+
ignore_public_acls: true,
|
77
|
+
block_public_policy: true,
|
78
|
+
restrict_public_buckets: true,
|
79
|
+
}
|
80
|
+
})
|
81
|
+
|
82
|
+
@client.put_bucket_encryption({
|
83
|
+
bucket: bucket,
|
84
|
+
server_side_encryption_configuration: {
|
85
|
+
rules: [
|
86
|
+
{
|
87
|
+
apply_server_side_encryption_by_default: {
|
88
|
+
sse_algorithm: "AES256"
|
89
|
+
}
|
90
|
+
}
|
91
|
+
]
|
92
|
+
}
|
93
|
+
})
|
94
|
+
end
|
95
|
+
|
66
96
|
end
|
67
97
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'aws-sdk-s3'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module CfnVpn
|
6
|
+
class S3Bucket
|
7
|
+
|
8
|
+
def initialize(region, name)
|
9
|
+
@client = Aws::S3::Client.new(region: region)
|
10
|
+
@name = name
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate_bucket_name
|
14
|
+
return "cfnvpn-#{@name}-#{SecureRandom.hex}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_bucket(bucket)
|
18
|
+
@client.create_bucket({
|
19
|
+
bucket: bucket,
|
20
|
+
acl: 'private'
|
21
|
+
})
|
22
|
+
|
23
|
+
@client.put_public_access_block({
|
24
|
+
bucket: bucket,
|
25
|
+
public_access_block_configuration: {
|
26
|
+
block_public_acls: true,
|
27
|
+
ignore_public_acls: true,
|
28
|
+
block_public_policy: true,
|
29
|
+
restrict_public_buckets: true,
|
30
|
+
}
|
31
|
+
})
|
32
|
+
|
33
|
+
@client.put_bucket_encryption({
|
34
|
+
bucket: bucket,
|
35
|
+
server_side_encryption_configuration: {
|
36
|
+
rules: [
|
37
|
+
{
|
38
|
+
apply_server_side_encryption_by_default: {
|
39
|
+
sse_algorithm: "AES256"
|
40
|
+
}
|
41
|
+
}
|
42
|
+
]
|
43
|
+
}
|
44
|
+
})
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
data/lib/cfnvpn/string.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'zip'
|
2
|
+
require 'securerandom'
|
3
|
+
require 'aws-sdk-s3'
|
4
|
+
require 'cfnvpn/log'
|
5
|
+
|
6
|
+
module CfnVpn
|
7
|
+
module Templates
|
8
|
+
class Lambdas
|
9
|
+
|
10
|
+
def self.package_lambda(name:, bucket:, func:, files:)
|
11
|
+
lambdas_dir = File.join(File.dirname(File.expand_path(__FILE__)), 'lambdas')
|
12
|
+
FileUtils.mkdir_p(lambdas_dir)
|
13
|
+
|
14
|
+
CfnVpn::Log.logger.debug "zipping lambda function #{func}"
|
15
|
+
zipfile_name = "#{func}-#{SecureRandom.hex}.zip"
|
16
|
+
zipfile_path = "#{CfnVpn.cfnvpn_path}/#{name}/lambdas"
|
17
|
+
FileUtils.mkdir_p(zipfile_path)
|
18
|
+
Zip::File.open("#{zipfile_path}/#{zipfile_name}", Zip::File::CREATE) do |zipfile|
|
19
|
+
files.each do |file|
|
20
|
+
zipfile.add(file, File.join("#{lambdas_dir}/#{func}", file))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
bucket = Aws::S3::Bucket.new(bucket)
|
25
|
+
object = bucket.object("cfnvpn/lambdas/#{name}/#{zipfile_name}")
|
26
|
+
CfnVpn::Log.logger.debug "uploading #{zipfile_name} to s3://#{bucket}/#{object.key}"
|
27
|
+
object.upload_file("#{zipfile_path}/#{zipfile_name}")
|
28
|
+
|
29
|
+
return object.key
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,175 @@
|
|
1
|
+
import socket
|
2
|
+
import boto3
|
3
|
+
from botocore.exceptions import ClientError
|
4
|
+
import logging
|
5
|
+
|
6
|
+
logger = logging.getLogger()
|
7
|
+
logger.setLevel(logging.INFO)
|
8
|
+
|
9
|
+
|
10
|
+
def delete_route(client, vpn_endpoint, subnet, cidr):
|
11
|
+
client.delete_client_vpn_route(
|
12
|
+
ClientVpnEndpointId=vpn_endpoint,
|
13
|
+
TargetVpcSubnetId=subnet,
|
14
|
+
DestinationCidrBlock=cidr,
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
def create_route(client, event, cidr):
|
19
|
+
client.create_client_vpn_route(
|
20
|
+
ClientVpnEndpointId=event['ClientVpnEndpointId'],
|
21
|
+
DestinationCidrBlock=cidr,
|
22
|
+
TargetVpcSubnetId=event['TargetSubnet'],
|
23
|
+
Description=f"cfnvpn auto generated route for endpoint {event['Record']}. {event['Description']}"
|
24
|
+
)
|
25
|
+
|
26
|
+
|
27
|
+
def revoke_route_auth(client, event, cidr, group = None):
|
28
|
+
args = {
|
29
|
+
'ClientVpnEndpointId': event['ClientVpnEndpointId'],
|
30
|
+
'TargetNetworkCidr': cidr
|
31
|
+
}
|
32
|
+
|
33
|
+
if group is None:
|
34
|
+
args['RevokeAllGroups'] = True
|
35
|
+
else:
|
36
|
+
args['AccessGroupId'] = group
|
37
|
+
|
38
|
+
client.revoke_client_vpn_ingress(**args)
|
39
|
+
|
40
|
+
|
41
|
+
def authorize_route(client, event, cidr, group = None):
|
42
|
+
args = {
|
43
|
+
'ClientVpnEndpointId': event['ClientVpnEndpointId'],
|
44
|
+
'TargetNetworkCidr': cidr,
|
45
|
+
'Description': f"cfnvpn auto generated authorization for endpoint {event['Record']}. {event['Description']}"
|
46
|
+
}
|
47
|
+
|
48
|
+
if group is None:
|
49
|
+
args['AuthorizeAllGroups'] = True
|
50
|
+
else:
|
51
|
+
args['AccessGroupId'] = group
|
52
|
+
|
53
|
+
client.authorize_client_vpn_ingress(**args)
|
54
|
+
|
55
|
+
|
56
|
+
def get_routes(client, event):
|
57
|
+
response = client.describe_client_vpn_routes(
|
58
|
+
ClientVpnEndpointId=event['ClientVpnEndpointId'],
|
59
|
+
Filters=[
|
60
|
+
{
|
61
|
+
'Name': 'origin',
|
62
|
+
'Values': ['add-route']
|
63
|
+
}
|
64
|
+
]
|
65
|
+
)
|
66
|
+
|
67
|
+
routes = [route for route in response['Routes'] if event['Record'] in route['Description']]
|
68
|
+
logger.info(f"found {len(routes)} exisiting routes for {event['Record']}")
|
69
|
+
return routes
|
70
|
+
|
71
|
+
|
72
|
+
def get_rules(client, vpn_endpoint, cidr):
|
73
|
+
response = client.describe_client_vpn_authorization_rules(
|
74
|
+
ClientVpnEndpointId=vpn_endpoint,
|
75
|
+
Filters=[
|
76
|
+
{
|
77
|
+
'Name': 'destination-cidr',
|
78
|
+
'Values': [cidr]
|
79
|
+
}
|
80
|
+
]
|
81
|
+
)
|
82
|
+
return response['AuthorizationRules']
|
83
|
+
|
84
|
+
|
85
|
+
def handler(event,context):
|
86
|
+
|
87
|
+
# DNS lookup on the dns record and return all IPS for the endpoint
|
88
|
+
try:
|
89
|
+
cidrs = [ ip + "/32" for ip in socket.gethostbyname_ex(event['Record'])[-1]]
|
90
|
+
logger.info(f"resolved endpoint {event['Record']} to {cidrs}")
|
91
|
+
except socket.gaierror as e:
|
92
|
+
logger.exception(f"failed to resolve record {event['Record']}")
|
93
|
+
return 'KO'
|
94
|
+
|
95
|
+
client = boto3.client('ec2')
|
96
|
+
routes = get_routes(client, event)
|
97
|
+
|
98
|
+
for cidr in cidrs:
|
99
|
+
route = next((route for route in routes if route['DestinationCidr'] == cidr), None)
|
100
|
+
|
101
|
+
# if there are no existing routes for the endpoint cidr create a new route
|
102
|
+
if route is None:
|
103
|
+
try:
|
104
|
+
create_route(client, event, cidr)
|
105
|
+
if 'Groups' in event:
|
106
|
+
for group in event['Groups']:
|
107
|
+
authorize_route(client, event, cidr, group)
|
108
|
+
else:
|
109
|
+
authorize_route(client, event, cidr)
|
110
|
+
except ClientError as e:
|
111
|
+
if e.response['Error']['Code'] == 'InvalidClientVpnDuplicateRoute':
|
112
|
+
logger.error(f"route for CIDR {cidr} already exists with a different endpoint")
|
113
|
+
continue
|
114
|
+
raise e
|
115
|
+
|
116
|
+
# if the route already exists
|
117
|
+
else:
|
118
|
+
|
119
|
+
logger.info(f"route for cidr {cidr} is already in place")
|
120
|
+
|
121
|
+
# if the target subnet has changed in the payload, recreate the routes to use the new subnet
|
122
|
+
if route['TargetSubnet'] != event['TargetSubnet']:
|
123
|
+
logger.info(f"target subnet for route for {cidr} has changed, recreating the route")
|
124
|
+
delete_route(client, event['ClientVpnEndpointId'], route['TargetSubnet'], cidr)
|
125
|
+
create_route(client, event, cidr)
|
126
|
+
|
127
|
+
logger.info(f"checking authorization rules for the route")
|
128
|
+
|
129
|
+
# check the rules match the payload
|
130
|
+
rules = get_rules(client, event['ClientVpnEndpointId'], cidr)
|
131
|
+
existing_groups = [rule['GroupId'] for rule in rules]
|
132
|
+
if 'Groups' in event:
|
133
|
+
# remove expired rules not defined in the payload anymore
|
134
|
+
expired_rules = [rule for rule in rules if rule['GroupId'] not in event['Groups']]
|
135
|
+
for rule in expired_rules:
|
136
|
+
logger.info(f"removing expired authorization rule for group {rule['GroupId']} for route {cidr}")
|
137
|
+
revoke_route_auth(client, event, cidr, rule['GroupId'])
|
138
|
+
# add new rules defined in the payload
|
139
|
+
new_rules = [group for group in event['Groups'] if group not in existing_groups]
|
140
|
+
for group in new_rules:
|
141
|
+
logger.info(f"creating new authorization rule for group {rule['GroupId']} for route {cidr}")
|
142
|
+
authorize_route(client, event, cidr, group)
|
143
|
+
else:
|
144
|
+
# if amount of rules for the cidr is greater than 1 when no groups are specified in the payload
|
145
|
+
# we'll assume that all groups have been removed from the payload so we'll remove all existing rules and add a rule for allow all
|
146
|
+
if len(rules) > 1:
|
147
|
+
logger.info(f"creating an allow all rule for route {cidr}")
|
148
|
+
revoke_route_auth(client, event, cidr)
|
149
|
+
authorize_route(client, event, cidr)
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
|
154
|
+
# clean up any expired routes when the ips for an endpoint change
|
155
|
+
expired_routes = [route for route in routes if route['DestinationCidr'] not in cidrs]
|
156
|
+
for route in expired_routes:
|
157
|
+
logger.info(f"removing expired route {route['DestinationCidr']} for endpoint {event['Record']}")
|
158
|
+
|
159
|
+
try:
|
160
|
+
revoke_route_auth(client, event, route['DestinationCidr'])
|
161
|
+
except ClientError as e:
|
162
|
+
if e.response['Error']['Code'] == 'InvalidClientVpnEndpointAuthorizationRuleNotFound':
|
163
|
+
pass
|
164
|
+
else:
|
165
|
+
raise e
|
166
|
+
|
167
|
+
try:
|
168
|
+
delete_route(client, event['ClientVpnEndpointId'], route['TargetSubnet'], route['DestinationCidr'])
|
169
|
+
except ClientError as e:
|
170
|
+
if e.response['Error']['Code'] == 'InvalidClientVpnRouteNotFound':
|
171
|
+
pass
|
172
|
+
else:
|
173
|
+
raise e
|
174
|
+
|
175
|
+
return 'OK'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import boto3
|
2
|
+
import logging
|
3
|
+
|
4
|
+
logger = logging.getLogger()
|
5
|
+
logger.setLevel(logging.INFO)
|
6
|
+
|
7
|
+
def handler(event, context):
|
8
|
+
|
9
|
+
logger.info(f"updating cfn-vpn stack {event['StackName']} parameter AssociateSubnets with value {event['AssociateSubnets']}")
|
10
|
+
|
11
|
+
if event['AssociateSubnets'] == 'false':
|
12
|
+
logger.info(f"terminating current vpn sessions to {event['ClientVpnEndpointId']}")
|
13
|
+
ec2 = boto3.client('ec2')
|
14
|
+
resp = ec2.describe_client_vpn_connections(ClientVpnEndpointId=event['ClientVpnEndpointId'])
|
15
|
+
for conn in resp['Connections']:
|
16
|
+
if conn['Status']['Code'] == 'active':
|
17
|
+
ec2.terminate_client_vpn_connections(
|
18
|
+
ClientVpnEndpointId=event['ClientVpnEndpointId'],
|
19
|
+
ConnectionId=conn['ConnectionId']
|
20
|
+
)
|
21
|
+
logger.info(f"terminated session {conn['ConnectionId']}")
|
22
|
+
|
23
|
+
client = boto3.client('cloudformation')
|
24
|
+
logger.info(client.update_stack(
|
25
|
+
StackName=event['StackName'],
|
26
|
+
UsePreviousTemplate=True,
|
27
|
+
Capabilities=['CAPABILITY_IAM'],
|
28
|
+
Parameters=[
|
29
|
+
{
|
30
|
+
'ParameterKey': 'AssociateSubnets',
|
31
|
+
'ParameterValue': event['AssociateSubnets']
|
32
|
+
}
|
33
|
+
]
|
34
|
+
))
|
35
|
+
|
36
|
+
return 'OK'
|
data/lib/cfnvpn/templates/vpn.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'cfndsl'
|
2
2
|
require 'cfnvpn/templates/helper'
|
3
|
+
require 'cfnvpn/templates/lambdas'
|
3
4
|
|
4
5
|
module CfnVpn
|
5
6
|
module Templates
|
@@ -69,6 +70,7 @@ module CfnVpn
|
|
69
70
|
SplitTunnel config[:split_tunnel]
|
70
71
|
}
|
71
72
|
|
73
|
+
network_assoc_dependson = []
|
72
74
|
config[:subnet_ids].each_with_index do |subnet, index|
|
73
75
|
suffix = index == 0 ? "" : "For#{subnet.resource_safe}"
|
74
76
|
|
@@ -78,74 +80,112 @@ module CfnVpn
|
|
78
80
|
SubnetId subnet
|
79
81
|
}
|
80
82
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
AccessGroupId group
|
88
|
-
ClientVpnEndpointId Ref(:ClientVpnEndpoint)
|
89
|
-
TargetNetworkCidr CfnVpn::Templates::Helper.get_auth_cidr(config[:region], subnet)
|
90
|
-
}
|
91
|
-
end
|
92
|
-
else
|
93
|
-
EC2_ClientVpnAuthorizationRule(:"TargetNetworkAuthorizationRule#{suffix}") {
|
83
|
+
network_assoc_dependson << "ClientVpnTargetNetworkAssociation#{suffix}"
|
84
|
+
end
|
85
|
+
|
86
|
+
if config[:default_groups].any?
|
87
|
+
config[:default_groups].each do |group|
|
88
|
+
EC2_ClientVpnAuthorizationRule(:"TargetNetworkAuthorizationRule#{group.resource_safe}"[0..255]) {
|
94
89
|
Condition(:EnableSubnetAssociation)
|
95
|
-
DependsOn
|
90
|
+
DependsOn network_assoc_dependson if network_assoc_dependson.any?
|
96
91
|
Description FnSub("#{name} client-vpn auth rule for subnet association")
|
97
|
-
|
92
|
+
AccessGroupId group
|
98
93
|
ClientVpnEndpointId Ref(:ClientVpnEndpoint)
|
99
|
-
TargetNetworkCidr CfnVpn::Templates::Helper.get_auth_cidr(config[:region],
|
94
|
+
TargetNetworkCidr CfnVpn::Templates::Helper.get_auth_cidr(config[:region], config[:subnet_ids].first)
|
100
95
|
}
|
101
96
|
end
|
97
|
+
else
|
98
|
+
EC2_ClientVpnAuthorizationRule(:"TargetNetworkAuthorizationRule") {
|
99
|
+
Condition(:EnableSubnetAssociation)
|
100
|
+
DependsOn network_assoc_dependson if network_assoc_dependson.any?
|
101
|
+
Description FnSub("#{name} client-vpn auth rule for subnet association")
|
102
|
+
AuthorizeAllGroups true
|
103
|
+
ClientVpnEndpointId Ref(:ClientVpnEndpoint)
|
104
|
+
TargetNetworkCidr CfnVpn::Templates::Helper.get_auth_cidr(config[:region], config[:subnet_ids].first)
|
105
|
+
}
|
106
|
+
end
|
102
107
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
108
|
+
if !config[:internet_route].nil?
|
109
|
+
EC2_ClientVpnRoute(:RouteToInternet) {
|
110
|
+
Condition(:EnableSubnetAssociation)
|
111
|
+
DependsOn network_assoc_dependson if network_assoc_dependson.any?
|
112
|
+
Description "Route to the internet through subnet #{config[:internet_route]}"
|
113
|
+
ClientVpnEndpointId Ref(:ClientVpnEndpoint)
|
114
|
+
DestinationCidrBlock '0.0.0.0/0'
|
115
|
+
TargetVpcSubnetId config[:internet_route]
|
116
|
+
}
|
117
|
+
|
118
|
+
EC2_ClientVpnAuthorizationRule(:RouteToInternetAuthorizationRule) {
|
119
|
+
Condition(:EnableSubnetAssociation)
|
120
|
+
DependsOn network_assoc_dependson if network_assoc_dependson.any?
|
121
|
+
Description "Internet route authorization from subnet #{config[:internet_route]}"
|
122
|
+
AuthorizeAllGroups true
|
123
|
+
ClientVpnEndpointId Ref(:ClientVpnEndpoint)
|
124
|
+
TargetNetworkCidr '0.0.0.0/0'
|
125
|
+
}
|
126
|
+
|
127
|
+
output(:InternetRoute, config[:internet_route])
|
128
|
+
end
|
129
|
+
|
130
|
+
dns_routes = config[:routes].select {|route| route.has_key?(:dns)}
|
131
|
+
cidr_routes = config[:routes].select {|route| route.has_key?(:cidr)}
|
132
|
+
|
133
|
+
if dns_routes.any?
|
134
|
+
auto_route_populator(name, config[:bucket])
|
135
|
+
|
136
|
+
dns_routes.each do |route|
|
137
|
+
input = {
|
138
|
+
Record: route[:dns],
|
139
|
+
ClientVpnEndpointId: "${ClientVpnEndpoint}",
|
140
|
+
TargetSubnet: route[:subnet],
|
141
|
+
Description: route[:desc]
|
111
142
|
}
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
143
|
+
|
144
|
+
if route[:groups].any?
|
145
|
+
input[:Groups] = route[:groups]
|
146
|
+
end
|
147
|
+
|
148
|
+
Events_Rule(:"CfnVpnAutoRoutePopulatorEvent#{route[:dns].resource_safe}"[0..255]) {
|
149
|
+
State 'ENABLED'
|
150
|
+
Description "cfnvpn auto route populator schedule for #{route[:dns]}"
|
151
|
+
ScheduleExpression "rate(5 minutes)"
|
152
|
+
Targets([
|
153
|
+
{
|
154
|
+
Arn: FnGetAtt(:CfnVpnAutoRoutePopulator, :Arn),
|
155
|
+
Id: "cfnvpnautoroutepopulator#{route[:dns].event_id_safe}",
|
156
|
+
Input: FnSub(input.to_json)
|
157
|
+
}
|
158
|
+
])
|
120
159
|
}
|
121
|
-
|
122
|
-
output(:InternetRoute, config[:internet_route])
|
123
160
|
end
|
124
161
|
end
|
125
162
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
163
|
+
if cidr_routes.any?
|
164
|
+
cidr_routes.each do |route|
|
165
|
+
EC2_ClientVpnRoute(:"#{route[:cidr].resource_safe}VpnRoute") {
|
166
|
+
Description "cfnvpn static route for #{route[:cidr]}. #{route[:desc]}"
|
167
|
+
ClientVpnEndpointId Ref(:ClientVpnEndpoint)
|
168
|
+
DestinationCidrBlock route[:cidr]
|
169
|
+
TargetVpcSubnetId route[:subnet]
|
170
|
+
}
|
171
|
+
|
172
|
+
if route[:groups].any?
|
173
|
+
route[:groups].each do |group|
|
174
|
+
EC2_ClientVpnAuthorizationRule(:"#{route[:cidr].resource_safe}AuthorizationRule#{group.resource_safe}"[0..255]) {
|
175
|
+
Description "cfnvpn static authorization rule for group #{group} to route #{route[:cidr]}. #{route[:desc]}"
|
176
|
+
AccessGroupId group
|
177
|
+
ClientVpnEndpointId Ref(:ClientVpnEndpoint)
|
178
|
+
TargetNetworkCidr route[:cidr]
|
179
|
+
}
|
180
|
+
end
|
181
|
+
else
|
182
|
+
EC2_ClientVpnAuthorizationRule(:"#{route[:cidr].resource_safe}AllowAllAuthorizationRule") {
|
183
|
+
Description "cfnvpn static allow all authorization rule to route #{route[:cidr]}. #{route[:desc]}"
|
184
|
+
AuthorizeAllGroups true
|
138
185
|
ClientVpnEndpointId Ref(:ClientVpnEndpoint)
|
139
186
|
TargetNetworkCidr route[:cidr]
|
140
187
|
}
|
141
188
|
end
|
142
|
-
else
|
143
|
-
EC2_ClientVpnAuthorizationRule(:"#{route[:cidr].resource_safe}AllowAllAuthorizationRule") {
|
144
|
-
Description route[:desc]
|
145
|
-
AuthorizeAllGroups true
|
146
|
-
ClientVpnEndpointId Ref(:ClientVpnEndpoint)
|
147
|
-
TargetNetworkCidr route[:cidr]
|
148
|
-
}
|
149
189
|
end
|
150
190
|
end
|
151
191
|
|
@@ -156,13 +196,13 @@ module CfnVpn
|
|
156
196
|
Type 'String'
|
157
197
|
Value config.to_json
|
158
198
|
Tags({
|
159
|
-
Name:
|
199
|
+
Name: "#{name}-cfnvpn-config",
|
160
200
|
Environment: 'cfnvpn'
|
161
201
|
})
|
162
202
|
}
|
163
203
|
|
164
204
|
if config[:start] || config[:stop]
|
165
|
-
scheduler(name, config[:start], config[:stop])
|
205
|
+
scheduler(name, config[:start], config[:stop], config[:bucket])
|
166
206
|
output(:Start, config[:start]) if config[:start]
|
167
207
|
output(:Stop, config[:stop]) if config[:stop]
|
168
208
|
end
|
@@ -188,7 +228,92 @@ module CfnVpn
|
|
188
228
|
Output(name) { Value value }
|
189
229
|
end
|
190
230
|
|
191
|
-
def
|
231
|
+
def auto_route_populator(name, bucket)
|
232
|
+
IAM_Role(:CfnVpnAutoRoutePopulatorRole) {
|
233
|
+
AssumeRolePolicyDocument({
|
234
|
+
Version: '2012-10-17',
|
235
|
+
Statement: [{
|
236
|
+
Effect: 'Allow',
|
237
|
+
Principal: { Service: [ 'lambda.amazonaws.com' ] },
|
238
|
+
Action: [ 'sts:AssumeRole' ]
|
239
|
+
}]
|
240
|
+
})
|
241
|
+
Path '/cfnvpn/'
|
242
|
+
Policies([
|
243
|
+
{
|
244
|
+
PolicyName: 'client-vpn',
|
245
|
+
PolicyDocument: {
|
246
|
+
Version: '2012-10-17',
|
247
|
+
Statement: [{
|
248
|
+
Effect: 'Allow',
|
249
|
+
Action: [
|
250
|
+
'ec2:AuthorizeClientVpnIngress',
|
251
|
+
'ec2:RevokeClientVpnIngress',
|
252
|
+
'ec2:DescribeClientVpnAuthorizationRules',
|
253
|
+
'ec2:DescribeClientVpnEndpoints',
|
254
|
+
'ec2:DescribeClientVpnRoutes',
|
255
|
+
'ec2:CreateClientVpnRoute',
|
256
|
+
'ec2:DeleteClientVpnRoute'
|
257
|
+
],
|
258
|
+
Resource: '*'
|
259
|
+
}]
|
260
|
+
}
|
261
|
+
},
|
262
|
+
{
|
263
|
+
PolicyName: 'logging',
|
264
|
+
PolicyDocument: {
|
265
|
+
Version: '2012-10-17',
|
266
|
+
Statement: [{
|
267
|
+
Effect: 'Allow',
|
268
|
+
Action: [
|
269
|
+
'logs:DescribeLogGroups',
|
270
|
+
'logs:CreateLogGroup',
|
271
|
+
'logs:CreateLogStream',
|
272
|
+
'logs:DescribeLogStreams',
|
273
|
+
'logs:PutLogEvents'
|
274
|
+
],
|
275
|
+
Resource: '*'
|
276
|
+
}]
|
277
|
+
}
|
278
|
+
}
|
279
|
+
])
|
280
|
+
Tags([
|
281
|
+
{ Key: 'Name', Value: "#{name}-cfnvpn-auto-route-populator-role" },
|
282
|
+
{ Key: 'Environment', Value: 'cfnvpn' }
|
283
|
+
])
|
284
|
+
}
|
285
|
+
|
286
|
+
s3_key = CfnVpn::Templates::Lambdas.package_lambda(name: name, bucket: bucket, func: 'auto_route_populator', files: ['app.py'])
|
287
|
+
|
288
|
+
Lambda_Function(:CfnVpnAutoRoutePopulator) {
|
289
|
+
Runtime 'python3.8'
|
290
|
+
Role FnGetAtt(:CfnVpnAutoRoutePopulatorRole, :Arn)
|
291
|
+
MemorySize '128'
|
292
|
+
Handler 'app.handler'
|
293
|
+
Timeout 60
|
294
|
+
Code({
|
295
|
+
S3Bucket: bucket,
|
296
|
+
S3Key: s3_key
|
297
|
+
})
|
298
|
+
Tags([
|
299
|
+
{ Key: 'Name', Value: "#{name}-cfnvpn-auto-route-populator" },
|
300
|
+
{ Key: 'Environment', Value: 'cfnvpn' }
|
301
|
+
])
|
302
|
+
}
|
303
|
+
|
304
|
+
Logs_LogGroup(:CfnVpnAutoRoutePopulatorLogGroup) {
|
305
|
+
LogGroupName FnSub("/aws/lambda/${CfnVpnAutoRoutePopulator}")
|
306
|
+
RetentionInDays 30
|
307
|
+
}
|
308
|
+
|
309
|
+
Lambda_Permission(:CfnVpnAutoRoutePopulatorFunctionPermissions) {
|
310
|
+
FunctionName Ref(:CfnVpnAutoRoutePopulator)
|
311
|
+
Action 'lambda:InvokeFunction'
|
312
|
+
Principal 'events.amazonaws.com'
|
313
|
+
}
|
314
|
+
end
|
315
|
+
|
316
|
+
def scheduler(name, start, stop, bucket)
|
192
317
|
IAM_Role(:ClientVpnSchedulerRole) {
|
193
318
|
AssumeRolePolicyDocument({
|
194
319
|
Version: '2012-10-17',
|
@@ -258,46 +383,17 @@ module CfnVpn
|
|
258
383
|
])
|
259
384
|
}
|
260
385
|
|
386
|
+
s3_key = CfnVpn::Templates::Lambdas.package_lambda(name: name, bucket: bucket, func: 'scheduler', files: ['app.py'])
|
387
|
+
|
261
388
|
Lambda_Function(:ClientVpnSchedulerFunction) {
|
262
|
-
Runtime 'python3.
|
389
|
+
Runtime 'python3.8'
|
263
390
|
Role FnGetAtt(:ClientVpnSchedulerRole, :Arn)
|
264
391
|
MemorySize '128'
|
265
|
-
Handler '
|
392
|
+
Handler 'app.handler'
|
393
|
+
Timeout 60
|
266
394
|
Code({
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
def handler(event, context):
|
271
|
-
|
272
|
-
print(f"updating cfn-vpn stack {event['StackName']} parameter AssociateSubnets with value {event['AssociateSubnets']}")
|
273
|
-
|
274
|
-
if event['AssociateSubnets'] == 'false':
|
275
|
-
print(f"terminating current vpn sessions to {event['ClientVpnEndpointId']}")
|
276
|
-
ec2 = boto3.client('ec2')
|
277
|
-
resp = ec2.describe_client_vpn_connections(ClientVpnEndpointId=event['ClientVpnEndpointId'])
|
278
|
-
for conn in resp['Connections']:
|
279
|
-
if conn['Status']['Code'] == 'active':
|
280
|
-
ec2.terminate_client_vpn_connections(
|
281
|
-
ClientVpnEndpointId=event['ClientVpnEndpointId'],
|
282
|
-
ConnectionId=conn['ConnectionId']
|
283
|
-
)
|
284
|
-
print(f"terminated session {conn['ConnectionId']}")
|
285
|
-
|
286
|
-
client = boto3.client('cloudformation')
|
287
|
-
print(client.update_stack(
|
288
|
-
StackName=event['StackName'],
|
289
|
-
UsePreviousTemplate=True,
|
290
|
-
Capabilities=['CAPABILITY_IAM'],
|
291
|
-
Parameters=[
|
292
|
-
{
|
293
|
-
'ParameterKey': 'AssociateSubnets',
|
294
|
-
'ParameterValue': event['AssociateSubnets']
|
295
|
-
}
|
296
|
-
]
|
297
|
-
))
|
298
|
-
|
299
|
-
return 'OK'
|
300
|
-
EOS
|
395
|
+
S3Bucket: bucket,
|
396
|
+
S3Key: s3_key
|
301
397
|
})
|
302
398
|
Tags([
|
303
399
|
{ Key: 'Name', Value: "#{name}-cfnvpn-scheduler-function" },
|
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
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guslington
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -78,6 +78,20 @@ dependencies:
|
|
78
78
|
- - '='
|
79
79
|
- !ruby/object:Gem::Version
|
80
80
|
version: 2.0.4
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rubyzip
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '2.3'
|
88
|
+
type: :runtime
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - "~>"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '2.3'
|
81
95
|
- !ruby/object:Gem::Dependency
|
82
96
|
name: aws-sdk-ec2
|
83
97
|
requirement: !ruby/object:Gem::Requirement
|
@@ -254,8 +268,12 @@ files:
|
|
254
268
|
- lib/cfnvpn/globals.rb
|
255
269
|
- lib/cfnvpn/log.rb
|
256
270
|
- lib/cfnvpn/s3.rb
|
271
|
+
- lib/cfnvpn/s3_bucket.rb
|
257
272
|
- lib/cfnvpn/string.rb
|
258
273
|
- lib/cfnvpn/templates/helper.rb
|
274
|
+
- lib/cfnvpn/templates/lambdas.rb
|
275
|
+
- lib/cfnvpn/templates/lambdas/auto_route_populator/app.py
|
276
|
+
- lib/cfnvpn/templates/lambdas/scheduler/app.py
|
259
277
|
- lib/cfnvpn/templates/vpn.rb
|
260
278
|
- lib/cfnvpn/version.rb
|
261
279
|
homepage: https://github.com/base2services/aws-client-vpn
|