cfn-vpn 1.2.0 → 1.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0faff982b39ef508e55845e2861754915ecca87a1db0df635eb75c718850c096
4
- data.tar.gz: 6956ade9846ca34dab966f382f2c707921acd982be8d04f8f6fac47951a2ae44
3
+ metadata.gz: e435821426e5e0e4cf69244013bf0ac1cd2ae030a024fb7b12827543f81c87af
4
+ data.tar.gz: 9c8f7968839a339966f5eb8aa9680c625764c326f84946eb8bcf08d6da818cd1
5
5
  SHA512:
6
- metadata.gz: 0dcb165737f775d3a8bb9c9dd649544c801ee397a5e2cb0c800e40de0a88b221b05e3bfd4e8346a0b4287c8f0076462c02715c16c0703ffdd083005d062294e3
7
- data.tar.gz: a83c04ff87f444e440ea7cb1e99b835a380fb7c92d48013d59e653dec8e00b56d35fd1075f88cfe5d94a81d6766cab3089dd47678e6b9b3f6d00490129db7b9b
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.0.0)
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.0)
19
- aws-partitions (1.390.0)
20
- aws-sdk-acm (1.38.0)
21
- aws-sdk-core (~> 3, >= 3.109.0)
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.44.0)
24
- aws-sdk-core (~> 3, >= 3.109.0)
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.109.2)
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.208.0)
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.39.0)
35
- aws-sdk-core (~> 3, >= 3.109.0)
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.84.0)
38
- aws-sdk-core (~> 3, >= 3.109.0)
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.97.0)
42
- aws-sdk-core (~> 3, >= 3.109.0)
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.2)
45
+ aws-sigv4 (1.2.3)
45
46
  aws-eventstream (~> 1, >= 1.0.2)
46
- cfndsl (1.2.0)
47
+ cfndsl (1.3.1)
47
48
  hana (~> 1.3)
48
- hana (1.3.6)
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.0.1
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 federated type authentication for AWS Client-VPN. It currently doesn't support Active Directory type authentication.
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
- ## Routes Command
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
- Options:
11
- r, [--region=REGION] # AWS Region
12
- # Default: ap-southeast-2
13
- [--verbose], [--no-verbose] # set log level to debug
14
- [--cidr=CIDR] # cidr range
15
- [--subnet=SUBNET] # the target vpc subnet to route through, if none is supplied the default subnet is used
16
- [--desc=DESC] # description of the route
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 route run the routes command along with the `--cidr` option of the route to delete and the delete option
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
- ### Manage Authorization Groups
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
@@ -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'],@options['bucket'],@name)
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
- @route = @config[:routes].detect {|route| route[:cidr] == @options[:cidr]}
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
- CfnVpn::Log.logger.info "deleting route #{@options[:cidr]} "
42
- @config[:routes].reject! {|route| route[:cidr] == @options[:cidr]}
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 "modifying groups for existing route #{@options[:cidr]}"
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
- @route[:groups].reject! {|group| @options[:add_groups].include? group}
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, "route for cidr #{@options[:cidr]}"),
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 get_routes
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
@@ -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
@@ -11,6 +11,10 @@ class String
11
11
  self.gsub(/[^a-zA-Z0-9]/, "").capitalize
12
12
  end
13
13
 
14
+ def event_id_safe
15
+ self.gsub('*', 'wildcard').gsub(/[^\.\-_A-Za-z0-9]+/, "").downcase
16
+ end
17
+
14
18
  def colorize(color_code)
15
19
  "\e[#{color_code}m#{self}\e[0m"
16
20
  end
@@ -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'
@@ -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
- if config[:default_groups].any?
82
- config[:default_groups].each do |group|
83
- EC2_ClientVpnAuthorizationRule(:"TargetNetworkAuthorizationRule#{suffix}#{group.resource_safe}"[0..255]) {
84
- Condition(:EnableSubnetAssociation)
85
- DependsOn "ClientVpnTargetNetworkAssociation#{suffix}"
86
- Description FnSub("#{name} client-vpn auth rule for subnet association")
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 "ClientVpnTargetNetworkAssociation#{suffix}"
90
+ DependsOn network_assoc_dependson if network_assoc_dependson.any?
96
91
  Description FnSub("#{name} client-vpn auth rule for subnet association")
97
- AuthorizeAllGroups true
92
+ AccessGroupId group
98
93
  ClientVpnEndpointId Ref(:ClientVpnEndpoint)
99
- TargetNetworkCidr CfnVpn::Templates::Helper.get_auth_cidr(config[:region], subnet)
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
- if subnet == config[:internet_route]
104
- EC2_ClientVpnRoute(:RouteToInternet) {
105
- Condition(:EnableSubnetAssociation)
106
- DependsOn "ClientVpnTargetNetworkAssociation#{suffix}"
107
- Description 'Route to the internet'
108
- ClientVpnEndpointId Ref(:ClientVpnEndpoint)
109
- DestinationCidrBlock '0.0.0.0/0'
110
- TargetVpcSubnetId config[:internet_route]
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
- EC2_ClientVpnAuthorizationRule(:RouteToInternetAuthorizationRule) {
114
- Condition(:EnableSubnetAssociation)
115
- DependsOn "ClientVpnTargetNetworkAssociation#{suffix}"
116
- Description 'Route to the internet'
117
- AuthorizeAllGroups true
118
- ClientVpnEndpointId Ref(:ClientVpnEndpoint)
119
- TargetNetworkCidr '0.0.0.0/0'
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
- config[:routes].each do |route|
127
- EC2_ClientVpnRoute(:"#{route[:cidr].resource_safe}VpnRoute") {
128
- Description route[:desc]
129
- ClientVpnEndpointId Ref(:ClientVpnEndpoint)
130
- DestinationCidrBlock route[:cidr]
131
- TargetVpcSubnetId route[:subnet]
132
- }
133
- if route[:groups].any?
134
- route[:groups].each do |group|
135
- EC2_ClientVpnAuthorizationRule(:"#{route[:cidr].resource_safe}AuthorizationRule#{group.resource_safe}"[0..255]) {
136
- Description route[:desc]
137
- AccessGroupId group
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: "#{name}-cfnvpn-config",
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 scheduler(name, start, stop)
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.7'
389
+ Runtime 'python3.8'
263
390
  Role FnGetAtt(:ClientVpnSchedulerRole, :Arn)
264
391
  MemorySize '128'
265
- Handler 'index.handler'
392
+ Handler 'app.handler'
393
+ Timeout 60
266
394
  Code({
267
- ZipFile: <<~EOS
268
- import boto3
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" },
@@ -1,4 +1,4 @@
1
1
  module CfnVpn
2
- VERSION = "1.2.0".freeze
2
+ VERSION = "1.3.0".freeze
3
3
  CHANGE_SET_VERSION = VERSION.gsub('.', '-').freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfn-vpn
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
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-03-18 00:00:00.000000000 Z
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