cfn-vpn 0.4.2 → 1.2.0

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