cfn-vpn 0.4.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) 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/Dockerfile +26 -0
  6. data/Gemfile.lock +30 -38
  7. data/README.md +1 -232
  8. data/cfn-vpn.gemspec +3 -5
  9. data/docs/README.md +44 -0
  10. data/docs/certificate-users.md +89 -0
  11. data/docs/getting-started.md +128 -0
  12. data/docs/modifying.md +67 -0
  13. data/docs/routes.md +84 -0
  14. data/docs/scheduling.md +32 -0
  15. data/docs/sessions.md +27 -0
  16. data/lib/cfnvpn.rb +31 -27
  17. data/lib/cfnvpn/{client.rb → actions/client.rb} +11 -8
  18. data/lib/cfnvpn/{embedded.rb → actions/embedded.rb} +21 -19
  19. data/lib/cfnvpn/actions/init.rb +140 -0
  20. data/lib/cfnvpn/actions/modify.rb +149 -0
  21. data/lib/cfnvpn/actions/params.rb +73 -0
  22. data/lib/cfnvpn/{revoke.rb → actions/revoke.rb} +10 -8
  23. data/lib/cfnvpn/actions/routes.rb +144 -0
  24. data/lib/cfnvpn/{sessions.rb → actions/sessions.rb} +7 -6
  25. data/lib/cfnvpn/{share.rb → actions/share.rb} +10 -10
  26. data/lib/cfnvpn/actions/subnets.rb +78 -0
  27. data/lib/cfnvpn/certificates.rb +70 -20
  28. data/lib/cfnvpn/clientvpn.rb +34 -68
  29. data/lib/cfnvpn/compiler.rb +23 -0
  30. data/lib/cfnvpn/config.rb +34 -77
  31. data/lib/cfnvpn/{cloudformation.rb → deployer.rb} +48 -20
  32. data/lib/cfnvpn/globals.rb +16 -0
  33. data/lib/cfnvpn/log.rb +26 -26
  34. data/lib/cfnvpn/s3.rb +4 -4
  35. data/lib/cfnvpn/string.rb +29 -0
  36. data/lib/cfnvpn/templates/helper.rb +14 -0
  37. data/lib/cfnvpn/templates/vpn.rb +353 -0
  38. data/lib/cfnvpn/version.rb +1 -1
  39. metadata +56 -42
  40. data/lib/cfnvpn/cfhighlander.rb +0 -49
  41. data/lib/cfnvpn/init.rb +0 -107
  42. data/lib/cfnvpn/modify.rb +0 -102
  43. data/lib/cfnvpn/routes.rb +0 -83
  44. data/lib/cfnvpn/templates/cfnvpn.cfhighlander.rb.tt +0 -27
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
@@ -1,11 +1,12 @@
1
1
  require 'thor'
2
+ require 'fileutils'
2
3
  require 'cfnvpn/log'
3
4
  require 'cfnvpn/s3'
5
+ require 'cfnvpn/globals'
4
6
 
5
- module CfnVpn
7
+ module CfnVpn::Actions
6
8
  class Client < Thor::Group
7
- include Thor::Actions
8
- include CfnVpn::Log
9
+ include Thor::Actions
9
10
 
10
11
  argument :name
11
12
 
@@ -15,26 +16,28 @@ module CfnVpn
15
16
 
16
17
  class_option :bucket, desc: 's3 bucket', required: true
17
18
  class_option :client_cn, desc: 'client certificate common name', required: true
19
+ class_option :easyrsa_local, type: :boolean, default: false, desc: 'run the easyrsa executable from your local rather than from docker'
18
20
 
19
21
  def self.source_root
20
22
  File.dirname(__FILE__)
21
23
  end
22
24
 
23
25
  def set_loglevel
24
- Log.logger.level = Logger::DEBUG if @options['verbose']
26
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
25
27
  end
26
28
 
27
29
  def set_directory
28
- @build_dir = "#{ENV['HOME']}/.cfnvpn/#{@name}"
30
+ @build_dir = "#{CfnVpn.cfnvpn_path}/#{@name}"
29
31
  @cert_dir = "#{@build_dir}/certificates"
32
+ FileUtils.mkdir_p(@cert_dir)
30
33
  end
31
34
 
32
35
  def create_certificate
33
36
  s3 = CfnVpn::S3.new(@options['region'],@options['bucket'],@name)
34
37
  s3.get_object("#{@cert_dir}/ca.tar.gz")
35
- Log.logger.info "Generating new client certificate #{@options['client_cn']} using openvpn easy-rsa"
36
- cert = CfnVpn::Certificates.new(@build_dir,@name)
37
- Log.logger.debug cert.generate_client(@options['client_cn'])
38
+ CfnVpn::Log.logger.info "Generating new client certificate #{@options['client_cn']} using openvpn easy-rsa"
39
+ cert = CfnVpn::Certificates.new(@build_dir,@name,@options['easyrsa_local'])
40
+ CfnVpn::Log.logger.debug cert.generate_client(@options['client_cn'])
38
41
  s3.store_object("#{@cert_dir}/#{@options['client_cn']}.tar.gz")
39
42
  end
40
43
 
@@ -1,10 +1,11 @@
1
1
  require 'cfnvpn/log'
2
2
  require 'cfnvpn/s3'
3
+ require 'cfnvpn/globals'
3
4
 
4
- module CfnVpn
5
+ module CfnVpn::Actions
5
6
  class Embedded < Thor::Group
6
7
  include Thor::Actions
7
- include CfnVpn::Log
8
+
8
9
 
9
10
  argument :name
10
11
 
@@ -13,7 +14,8 @@ module CfnVpn
13
14
  class_option :verbose, desc: 'set log level to debug', type: :boolean
14
15
 
15
16
  class_option :bucket, required: true, desc: 'S3 bucket'
16
- class_option :client_cn, required: true, desc: 'Client certificates to download'
17
+ class_option :client_cn, required: true, default: false, desc: 'Client certificates to download'
18
+ class_option :easyrsa_local, type: :boolean, default: false, desc: 'run the easyrsa executable from your local rather than from docker'
17
19
  class_option :ignore_routes, alias: :i, type: :boolean, desc: 'Ignore client VPN pushed routes and set routes in config file'
18
20
 
19
21
  def self.source_root
@@ -21,13 +23,13 @@ module CfnVpn
21
23
  end
22
24
 
23
25
  def set_loglevel
24
- Log.logger.level = Logger::DEBUG if @options['verbose']
26
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
25
27
  end
26
28
 
27
29
  def create_config_directory
28
- @build_dir = "#{ENV['HOME']}/.cfnvpn/#{@name}"
30
+ @build_dir = "#{CfnVpn.cfnvpn_path}/#{@name}"
29
31
  @config_dir = "#{@build_dir}/config"
30
- Log.logger.debug("Creating config directory #{@config_dir}")
32
+ CfnVpn::Log.logger.debug("Creating config directory #{@config_dir}")
31
33
  FileUtils.mkdir_p(@config_dir)
32
34
  end
33
35
 
@@ -38,18 +40,18 @@ module CfnVpn
38
40
  end
39
41
 
40
42
  if download
41
- 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}"
42
44
  s3 = CfnVpn::S3.new(@options['region'],@options['bucket'],@name)
43
45
  s3.get_object("#{@config_dir}/#{@options['client_cn']}.tar.gz")
44
- cert = CfnVpn::Certificates.new(@build_dir,@name)
45
- Log.logger.debug cert.extract_certificate(@options['client_cn'])
46
+ cert = CfnVpn::Certificates.new(@build_dir,@name,@options['easyrsa_local'])
47
+ CfnVpn::Log.logger.debug cert.extract_certificate(@options['client_cn'])
46
48
  end
47
49
  end
48
50
 
49
51
  def download_config
50
52
  vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
51
53
  @endpoint_id = vpn.get_endpoint_id()
52
- Log.logger.debug "downloading client config for #{@endpoint_id}"
54
+ CfnVpn::Log.logger.debug "downloading client config for #{@endpoint_id}"
53
55
  @config = vpn.get_config(@endpoint_id)
54
56
  string = (0...8).map { (65 + rand(26)).chr.downcase }.join
55
57
  @config.sub!(@endpoint_id, "#{string}.#{@endpoint_id}")
@@ -57,29 +59,29 @@ module CfnVpn
57
59
 
58
60
  def add_routes
59
61
  if @options['ignore_routes']
60
- Log.logger.debug "Ignoring routes pushed by the client vpn"
62
+ CfnVpn::Log.logger.debug "Ignoring routes pushed by the client vpn"
61
63
  @config.concat("\nroute-nopull\n")
62
64
  vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
63
65
  routes = vpn.get_route_with_mask
64
- Log.logger.debug "Found routes #{routes}"
66
+ CfnVpn::Log.logger.debug "Found routes #{routes}"
65
67
  routes.each do |r|
66
68
  @config.concat("route #{r[:route]} #{r[:mask]}\n")
67
69
  end
68
70
  dns_servers = vpn.get_dns_servers()
69
71
  if dns_servers.any?
70
- Log.logger.debug "Found DNS servers #{dns_servers.join(' ')}"
72
+ CfnVpn::Log.logger.debug "Found DNS servers #{dns_servers.join(' ')}"
71
73
  @config.concat("dhcp-option DNS #{dns_servers.first}\n")
72
74
  end
73
75
  end
74
76
  end
75
77
 
76
78
  def embed_certs
77
- cert = CfnVpn::Certificates.new(@build_dir,@name)
78
- Log.logger.debug cert.extract_certificate(@options['client_cn'])
79
- Log.logger.debug "Reading extracted certificate and private key"
79
+ cert = CfnVpn::Certificates.new(@build_dir,@name,@options['easyrsa_local'])
80
+ CfnVpn::Log.logger.debug cert.extract_certificate(@options['client_cn'])
81
+ CfnVpn::Log.logger.debug "Reading extracted certificate and private key"
80
82
  key = File.read("#{@config_dir}/#{@options['client_cn']}.key")
81
83
  crt = File.read("#{@config_dir}/#{@options['client_cn']}.crt")
82
- Log.logger.debug "Embedding certificate and private key into config"
84
+ CfnVpn::Log.logger.debug "Embedding certificate and private key into config"
83
85
  @config.concat("\n<key>\n#{key}\n</key>\n")
84
86
  @config.concat("\n<cert>\n#{crt}\n</cert>\n")
85
87
  end
@@ -92,11 +94,11 @@ module CfnVpn
92
94
  def get_presigned_url
93
95
  @cn = @options['client_cn']
94
96
  @config_url = @s3.get_url("#{@name}_#{@cn}.config.ovpn")
95
- Log.logger.debug "Config presigned url: #{@config_url}"
97
+ CfnVpn::Log.logger.debug "Config presigned url: #{@config_url}"
96
98
  end
97
99
 
98
100
  def display_url
99
- Log.logger.info "Share the below instructions with the user..."
101
+ CfnVpn::Log.logger.info "Share the below instructions with the user..."
100
102
  say "\nDownload the embedded config from the below presigned URL which will expire in 1 hour."
101
103
  say "\nConfig:\n"
102
104
  say "\tcurl #{@config_url} > #{@name}_#{@cn}.config.ovpn", :cyan
@@ -0,0 +1,140 @@
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
+
10
+ module CfnVpn::Actions
11
+ class Init < Thor::Group
12
+ include Thor::Actions
13
+
14
+
15
+ argument :name
16
+
17
+ class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
18
+ class_option :verbose, desc: 'set log level to debug', type: :boolean
19
+
20
+ class_option :server_cn, required: true, desc: 'server certificate common name'
21
+ class_option :client_cn, desc: 'client certificate common name'
22
+ 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
+
25
+ class_option :subnet_ids, required: true, type: :array, desc: 'subnet id to associate your vpn with'
26
+ class_option :default_groups, default: [], type: :array, desc: 'groups to allow through the subnet associations when using federated auth'
27
+ class_option :cidr, default: '10.250.0.0/16', desc: 'cidr from which to assign client IP addresses'
28
+ class_option :dns_servers, default: [], type: :array, desc: 'DNS Servers to push to clients.'
29
+
30
+ class_option :split_tunnel, type: :boolean, default: true, desc: 'only push routes to the client on the vpn endpoint'
31
+ class_option :internet_route, type: :string, desc: '[subnet-id] create a default route to the internet through a subnet'
32
+ class_option :protocol, type: :string, default: 'udp', enum: ['udp','tcp'], desc: 'set the protocol for the vpn connections'
33
+
34
+ class_option :start, type: :string, desc: 'cloudwatch event cron schedule in UTC to associate subnets to the client vpn'
35
+ class_option :stop, type: :string, desc: 'cloudwatch event cron schedule in UTC to disassociate subnets to the client vpn'
36
+
37
+ class_option :saml_arn, desc: 'IAM SAML idenditiy providor arn if using SAML federated authentication'
38
+ class_option :directory_id, desc: 'AWS Directory Service directory id if using Active Directory authentication'
39
+
40
+ def self.source_root
41
+ File.dirname(__FILE__)
42
+ end
43
+
44
+ def set_loglevel
45
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
46
+ end
47
+
48
+ def create_build_directory
49
+ @build_dir = "#{CfnVpn.cfnvpn_path}/#{@name}"
50
+ CfnVpn::Log.logger.debug "creating directory #{@build_dir}"
51
+ FileUtils.mkdir_p(@build_dir)
52
+ end
53
+
54
+ def initialize_config
55
+ @config = {
56
+ region: @options['region'],
57
+ subnet_ids: @options['subnet_ids'],
58
+ cidr: @options['cidr'],
59
+ dns_servers: @options['dns_servers'],
60
+ split_tunnel: @options['split_tunnel'],
61
+ internet_route: @options['internet_route'],
62
+ protocol: @options['protocol'],
63
+ start: @options['start'],
64
+ stop: @options['stop'],
65
+ saml_arn: @options['saml_arn'],
66
+ directory_id: @options['directory_id'],
67
+ routes: []
68
+ }
69
+ end
70
+
71
+ def set_type
72
+ if @options['saml_arn']
73
+ @config[:type] = 'federated'
74
+ @config[:default_groups] = @options['default_groups']
75
+ elsif @options['directory_id']
76
+ @config[:type] = 'active-directory'
77
+ @config[:default_groups] = @options['default_groups']
78
+ else
79
+ @config[:type] = 'certificate'
80
+ @config[:default_groups] = []
81
+ end
82
+ CfnVpn::Log.logger.info "initialising #{@config[:type]} client vpn"
83
+ end
84
+
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
+ def stack_exist
95
+ @deployer = CfnVpn::Deployer.new(@options['region'],@name)
96
+ if @deployer.does_cf_stack_exist()
97
+ CfnVpn::Log.logger.error "#{@name}-cfnvpn stack already exists in this account in region #{@options['region']}, use the modify command to alter the stack"
98
+ exit 1
99
+ end
100
+ end
101
+
102
+ # create certificates
103
+ def generate_server_certificates
104
+ CfnVpn::Log.logger.info "Generating certificates using openvpn easy-rsa"
105
+ cert = CfnVpn::Certificates.new(@build_dir,@name,@options['easyrsa_local'])
106
+ @client_cn = @options['client_cn'] ? @options['client_cn'] : "client-vpn.#{@options['server_cn']}"
107
+ cert.generate_ca(@options['server_cn'],@client_cn)
108
+ end
109
+
110
+ def upload_certificates
111
+ cert = CfnVpn::Certificates.new(@build_dir,@name,@options['easyrsa_local'])
112
+ @config[:server_cert_arn] = cert.upload_certificates(@options['region'],'server','server',@options['server_cn'])
113
+ if @config[:type] == 'certificate'
114
+ # we only need the server certificate to ACM if it is a SAML federated client vpn
115
+ @config[:client_cert_arn] = cert.upload_certificates(@options['region'],@client_cn,'client')
116
+ # and only need to upload the certs to s3 if using certificate authenitcation
117
+ s3 = CfnVpn::S3.new(@options['region'],@options['bucket'],@name)
118
+ s3.store_object("#{@build_dir}/certificates/ca.tar.gz")
119
+ end
120
+ end
121
+
122
+ def deploy_vpn
123
+ compiler = CfnVpn::Compiler.new(@name, @config)
124
+ template_body = compiler.compile
125
+ CfnVpn::Log.logger.info "Launching cloudformation stack #{@name}-cfnvpn in #{@options['region']}"
126
+ change_set, change_set_type = @deployer.create_change_set(template_body: template_body)
127
+ @deployer.wait_for_changeset(change_set.id)
128
+ @deployer.execute_change_set(change_set.id)
129
+ @deployer.wait_for_execute(change_set_type)
130
+ CfnVpn::Log.logger.info "Changeset #{change_set_type} complete"
131
+ end
132
+
133
+ def finish
134
+ vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
135
+ @endpoint_id = vpn.get_endpoint_id()
136
+ CfnVpn::Log.logger.info "Client VPN #{@endpoint_id} created. Run `cfn-vpn config #{@name}` to setup the client config"
137
+ end
138
+
139
+ end
140
+ end
@@ -0,0 +1,149 @@
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
+
12
+ module CfnVpn::Actions
13
+ class Modify < Thor::Group
14
+ include Thor::Actions
15
+
16
+
17
+ argument :name
18
+
19
+ class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
20
+ class_option :verbose, desc: 'set log level to debug', type: :boolean
21
+
22
+ class_option :subnet_ids, type: :array, desc: 'overwrite all subnet associations'
23
+ class_option :add_subnet_ids, type: :array, desc: 'add to existing subnet associations'
24
+ class_option :del_subnet_ids, type: :array, desc: 'delete subnet associations'
25
+
26
+ class_option :default_groups, type: :array, desc: 'groups to allow through the subnet associations when using federated auth'
27
+
28
+ class_option :dns_servers, type: :array, desc: 'DNS Servers to push to clients.'
29
+ class_option :no_dns_servers, type: :boolean, desc: 'Remove the DNS Servers from the client vpn'
30
+
31
+ class_option :cidr, desc: 'cidr from which to assign client IP addresses'
32
+ class_option :split_tunnel, type: :boolean, desc: 'only push routes to the client on the vpn endpoint'
33
+ class_option :internet_route, type: :string, desc: '[subnet-id] create a default route to the internet through a subnet'
34
+ class_option :protocol, type: :string, enum: ['udp','tcp'], desc: 'set the protocol for the vpn connections'
35
+
36
+ class_option :start, type: :string, desc: 'cloudwatch event cron schedule in UTC to associate subnets to the client vpn'
37
+ class_option :stop, type: :string, desc: 'cloudwatch event cron schedule in UTC to disassociate subnets to the client vpn'
38
+
39
+ class_option :param_yaml, type: :string, desc: 'pass in cfnvpn params through YAML file'
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 stack_exist
56
+ @deployer = CfnVpn::Deployer.new(@options['region'],@name)
57
+ if !@deployer.does_cf_stack_exist()
58
+ 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"
59
+ exit 1
60
+ end
61
+ end
62
+
63
+ def initialize_config
64
+ @config = CfnVpn::Config.get_config(@options[:region], @name)
65
+
66
+ CfnVpn::Log.logger.debug "Current config:\n#{@config}"
67
+
68
+ if @options[:param_yaml]
69
+ CfnVpn::Log.logger.debug "Loading config from YAML file #{@options[:param_yaml]}"
70
+ @config = CfnVpn::Config.get_config_from_yaml_file(@options[:param_yaml])
71
+ else
72
+ CfnVpn::Log.logger.debug "Loading config from options"
73
+ @options.each do |key, value|
74
+ next if [:verbose].include? key
75
+ @config[key.to_sym] = value
76
+ end
77
+
78
+ if @options['add_subnet_ids']
79
+ @config[:subnet_ids].concat @options['add_subnet_ids']
80
+ end
81
+
82
+ if @options['del_subnet_ids']
83
+ @config[:subnet_ids].reject!{ |subnet| @options['del_subnet_ids'].include? subnet }
84
+ end
85
+
86
+ if @options['no_dns_servers']
87
+ @config[:dns_servers] = []
88
+ end
89
+ end
90
+
91
+ if (@config[:saml_arn] || @config[:directory_id]) && @options[:default_groups]
92
+ @config[:default_groups] = @options[:default_groups]
93
+ end
94
+
95
+ CfnVpn::Log.logger.debug "Modified config:\n#{@config}"
96
+ end
97
+
98
+ def deploy_vpn
99
+ compiler = CfnVpn::Compiler.new(@name, @config)
100
+ template_body = compiler.compile
101
+ CfnVpn::Log.logger.info "Creating cloudformation changeset for stack #{@name}-cfnvpn in #{@options['region']}"
102
+ change_set, change_set_type = @deployer.create_change_set(template_body: template_body)
103
+ @deployer.wait_for_changeset(change_set.id)
104
+ changeset_response = @deployer.get_change_set(change_set.id)
105
+
106
+ changes = {"Add" => [], "Modify" => [], "Remove" => []}
107
+ change_colours = {"Add" => "green", "Modify" => 'yellow', "Remove" => 'red'}
108
+
109
+ changeset_response.changes.each do |change|
110
+ action = change.resource_change.action
111
+ changes[action].push([
112
+ change.resource_change.logical_resource_id,
113
+ change.resource_change.resource_type,
114
+ change.resource_change.replacement ? change.resource_change.replacement : 'N/A',
115
+ change.resource_change.details.collect {|detail| detail.target.name }.join(' , ')
116
+ ])
117
+ end
118
+
119
+ changes.each do |type, rows|
120
+ next if !rows.any?
121
+ puts "\n"
122
+ table = Terminal::Table.new(
123
+ :title => type,
124
+ :headings => ['Logical Resource Id', 'Resource Type', 'Replacement', 'Changes'],
125
+ :rows => rows)
126
+ puts table.to_s.send(change_colours[type])
127
+ end
128
+
129
+ CfnVpn::Log.logger.info "Cloudformation changeset changes:"
130
+ puts "\n"
131
+ continue = yes? "Continue?", :green
132
+ if !continue
133
+ CfnVpn::Log.logger.info("Cancelled cfn-vpn modifiy #{@name}")
134
+ exit 1
135
+ end
136
+
137
+ @deployer.execute_change_set(change_set.id)
138
+ @deployer.wait_for_execute(change_set_type)
139
+ CfnVpn::Log.logger.info "Changeset #{change_set_type} complete"
140
+ end
141
+
142
+ def finish
143
+ vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
144
+ @endpoint_id = vpn.get_endpoint_id()
145
+ CfnVpn::Log.logger.info "Client VPN #{@endpoint_id} modified."
146
+ end
147
+
148
+ end
149
+ end