cfn-vpn 0.5.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 -247
- 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} +47 -19
- 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
|