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