cfn-vpn 0.5.1 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
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