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