cfn-vpn 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +8 -7
- data/README.md +129 -32
- data/cfn-vpn.gemspec +2 -1
- data/lib/cfnvpn.rb +19 -3
- data/lib/cfnvpn/certificates.rb +38 -23
- data/lib/cfnvpn/client.rb +42 -0
- data/lib/cfnvpn/clientvpn.rb +119 -1
- data/lib/cfnvpn/config.rb +40 -7
- data/lib/cfnvpn/init.rb +5 -2
- data/lib/cfnvpn/revoke.rb +49 -0
- data/lib/cfnvpn/routes.rb +83 -0
- data/lib/cfnvpn/s3.rb +57 -0
- data/lib/cfnvpn/sessions.rb +64 -0
- data/lib/cfnvpn/share.rb +85 -0
- data/lib/cfnvpn/version.rb +1 -1
- metadata +29 -4
- data/lib/cfnvpn/ssm.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb761d025f597c4de716819ae68760c9852186b37505dc565f26c60fa4679788
|
4
|
+
data.tar.gz: 8347f65e8c83d6a4b579b2f5a3e5209e0c24e7e59971be79afcc75adf4ebc2ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e8bf02b14cde539575af2a00306aee0a582e0907db6b103520d001b02aab3daf5f4a331fc5fe4363829ef764771b210eacd4e72f526d16901d6cfe64a3c7607
|
7
|
+
data.tar.gz: 1cf6528468ccd43734549ef00f35238b76ae190691aceddc2e29aed5984cb98ea06e2a5ae66b46e19cad1048a107f1293a9aa877e9f624a15d8ec5cb07d012ad
|
data/Gemfile.lock
CHANGED
@@ -1,20 +1,21 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cfn-vpn (0.
|
4
|
+
cfn-vpn (0.2.0)
|
5
5
|
aws-sdk-acm (~> 1, < 2)
|
6
6
|
aws-sdk-cloudformation (~> 1, < 2)
|
7
7
|
aws-sdk-ec2 (~> 1.95, < 2)
|
8
|
-
aws-sdk-
|
8
|
+
aws-sdk-s3 (~> 1, < 2)
|
9
9
|
cfhighlander (~> 0.9, < 1)
|
10
10
|
cfndsl (~> 0.17, < 1)
|
11
|
+
terminal-table (~> 1, < 2)
|
11
12
|
thor (~> 0.20)
|
12
13
|
|
13
14
|
GEM
|
14
15
|
remote: https://rubygems.org/
|
15
16
|
specs:
|
16
17
|
aws-eventstream (1.0.3)
|
17
|
-
aws-partitions (1.
|
18
|
+
aws-partitions (1.180.0)
|
18
19
|
aws-sdk-acm (1.23.0)
|
19
20
|
aws-sdk-core (~> 3, >= 3.56.0)
|
20
21
|
aws-sigv4 (~> 1.1)
|
@@ -26,7 +27,7 @@ GEM
|
|
26
27
|
aws-partitions (~> 1.0)
|
27
28
|
aws-sigv4 (~> 1.1)
|
28
29
|
jmespath (~> 1.0)
|
29
|
-
aws-sdk-ec2 (1.
|
30
|
+
aws-sdk-ec2 (1.96.0)
|
30
31
|
aws-sdk-core (~> 3, >= 3.56.0)
|
31
32
|
aws-sigv4 (~> 1.1)
|
32
33
|
aws-sdk-kms (1.22.0)
|
@@ -36,9 +37,6 @@ GEM
|
|
36
37
|
aws-sdk-core (~> 3, >= 3.56.0)
|
37
38
|
aws-sdk-kms (~> 1)
|
38
39
|
aws-sigv4 (~> 1.1)
|
39
|
-
aws-sdk-ssm (1.50.0)
|
40
|
-
aws-sdk-core (~> 3, >= 3.56.0)
|
41
|
-
aws-sigv4 (~> 1.1)
|
42
40
|
aws-sigv4 (1.1.0)
|
43
41
|
aws-eventstream (~> 1.0, >= 1.0.2)
|
44
42
|
cfhighlander (0.9.0)
|
@@ -61,7 +59,10 @@ GEM
|
|
61
59
|
netaddr (1.5.1)
|
62
60
|
rake (10.5.0)
|
63
61
|
rubyzip (1.2.3)
|
62
|
+
terminal-table (1.8.0)
|
63
|
+
unicode-display_width (~> 1.1, >= 1.1.1)
|
64
64
|
thor (0.20.3)
|
65
|
+
unicode-display_width (1.6.0)
|
65
66
|
|
66
67
|
PLATFORMS
|
67
68
|
ruby
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# CfnVpn
|
2
2
|
|
3
|
-
Manages the resources required to create a client vpn in AWS.
|
3
|
+
Manages the resources required to create a [client vpn](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/what-is.html) in AWS.
|
4
4
|
Uses cloudformation to manage the state of the vpn resources.
|
5
5
|
|
6
6
|
## Installation
|
@@ -16,55 +16,152 @@ Install [docker](https://docs.docker.com/install/)
|
|
16
16
|
Docker is required to generate the certificates required for the client vpn.
|
17
17
|
The gem uses [openvpn/easy-rsa](https://github.com/OpenVPN/easy-rsa) project in [base2/aws-client-vpn](https://hub.docker.com/r/base2/aws-client-vpn) dokcer image.
|
18
18
|
|
19
|
+
Setup your [AWS credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) by either setting a profile or exporting them as environment variables.
|
20
|
+
|
19
21
|
## Usage
|
20
22
|
|
21
|
-
|
23
|
+
```bash
|
24
|
+
Commands:
|
25
|
+
cfn-vpn --version, -v # print the version
|
26
|
+
cfn-vpn client [name] --bucket=BUCKET --client-cn=CLIENT_CN # Create a new client certificate
|
27
|
+
cfn-vpn config [name] --bucket=BUCKET --client-cn=CLIENT_CN # Retrieve the config for the AWS Client VPN
|
28
|
+
cfn-vpn help [COMMAND] # Describe available commands or one specific command
|
29
|
+
cfn-vpn init [name] --bucket=BUCKET --server-cn=SERVER_CN --subnet-id=SUBNET_ID # Create a AWS Client VPN
|
30
|
+
cfn-vpn modify [name] # Modify your AWS Client VPN
|
31
|
+
cfn-vpn revoke [name] --bucket=BUCKET --client-cn=CLIENT_CN # Revoke a client certificate
|
32
|
+
cfn-vpn routes [name] # List, add or delete client vpn routes
|
33
|
+
cfn-vpn sessions [name] # List and kill current vpn connections
|
34
|
+
cfn-vpn share [name] --bucket=BUCKET --client-cn=CLIENT_CN # Provide a user with a s3 signed download for certificates and config
|
35
|
+
```
|
22
36
|
|
23
|
-
|
37
|
+
Global options
|
24
38
|
|
25
39
|
```bash
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
40
|
+
p, [--profile=PROFILE] # AWS Profile
|
41
|
+
r, [--region=REGION] # AWS Region
|
42
|
+
# Default: ENV['AWS_REGION']
|
43
|
+
[--verbose], [--no-verbose] # set log level to debug
|
30
44
|
```
|
31
45
|
|
32
|
-
### init
|
33
46
|
|
34
|
-
|
47
|
+
### Create a new AWS Client VPN
|
35
48
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
This will create a new client vpn endpoint, associates it with a subnet and sets up a route to the internet.
|
50
|
+
During this process a new CA and certificate and keys are generated using [openvpn/easy-rsa](https://github.com/OpenVPN/easy-rsa) and uploaded to ACM.
|
51
|
+
These keys are bundled in a tar and stored encrypted in your provided s3 bucket.
|
52
|
+
|
53
|
+
`cfn-vpn init myvpn --bucket mybucket --server-cn myvpn.domain.tld --subnet-id subnet-123456ab`
|
54
|
+
|
55
|
+
|
56
|
+
### Create a new client
|
57
|
+
|
58
|
+
This will generate a new client certificate and key against the CA generated in the `init`.
|
59
|
+
It will be bundled into a tar and stored encrypted in your provided s3 bucket.
|
60
|
+
|
61
|
+
`cfn-vpn client myvpn --client-cn user1 --bucket mybucket`
|
62
|
+
|
63
|
+
|
64
|
+
### Revoke a client
|
65
|
+
|
66
|
+
This will revoke the client certificate and apply to the client VPN endpoint.
|
67
|
+
Note this wont terminate the session but will stop the client from reconnecting using the certificate.
|
68
|
+
|
69
|
+
`cfn-vpn revoke myvpn --client-cn user1 --bucket mybucket`
|
70
|
+
|
71
|
+
|
72
|
+
### Download the config file
|
73
|
+
|
74
|
+
This will download the client certificate bundle from s3 and the Client VPN config file from the endpoint.
|
75
|
+
The config will be modified to include the local path of the client cert and key.
|
76
|
+
|
77
|
+
`cfn-vpn config myvpn --client-cn user1 --bucket mybucket`
|
78
|
+
|
79
|
+
*Optional:*
|
80
|
+
|
81
|
+
`--ignore-routes` By deafult AWS Client VPN will push all routes from your local through the VPN connection. Select this flag to only push routes specified in the Client VPN route table.
|
82
|
+
|
83
|
+
|
84
|
+
### Modify the Client VPN config
|
85
|
+
|
86
|
+
This will modify some attributes of the client vpn endpoint.
|
87
|
+
|
88
|
+
`cfn-vpn config myvpn --dns-servers 8.8.8.8,8.8.4.4`
|
89
|
+
|
90
|
+
*Optional:*
|
91
|
+
|
92
|
+
`--dns-servers` Change the DNS servers pushed by the VPN.
|
93
|
+
`--subnet-id` Change the associated subnet.
|
94
|
+
`--cidr` Change the Client CIDR range.
|
95
|
+
|
96
|
+
|
97
|
+
### Share client certificates with a user
|
98
|
+
|
99
|
+
This will generate a presigned url for the client's certificate and config file to allow them to download them to their local computer.
|
100
|
+
|
101
|
+
`cfn-vpn share myvpn --client-cn user1 --bucket mybucket`
|
102
|
+
|
103
|
+
You can then share the output with your user
|
104
|
+
|
105
|
+
```
|
106
|
+
Download the certificates and config from the bellow presigned URLs which will expire in 1 hour.
|
107
|
+
|
108
|
+
Certificate:
|
109
|
+
<presigned url>
|
110
|
+
|
111
|
+
Config:
|
112
|
+
<presigned url>
|
113
|
+
|
114
|
+
Extract the certificates from the tar and place into a safe location.
|
115
|
+
tar xzfv user1.tar.gz -C <path>
|
116
|
+
|
117
|
+
Modify base2-ciinabox.config.ovpn to include the full location of your extracted certificates
|
118
|
+
echo "key /<path>/user1.key" >> myvpn.config.ovpn
|
119
|
+
echo "cert /<path>/user1.crt" >> myvpn.config.ovpn
|
120
|
+
|
121
|
+
Open myvpn.config.ovpn with your favourite openvpn client.
|
50
122
|
```
|
51
123
|
|
52
|
-
### config
|
53
124
|
|
54
|
-
|
125
|
+
### Show and Kill Current Connections
|
126
|
+
|
127
|
+
This is show a table of current connections on the vpn. You can then kill sessions by using the connection id.
|
55
128
|
|
56
129
|
```bash
|
57
|
-
|
58
|
-
|
130
|
+
$ cfn-vpn sessions myvpn
|
131
|
+
+-------------+---------------------+--------+-------------+-----------------------------------+---------------+--------------+
|
132
|
+
| Common Name | Connected (UTC) | Status | IP Address | Connection ID | Ingress Bytes | Egress Bytes |
|
133
|
+
+-------------+---------------------+--------+-------------+-----------------------------------+---------------+--------------+
|
134
|
+
| user1 | 2019-06-28 04:58:19 | active | 10.250.0.98 | cvpn-connection-05bcc579cb3fdf9a3 | 3000 | 2679 |
|
135
|
+
+-------------+---------------------+--------+-------------+-----------------------------------+---------------+--------------+
|
136
|
+
```
|
137
|
+
|
138
|
+
Specify the `--kill` flag with the connection id to kill the session.
|
139
|
+
|
140
|
+
`cfn-vpn sessions myvpn --kill cvpn-connection-05bcc579cb3fdf9a3`
|
141
|
+
|
142
|
+
|
143
|
+
### Show, Add and Remove Routes
|
59
144
|
|
60
|
-
|
61
|
-
[--profile=PROFILE] # AWS Profile
|
62
|
-
[--region=REGION] # AWS Region
|
63
|
-
# Default: ap-southeast-2
|
145
|
+
This will display the route table from the Client VPN.
|
64
146
|
|
65
|
-
|
147
|
+
```bash
|
148
|
+
+---------------+-----------------------+--------+-----------------+------+-----------+
|
149
|
+
| Route | Description | Status | Target | Type | Origin |
|
150
|
+
+---------------+-----------------------+--------+-----------------+------+-----------+
|
151
|
+
| 10.0.0.0/16 | Default Route | active | subnet-123456ab | Nat | associate |
|
152
|
+
| 0.0.0.0/0 | Route to the internet | active | subnet-123456ab | Nat | add-route |
|
153
|
+
+---------------+-----------------------+--------+-----------------+------+-----------+
|
66
154
|
```
|
67
155
|
|
156
|
+
to add a new route specify the `--add` flag with the cidr and a description with the `--desc` flag.
|
157
|
+
|
158
|
+
`cfn-vpn routes myvpn --add 10.10.0.0/16 --desc "route to peered vpc"`
|
159
|
+
|
160
|
+
to delete a route specify the `--del` flag with the cidr you want to delete.
|
161
|
+
|
162
|
+
`cfn-vpn routes myvpn --del 10.10.0.0/16`
|
163
|
+
|
164
|
+
|
68
165
|
## Contributing
|
69
166
|
|
70
167
|
Bug reports and pull requests are welcome on GitHub at https://github.com/base2services/aws-client-vpn.
|
data/cfn-vpn.gemspec
CHANGED
@@ -36,11 +36,12 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.require_paths = ["lib"]
|
37
37
|
|
38
38
|
spec.add_dependency "thor", "~> 0.20"
|
39
|
+
spec.add_dependency "terminal-table", '~> 1', '<2'
|
39
40
|
spec.add_dependency 'cfhighlander', '~> 0.9', '<1'
|
40
41
|
spec.add_dependency 'cfndsl', '~> 0.17', '<1'
|
41
42
|
spec.add_runtime_dependency 'aws-sdk-ec2', '~> 1.95', '<2'
|
42
43
|
spec.add_runtime_dependency 'aws-sdk-acm', '~> 1', '<2'
|
43
|
-
spec.add_runtime_dependency 'aws-sdk-
|
44
|
+
spec.add_runtime_dependency 'aws-sdk-s3', '~> 1', '<2'
|
44
45
|
spec.add_runtime_dependency 'aws-sdk-cloudformation', '~> 1', '<2'
|
45
46
|
|
46
47
|
spec.add_development_dependency "bundler", "~> 2.0"
|
data/lib/cfnvpn.rb
CHANGED
@@ -3,6 +3,11 @@ require 'cfnvpn/version'
|
|
3
3
|
require 'cfnvpn/init'
|
4
4
|
require 'cfnvpn/modify'
|
5
5
|
require 'cfnvpn/config'
|
6
|
+
require 'cfnvpn/client'
|
7
|
+
require 'cfnvpn/revoke'
|
8
|
+
require 'cfnvpn/sessions'
|
9
|
+
require 'cfnvpn/routes'
|
10
|
+
require 'cfnvpn/share'
|
6
11
|
|
7
12
|
module CfnVpn
|
8
13
|
class Cli < Thor
|
@@ -13,7 +18,6 @@ module CfnVpn
|
|
13
18
|
puts CfnVpn::VERSION
|
14
19
|
end
|
15
20
|
|
16
|
-
# Initializes ciinabox configuration
|
17
21
|
register CfnVpn::Init, 'init', 'init [name]', 'Create a AWS Client VPN'
|
18
22
|
tasks["init"].options = CfnVpn::Init.class_options
|
19
23
|
|
@@ -23,8 +27,20 @@ module CfnVpn
|
|
23
27
|
register CfnVpn::Config, 'config', 'config [name]', 'Retrieve the config for the AWS Client VPN'
|
24
28
|
tasks["config"].options = CfnVpn::Config.class_options
|
25
29
|
|
26
|
-
|
30
|
+
register CfnVpn::Client, 'client', 'client [name]', 'Create a new client certificate'
|
31
|
+
tasks["client"].options = CfnVpn::Client.class_options
|
32
|
+
|
33
|
+
register CfnVpn::Revoke, 'revoke', 'revoke [name]', 'Revoke a client certificate'
|
34
|
+
tasks["revoke"].options = CfnVpn::Revoke.class_options
|
35
|
+
|
36
|
+
register CfnVpn::Sessions, 'sessions', 'sessions [name]', 'List and kill current vpn connections'
|
37
|
+
tasks["sessions"].options = CfnVpn::Sessions.class_options
|
27
38
|
|
28
|
-
|
39
|
+
register CfnVpn::Routes, 'routes', 'routes [name]', 'List, add or delete client vpn routes'
|
40
|
+
tasks["routes"].options = CfnVpn::Routes.class_options
|
29
41
|
|
42
|
+
register CfnVpn::Share, 'share', 'share [name]', 'Provide a user with a s3 signed download for certificates and config'
|
43
|
+
tasks["share"].options = CfnVpn::Share.class_options
|
44
|
+
|
45
|
+
end
|
30
46
|
end
|
data/lib/cfnvpn/certificates.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'cfnvpn/acm'
|
3
|
-
require 'cfnvpn/
|
3
|
+
require 'cfnvpn/s3'
|
4
4
|
require 'cfnvpn/log'
|
5
5
|
|
6
6
|
module CfnVpn
|
@@ -11,16 +11,34 @@ module CfnVpn
|
|
11
11
|
@cfnvpn_name = cfnvpn_name
|
12
12
|
@config_dir = "#{build_dir}/config"
|
13
13
|
@cert_dir = "#{build_dir}/certificates"
|
14
|
+
@docker_cmd = %w(docker run -it --rm)
|
15
|
+
@easyrsa_image = "base2/aws-client-vpn"
|
14
16
|
FileUtils.mkdir_p(@cert_dir)
|
15
17
|
end
|
16
18
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
return `#{
|
19
|
+
def generate_ca(server_cn,client_cn)
|
20
|
+
@docker_cmd << "-e EASYRSA_REQ_CN=#{server_cn}"
|
21
|
+
@docker_cmd << "-e EASYRSA_CLIENT_CN=#{client_cn}"
|
22
|
+
@docker_cmd << "-v #{@cert_dir}:/easy-rsa/output"
|
23
|
+
@docker_cmd << @easyrsa_image
|
24
|
+
@docker_cmd << "sh -c 'create-ca'"
|
25
|
+
return `#{@docker_cmd.join(' ')}`
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate_client(client_cn)
|
29
|
+
@docker_cmd << "-e EASYRSA_CLIENT_CN=#{client_cn}"
|
30
|
+
@docker_cmd << "-v #{@cert_dir}:/easy-rsa/output"
|
31
|
+
@docker_cmd << @easyrsa_image
|
32
|
+
@docker_cmd << "sh -c 'create-client'"
|
33
|
+
return `#{@docker_cmd.join(' ')}`
|
34
|
+
end
|
35
|
+
|
36
|
+
def revoke_client(client_cn)
|
37
|
+
@docker_cmd << "-e EASYRSA_CLIENT_CN=#{client_cn}"
|
38
|
+
@docker_cmd << "-v #{@cert_dir}:/easy-rsa/output"
|
39
|
+
@docker_cmd << @easyrsa_image
|
40
|
+
@docker_cmd << "sh -c 'revoke-client'"
|
41
|
+
return `#{@docker_cmd.join(' ')}`
|
24
42
|
end
|
25
43
|
|
26
44
|
def upload_certificates(region,cert,type,cn=nil)
|
@@ -32,23 +50,20 @@ module CfnVpn
|
|
32
50
|
return arn
|
33
51
|
end
|
34
52
|
|
35
|
-
def store_certificate(
|
36
|
-
|
37
|
-
|
53
|
+
def store_certificate(bucket,bundle)
|
54
|
+
s3 = CfnVpn::S3.new(@region,bucket,@name)
|
55
|
+
s3.store_object("#{@cert_dir}/#{bundle}")
|
56
|
+
end
|
57
|
+
|
58
|
+
def retrieve_certificate(bucket,bundle)
|
59
|
+
s3 = CfnVpn::S3.new(@region,bucket,@name)
|
60
|
+
s3.get_object("#{@cert_dir}/#{bundle}")
|
38
61
|
end
|
39
62
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
Log.logger.warn "overriding existing #{name}"
|
45
|
-
File.write(file, cert_body)
|
46
|
-
else
|
47
|
-
Log.logger.info "#{name} already exists"
|
48
|
-
end
|
49
|
-
else
|
50
|
-
File.write(file, cert_body)
|
51
|
-
end
|
63
|
+
def extract_certificate(client_cn)
|
64
|
+
tar = "#{@config_dir}/#{client_cn}.tar.gz"
|
65
|
+
`tar xzfv #{tar} -C #{@config_dir} --strip 2`
|
66
|
+
File.delete(tar) if File.exist?(tar)
|
52
67
|
end
|
53
68
|
|
54
69
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'cfnvpn/log'
|
3
|
+
require 'cfnvpn/s3'
|
4
|
+
|
5
|
+
module CfnVpn
|
6
|
+
class Client < Thor::Group
|
7
|
+
include Thor::Actions
|
8
|
+
include CfnVpn::Log
|
9
|
+
|
10
|
+
argument :name
|
11
|
+
|
12
|
+
class_option :profile, aliases: :p, desc: 'AWS Profile'
|
13
|
+
class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
|
14
|
+
class_option :verbose, desc: 'set log level to debug', type: :boolean
|
15
|
+
|
16
|
+
class_option :bucket, desc: 's3 bucket', required: true
|
17
|
+
class_option :client_cn, desc: 'client certificate common name', required: true
|
18
|
+
|
19
|
+
def self.source_root
|
20
|
+
File.dirname(__FILE__)
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_loglevel
|
24
|
+
Log.logger.level = Logger::DEBUG if @options['verbose']
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_directory
|
28
|
+
@build_dir = "#{ENV['HOME']}/.cfnvpn/#{@name}"
|
29
|
+
@cert_dir = "#{@build_dir}/certificates"
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_certificate
|
33
|
+
s3 = CfnVpn::S3.new(@options['region'],@options['bucket'],@name)
|
34
|
+
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
|
+
s3.store_object("#{@cert_dir}/#{@options['client_cn']}.tar.gz")
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
data/lib/cfnvpn/clientvpn.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'aws-sdk-ec2'
|
2
2
|
require 'cfnvpn/log'
|
3
|
+
require 'netaddr'
|
3
4
|
|
4
5
|
module CfnVpn
|
5
6
|
class ClientVpn
|
@@ -18,13 +19,17 @@ module CfnVpn
|
|
18
19
|
Log.logger.error "unable to find endpoint with tag Key: cfnvpn:name with Value: #{@name}"
|
19
20
|
raise "Unable to find client vpn"
|
20
21
|
end
|
21
|
-
resp.client_vpn_endpoints.first
|
22
|
+
return resp.client_vpn_endpoints.first
|
22
23
|
end
|
23
24
|
|
24
25
|
def get_endpoint_id()
|
25
26
|
return get_endpoint().client_vpn_endpoint_id
|
26
27
|
end
|
27
28
|
|
29
|
+
def get_dns_servers()
|
30
|
+
return get_endpoint().dns_servers
|
31
|
+
end
|
32
|
+
|
28
33
|
def get_config(endpoint_id)
|
29
34
|
resp = @client.export_client_vpn_client_configuration({
|
30
35
|
client_vpn_endpoint_id: endpoint_id
|
@@ -32,5 +37,118 @@ module CfnVpn
|
|
32
37
|
return resp.client_configuration
|
33
38
|
end
|
34
39
|
|
40
|
+
def get_rekove_list(endpoint_id)
|
41
|
+
resp = @client.export_client_vpn_client_certificate_revocation_list({
|
42
|
+
client_vpn_endpoint_id: endpoint_id
|
43
|
+
})
|
44
|
+
return resp.certificate_revocation_list
|
45
|
+
end
|
46
|
+
|
47
|
+
def put_revoke_list(endpoint_id,revoke_list)
|
48
|
+
list = File.read(revoke_list)
|
49
|
+
@client.import_client_vpn_client_certificate_revocation_list({
|
50
|
+
client_vpn_endpoint_id: endpoint_id,
|
51
|
+
certificate_revocation_list: list
|
52
|
+
})
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_sessions(endpoint_id)
|
56
|
+
params = {
|
57
|
+
client_vpn_endpoint_id: endpoint_id,
|
58
|
+
max_results: 20
|
59
|
+
}
|
60
|
+
resp = @client.describe_client_vpn_connections(params)
|
61
|
+
return resp.connections
|
62
|
+
end
|
63
|
+
|
64
|
+
def kill_session(endpoint_id, connection_id)
|
65
|
+
@client.terminate_client_vpn_connections({
|
66
|
+
client_vpn_endpoint_id: endpoint_id,
|
67
|
+
connection_id: connection_id
|
68
|
+
})
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_target_networks(endpoint_id)
|
72
|
+
resp = @client.describe_client_vpn_target_networks({
|
73
|
+
client_vpn_endpoint_id: endpoint_id
|
74
|
+
})
|
75
|
+
return resp.client_vpn_target_networks.first
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_route(cidr,description)
|
79
|
+
endpoint_id = get_endpoint_id()
|
80
|
+
subnet_id = get_target_networks(endpoint_id).target_network_id
|
81
|
+
|
82
|
+
@client.create_client_vpn_route({
|
83
|
+
client_vpn_endpoint_id: endpoint_id,
|
84
|
+
destination_cidr_block: cidr,
|
85
|
+
target_vpc_subnet_id: subnet_id,
|
86
|
+
description: description
|
87
|
+
})
|
88
|
+
|
89
|
+
resp = @client.authorize_client_vpn_ingress({
|
90
|
+
client_vpn_endpoint_id: endpoint_id,
|
91
|
+
target_network_cidr: cidr,
|
92
|
+
authorize_all_groups: true,
|
93
|
+
description: description
|
94
|
+
})
|
95
|
+
|
96
|
+
return resp.status
|
97
|
+
end
|
98
|
+
|
99
|
+
def del_route(cidr)
|
100
|
+
endpoint_id = get_endpoint_id()
|
101
|
+
subnet_id = get_target_networks(endpoint_id).target_network_id
|
102
|
+
|
103
|
+
revoke = @client.revoke_client_vpn_ingress({
|
104
|
+
revoke_all_groups: true,
|
105
|
+
client_vpn_endpoint_id: endpoint_id,
|
106
|
+
target_network_cidr: cidr
|
107
|
+
})
|
108
|
+
|
109
|
+
route = @client.delete_client_vpn_route({
|
110
|
+
client_vpn_endpoint_id: endpoint_id,
|
111
|
+
target_vpc_subnet_id: subnet_id,
|
112
|
+
destination_cidr_block: cidr
|
113
|
+
})
|
114
|
+
|
115
|
+
return route.status, revoke.status
|
116
|
+
end
|
117
|
+
|
118
|
+
def get_routes()
|
119
|
+
endpoint_id = get_endpoint_id()
|
120
|
+
resp = @client.describe_client_vpn_routes({
|
121
|
+
client_vpn_endpoint_id: endpoint_id,
|
122
|
+
max_results: 20
|
123
|
+
})
|
124
|
+
return resp.routes
|
125
|
+
end
|
126
|
+
|
127
|
+
def route_exists?(cidr)
|
128
|
+
routes = get_routes()
|
129
|
+
resp = routes.select { |route| route if route.destination_cidr == cidr }
|
130
|
+
return resp.any?
|
131
|
+
end
|
132
|
+
|
133
|
+
def get_routes()
|
134
|
+
endpoint_id = get_endpoint_id()
|
135
|
+
resp = @client.describe_client_vpn_routes({
|
136
|
+
client_vpn_endpoint_id: endpoint_id,
|
137
|
+
max_results: 20
|
138
|
+
})
|
139
|
+
return resp.routes
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_route_with_mask()
|
143
|
+
routes = get_routes()
|
144
|
+
routes
|
145
|
+
.select { |r| r if r.destination_cidr != '0.0.0.0/0' }
|
146
|
+
.collect { |r| { route: r.destination_cidr.split('/').first, mask: NetAddr::CIDR.create(r.destination_cidr).wildcard_mask }}
|
147
|
+
end
|
148
|
+
|
149
|
+
def valid_cidr?(cidr)
|
150
|
+
return !(cidr =~ /^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$/).nil?
|
151
|
+
end
|
152
|
+
|
35
153
|
end
|
36
154
|
end
|
data/lib/cfnvpn/config.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'cfnvpn/clientvpn'
|
2
|
-
require 'cfnvpn/ssm'
|
3
2
|
require 'cfnvpn/log'
|
4
3
|
|
5
4
|
module CfnVpn
|
@@ -12,9 +11,10 @@ module CfnVpn
|
|
12
11
|
class_option :profile, desc: 'AWS Profile'
|
13
12
|
class_option :region, default: ENV['AWS_REGION'], desc: 'AWS Region'
|
14
13
|
class_option :verbose, desc: 'set log level to debug', type: :boolean
|
14
|
+
class_option :bucket, required: true, desc: 's3 bucket'
|
15
|
+
class_option :client_cn, required: true, desc: "client certificates to download"
|
15
16
|
|
16
|
-
class_option :
|
17
|
-
class_option :crt_path, required: true, desc: 'full file path to the client vpn certificate'
|
17
|
+
class_option :ignore_routes, alias: :i, type: :boolean, desc: "Ignore client VPN pushed routes and set routes in config file"
|
18
18
|
|
19
19
|
def self.source_root
|
20
20
|
File.dirname(__FILE__)
|
@@ -25,8 +25,8 @@ module CfnVpn
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def create_config_directory
|
28
|
-
@
|
29
|
-
@config_dir = "#{@
|
28
|
+
@build_dir = "#{ENV['HOME']}/.cfnvpn/#{@name}"
|
29
|
+
@config_dir = "#{@build_dir}/config"
|
30
30
|
Log.logger.debug("Creating config directory #{@config_dir}")
|
31
31
|
FileUtils.mkdir_p(@config_dir)
|
32
32
|
end
|
@@ -38,11 +38,44 @@ module CfnVpn
|
|
38
38
|
@config = vpn.get_config(@endpoint_id)
|
39
39
|
end
|
40
40
|
|
41
|
+
def download_certificates
|
42
|
+
download = true
|
43
|
+
if File.exists?("#{@config_dir}/#{@options['client_cn']}.crt")
|
44
|
+
download = yes? "Certificates for #{@options['client_cn']} already exist in #{@config_dir}. Do you want to download again? ", :green
|
45
|
+
end
|
46
|
+
|
47
|
+
if download
|
48
|
+
Log.logger.info "Downloading certificates for #{@options['client_cn']} to #{@config_dir}"
|
49
|
+
s3 = CfnVpn::S3.new(@options['region'],@options['bucket'],@name)
|
50
|
+
s3.get_object("#{@config_dir}/#{@options['client_cn']}.tar.gz")
|
51
|
+
cert = CfnVpn::Certificates.new(@build_dir,@name)
|
52
|
+
Log.logger.debug cert.extract_certificate(@options['client_cn'])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
41
56
|
def alter_config
|
42
57
|
string = (0...8).map { (65 + rand(26)).chr.downcase }.join
|
43
58
|
@config.sub!(@endpoint_id, "#{string}.#{@endpoint_id}")
|
44
|
-
@config.concat("\n\ncert #{@options['
|
45
|
-
@config.concat("\nkey #{@options['
|
59
|
+
@config.concat("\n\ncert #{@config_dir}/#{@options['client_cn']}.crt")
|
60
|
+
@config.concat("\nkey #{@config_dir}/#{@options['client_cn']}.key\n")
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_routes
|
64
|
+
if @options['ignore_routes']
|
65
|
+
Log.logger.debug "Ignoring routes pushed by the client vpn"
|
66
|
+
@config.concat("\nroute-nopull\n")
|
67
|
+
vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
|
68
|
+
routes = vpn.get_route_with_mask
|
69
|
+
Log.logger.debug "Found routes #{routes}"
|
70
|
+
routes.each do |r|
|
71
|
+
@config.concat("route #{r[:route]} #{r[:mask]}\n")
|
72
|
+
end
|
73
|
+
dns_servers = vpn.get_dns_servers()
|
74
|
+
if dns_servers.any?
|
75
|
+
Log.logger.debug "Found DNS servers #{dns_servers.join(' ')}"
|
76
|
+
@config.concat("dhcp-option DNS #{dns_servers.first}\n")
|
77
|
+
end
|
78
|
+
end
|
46
79
|
end
|
47
80
|
|
48
81
|
def write_config
|
data/lib/cfnvpn/init.rb
CHANGED
@@ -20,6 +20,7 @@ module CfnVpn
|
|
20
20
|
|
21
21
|
class_option :server_cn, required: true, desc: 'server certificate common name'
|
22
22
|
class_option :client_cn, desc: 'client certificate common name'
|
23
|
+
class_option :bucket, required: true, desc: 's3 bucket'
|
23
24
|
|
24
25
|
class_option :subnet_id, required: true, desc: 'subnet id to associate your vpn with'
|
25
26
|
class_option :cidr, default: '10.250.0.0/16', desc: 'cidr from which to assign client IP addresses'
|
@@ -61,14 +62,16 @@ module CfnVpn
|
|
61
62
|
def generate_server_certificates
|
62
63
|
Log.logger.info "Generating certificates using openvpn easy-rsa"
|
63
64
|
cert = CfnVpn::Certificates.new(@build_dir,@name)
|
64
|
-
@client_cn = @options['client_cn'] ? @options['client_cn'] : "
|
65
|
-
Log.logger.debug cert.
|
65
|
+
@client_cn = @options['client_cn'] ? @options['client_cn'] : "client-vpn.#{@options['server_cn']}"
|
66
|
+
Log.logger.debug cert.generate_ca(@options['server_cn'],@client_cn)
|
66
67
|
end
|
67
68
|
|
68
69
|
def upload_certificates
|
69
70
|
cert = CfnVpn::Certificates.new(@build_dir,@name)
|
70
71
|
@config['parameters']['ServerCertificateArn'] = cert.upload_certificates(@options['region'],'server','server',@options['server_cn'])
|
71
72
|
@config['parameters']['ClientCertificateArn'] = cert.upload_certificates(@options['region'],@client_cn,'client')
|
73
|
+
s3 = CfnVpn::S3.new(@options['region'],@options['bucket'],@name)
|
74
|
+
s3.store_object("#{@build_dir}/certificates/ca.tar.gz")
|
72
75
|
end
|
73
76
|
|
74
77
|
def deploy_vpn
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'cfnvpn/log'
|
3
|
+
require 'cfnvpn/s3'
|
4
|
+
|
5
|
+
module CfnVpn
|
6
|
+
class Revoke < Thor::Group
|
7
|
+
include Thor::Actions
|
8
|
+
include CfnVpn::Log
|
9
|
+
|
10
|
+
argument :name
|
11
|
+
|
12
|
+
class_option :profile, aliases: :p, desc: 'AWS Profile'
|
13
|
+
class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
|
14
|
+
class_option :verbose, desc: 'set log level to debug', type: :boolean
|
15
|
+
|
16
|
+
class_option :bucket, desc: 's3 bucket', required: true
|
17
|
+
class_option :client_cn, desc: 'client certificate common name', required: true
|
18
|
+
|
19
|
+
def self.source_root
|
20
|
+
File.dirname(__FILE__)
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_loglevel
|
24
|
+
Log.logger.level = Logger::DEBUG if @options['verbose']
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_directory
|
28
|
+
@build_dir = "#{ENV['HOME']}/.cfnvpn/#{@name}"
|
29
|
+
@cert_dir = "#{@build_dir}/certificates"
|
30
|
+
end
|
31
|
+
|
32
|
+
def revoke_certificate
|
33
|
+
cert = CfnVpn::Certificates.new(@build_dir,@name)
|
34
|
+
s3 = CfnVpn::S3.new(@options['region'],@options['bucket'],@name)
|
35
|
+
s3.get_object("#{@cert_dir}/ca.tar.gz")
|
36
|
+
s3.get_object("#{@cert_dir}/#{@options['client_cn']}.tar.gz")
|
37
|
+
Log.logger.info "Generating new client certificate #{@options['client_cn']} using openvpn easy-rsa"
|
38
|
+
Log.logger.debug cert.revoke_client(@options['client_cn'])
|
39
|
+
end
|
40
|
+
|
41
|
+
def apply_rekocation_list
|
42
|
+
vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
|
43
|
+
endpoint_id = vpn.get_endpoint_id()
|
44
|
+
vpn.put_revoke_list(endpoint_id,"#{@cert_dir}/crl.pem")
|
45
|
+
Log.logger.info("revoked client #{@options['client_cn']} from #{endpoint_id}")
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'cfnvpn/log'
|
3
|
+
require 'cfnvpn/s3'
|
4
|
+
|
5
|
+
module CfnVpn
|
6
|
+
class Routes < Thor::Group
|
7
|
+
include Thor::Actions
|
8
|
+
include CfnVpn::Log
|
9
|
+
|
10
|
+
argument :name
|
11
|
+
|
12
|
+
class_option :profile, aliases: :p, desc: 'AWS Profile'
|
13
|
+
class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
|
14
|
+
class_option :verbose, desc: 'set log level to debug', type: :boolean
|
15
|
+
|
16
|
+
class_option :add, desc: 'add cidr to route through the client vpn'
|
17
|
+
class_option :del, desc: 'delete cidr route from the client vpn'
|
18
|
+
class_option :desc, desc: 'description of the route'
|
19
|
+
|
20
|
+
def self.source_root
|
21
|
+
File.dirname(__FILE__)
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_loglevel
|
25
|
+
Log.logger.level = Logger::DEBUG if @options['verbose']
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_directory
|
29
|
+
@build_dir = "#{ENV['HOME']}/.cfnvpn/#{@name}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_route
|
33
|
+
if !@options['add'].nil?
|
34
|
+
if @options['desc'].nil?
|
35
|
+
Log.logger.error "--desc option must be provided if adding a new route"
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
|
40
|
+
|
41
|
+
if vpn.route_exists?(@options['add'])
|
42
|
+
Log.logger.error "route #{@options['add']} already exists in the client vpn"
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
|
46
|
+
Log.logger.info "Adding new route for #{@options['add']}"
|
47
|
+
vpn.add_route(@options['add'],@options['desc'])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def del_route
|
52
|
+
if !@options['del'].nil?
|
53
|
+
vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
|
54
|
+
|
55
|
+
if !vpn.route_exists?(@options['del'])
|
56
|
+
Log.logger.error "route #{@options['del']} doesn't exist in the client vpn"
|
57
|
+
exit 1
|
58
|
+
end
|
59
|
+
delete = yes? "Delete route #{@options['del']}?", :yellow
|
60
|
+
if delete
|
61
|
+
Log.logger.info "Deleting route for #{@options['del']}"
|
62
|
+
vpn.del_route(@options['del'])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_routes
|
68
|
+
vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
|
69
|
+
@routes = vpn.get_routes()
|
70
|
+
end
|
71
|
+
|
72
|
+
def display_routes
|
73
|
+
rows = @routes.collect do |s|
|
74
|
+
[ s.destination_cidr, s.description, s.status.code, s.target_subnet, s.type, s.origin ]
|
75
|
+
end
|
76
|
+
table = Terminal::Table.new(
|
77
|
+
:headings => ['Route', 'Description', 'Status', 'Target', 'Type', 'Origin'],
|
78
|
+
:rows => rows)
|
79
|
+
puts table
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
data/lib/cfnvpn/s3.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'aws-sdk-s3'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module CfnVpn
|
5
|
+
class S3
|
6
|
+
|
7
|
+
def initialize(region, bucket, name)
|
8
|
+
@client = Aws::S3::Client.new(region: region)
|
9
|
+
@bucket = bucket
|
10
|
+
@name = name
|
11
|
+
@path = "cfnvpn/certificates/#{@name}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def store_object(file)
|
15
|
+
body = File.open(file, 'rb').read
|
16
|
+
file_name = file.split('/').last
|
17
|
+
Log.logger.debug("uploading #{file} to s3://#{@bucket}/#{@path}/#{file_name}")
|
18
|
+
@client.put_object({
|
19
|
+
body: body,
|
20
|
+
bucket: @bucket,
|
21
|
+
key: "#{@path}/#{file_name}",
|
22
|
+
server_side_encryption: "AES256",
|
23
|
+
tagging: "cfnvpn:name=#{@name}"
|
24
|
+
})
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_object(file)
|
28
|
+
file_name = file.split('/').last
|
29
|
+
Log.logger.debug("downloading s3://#{@bucket}/#{@path}/#{file_name} to #{file}")
|
30
|
+
@client.get_object(
|
31
|
+
response_target: file,
|
32
|
+
bucket: @bucket,
|
33
|
+
key: "#{@path}/#{file_name}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def store_config(config)
|
37
|
+
Log.logger.debug("uploading config to s3://#{@bucket}/#{@path}/#{@name}.config.ovpn")
|
38
|
+
@client.put_object({
|
39
|
+
body: config,
|
40
|
+
bucket: @bucket,
|
41
|
+
key: "#{@path}/#{@name}.config.ovpn",
|
42
|
+
tagging: "cfnvpn:name=#{@name}"
|
43
|
+
})
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_url(file)
|
47
|
+
presigner = Aws::S3::Presigner.new(client: @client)
|
48
|
+
params = {
|
49
|
+
bucket: @bucket,
|
50
|
+
key: "#{@path}/#{file}",
|
51
|
+
expires_in: 3600
|
52
|
+
}
|
53
|
+
presigner.presigned_url(:get_object, params)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'terminal-table'
|
3
|
+
require 'cfnvpn/log'
|
4
|
+
require 'cfnvpn/clientvpn'
|
5
|
+
|
6
|
+
module CfnVpn
|
7
|
+
class Sessions < Thor::Group
|
8
|
+
include Thor::Actions
|
9
|
+
include CfnVpn::Log
|
10
|
+
|
11
|
+
argument :name
|
12
|
+
|
13
|
+
class_option :profile, aliases: :p, desc: 'AWS Profile'
|
14
|
+
class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
|
15
|
+
class_option :verbose, desc: 'set log level to debug', type: :boolean
|
16
|
+
|
17
|
+
class_option :kill, desc: 'connection id to kill the connection'
|
18
|
+
|
19
|
+
def self.source_root
|
20
|
+
File.dirname(__FILE__)
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_loglevel
|
24
|
+
Log.logger.level = Logger::DEBUG if @options['verbose']
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_directory
|
28
|
+
@build_dir = "#{ENV['HOME']}/.cfnvpn/#{@name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_endpoint
|
32
|
+
@vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
|
33
|
+
@endpoint_id = @vpn.get_endpoint_id()
|
34
|
+
end
|
35
|
+
|
36
|
+
def kill_session
|
37
|
+
if !@options['kill'].nil?
|
38
|
+
sessions = @vpn.get_sessions(@endpoint_id)
|
39
|
+
session = sessions.select { |s| s if s.connection_id == @options['kill'] }.first
|
40
|
+
if session.any? && session.status.code == "active"
|
41
|
+
terminate = yes? "Terminate connection #{@options['kill']} for #{session.common_name}?", :yellow
|
42
|
+
if terminate
|
43
|
+
Log.logger.info "Terminating connection #{@options['kill']} for #{session.common_name}"
|
44
|
+
@vpn.kill_session(@endpoint_id,@options['kill'])
|
45
|
+
end
|
46
|
+
else
|
47
|
+
Log.logger.error "Connection id #{@options['kill']} doesn't exist or is not active"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def display_sessions
|
53
|
+
sessions = @vpn.get_sessions(@endpoint_id)
|
54
|
+
rows = sessions.collect do |s|
|
55
|
+
[ s.common_name, s.connection_established_time, s.status.code, s.client_ip, s.connection_id, s.ingress_bytes, s.egress_bytes ]
|
56
|
+
end
|
57
|
+
table = Terminal::Table.new(
|
58
|
+
:headings => ['Common Name', 'Connected (UTC)', 'Status', 'IP Address', 'Connection ID', 'Ingress Bytes', 'Egress Bytes'],
|
59
|
+
:rows => rows)
|
60
|
+
puts table
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
data/lib/cfnvpn/share.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'cfnvpn/log'
|
2
|
+
require 'cfnvpn/s3'
|
3
|
+
|
4
|
+
module CfnVpn
|
5
|
+
class Share < Thor::Group
|
6
|
+
include Thor::Actions
|
7
|
+
include CfnVpn::Log
|
8
|
+
|
9
|
+
argument :name
|
10
|
+
|
11
|
+
class_option :profile, desc: 'AWS Profile'
|
12
|
+
class_option :region, default: ENV['AWS_REGION'], desc: 'AWS Region'
|
13
|
+
class_option :verbose, desc: 'set log level to debug', type: :boolean
|
14
|
+
|
15
|
+
class_option :bucket, required: true, desc: 's3 bucket'
|
16
|
+
class_option :client_cn, required: true, desc: "client certificates to download"
|
17
|
+
class_option :ignore_routes, alias: :i, type: :boolean, desc: "Ignore client VPN pushed routes and set routes in config file"
|
18
|
+
|
19
|
+
def self.source_root
|
20
|
+
File.dirname(__FILE__)
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_loglevel
|
24
|
+
Log.logger.level = Logger::DEBUG if @options['verbose']
|
25
|
+
end
|
26
|
+
|
27
|
+
def copy_config_to_s3
|
28
|
+
vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
|
29
|
+
@endpoint_id = vpn.get_endpoint_id()
|
30
|
+
Log.logger.debug "downloading client config for #{@endpoint_id}"
|
31
|
+
@config = vpn.get_config(@endpoint_id)
|
32
|
+
string = (0...8).map { (65 + rand(26)).chr.downcase }.join
|
33
|
+
@config.sub!(@endpoint_id, "#{string}.#{@endpoint_id}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_routes
|
37
|
+
if @options['ignore_routes']
|
38
|
+
Log.logger.debug "Ignoring routes pushed by the client vpn"
|
39
|
+
@config.concat("\nroute-nopull\n")
|
40
|
+
vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
|
41
|
+
routes = vpn.get_route_with_mask
|
42
|
+
Log.logger.debug "Found routes #{routes}"
|
43
|
+
routes.each do |r|
|
44
|
+
@config.concat("route #{r[:route]} #{r[:mask]}\n")
|
45
|
+
end
|
46
|
+
dns_servers = vpn.get_dns_servers()
|
47
|
+
if dns_servers.any?
|
48
|
+
Log.logger.debug "Found DNS servers #{dns_servers.join(' ')}"
|
49
|
+
@config.concat("dhcp-option DNS #{dns_servers.first}\n")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def upload_config
|
55
|
+
@s3 = CfnVpn::S3.new(@options['region'],@options['bucket'],@name)
|
56
|
+
@s3.store_config(@config)
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_certificate_url
|
60
|
+
@certificate_url = @s3.get_url("#{@options['client_cn']}.tar.gz")
|
61
|
+
Log.logger.debug "Certificate presigned url: #{@certificate_url}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_config_url
|
65
|
+
@config_url = @s3.get_url("#{@name}.config.ovpn")
|
66
|
+
Log.logger.debug "Config presigned url: #{@config_url}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def display_instructions
|
70
|
+
Log.logger.info "Share the bellow instruction with the user..."
|
71
|
+
say "\nDownload the certificates and config from the bellow presigned URLs which will expire in 1 hour."
|
72
|
+
say "\nCertificate:"
|
73
|
+
say "\tcurl #{@certificate_url} > #{@options['client_cn']}.tar.gz", :cyan
|
74
|
+
say "\nConfig:\n"
|
75
|
+
say "\tcurl #{@certificate_url} > #{@name}.config.ovpn", :cyan
|
76
|
+
say "\nExtract the certificates from the tar and place into a safe location."
|
77
|
+
say "\ttar xzfv #{@options['client_cn']}.tar.gz -C <path> --strip 2", :cyan
|
78
|
+
say "\nModify #{@name}.config.ovpn to include the full location of your extracted certificates"
|
79
|
+
say "\techo \"key /<path>/#{@options['client_cn']}.key\" >> #{@name}.config.ovpn", :cyan
|
80
|
+
say "\techo \"cert /<path>/#{@options['client_cn']}.crt\" >> #{@name}.config.ovpn", :cyan
|
81
|
+
say "\nOpen #{@name}.config.ovpn with your favourite openvpn client."
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
data/lib/cfnvpn/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cfn-vpn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guslington
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-06-
|
11
|
+
date: 2019-06-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -24,6 +24,26 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.20'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: terminal-table
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '2'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2'
|
27
47
|
- !ruby/object:Gem::Dependency
|
28
48
|
name: cfhighlander
|
29
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -105,7 +125,7 @@ dependencies:
|
|
105
125
|
- !ruby/object:Gem::Version
|
106
126
|
version: '2'
|
107
127
|
- !ruby/object:Gem::Dependency
|
108
|
-
name: aws-sdk-
|
128
|
+
name: aws-sdk-s3
|
109
129
|
requirement: !ruby/object:Gem::Requirement
|
110
130
|
requirements:
|
111
131
|
- - "~>"
|
@@ -193,13 +213,18 @@ files:
|
|
193
213
|
- lib/cfnvpn/acm.rb
|
194
214
|
- lib/cfnvpn/certificates.rb
|
195
215
|
- lib/cfnvpn/cfhighlander.rb
|
216
|
+
- lib/cfnvpn/client.rb
|
196
217
|
- lib/cfnvpn/clientvpn.rb
|
197
218
|
- lib/cfnvpn/cloudformation.rb
|
198
219
|
- lib/cfnvpn/config.rb
|
199
220
|
- lib/cfnvpn/init.rb
|
200
221
|
- lib/cfnvpn/log.rb
|
201
222
|
- lib/cfnvpn/modify.rb
|
202
|
-
- lib/cfnvpn/
|
223
|
+
- lib/cfnvpn/revoke.rb
|
224
|
+
- lib/cfnvpn/routes.rb
|
225
|
+
- lib/cfnvpn/s3.rb
|
226
|
+
- lib/cfnvpn/sessions.rb
|
227
|
+
- lib/cfnvpn/share.rb
|
203
228
|
- lib/cfnvpn/templates/cfnvpn.cfhighlander.rb.tt
|
204
229
|
- lib/cfnvpn/version.rb
|
205
230
|
homepage: https://github.com/base2services/aws-client-vpn
|
data/lib/cfnvpn/ssm.rb
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
require 'aws-sdk-ssm'
|
2
|
-
require 'fileutils'
|
3
|
-
require 'cfnvpn/log'
|
4
|
-
|
5
|
-
module CfnVpn
|
6
|
-
class SSM
|
7
|
-
include CfnVpn::Log
|
8
|
-
|
9
|
-
def initialize(name,region,cert_dir)
|
10
|
-
@name = name
|
11
|
-
@cert_dir = cert_dir
|
12
|
-
@path_prefix = "/cfnvpn/#{@name}"
|
13
|
-
@client = Aws::SSM::Client.new(region: region)
|
14
|
-
end
|
15
|
-
|
16
|
-
def get_parameter(cert)
|
17
|
-
begin
|
18
|
-
resp = @client.get_parameter({
|
19
|
-
name: "#{@path_prefix}/#{cert}",
|
20
|
-
with_decryption: true
|
21
|
-
})
|
22
|
-
rescue Aws::SSM::Errors::ParameterNotFound
|
23
|
-
Log.logger.debug("Parameter #{@path_prefix}/#{cert} not found")
|
24
|
-
return false
|
25
|
-
end
|
26
|
-
Log.logger.debug("found parameter #{@path_prefix}/#{cert}")
|
27
|
-
return resp.parameter.value
|
28
|
-
end
|
29
|
-
|
30
|
-
def put_parameter(cert)
|
31
|
-
certificate = File.read("#{@cert_dir}/#{cert}")
|
32
|
-
Log.logger.debug("Reading certificate #{@cert_dir}/#{cert}")
|
33
|
-
ext = cert.split('.').last
|
34
|
-
@client.put_parameter({
|
35
|
-
name: "#{@path_prefix}/#{@name}.#{ext}",
|
36
|
-
description: "cfn-vpn #{@name} #{cert}",
|
37
|
-
value: certificate,
|
38
|
-
type: "SecureString",
|
39
|
-
overwrite: false,
|
40
|
-
tags: [
|
41
|
-
{ key: "cfnvpn:name", value: @name },
|
42
|
-
{ key: "cfnvpn:certificate", value: cert }
|
43
|
-
],
|
44
|
-
tier: "Advanced",
|
45
|
-
})
|
46
|
-
Log.logger.info("Stored #{cert} in ssm parameter #{@path_prefix}/#{@name}.#{ext}")
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
end
|