cfn-vpn 0.5.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/build-gem.yml +25 -0
- data/.github/workflows/release-gem.yml +34 -0
- data/.github/workflows/release-image.yml +33 -0
- data/Gemfile.lock +33 -39
- data/README.md +1 -232
- data/cfn-vpn.gemspec +4 -4
- data/docs/README.md +44 -0
- data/docs/certificate-users.md +89 -0
- data/docs/getting-started.md +128 -0
- data/docs/modifying.md +67 -0
- data/docs/routes.md +98 -0
- data/docs/scheduling.md +32 -0
- data/docs/sessions.md +27 -0
- data/lib/cfnvpn.rb +31 -27
- data/lib/cfnvpn/{client.rb → actions/client.rb} +5 -6
- data/lib/cfnvpn/{embedded.rb → actions/embedded.rb} +15 -15
- data/lib/cfnvpn/actions/init.rb +144 -0
- data/lib/cfnvpn/actions/modify.rb +169 -0
- data/lib/cfnvpn/actions/params.rb +73 -0
- data/lib/cfnvpn/{revoke.rb → actions/revoke.rb} +6 -6
- data/lib/cfnvpn/actions/routes.rb +196 -0
- data/lib/cfnvpn/{sessions.rb → actions/sessions.rb} +5 -5
- data/lib/cfnvpn/{share.rb → actions/share.rb} +10 -10
- data/lib/cfnvpn/actions/subnets.rb +78 -0
- data/lib/cfnvpn/certificates.rb +5 -5
- data/lib/cfnvpn/clientvpn.rb +49 -65
- data/lib/cfnvpn/compiler.rb +23 -0
- data/lib/cfnvpn/config.rb +34 -78
- data/lib/cfnvpn/{cloudformation.rb → deployer.rb} +48 -20
- data/lib/cfnvpn/log.rb +26 -26
- data/lib/cfnvpn/s3.rb +34 -4
- data/lib/cfnvpn/s3_bucket.rb +48 -0
- data/lib/cfnvpn/string.rb +33 -0
- data/lib/cfnvpn/templates/helper.rb +14 -0
- data/lib/cfnvpn/templates/lambdas.rb +35 -0
- data/lib/cfnvpn/templates/lambdas/auto_route_populator/app.py +175 -0
- data/lib/cfnvpn/templates/lambdas/scheduler/app.py +36 -0
- data/lib/cfnvpn/templates/vpn.rb +449 -0
- data/lib/cfnvpn/version.rb +1 -1
- metadata +73 -23
- data/lib/cfnvpn/cfhighlander.rb +0 -49
- data/lib/cfnvpn/init.rb +0 -109
- data/lib/cfnvpn/modify.rb +0 -103
- data/lib/cfnvpn/routes.rb +0 -84
- data/lib/cfnvpn/templates/cfnvpn.cfhighlander.rb.tt +0 -27
data/docs/scheduling.md
ADDED
@@ -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/
|
6
|
-
require 'cfnvpn/
|
7
|
-
require 'cfnvpn/
|
8
|
-
require 'cfnvpn/
|
9
|
-
require 'cfnvpn/
|
10
|
-
require 'cfnvpn/
|
11
|
-
require 'cfnvpn/
|
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::
|
29
|
-
tasks["
|
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::
|
32
|
-
tasks["
|
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::
|
35
|
-
tasks["
|
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::
|
38
|
-
tasks["
|
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::
|
41
|
-
tasks["
|
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::
|
44
|
-
tasks["
|
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::
|
47
|
-
tasks["
|
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
|
-
|
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
|