cfn-vpn 0.5.1 → 1.3.1

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build-gem.yml +25 -0
  3. data/.github/workflows/release-gem.yml +34 -0
  4. data/.github/workflows/release-image.yml +33 -0
  5. data/Gemfile.lock +33 -39
  6. data/README.md +1 -247
  7. data/cfn-vpn.gemspec +4 -4
  8. data/docs/README.md +44 -0
  9. data/docs/certificate-users.md +89 -0
  10. data/docs/getting-started.md +128 -0
  11. data/docs/modifying.md +67 -0
  12. data/docs/routes.md +98 -0
  13. data/docs/scheduling.md +32 -0
  14. data/docs/sessions.md +27 -0
  15. data/lib/cfnvpn.rb +31 -27
  16. data/lib/cfnvpn/{client.rb → actions/client.rb} +5 -6
  17. data/lib/cfnvpn/{embedded.rb → actions/embedded.rb} +15 -15
  18. data/lib/cfnvpn/actions/init.rb +144 -0
  19. data/lib/cfnvpn/actions/modify.rb +169 -0
  20. data/lib/cfnvpn/actions/params.rb +73 -0
  21. data/lib/cfnvpn/{revoke.rb → actions/revoke.rb} +6 -6
  22. data/lib/cfnvpn/actions/routes.rb +196 -0
  23. data/lib/cfnvpn/{sessions.rb → actions/sessions.rb} +5 -5
  24. data/lib/cfnvpn/{share.rb → actions/share.rb} +10 -10
  25. data/lib/cfnvpn/actions/subnets.rb +78 -0
  26. data/lib/cfnvpn/certificates.rb +5 -5
  27. data/lib/cfnvpn/clientvpn.rb +49 -65
  28. data/lib/cfnvpn/compiler.rb +23 -0
  29. data/lib/cfnvpn/config.rb +34 -78
  30. data/lib/cfnvpn/{cloudformation.rb → deployer.rb} +47 -19
  31. data/lib/cfnvpn/log.rb +26 -26
  32. data/lib/cfnvpn/s3.rb +34 -4
  33. data/lib/cfnvpn/s3_bucket.rb +48 -0
  34. data/lib/cfnvpn/string.rb +33 -0
  35. data/lib/cfnvpn/templates/helper.rb +14 -0
  36. data/lib/cfnvpn/templates/lambdas.rb +35 -0
  37. data/lib/cfnvpn/templates/lambdas/auto_route_populator/app.py +175 -0
  38. data/lib/cfnvpn/templates/lambdas/scheduler/app.py +36 -0
  39. data/lib/cfnvpn/templates/vpn.rb +449 -0
  40. data/lib/cfnvpn/version.rb +1 -1
  41. metadata +73 -23
  42. data/lib/cfnvpn/cfhighlander.rb +0 -49
  43. data/lib/cfnvpn/init.rb +0 -109
  44. data/lib/cfnvpn/modify.rb +0 -103
  45. data/lib/cfnvpn/routes.rb +0 -84
  46. data/lib/cfnvpn/templates/cfnvpn.cfhighlander.rb.tt +0 -27
@@ -0,0 +1,32 @@
1
+ # Stop and Start Client-VPN
2
+
3
+ Stopping and starting the VPN can help save AWS costs when you're not using the VPN. AWS pricing model for Client-VPN is per associated subnet per hour so we can achieve this by disassociating the subnets when the VPN is not required and the associated them again when required.
4
+
5
+ This can be achieved through `cfn-vpn` in 2 ways, by a cli command or via a cloudwatch event schedule.
6
+
7
+ ## CLI Command
8
+
9
+ Use the following commands to stop and start your Client-VPN
10
+
11
+ ### Disassociate
12
+
13
+ ```sh
14
+ cfn-vpn subnets [name] --disassociate
15
+ ```
16
+
17
+ ### Associate
18
+
19
+ ```sh
20
+ cfn-vpn subnets [name] --associate
21
+ ```
22
+
23
+ ## Schedule
24
+
25
+ A CloudWatch cron schedule with a lambda function can be setup to stop and start your Client-VPN. This can be achieved by modifying the `cfn-vpn` stack with the required cron schedules.
26
+ To see the CloudWatch cron syntax please visit the [AWS docs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions) for further info
27
+
28
+ ```sh
29
+ cfn-vpn modify [name] --stop "10 6 * * ? *" --start "00 20 ? * MON-FRI *"
30
+ ```
31
+
32
+ One or both of `--start` and `--stop` can be supplied, for example if you wanted an on demand start with a scheduled stop.
data/docs/sessions.md ADDED
@@ -0,0 +1,27 @@
1
+ # Managing Client-VPN Sessions
2
+
3
+ ## Show Sessions
4
+
5
+ This is show a table of current connections on the vpn. You can then kill sessions by using the connection id.
6
+
7
+ ```sh
8
+ cfn-vpn sessions [name]
9
+ ```
10
+
11
+ The sessions are displayed in a table format
12
+
13
+ ```bash
14
+ +-------------+---------------------+--------+-------------+-----------------------------------+---------------+--------------+
15
+ | Common Name | Connected (UTC) | Status | IP Address | Connection ID | Ingress Bytes | Egress Bytes |
16
+ +-------------+---------------------+--------+-------------+-----------------------------------+---------------+--------------+
17
+ | user1 | 2019-06-28 04:58:19 | active | 10.250.0.98 | cvpn-connection-05bcc579cb3fdf9a3 | 3000 | 2679 |
18
+ +-------------+---------------------+--------+-------------+-----------------------------------+---------------+--------------+
19
+ ```
20
+
21
+ ## Terminate a Session
22
+
23
+ To terminate a Client-VPN session specify the `--kill` flag with the connection id to kill the session.
24
+
25
+ ```sh
26
+ cfn-vpn sessions myvpn --kill cvpn-connection-05bcc579cb3fdf9a3
27
+ ```
data/lib/cfnvpn.rb CHANGED
@@ -1,14 +1,15 @@
1
1
  require 'thor'
2
2
  require 'cfnvpn/version'
3
- require 'cfnvpn/init'
4
- require 'cfnvpn/modify'
5
- require 'cfnvpn/config'
6
- require 'cfnvpn/client'
7
- require 'cfnvpn/revoke'
8
- require 'cfnvpn/sessions'
9
- require 'cfnvpn/routes'
10
- require 'cfnvpn/share'
11
- require 'cfnvpn/embedded'
3
+ require 'cfnvpn/actions/init'
4
+ require 'cfnvpn/actions/modify'
5
+ require 'cfnvpn/actions/client'
6
+ require 'cfnvpn/actions/revoke'
7
+ require 'cfnvpn/actions/sessions'
8
+ require 'cfnvpn/actions/routes'
9
+ require 'cfnvpn/actions/share'
10
+ require 'cfnvpn/actions/embedded'
11
+ require 'cfnvpn/actions/subnets'
12
+ require 'cfnvpn/actions/params'
12
13
 
13
14
  module CfnVpn
14
15
  class Cli < Thor
@@ -19,32 +20,35 @@ module CfnVpn
19
20
  puts CfnVpn::VERSION
20
21
  end
21
22
 
22
- register CfnVpn::Init, 'init', 'init [name]', 'Create a AWS Client VPN'
23
- tasks["init"].options = CfnVpn::Init.class_options
23
+ register CfnVpn::Actions::Init, 'init', 'init [name]', 'Create a AWS Client VPN'
24
+ tasks["init"].options = CfnVpn::Actions::Init.class_options
24
25
 
25
- register CfnVpn::Modify, 'modify', 'modify [name]', 'Modify your AWS Client VPN'
26
- tasks["modify"].options = CfnVpn::Modify.class_options
26
+ register CfnVpn::Actions::Modify, 'modify', 'modify [name]', 'Modify your AWS Client VPN'
27
+ tasks["modify"].options = CfnVpn::Actions::Modify.class_options
27
28
 
28
- register CfnVpn::Config, 'config', 'config [name]', 'Retrieve the config for the AWS Client VPN'
29
- tasks["config"].options = CfnVpn::Config.class_options
29
+ register CfnVpn::Actions::Client, 'client', 'client [name]', 'Create a new client certificate'
30
+ tasks["client"].options = CfnVpn::Actions::Client.class_options
30
31
 
31
- register CfnVpn::Client, 'client', 'client [name]', 'Create a new client certificate'
32
- tasks["client"].options = CfnVpn::Client.class_options
32
+ register CfnVpn::Actions::Revoke, 'revoke', 'revoke [name]', 'Revoke a client certificate'
33
+ tasks["revoke"].options = CfnVpn::Actions::Revoke.class_options
33
34
 
34
- register CfnVpn::Revoke, 'revoke', 'revoke [name]', 'Revoke a client certificate'
35
- tasks["revoke"].options = CfnVpn::Revoke.class_options
35
+ register CfnVpn::Actions::Sessions, 'sessions', 'sessions [name]', 'List and kill current vpn connections'
36
+ tasks["sessions"].options = CfnVpn::Actions::Sessions.class_options
36
37
 
37
- register CfnVpn::Sessions, 'sessions', 'sessions [name]', 'List and kill current vpn connections'
38
- tasks["sessions"].options = CfnVpn::Sessions.class_options
38
+ register CfnVpn::Actions::Routes, 'routes', 'routes [name]', 'List, add or delete client vpn routes'
39
+ tasks["routes"].options = CfnVpn::Actions::Routes.class_options
39
40
 
40
- register CfnVpn::Routes, 'routes', 'routes [name]', 'List, add or delete client vpn routes'
41
- tasks["routes"].options = CfnVpn::Routes.class_options
41
+ register CfnVpn::Actions::Share, 'share', 'share [name]', 'Provide a user with a s3 signed download for certificates and config'
42
+ tasks["share"].options = CfnVpn::Actions::Share.class_options
42
43
 
43
- register CfnVpn::Share, 'share', 'share [name]', 'Provide a user with a s3 signed download for certificates and config'
44
- tasks["share"].options = CfnVpn::Share.class_options
44
+ register CfnVpn::Actions::Embedded, 'embedded', 'embedded [name]', 'Embed client certs into config and generate S3 presigned URL'
45
+ tasks["embedded"].options = CfnVpn::Actions::Embedded.class_options
45
46
 
46
- register CfnVpn::Embedded, 'embedded', 'embedded [name]', 'Embed client certs into config and generate S3 presigned URL'
47
- tasks["embedded"].options = CfnVpn::Embedded.class_options
47
+ register CfnVpn::Actions::Subnets, 'subnets', 'subnets [name]', 'Manage subnet associations for the client vpn'
48
+ tasks["subnets"].options = CfnVpn::Actions::Subnets.class_options
49
+
50
+ register CfnVpn::Actions::Params, 'params', 'params [name]', 'Display or dump to yaml the current cfnvpn params'
51
+ tasks["params"].options = CfnVpn::Actions::Params.class_options
48
52
 
49
53
  end
50
54
  end
@@ -4,10 +4,9 @@ require 'cfnvpn/log'
4
4
  require 'cfnvpn/s3'
5
5
  require 'cfnvpn/globals'
6
6
 
7
- module CfnVpn
7
+ module CfnVpn::Actions
8
8
  class Client < Thor::Group
9
- include Thor::Actions
10
- include CfnVpn::Log
9
+ include Thor::Actions
11
10
 
12
11
  argument :name
13
12
 
@@ -24,7 +23,7 @@ module CfnVpn
24
23
  end
25
24
 
26
25
  def set_loglevel
27
- Log.logger.level = Logger::DEBUG if @options['verbose']
26
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
28
27
  end
29
28
 
30
29
  def set_directory
@@ -36,9 +35,9 @@ module CfnVpn
36
35
  def create_certificate
37
36
  s3 = CfnVpn::S3.new(@options['region'],@options['bucket'],@name)
38
37
  s3.get_object("#{@cert_dir}/ca.tar.gz")
39
- Log.logger.info "Generating new client certificate #{@options['client_cn']} using openvpn easy-rsa"
38
+ CfnVpn::Log.logger.info "Generating new client certificate #{@options['client_cn']} using openvpn easy-rsa"
40
39
  cert = CfnVpn::Certificates.new(@build_dir,@name,@options['easyrsa_local'])
41
- Log.logger.debug cert.generate_client(@options['client_cn'])
40
+ CfnVpn::Log.logger.debug cert.generate_client(@options['client_cn'])
42
41
  s3.store_object("#{@cert_dir}/#{@options['client_cn']}.tar.gz")
43
42
  end
44
43
 
@@ -2,10 +2,10 @@ require 'cfnvpn/log'
2
2
  require 'cfnvpn/s3'
3
3
  require 'cfnvpn/globals'
4
4
 
5
- module CfnVpn
5
+ module CfnVpn::Actions
6
6
  class Embedded < Thor::Group
7
7
  include Thor::Actions
8
- include CfnVpn::Log
8
+
9
9
 
10
10
  argument :name
11
11
 
@@ -23,13 +23,13 @@ module CfnVpn
23
23
  end
24
24
 
25
25
  def set_loglevel
26
- Log.logger.level = Logger::DEBUG if @options['verbose']
26
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
27
27
  end
28
28
 
29
29
  def create_config_directory
30
30
  @build_dir = "#{CfnVpn.cfnvpn_path}/#{@name}"
31
31
  @config_dir = "#{@build_dir}/config"
32
- Log.logger.debug("Creating config directory #{@config_dir}")
32
+ CfnVpn::Log.logger.debug("Creating config directory #{@config_dir}")
33
33
  FileUtils.mkdir_p(@config_dir)
34
34
  end
35
35
 
@@ -40,18 +40,18 @@ module CfnVpn
40
40
  end
41
41
 
42
42
  if download
43
- Log.logger.info "Downloading certificates for #{@options['client_cn']} to #{@config_dir}"
43
+ CfnVpn::Log.logger.info "Downloading certificates for #{@options['client_cn']} to #{@config_dir}"
44
44
  s3 = CfnVpn::S3.new(@options['region'],@options['bucket'],@name)
45
45
  s3.get_object("#{@config_dir}/#{@options['client_cn']}.tar.gz")
46
46
  cert = CfnVpn::Certificates.new(@build_dir,@name,@options['easyrsa_local'])
47
- Log.logger.debug cert.extract_certificate(@options['client_cn'])
47
+ CfnVpn::Log.logger.debug cert.extract_certificate(@options['client_cn'])
48
48
  end
49
49
  end
50
50
 
51
51
  def download_config
52
52
  vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
53
53
  @endpoint_id = vpn.get_endpoint_id()
54
- Log.logger.debug "downloading client config for #{@endpoint_id}"
54
+ CfnVpn::Log.logger.debug "downloading client config for #{@endpoint_id}"
55
55
  @config = vpn.get_config(@endpoint_id)
56
56
  string = (0...8).map { (65 + rand(26)).chr.downcase }.join
57
57
  @config.sub!(@endpoint_id, "#{string}.#{@endpoint_id}")
@@ -59,17 +59,17 @@ module CfnVpn
59
59
 
60
60
  def add_routes
61
61
  if @options['ignore_routes']
62
- Log.logger.debug "Ignoring routes pushed by the client vpn"
62
+ CfnVpn::Log.logger.debug "Ignoring routes pushed by the client vpn"
63
63
  @config.concat("\nroute-nopull\n")
64
64
  vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
65
65
  routes = vpn.get_route_with_mask
66
- Log.logger.debug "Found routes #{routes}"
66
+ CfnVpn::Log.logger.debug "Found routes #{routes}"
67
67
  routes.each do |r|
68
68
  @config.concat("route #{r[:route]} #{r[:mask]}\n")
69
69
  end
70
70
  dns_servers = vpn.get_dns_servers()
71
71
  if dns_servers.any?
72
- Log.logger.debug "Found DNS servers #{dns_servers.join(' ')}"
72
+ CfnVpn::Log.logger.debug "Found DNS servers #{dns_servers.join(' ')}"
73
73
  @config.concat("dhcp-option DNS #{dns_servers.first}\n")
74
74
  end
75
75
  end
@@ -77,11 +77,11 @@ module CfnVpn
77
77
 
78
78
  def embed_certs
79
79
  cert = CfnVpn::Certificates.new(@build_dir,@name,@options['easyrsa_local'])
80
- Log.logger.debug cert.extract_certificate(@options['client_cn'])
81
- Log.logger.debug "Reading extracted certificate and private key"
80
+ CfnVpn::Log.logger.debug cert.extract_certificate(@options['client_cn'])
81
+ CfnVpn::Log.logger.debug "Reading extracted certificate and private key"
82
82
  key = File.read("#{@config_dir}/#{@options['client_cn']}.key")
83
83
  crt = File.read("#{@config_dir}/#{@options['client_cn']}.crt")
84
- Log.logger.debug "Embedding certificate and private key into config"
84
+ CfnVpn::Log.logger.debug "Embedding certificate and private key into config"
85
85
  @config.concat("\n<key>\n#{key}\n</key>\n")
86
86
  @config.concat("\n<cert>\n#{crt}\n</cert>\n")
87
87
  end
@@ -94,11 +94,11 @@ module CfnVpn
94
94
  def get_presigned_url
95
95
  @cn = @options['client_cn']
96
96
  @config_url = @s3.get_url("#{@name}_#{@cn}.config.ovpn")
97
- Log.logger.debug "Config presigned url: #{@config_url}"
97
+ CfnVpn::Log.logger.debug "Config presigned url: #{@config_url}"
98
98
  end
99
99
 
100
100
  def display_url
101
- Log.logger.info "Share the below instructions with the user..."
101
+ CfnVpn::Log.logger.info "Share the below instructions with the user..."
102
102
  say "\nDownload the embedded config from the below presigned URL which will expire in 1 hour."
103
103
  say "\nConfig:\n"
104
104
  say "\tcurl #{@config_url} > #{@name}_#{@cn}.config.ovpn", :cyan
@@ -0,0 +1,144 @@
1
+ require 'thor'
2
+ require 'fileutils'
3
+ require 'cfnvpn/deployer'
4
+ require 'cfnvpn/certificates'
5
+ require 'cfnvpn/compiler'
6
+ require 'cfnvpn/log'
7
+ require 'cfnvpn/clientvpn'
8
+ require 'cfnvpn/globals'
9
+ require 'cfnvpn/s3_bucket'
10
+
11
+ module CfnVpn::Actions
12
+ class Init < Thor::Group
13
+ include Thor::Actions
14
+
15
+
16
+ argument :name
17
+
18
+ class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
19
+ class_option :verbose, desc: 'set log level to debug', type: :boolean
20
+
21
+ class_option :server_cn, required: true, desc: 'server certificate common name'
22
+ class_option :client_cn, desc: 'client certificate common name'
23
+ class_option :easyrsa_local, type: :boolean, default: false, desc: 'run the easyrsa executable from your local rather than from docker'
24
+ class_option :bucket, desc: 's3 bucket, if not set one will be generated for you'
25
+
26
+ class_option :subnet_ids, required: true, type: :array, desc: 'subnet id to associate your vpn with'
27
+ class_option :default_groups, default: [], type: :array, desc: 'groups to allow through the subnet associations when using federated auth'
28
+ class_option :cidr, default: '10.250.0.0/16', desc: 'cidr from which to assign client IP addresses'
29
+ class_option :dns_servers, default: [], type: :array, desc: 'DNS Servers to push to clients.'
30
+
31
+ class_option :split_tunnel, type: :boolean, default: true, desc: 'only push routes to the client on the vpn endpoint'
32
+ class_option :internet_route, type: :string, desc: '[subnet-id] create a default route to the internet through a subnet'
33
+ class_option :protocol, type: :string, default: 'udp', enum: ['udp','tcp'], desc: 'set the protocol for the vpn connections'
34
+
35
+ class_option :start, type: :string, desc: 'cloudwatch event cron schedule in UTC to associate subnets to the client vpn'
36
+ class_option :stop, type: :string, desc: 'cloudwatch event cron schedule in UTC to disassociate subnets to the client vpn'
37
+
38
+ class_option :saml_arn, desc: 'IAM SAML idenditiy providor arn if using SAML federated authentication'
39
+ class_option :directory_id, desc: 'AWS Directory Service directory id if using Active Directory authentication'
40
+
41
+ def self.source_root
42
+ File.dirname(__FILE__)
43
+ end
44
+
45
+ def set_loglevel
46
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
47
+ end
48
+
49
+ def create_build_directory
50
+ @build_dir = "#{CfnVpn.cfnvpn_path}/#{@name}"
51
+ CfnVpn::Log.logger.debug "creating directory #{@build_dir}"
52
+ FileUtils.mkdir_p(@build_dir)
53
+ end
54
+
55
+ def initialize_config
56
+ @config = {
57
+ region: @options['region'],
58
+ subnet_ids: @options['subnet_ids'],
59
+ cidr: @options['cidr'],
60
+ dns_servers: @options['dns_servers'],
61
+ split_tunnel: @options['split_tunnel'],
62
+ internet_route: @options['internet_route'],
63
+ protocol: @options['protocol'],
64
+ start: @options['start'],
65
+ stop: @options['stop'],
66
+ saml_arn: @options['saml_arn'],
67
+ directory_id: @options['directory_id'],
68
+ routes: []
69
+ }
70
+ end
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
+
84
+ def set_type
85
+ if @options['saml_arn']
86
+ @config[:type] = 'federated'
87
+ @config[:default_groups] = @options['default_groups']
88
+ elsif @options['directory_id']
89
+ @config[:type] = 'active-directory'
90
+ @config[:default_groups] = @options['default_groups']
91
+ else
92
+ @config[:type] = 'certificate'
93
+ @config[:default_groups] = []
94
+ end
95
+ CfnVpn::Log.logger.info "initialising #{@config[:type]} client vpn"
96
+ end
97
+
98
+ def stack_exist
99
+ @deployer = CfnVpn::Deployer.new(@options['region'],@name)
100
+ if @deployer.does_cf_stack_exist()
101
+ CfnVpn::Log.logger.error "#{@name}-cfnvpn stack already exists in this account in region #{@options['region']}, use the modify command to alter the stack"
102
+ exit 1
103
+ end
104
+ end
105
+
106
+ # create certificates
107
+ def generate_server_certificates
108
+ CfnVpn::Log.logger.info "Generating certificates using openvpn easy-rsa"
109
+ cert = CfnVpn::Certificates.new(@build_dir,@name,@options['easyrsa_local'])
110
+ @client_cn = @options['client_cn'] ? @options['client_cn'] : "client-vpn.#{@options['server_cn']}"
111
+ cert.generate_ca(@options['server_cn'],@client_cn)
112
+ end
113
+
114
+ def upload_certificates
115
+ cert = CfnVpn::Certificates.new(@build_dir,@name,@options['easyrsa_local'])
116
+ @config[:server_cert_arn] = cert.upload_certificates(@options['region'],'server','server',@options['server_cn'])
117
+ if @config[:type] == 'certificate'
118
+ # we only need the server certificate to ACM if it is a SAML federated client vpn
119
+ @config[:client_cert_arn] = cert.upload_certificates(@options['region'],@client_cn,'client')
120
+ # and only need to upload the certs to s3 if using certificate authenitcation
121
+ s3 = CfnVpn::S3.new(@options['region'],@config[:bucket],@name)
122
+ s3.store_object("#{@build_dir}/certificates/ca.tar.gz")
123
+ end
124
+ end
125
+
126
+ def deploy_vpn
127
+ compiler = CfnVpn::Compiler.new(@name, @config)
128
+ template_body = compiler.compile
129
+ CfnVpn::Log.logger.info "Launching cloudformation stack #{@name}-cfnvpn in #{@options['region']}"
130
+ change_set, change_set_type = @deployer.create_change_set(template_body: template_body)
131
+ @deployer.wait_for_changeset(change_set.id)
132
+ @deployer.execute_change_set(change_set.id)
133
+ @deployer.wait_for_execute(change_set_type)
134
+ CfnVpn::Log.logger.info "Changeset #{change_set_type} complete"
135
+ end
136
+
137
+ def finish
138
+ vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
139
+ @endpoint_id = vpn.get_endpoint_id()
140
+ CfnVpn::Log.logger.info "Client VPN #{@endpoint_id} created. Run `cfn-vpn config #{@name}` to setup the client config"
141
+ end
142
+
143
+ end
144
+ end
@@ -0,0 +1,169 @@
1
+ require 'thor'
2
+ require 'fileutils'
3
+ require 'terminal-table'
4
+ require 'cfnvpn/deployer'
5
+ require 'cfnvpn/certificates'
6
+ require 'cfnvpn/compiler'
7
+ require 'cfnvpn/log'
8
+ require 'cfnvpn/clientvpn'
9
+ require 'cfnvpn/globals'
10
+ require 'cfnvpn/config'
11
+ require 'cfnvpn/s3_bucket'
12
+
13
+ module CfnVpn::Actions
14
+ class Modify < Thor::Group
15
+ include Thor::Actions
16
+
17
+
18
+ argument :name
19
+
20
+ class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
21
+ class_option :verbose, desc: 'set log level to debug', type: :boolean
22
+
23
+ class_option :subnet_ids, type: :array, desc: 'overwrite all subnet associations'
24
+ class_option :add_subnet_ids, type: :array, desc: 'add to existing subnet associations'
25
+ class_option :del_subnet_ids, type: :array, desc: 'delete subnet associations'
26
+
27
+ class_option :default_groups, type: :array, desc: 'groups to allow through the subnet associations when using federated auth'
28
+
29
+ class_option :dns_servers, type: :array, desc: 'DNS Servers to push to clients.'
30
+ class_option :no_dns_servers, type: :boolean, desc: 'Remove the DNS Servers from the client vpn'
31
+
32
+ class_option :cidr, desc: 'cidr from which to assign client IP addresses'
33
+ class_option :split_tunnel, type: :boolean, desc: 'only push routes to the client on the vpn endpoint'
34
+ class_option :internet_route, type: :string, desc: '[subnet-id] create a default route to the internet through a subnet'
35
+ class_option :protocol, type: :string, enum: ['udp','tcp'], desc: 'set the protocol for the vpn connections'
36
+
37
+ class_option :start, type: :string, desc: 'cloudwatch event cron schedule in UTC to associate subnets to the client vpn'
38
+ class_option :stop, type: :string, desc: 'cloudwatch event cron schedule in UTC to disassociate subnets to the client vpn'
39
+
40
+ class_option :param_yaml, type: :string, desc: 'pass in cfnvpn params through YAML file'
41
+
42
+ class_option :bucket, desc: 's3 bucket'
43
+
44
+ def self.source_root
45
+ File.dirname(__FILE__)
46
+ end
47
+
48
+ def set_loglevel
49
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
50
+ end
51
+
52
+ def create_build_directory
53
+ @build_dir = "#{CfnVpn.cfnvpn_path}/#{@name}"
54
+ CfnVpn::Log.logger.debug "creating directory #{@build_dir}"
55
+ FileUtils.mkdir_p(@build_dir)
56
+ end
57
+
58
+ def stack_exist
59
+ @deployer = CfnVpn::Deployer.new(@options['region'],@name)
60
+ if !@deployer.does_cf_stack_exist()
61
+ CfnVpn::Log.logger.error "#{@name}-cfnvpn stack doesn't exists in this account in region #{@options['region']}\n Try running `cfn-vpn init #{@name}` to setup the stack"
62
+ exit 1
63
+ end
64
+ end
65
+
66
+ def initialize_config
67
+ @config = CfnVpn::Config.get_config(@options[:region], @name)
68
+
69
+ CfnVpn::Log.logger.debug "Current config:\n#{@config}"
70
+
71
+ if @options[:param_yaml]
72
+ CfnVpn::Log.logger.debug "Loading config from YAML file #{@options[:param_yaml]}"
73
+ @config = CfnVpn::Config.get_config_from_yaml_file(@options[:param_yaml])
74
+ else
75
+ CfnVpn::Log.logger.debug "Loading config from options"
76
+ @options.each do |key, value|
77
+ next if [:verbose].include? key
78
+ @config[key.to_sym] = value
79
+ end
80
+
81
+ if @options['add_subnet_ids']
82
+ @config[:subnet_ids].concat @options['add_subnet_ids']
83
+ end
84
+
85
+ if @options['del_subnet_ids']
86
+ @config[:subnet_ids].reject!{ |subnet| @options['del_subnet_ids'].include? subnet }
87
+ end
88
+
89
+ if @options['no_dns_servers']
90
+ @config[:dns_servers] = []
91
+ end
92
+ end
93
+
94
+ if (@config[:saml_arn] || @config[:directory_id]) && @options[:default_groups]
95
+ @config[:default_groups] = @options[:default_groups]
96
+ end
97
+
98
+ CfnVpn::Log.logger.debug "Modified config:\n#{@config}"
99
+ end
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
+
118
+ def deploy_vpn
119
+ compiler = CfnVpn::Compiler.new(@name, @config)
120
+ template_body = compiler.compile
121
+ CfnVpn::Log.logger.info "Creating cloudformation changeset for stack #{@name}-cfnvpn in #{@options['region']}"
122
+ change_set, change_set_type = @deployer.create_change_set(template_body: template_body)
123
+ @deployer.wait_for_changeset(change_set.id)
124
+ changeset_response = @deployer.get_change_set(change_set.id)
125
+
126
+ changes = {"Add" => [], "Modify" => [], "Remove" => []}
127
+ change_colours = {"Add" => "green", "Modify" => 'yellow', "Remove" => 'red'}
128
+
129
+ changeset_response.changes.each do |change|
130
+ action = change.resource_change.action
131
+ changes[action].push([
132
+ change.resource_change.logical_resource_id,
133
+ change.resource_change.resource_type,
134
+ change.resource_change.replacement ? change.resource_change.replacement : 'N/A',
135
+ change.resource_change.details.collect {|detail| detail.target.name }.join(' , ')
136
+ ])
137
+ end
138
+
139
+ changes.each do |type, rows|
140
+ next if !rows.any?
141
+ puts "\n"
142
+ table = Terminal::Table.new(
143
+ :title => type,
144
+ :headings => ['Logical Resource Id', 'Resource Type', 'Replacement', 'Changes'],
145
+ :rows => rows)
146
+ puts table.to_s.send(change_colours[type])
147
+ end
148
+
149
+ CfnVpn::Log.logger.info "Cloudformation changeset changes:"
150
+ puts "\n"
151
+ continue = yes? "Continue?", :green
152
+ if !continue
153
+ CfnVpn::Log.logger.info("Cancelled cfn-vpn modifiy #{@name}")
154
+ exit 1
155
+ end
156
+
157
+ @deployer.execute_change_set(change_set.id)
158
+ @deployer.wait_for_execute(change_set_type)
159
+ CfnVpn::Log.logger.info "Changeset #{change_set_type} complete"
160
+ end
161
+
162
+ def finish
163
+ vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
164
+ @endpoint_id = vpn.get_endpoint_id()
165
+ CfnVpn::Log.logger.info "Client VPN #{@endpoint_id} modified."
166
+ end
167
+
168
+ end
169
+ end