cfn-vpn 0.5.1 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build-gem.yml +25 -0
  3. data/.github/workflows/release-gem.yml +34 -0
  4. data/.github/workflows/release-image.yml +33 -0
  5. data/Gemfile.lock +33 -39
  6. data/README.md +1 -247
  7. data/cfn-vpn.gemspec +4 -4
  8. data/docs/README.md +44 -0
  9. data/docs/certificate-users.md +89 -0
  10. data/docs/getting-started.md +128 -0
  11. data/docs/modifying.md +67 -0
  12. data/docs/routes.md +98 -0
  13. data/docs/scheduling.md +32 -0
  14. data/docs/sessions.md +27 -0
  15. data/lib/cfnvpn.rb +31 -27
  16. data/lib/cfnvpn/{client.rb → actions/client.rb} +5 -6
  17. data/lib/cfnvpn/{embedded.rb → actions/embedded.rb} +15 -15
  18. data/lib/cfnvpn/actions/init.rb +144 -0
  19. data/lib/cfnvpn/actions/modify.rb +169 -0
  20. data/lib/cfnvpn/actions/params.rb +73 -0
  21. data/lib/cfnvpn/{revoke.rb → actions/revoke.rb} +6 -6
  22. data/lib/cfnvpn/actions/routes.rb +196 -0
  23. data/lib/cfnvpn/{sessions.rb → actions/sessions.rb} +5 -5
  24. data/lib/cfnvpn/{share.rb → actions/share.rb} +10 -10
  25. data/lib/cfnvpn/actions/subnets.rb +78 -0
  26. data/lib/cfnvpn/certificates.rb +5 -5
  27. data/lib/cfnvpn/clientvpn.rb +49 -65
  28. data/lib/cfnvpn/compiler.rb +23 -0
  29. data/lib/cfnvpn/config.rb +34 -78
  30. data/lib/cfnvpn/{cloudformation.rb → deployer.rb} +47 -19
  31. data/lib/cfnvpn/log.rb +26 -26
  32. data/lib/cfnvpn/s3.rb +34 -4
  33. data/lib/cfnvpn/s3_bucket.rb +48 -0
  34. data/lib/cfnvpn/string.rb +33 -0
  35. data/lib/cfnvpn/templates/helper.rb +14 -0
  36. data/lib/cfnvpn/templates/lambdas.rb +35 -0
  37. data/lib/cfnvpn/templates/lambdas/auto_route_populator/app.py +175 -0
  38. data/lib/cfnvpn/templates/lambdas/scheduler/app.py +36 -0
  39. data/lib/cfnvpn/templates/vpn.rb +449 -0
  40. data/lib/cfnvpn/version.rb +1 -1
  41. metadata +73 -23
  42. data/lib/cfnvpn/cfhighlander.rb +0 -49
  43. data/lib/cfnvpn/init.rb +0 -109
  44. data/lib/cfnvpn/modify.rb +0 -103
  45. data/lib/cfnvpn/routes.rb +0 -84
  46. data/lib/cfnvpn/templates/cfnvpn.cfhighlander.rb.tt +0 -27
@@ -0,0 +1,73 @@
1
+ require 'yaml'
2
+ require 'cfnvpn/config'
3
+ require 'cfnvpn/log'
4
+
5
+ module CfnVpn::Actions
6
+ class Params < Thor::Group
7
+ include Thor::Actions
8
+
9
+
10
+ argument :name
11
+
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 :dump, type: :boolean, desc: 'dump config to yaml file'
16
+ class_option :diff_yaml, desc: 'diff yaml file with deployed config'
17
+
18
+ def self.source_root
19
+ File.dirname(__FILE__)
20
+ end
21
+
22
+ def set_loglevel
23
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
24
+ end
25
+
26
+ def get_config
27
+ @config = CfnVpn::Config.get_config(@options[:region], @name)
28
+ end
29
+
30
+ def dump
31
+ CfnVpn::Config.dump_config_to_yaml_file(name, @config) if @options[:dump]
32
+ end
33
+
34
+ def setup_display
35
+ @headings = ['Param', 'Deployed Value']
36
+ @rows = []
37
+ end
38
+
39
+ def diff
40
+ if @options[:diff_yaml]
41
+ yaml_params = CfnVpn::Config.get_config_from_yaml_file(@options[:diff_yaml])
42
+
43
+ @headings << 'YAML Value'
44
+ @config.each do |key, value|
45
+ row = [key, value]
46
+ if yaml_params.has_key? key
47
+ row << yaml_params[key]
48
+ else
49
+ row << nil
50
+ end
51
+
52
+ if row[1] != row[2]
53
+ row[1] = row[1].to_s.red
54
+ row[2] = row[2].to_s.red
55
+ end
56
+
57
+ @rows << row
58
+ end
59
+ else
60
+ @rows = @config.to_a
61
+ end
62
+ end
63
+
64
+ def display
65
+ table = Terminal::Table.new(
66
+ :title => 'Params',
67
+ :headings => @headings,
68
+ :rows => @rows)
69
+ puts table
70
+ end
71
+
72
+ end
73
+ end
@@ -3,10 +3,10 @@ require 'cfnvpn/log'
3
3
  require 'cfnvpn/s3'
4
4
  require 'cfnvpn/globals'
5
5
 
6
- module CfnVpn
6
+ module CfnVpn::Actions
7
7
  class Revoke < Thor::Group
8
8
  include Thor::Actions
9
- include CfnVpn::Log
9
+
10
10
 
11
11
  argument :name
12
12
 
@@ -23,7 +23,7 @@ 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 set_directory
@@ -36,15 +36,15 @@ module CfnVpn
36
36
  s3 = CfnVpn::S3.new(@options['region'],@options['bucket'],@name)
37
37
  s3.get_object("#{@cert_dir}/ca.tar.gz")
38
38
  s3.get_object("#{@cert_dir}/#{@options['client_cn']}.tar.gz")
39
- Log.logger.info "Generating new client certificate #{@options['client_cn']} using openvpn easy-rsa"
40
- Log.logger.debug cert.revoke_client(@options['client_cn'])
39
+ CfnVpn::Log.logger.info "Generating new client certificate #{@options['client_cn']} using openvpn easy-rsa"
40
+ CfnVpn::Log.logger.debug cert.revoke_client(@options['client_cn'])
41
41
  end
42
42
 
43
43
  def apply_rekocation_list
44
44
  vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
45
45
  endpoint_id = vpn.get_endpoint_id()
46
46
  vpn.put_revoke_list(endpoint_id,"#{@cert_dir}/crl.pem")
47
- Log.logger.info("revoked client #{@options['client_cn']} from #{endpoint_id}")
47
+ CfnVpn::Log.logger.info("revoked client #{@options['client_cn']} from #{endpoint_id}")
48
48
  end
49
49
 
50
50
  end
@@ -0,0 +1,196 @@
1
+ require 'thor'
2
+ require 'cfnvpn/log'
3
+ require 'cfnvpn/s3'
4
+ require 'cfnvpn/globals'
5
+
6
+ module CfnVpn::Actions
7
+ class Routes < Thor::Group
8
+ include Thor::Actions
9
+
10
+ argument :name
11
+
12
+ class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
13
+ class_option :verbose, desc: 'set log level to debug', type: :boolean
14
+
15
+ class_option :cidr, desc: 'cidr range'
16
+ class_option :dns, desc: 'dns record to auto lookup ip'
17
+ class_option :subnet, desc: 'the target vpc subnet to route through, if none is supplied the default subnet is used'
18
+ class_option :desc, desc: 'description of the route'
19
+
20
+ class_option :groups, type: :array, desc: 'override all authorised groups on thr route'
21
+ class_option :add_groups, type: :array, desc: 'add authorised groups to an existing route'
22
+ class_option :del_groups, type: :array, desc: 'remove authorised groups from an existing route'
23
+
24
+ class_option :delete, type: :boolean, desc: 'delete the route from the client vpn'
25
+
26
+ def self.source_root
27
+ File.dirname(__FILE__)
28
+ end
29
+
30
+ def set_loglevel
31
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
32
+ end
33
+
34
+ def set_config
35
+ @config = CfnVpn::Config.get_config(@options[:region], @name)
36
+
37
+ if @options[:cidr] && @options[:dns]
38
+ CfnVpn::Log.logger.error "only one of --dns or --cidr can be set"
39
+ exit 1
40
+ end
41
+
42
+ if @options[:dns]
43
+ if @options[:dns].include?("*")
44
+ CfnVpn::Log.logger.error("wild card DNS resolution is not supported, use a record that will be resolved by the wild card instead")
45
+ exit 1
46
+ end
47
+ @route = @config[:routes].detect {|route| route[:dns] == @options[:dns]}
48
+ elsif @options[:cidr]
49
+ @route = @config[:routes].detect {|route| route[:cidr] == @options[:cidr]}
50
+ end
51
+ end
52
+
53
+ def set_route
54
+ @skip_update = false
55
+ @dns_route_cleanup = nil
56
+ if @route && @options[:delete]
57
+ if @options[:dns]
58
+ CfnVpn::Log.logger.info "deleting auto lookup route for endpoint #{@options[:dns]}"
59
+ @config[:routes].reject! {|route| route[:dns] == @options[:dns]}
60
+ @dns_route_cleanup = @options[:dns]
61
+ elsif @options[:cidr]
62
+ CfnVpn::Log.logger.info "deleting route #{@options[:cidr]}"
63
+ @config[:routes].reject! {|route| route[:cidr] == @options[:cidr]}
64
+ end
65
+ elsif @route
66
+ CfnVpn::Log.logger.info "existing route for #{@options[:cidr] ? @options[:cidr] : @options[:dns]} found"
67
+ if @options[:groups]
68
+ CfnVpn::Log.logger.info "replacing groups #{@route[:groups]} with new #{@options[:groups]} for route authorization rule"
69
+ @route[:groups] = @options[:groups]
70
+ end
71
+
72
+ if @options[:add_groups]
73
+ CfnVpn::Log.logger.info "adding new group(s) #{@options[:add_groups]} to route authorization rule"
74
+ @route[:groups].concat(@options[:add_groups]).uniq!
75
+ end
76
+
77
+ if @options[:del_groups]
78
+ CfnVpn::Log.logger.info "removing new group(s) #{@options[:del_groups]} to route authorization rule"
79
+ @route[:groups].reject! {|group| @options[:del_groups].include? group}
80
+ end
81
+
82
+ if @options[:desc]
83
+ CfnVpn::Log.logger.warn "description for this route cannot be updated in place. To alter delete the route and add with the new description"
84
+ end
85
+
86
+ if @options[:subnet]
87
+ CfnVpn::Log.logger.warn "the target subnet for this route cannot be updated in place. To alter delete the route and add with the new target subnet"
88
+ end
89
+ elsif !@route && @options[:cidr]
90
+ CfnVpn::Log.logger.info "adding new route for #{@options[:cidr]}"
91
+ @config[:routes] << {
92
+ cidr: @options[:cidr],
93
+ desc: @options.fetch(:desc, ""),
94
+ subnet: @options.fetch(:subnet, @config[:subnet_ids].first),
95
+ groups: @options.fetch(:groups, []) + @options.fetch(:add_groups, [])
96
+ }
97
+ elsif !@route && @options[:dns]
98
+ CfnVpn::Log.logger.info "adding new route lookup for dns record #{@options[:dns]}"
99
+ @config[:routes] << {
100
+ dns: @options[:dns],
101
+ desc: @options.fetch(:desc, ""),
102
+ subnet: @options.fetch(:subnet, @config[:subnet_ids].first),
103
+ groups: @options.fetch(:groups, []) + @options.fetch(:add_groups, [])
104
+ }
105
+ else
106
+ @skip_update = true
107
+ end
108
+
109
+ CfnVpn::Log.logger.debug "CONFIG: #{@config}"
110
+ end
111
+
112
+ def create_bucket_if_bucket_not_set
113
+ if !@config.has_key?(:bucket)
114
+ CfnVpn::Log.logger.error "no bucket found in the config, run the cfn-vpn modify #{name} command to add a bucket"
115
+ exit 1
116
+ end
117
+ end
118
+
119
+ def deploy_vpn
120
+ unless @skip_update
121
+ compiler = CfnVpn::Compiler.new(@name, @config)
122
+ template_body = compiler.compile
123
+ CfnVpn::Log.logger.info "Creating cloudformation changeset for stack #{@name}-cfnvpn in #{@options['region']}"
124
+ @deployer = CfnVpn::Deployer.new(@options['region'],@name)
125
+ change_set, change_set_type = @deployer.create_change_set(template_body: template_body)
126
+ @deployer.wait_for_changeset(change_set.id)
127
+ changeset_response = @deployer.get_change_set(change_set.id)
128
+
129
+ changes = {"Add" => [], "Modify" => [], "Remove" => []}
130
+ change_colours = {"Add" => "green", "Modify" => 'yellow', "Remove" => 'red'}
131
+
132
+ changeset_response.changes.each do |change|
133
+ action = change.resource_change.action
134
+ changes[action].push([
135
+ change.resource_change.logical_resource_id,
136
+ change.resource_change.resource_type,
137
+ change.resource_change.replacement ? change.resource_change.replacement : 'N/A',
138
+ change.resource_change.details.collect {|detail| detail.target.name }.join(' , ')
139
+ ])
140
+ end
141
+
142
+ changes.each do |type, rows|
143
+ next if !rows.any?
144
+ puts "\n"
145
+ table = Terminal::Table.new(
146
+ :title => type,
147
+ :headings => ['Logical Resource Id', 'Resource Type', 'Replacement', 'Changes'],
148
+ :rows => rows)
149
+ puts table.to_s.send(change_colours[type])
150
+ end
151
+
152
+ CfnVpn::Log.logger.info "Cloudformation changeset changes:"
153
+ puts "\n"
154
+ continue = yes? "Continue?", :green
155
+ if !continue
156
+ CfnVpn::Log.logger.info("Cancelled cfn-vpn modifiy #{@name}")
157
+ exit 1
158
+ end
159
+
160
+ @deployer.execute_change_set(change_set.id)
161
+ @deployer.wait_for_execute(change_set_type)
162
+ CfnVpn::Log.logger.info "Changeset #{change_set_type} complete"
163
+ end
164
+ end
165
+
166
+ def cleanup_dns_routes
167
+ @vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
168
+ unless @dns_route_cleanup.nil?
169
+ routes = @vpn.get_routes()
170
+ CfnVpn::Log.logger.info("Cleaning up expired routes for #{@dns_route_cleanup}")
171
+ expired_routes = routes.select {|route| route.description.include?(@dns_route_cleanup) }
172
+ expired_routes.each do |route|
173
+ @vpn.delete_route(route.destination_cidr, route.target_subnet)
174
+ @vpn.revoke_auth(route.destination_cidr)
175
+ end
176
+ end
177
+ end
178
+
179
+ def get_routes
180
+ @endpoint = @vpn.get_endpoint_id()
181
+ @routes = @vpn.get_routes()
182
+ end
183
+
184
+ def display_routes
185
+ rows = @routes.collect do |s|
186
+ groups = @vpn.get_groups_for_route(@endpoint, s.destination_cidr)
187
+ [ s.destination_cidr, s.description, s.status.code, s.target_subnet, s.type, s.origin, (!groups.join("").empty? ? groups.join(' ') : 'AllowAll') ]
188
+ end
189
+ table = Terminal::Table.new(
190
+ :headings => ['Route', 'Description', 'Status', 'Target', 'Type', 'Origin', 'Groups'],
191
+ :rows => rows)
192
+ puts table
193
+ end
194
+
195
+ end
196
+ end
@@ -4,10 +4,10 @@ require 'cfnvpn/log'
4
4
  require 'cfnvpn/clientvpn'
5
5
  require 'cfnvpn/globals'
6
6
 
7
- module CfnVpn
7
+ module CfnVpn::Actions
8
8
  class Sessions < Thor::Group
9
9
  include Thor::Actions
10
- include CfnVpn::Log
10
+
11
11
 
12
12
  argument :name
13
13
 
@@ -22,7 +22,7 @@ module CfnVpn
22
22
  end
23
23
 
24
24
  def set_loglevel
25
- Log.logger.level = Logger::DEBUG if @options['verbose']
25
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
26
26
  end
27
27
 
28
28
  def set_directory
@@ -41,11 +41,11 @@ module CfnVpn
41
41
  if session.any? && session.status.code == "active"
42
42
  terminate = yes? "Terminate connection #{@options['kill']} for #{session.common_name}?", :yellow
43
43
  if terminate
44
- Log.logger.info "Terminating connection #{@options['kill']} for #{session.common_name}"
44
+ CfnVpn::Log.logger.info "Terminating connection #{@options['kill']} for #{session.common_name}"
45
45
  @vpn.kill_session(@endpoint_id,@options['kill'])
46
46
  end
47
47
  else
48
- Log.logger.error "Connection id #{@options['kill']} doesn't exist or is not active"
48
+ CfnVpn::Log.logger.error "Connection id #{@options['kill']} doesn't exist or is not active"
49
49
  end
50
50
  end
51
51
  end
@@ -1,10 +1,10 @@
1
1
  require 'cfnvpn/log'
2
2
  require 'cfnvpn/s3'
3
3
 
4
- module CfnVpn
4
+ module CfnVpn::Actions
5
5
  class Share < Thor::Group
6
6
  include Thor::Actions
7
- include CfnVpn::Log
7
+
8
8
 
9
9
  argument :name
10
10
 
@@ -21,13 +21,13 @@ module CfnVpn
21
21
  end
22
22
 
23
23
  def set_loglevel
24
- Log.logger.level = Logger::DEBUG if @options['verbose']
24
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
25
25
  end
26
26
 
27
27
  def copy_config_to_s3
28
28
  vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
29
29
  @endpoint_id = vpn.get_endpoint_id()
30
- Log.logger.debug "downloading client config for #{@endpoint_id}"
30
+ CfnVpn::Log.logger.debug "downloading client config for #{@endpoint_id}"
31
31
  @config = vpn.get_config(@endpoint_id)
32
32
  string = (0...8).map { (65 + rand(26)).chr.downcase }.join
33
33
  @config.sub!(@endpoint_id, "#{string}.#{@endpoint_id}")
@@ -35,17 +35,17 @@ module CfnVpn
35
35
 
36
36
  def add_routes
37
37
  if @options['ignore_routes']
38
- Log.logger.debug "Ignoring routes pushed by the client vpn"
38
+ CfnVpn::Log.logger.debug "Ignoring routes pushed by the client vpn"
39
39
  @config.concat("\nroute-nopull\n")
40
40
  vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
41
41
  routes = vpn.get_route_with_mask
42
- Log.logger.debug "Found routes #{routes}"
42
+ CfnVpn::Log.logger.debug "Found routes #{routes}"
43
43
  routes.each do |r|
44
44
  @config.concat("route #{r[:route]} #{r[:mask]}\n")
45
45
  end
46
46
  dns_servers = vpn.get_dns_servers()
47
47
  if dns_servers.any?
48
- Log.logger.debug "Found DNS servers #{dns_servers.join(' ')}"
48
+ CfnVpn::Log.logger.debug "Found DNS servers #{dns_servers.join(' ')}"
49
49
  @config.concat("dhcp-option DNS #{dns_servers.first}\n")
50
50
  end
51
51
  end
@@ -58,16 +58,16 @@ module CfnVpn
58
58
 
59
59
  def get_certificate_url
60
60
  @certificate_url = @s3.get_url("#{@options['client_cn']}.tar.gz")
61
- Log.logger.debug "Certificate presigned url: #{@certificate_url}"
61
+ CfnVpn::Log.logger.debug "Certificate presigned url: #{@certificate_url}"
62
62
  end
63
63
 
64
64
  def get_config_url
65
65
  @config_url = @s3.get_url("#{@name}.config.ovpn")
66
- Log.logger.debug "Config presigned url: #{@config_url}"
66
+ CfnVpn::Log.logger.debug "Config presigned url: #{@config_url}"
67
67
  end
68
68
 
69
69
  def display_instructions
70
- Log.logger.info "Share the bellow instruction with the user..."
70
+ CfnVpn::Log.logger.info "Share the bellow instruction with the user..."
71
71
  say "\nDownload the certificates and config from the bellow presigned URLs which will expire in 1 hour."
72
72
  say "\nCertificate:"
73
73
  say "\tcurl #{@certificate_url} > #{@options['client_cn']}.tar.gz", :cyan
@@ -0,0 +1,78 @@
1
+ require 'thor'
2
+ require 'fileutils'
3
+ require 'cfnvpn/log'
4
+
5
+ module CfnVpn::Actions
6
+ class Subnets < Thor::Group
7
+ include Thor::Actions
8
+
9
+
10
+ argument :name
11
+
12
+ class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
13
+ class_option :verbose, desc: 'set log level to debug', type: :boolean
14
+
15
+ class_option :associate, aliases: :a, desc: 'associate all subnets with the client vpn', type: :boolean
16
+ class_option :disassociate, aliases: :d, desc: 'disassociate all subnets with the client vpn', type: :boolean
17
+
18
+ def set_loglevel
19
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
20
+ end
21
+
22
+ def stack_exist
23
+ @deployer = CfnVpn::Deployer.new(@options['region'],@name)
24
+ if !@deployer.does_cf_stack_exist()
25
+ CfnVpn::Log.logger.error "#{@name}-cfnvpn stack doesn't exists in this account and region #{@options['region']}"
26
+ exit 1
27
+ end
28
+ end
29
+
30
+ def associated?
31
+ @associated = @deployer.get_parameter_value('AssociateSubnets') == 'true'
32
+ end
33
+
34
+ def associate
35
+ if @options[:associate]
36
+ if !@associated
37
+ CfnVpn::Log.logger.info "Associating subnets ..."
38
+ change_set, change_set_type = @deployer.create_change_set(parameters: {"AssociateSubnets" => 'true'})
39
+ @deployer.wait_for_changeset(change_set.id)
40
+ @deployer.execute_change_set(change_set.id)
41
+ @deployer.wait_for_execute(change_set_type)
42
+ CfnVpn::Log.logger.info "Association complete"
43
+ else
44
+ CfnVpn::Log.logger.warn "Client-VPN #{name} subnets are already associated"
45
+ end
46
+ end
47
+ end
48
+
49
+ def disassociate
50
+ if @options[:disassociate]
51
+ if @associated
52
+ CfnVpn::Log.logger.info "Disassociating subnets ..."
53
+ change_set, change_set_type = @deployer.create_change_set(parameters: {"AssociateSubnets" => 'false'})
54
+ @deployer.wait_for_changeset(change_set.id)
55
+ @deployer.execute_change_set(change_set.id)
56
+ @deployer.wait_for_execute(change_set_type)
57
+ CfnVpn::Log.logger.info "Disassociation complete"
58
+ else
59
+ CfnVpn::Log.logger.warn "Client-VPN #{name} subnets are already disassociated"
60
+ end
61
+ end
62
+ end
63
+
64
+ def get_endpoint
65
+ @vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
66
+ @endpoint_id = @vpn.get_endpoint_id()
67
+ end
68
+
69
+ def associations
70
+ associations = @vpn.get_associations(@endpoint_id)
71
+ table = Terminal::Table.new(
72
+ :headings => ['ID', 'Subnet', 'Status', 'CIDR', 'AZ', 'Groups'],
73
+ :rows => associations.map {|ass| ass.values})
74
+ puts table
75
+ end
76
+
77
+ end
78
+ end