cfn-vpn 0.5.1 → 1.1.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build-gem.yml +25 -0
  3. data/.github/workflows/release-gem.yml +31 -0
  4. data/.github/workflows/release-image.yml +33 -0
  5. data/Gemfile.lock +30 -38
  6. data/README.md +1 -247
  7. data/cfn-vpn.gemspec +3 -2
  8. data/docs/README.md +44 -0
  9. data/docs/certificate-users.md +89 -0
  10. data/docs/getting-started.md +87 -0
  11. data/docs/modifying.md +67 -0
  12. data/docs/routes.md +82 -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 +130 -0
  19. data/lib/cfnvpn/actions/modify.rb +149 -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 +144 -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 +34 -68
  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 +4 -4
  33. data/lib/cfnvpn/string.rb +29 -0
  34. data/lib/cfnvpn/templates/helper.rb +14 -0
  35. data/lib/cfnvpn/templates/vpn.rb +344 -0
  36. data/lib/cfnvpn/version.rb +1 -1
  37. metadata +55 -22
  38. data/lib/cfnvpn/cfhighlander.rb +0 -49
  39. data/lib/cfnvpn/init.rb +0 -109
  40. data/lib/cfnvpn/modify.rb +0 -103
  41. data/lib/cfnvpn/routes.rb +0 -84
  42. 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,144 @@
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 :subnet, desc: 'the target vpc subnet to route through, if none is supplied the default subnet is used'
17
+ class_option :desc, desc: 'description of the route'
18
+
19
+ class_option :groups, type: :array, desc: 'override all authorised groups on thr route'
20
+ class_option :add_groups, type: :array, desc: 'add authorised groups to an existing route'
21
+ class_option :del_groups, type: :array, desc: 'remove authorised groups from an existing route'
22
+
23
+ class_option :delete, type: :boolean, desc: 'delete the route from the client vpn'
24
+
25
+ def self.source_root
26
+ File.dirname(__FILE__)
27
+ end
28
+
29
+ def set_loglevel
30
+ CfnVpn::Log.logger.level = Logger::DEBUG if @options['verbose']
31
+ end
32
+
33
+ def set_config
34
+ @config = CfnVpn::Config.get_config(@options[:region], @name)
35
+ @route = @config[:routes].detect {|route| route[:cidr] == @options[:cidr]}
36
+ end
37
+
38
+ def set_route
39
+ @skip_update = false
40
+ if @route && @options[:delete]
41
+ CfnVpn::Log.logger.info "deleting route #{@options[:cidr]} "
42
+ @config[:routes].reject! {|route| route[:cidr] == @options[:cidr]}
43
+ elsif @route
44
+ CfnVpn::Log.logger.info "modifying groups for existing route #{@options[:cidr]}"
45
+ if @options[:groups]
46
+ @route[:groups] = @options[:groups]
47
+ end
48
+
49
+ if @options[:add_groups]
50
+ @route[:groups].concat(@options[:add_groups]).uniq!
51
+ end
52
+
53
+ if @options[:del_groups]
54
+ @route[:groups].reject! {|group| @options[:add_groups].include? group}
55
+ end
56
+
57
+ if @options[:desc]
58
+ CfnVpn::Log.logger.warn "description for this route cannot be updated in place. To alter delete the route and add with the new description"
59
+ end
60
+
61
+ if @options[:subnet]
62
+ 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"
63
+ end
64
+ elsif !@route && @options[:cidr]
65
+ CfnVpn::Log.logger.info "adding new route for #{@options[:cidr]}"
66
+ @config[:routes] << {
67
+ cidr: @options[:cidr],
68
+ desc: @options.fetch(:desc, "route for cidr #{@options[:cidr]}"),
69
+ subnet: @options.fetch(:subnet, @config[:subnet_ids].first),
70
+ groups: @options.fetch(@options[:groups], []) + @options.fetch(@options[:add_groups], [])
71
+ }
72
+ else
73
+ @skip_update = true
74
+ end
75
+
76
+ CfnVpn::Log.logger.debug "CONFIG: #{@config}"
77
+ end
78
+
79
+ def deploy_vpn
80
+ unless @skip_update
81
+ compiler = CfnVpn::Compiler.new(@name, @config)
82
+ template_body = compiler.compile
83
+ CfnVpn::Log.logger.info "Creating cloudformation changeset for stack #{@name}-cfnvpn in #{@options['region']}"
84
+ @deployer = CfnVpn::Deployer.new(@options['region'],@name)
85
+ change_set, change_set_type = @deployer.create_change_set(template_body: template_body)
86
+ @deployer.wait_for_changeset(change_set.id)
87
+ changeset_response = @deployer.get_change_set(change_set.id)
88
+
89
+ changes = {"Add" => [], "Modify" => [], "Remove" => []}
90
+ change_colours = {"Add" => "green", "Modify" => 'yellow', "Remove" => 'red'}
91
+
92
+ changeset_response.changes.each do |change|
93
+ action = change.resource_change.action
94
+ changes[action].push([
95
+ change.resource_change.logical_resource_id,
96
+ change.resource_change.resource_type,
97
+ change.resource_change.replacement ? change.resource_change.replacement : 'N/A',
98
+ change.resource_change.details.collect {|detail| detail.target.name }.join(' , ')
99
+ ])
100
+ end
101
+
102
+ changes.each do |type, rows|
103
+ next if !rows.any?
104
+ puts "\n"
105
+ table = Terminal::Table.new(
106
+ :title => type,
107
+ :headings => ['Logical Resource Id', 'Resource Type', 'Replacement', 'Changes'],
108
+ :rows => rows)
109
+ puts table.to_s.send(change_colours[type])
110
+ end
111
+
112
+ CfnVpn::Log.logger.info "Cloudformation changeset changes:"
113
+ puts "\n"
114
+ continue = yes? "Continue?", :green
115
+ if !continue
116
+ CfnVpn::Log.logger.info("Cancelled cfn-vpn modifiy #{@name}")
117
+ exit 1
118
+ end
119
+
120
+ @deployer.execute_change_set(change_set.id)
121
+ @deployer.wait_for_execute(change_set_type)
122
+ CfnVpn::Log.logger.info "Changeset #{change_set_type} complete"
123
+ end
124
+ end
125
+
126
+ def get_routes
127
+ @vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
128
+ @endpoint = @vpn.get_endpoint_id()
129
+ @routes = @vpn.get_routes()
130
+ end
131
+
132
+ def display_routes
133
+ rows = @routes.collect do |s|
134
+ groups = @vpn.get_groups_for_route(@endpoint, s.destination_cidr)
135
+ [ s.destination_cidr, s.description, s.status.code, s.target_subnet, s.type, s.origin, (!groups.join("").empty? ? groups.join(' ') : 'AllowAll') ]
136
+ end
137
+ table = Terminal::Table.new(
138
+ :headings => ['Route', 'Description', 'Status', 'Target', 'Type', 'Origin', 'Groups'],
139
+ :rows => rows)
140
+ puts table
141
+ end
142
+
143
+ end
144
+ 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
@@ -6,7 +6,7 @@ require 'cfnvpn/log'
6
6
 
7
7
  module CfnVpn
8
8
  class Certificates
9
- include CfnVpn::Log
9
+
10
10
 
11
11
  def initialize(build_dir, cfnvpn_name, easyrsa_local = false)
12
12
  @cfnvpn_name = cfnvpn_name
@@ -44,7 +44,7 @@ module CfnVpn
44
44
  @docker_cmd << "-v #{@cert_dir}:/easy-rsa/output"
45
45
  @docker_cmd << @easyrsa_image
46
46
  @docker_cmd << "sh -c 'create-ca'"
47
- Log.logger.debug `#{@docker_cmd.join(' ')}`
47
+ CfnVpn::Log.logger.debug `#{@docker_cmd.join(' ')}`
48
48
  end
49
49
  end
50
50
 
@@ -59,7 +59,7 @@ module CfnVpn
59
59
  @docker_cmd << "-v #{@cert_dir}:/easy-rsa/output"
60
60
  @docker_cmd << @easyrsa_image
61
61
  @docker_cmd << "sh -c 'create-client'"
62
- Log.logger.debug `#{@docker_cmd.join(' ')}`
62
+ CfnVpn::Log.logger.debug `#{@docker_cmd.join(' ')}`
63
63
  end
64
64
  end
65
65
 
@@ -76,7 +76,7 @@ module CfnVpn
76
76
  @docker_cmd << "-v #{@cert_dir}:/easy-rsa/output"
77
77
  @docker_cmd << @easyrsa_image
78
78
  @docker_cmd << "sh -c 'revoke-client'"
79
- Log.logger.debug `#{@docker_cmd.join(' ')}`
79
+ CfnVpn::Log.logger.debug `#{@docker_cmd.join(' ')}`
80
80
  end
81
81
  end
82
82
 
@@ -84,7 +84,7 @@ module CfnVpn
84
84
  cn = cn.nil? ? cert : cn
85
85
  acm = CfnVpn::Acm.new(region, @cert_dir)
86
86
  arn = acm.import_certificate("#{cert}.crt", "#{cert}.key", "ca.crt")
87
- Log.logger.debug "Uploaded #{type} certificate to ACM #{arn}"
87
+ CfnVpn::Log.logger.debug "Uploaded #{type} certificate to ACM #{arn}"
88
88
  acm.tag_certificate(arn,cn,type,@cfnvpn_name)
89
89
  return arn
90
90
  end