cfn-vpn 0.5.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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