leap_cli 1.8.1 → 1.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/bin/leap +6 -12
  3. data/lib/leap_cli.rb +3 -23
  4. data/lib/leap_cli/bootstrap.rb +36 -12
  5. data/lib/leap_cli/commands/common.rb +88 -46
  6. data/lib/leap_cli/commands/new.rb +24 -17
  7. data/lib/leap_cli/commands/pre.rb +3 -1
  8. data/lib/leap_cli/core_ext/hash.rb +19 -0
  9. data/lib/leap_cli/leapfile.rb +47 -32
  10. data/lib/leap_cli/log.rb +196 -88
  11. data/lib/leap_cli/path.rb +5 -5
  12. data/lib/leap_cli/util.rb +28 -18
  13. data/lib/leap_cli/version.rb +8 -3
  14. data/vendor/acme-client/lib/acme-client.rb +1 -0
  15. data/vendor/acme-client/lib/acme/client.rb +122 -0
  16. data/vendor/acme-client/lib/acme/client/certificate.rb +30 -0
  17. data/vendor/acme-client/lib/acme/client/certificate_request.rb +111 -0
  18. data/vendor/acme-client/lib/acme/client/crypto.rb +98 -0
  19. data/vendor/acme-client/lib/acme/client/error.rb +16 -0
  20. data/vendor/acme-client/lib/acme/client/faraday_middleware.rb +123 -0
  21. data/vendor/acme-client/lib/acme/client/resources.rb +5 -0
  22. data/vendor/acme-client/lib/acme/client/resources/authorization.rb +44 -0
  23. data/vendor/acme-client/lib/acme/client/resources/challenges.rb +6 -0
  24. data/vendor/acme-client/lib/acme/client/resources/challenges/base.rb +43 -0
  25. data/vendor/acme-client/lib/acme/client/resources/challenges/dns01.rb +19 -0
  26. data/vendor/acme-client/lib/acme/client/resources/challenges/http01.rb +18 -0
  27. data/vendor/acme-client/lib/acme/client/resources/challenges/tls_sni01.rb +24 -0
  28. data/vendor/acme-client/lib/acme/client/resources/registration.rb +37 -0
  29. data/vendor/acme-client/lib/acme/client/self_sign_certificate.rb +60 -0
  30. data/vendor/acme-client/lib/acme/client/version.rb +7 -0
  31. data/vendor/base32/lib/base32.rb +67 -0
  32. data/vendor/certificate_authority/lib/certificate_authority.rb +2 -1
  33. data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +4 -4
  34. data/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb +7 -5
  35. data/vendor/certificate_authority/lib/certificate_authority/core_extensions.rb +46 -0
  36. data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +6 -2
  37. data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +10 -3
  38. data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +11 -9
  39. data/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb +3 -3
  40. data/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb +0 -2
  41. data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +8 -2
  42. data/vendor/certificate_authority/lib/certificate_authority/validations.rb +31 -0
  43. data/vendor/rsync_command/lib/rsync_command.rb +49 -12
  44. metadata +50 -91
  45. data/lib/leap/platform.rb +0 -90
  46. data/lib/leap_cli/config/environment.rb +0 -180
  47. data/lib/leap_cli/config/filter.rb +0 -178
  48. data/lib/leap_cli/config/manager.rb +0 -419
  49. data/lib/leap_cli/config/node.rb +0 -77
  50. data/lib/leap_cli/config/object.rb +0 -428
  51. data/lib/leap_cli/config/object_list.rb +0 -209
  52. data/lib/leap_cli/config/provider.rb +0 -22
  53. data/lib/leap_cli/config/secrets.rb +0 -87
  54. data/lib/leap_cli/config/sources.rb +0 -11
  55. data/lib/leap_cli/config/tag.rb +0 -25
  56. data/lib/leap_cli/lib_ext/capistrano_connections.rb +0 -16
  57. data/lib/leap_cli/logger.rb +0 -237
  58. data/lib/leap_cli/remote/leap_plugin.rb +0 -192
  59. data/lib/leap_cli/remote/puppet_plugin.rb +0 -26
  60. data/lib/leap_cli/remote/rsync_plugin.rb +0 -35
  61. data/lib/leap_cli/remote/tasks.rb +0 -51
  62. data/lib/leap_cli/ssh_key.rb +0 -195
  63. data/lib/leap_cli/util/remote_command.rb +0 -158
  64. data/lib/leap_cli/util/secret.rb +0 -55
  65. data/lib/leap_cli/util/x509.rb +0 -33
data/lib/leap_cli/path.rb CHANGED
@@ -3,7 +3,7 @@ require 'fileutils'
3
3
  module LeapCli; module Path
4
4
 
5
5
  def self.platform
6
- @platform
6
+ @platform ||= nil
7
7
  end
8
8
 
9
9
  def self.provider_base
@@ -40,14 +40,14 @@ module LeapCli; module Path
40
40
  [Path.provider, Path.provider_base].each do |base|
41
41
  if arg.is_a?(Symbol) || arg.is_a?(Array)
42
42
  named_path(arg, base).tap {|path|
43
- return path if File.exists?(path)
43
+ return path if File.exist?(path)
44
44
  }
45
45
  else
46
46
  File.join(base, arg).tap {|path|
47
- return path if File.exists?(path)
47
+ return path if File.exist?(path)
48
48
  }
49
49
  File.join(base, 'files', arg).tap {|path|
50
- return path if File.exists?(path)
50
+ return path if File.exist?(path)
51
51
  }
52
52
  end
53
53
  end
@@ -83,7 +83,7 @@ module LeapCli; module Path
83
83
  end
84
84
 
85
85
  def self.exists?(name, provider_dir=nil)
86
- File.exists?(named_path(name, provider_dir))
86
+ File.exist?(named_path(name, provider_dir))
87
87
  end
88
88
 
89
89
  def self.defined?(name)
data/lib/leap_cli/util.rb CHANGED
@@ -10,6 +10,10 @@ module LeapCli
10
10
 
11
11
  @@exit_status = nil
12
12
 
13
+ def log(*args, &block)
14
+ LeapCli.log(*args, &block)
15
+ end
16
+
13
17
  ##
14
18
  ## QUITTING
15
19
  ##
@@ -36,15 +40,14 @@ module LeapCli
36
40
  #
37
41
  # exit with error code and with a message that we are bailing out.
38
42
  #
39
- def bail!(*message)
40
- if block_given?
41
- LeapCli.set_log_level(3)
42
- yield
43
- elsif message
44
- log 0, *message
43
+ def bail!(*message, &block)
44
+ LeapCli.logger.log_level = 3 if LeapCli.logger.log_level < 3
45
+ if message.any?
46
+ log(0, *message, &block)
47
+ else
48
+ log(0, :bailing, "out", :color => :red, :style => :bold, &block)
45
49
  end
46
- log 0, :bail, ""
47
- raise SystemExit.new(@exit_status || 1)
50
+ raise SystemExit.new(exit_status || 1)
48
51
  end
49
52
 
50
53
  #
@@ -52,7 +55,7 @@ module LeapCli
52
55
  #
53
56
  def quit!(message='')
54
57
  puts(message)
55
- raise SystemExit.new(@exit_status || 0)
58
+ raise SystemExit.new(exit_status || 0)
56
59
  end
57
60
 
58
61
  #
@@ -119,7 +122,7 @@ module LeapCli
119
122
  base = options[:base] || Path.provider
120
123
  file_list = files.collect { |file_path|
121
124
  file_path = Path.named_path(file_path, base)
122
- File.exists?(file_path) ? Path.relative_path(file_path, base) : nil
125
+ File.exist?(file_path) ? Path.relative_path(file_path, base) : nil
123
126
  }.compact
124
127
  if file_list.length > 1
125
128
  bail! do
@@ -138,7 +141,7 @@ module LeapCli
138
141
  options = files.last.is_a?(Hash) ? files.pop : {}
139
142
  file_list = files.collect { |file_path|
140
143
  file_path = Path.named_path(file_path)
141
- !File.exists?(file_path) ? Path.relative_path(file_path) : nil
144
+ !File.exist?(file_path) ? Path.relative_path(file_path) : nil
142
145
  }.compact
143
146
  if file_list.length > 1
144
147
  bail! do
@@ -157,7 +160,7 @@ module LeapCli
157
160
  def file_exists?(*files)
158
161
  files.each do |file_path|
159
162
  file_path = Path.named_path(file_path)
160
- if !File.exists?(file_path)
163
+ if !File.exist?(file_path)
161
164
  return false
162
165
  end
163
166
  end
@@ -233,7 +236,7 @@ module LeapCli
233
236
  #
234
237
  def replace_file!(filepath, &block)
235
238
  filepath = Path.named_path(filepath)
236
- if !File.exists?(filepath)
239
+ if !File.exist?(filepath)
237
240
  content = yield(nil)
238
241
  unless content.nil?
239
242
  write_file!(filepath, content)
@@ -258,7 +261,7 @@ module LeapCli
258
261
 
259
262
  def remove_file!(filepath)
260
263
  filepath = Path.named_path(filepath)
261
- if File.exists?(filepath)
264
+ if File.exist?(filepath)
262
265
  if File.directory?(filepath)
263
266
  remove_directory!(filepath)
264
267
  else
@@ -298,7 +301,7 @@ module LeapCli
298
301
  def write_file!(filepath, contents)
299
302
  filepath = Path.named_path(filepath)
300
303
  ensure_dir File.dirname(filepath)
301
- existed = File.exists?(filepath)
304
+ existed = File.exist?(filepath)
302
305
  if existed
303
306
  if file_content_equals?(filepath, contents)
304
307
  log :nochange, filepath, 2
@@ -320,11 +323,11 @@ module LeapCli
320
323
  def rename_file!(oldpath, newpath)
321
324
  oldpath = Path.named_path(oldpath)
322
325
  newpath = Path.named_path(newpath)
323
- if File.exists? newpath
326
+ if File.exist? newpath
324
327
  log :skipping, "#{Path.relative_path(newpath)}, file already exists"
325
328
  return
326
329
  end
327
- if !File.exists? oldpath
330
+ if !File.exist? oldpath
328
331
  log :skipping, "#{Path.relative_path(oldpath)}, file is missing"
329
332
  return
330
333
  end
@@ -425,11 +428,18 @@ module LeapCli
425
428
  end
426
429
  end
427
430
 
431
+ def is_git_subrepo?(dir)
432
+ Dir.chdir(dir) do
433
+ `ls .gitrepo 2>/dev/null`
434
+ return $? == 0
435
+ end
436
+ end
437
+
428
438
  def current_git_branch(dir)
429
439
  Dir.chdir(dir) do
430
440
  branch = `git symbolic-ref HEAD 2>/dev/null`.strip
431
441
  if branch.chars.any?
432
- branch.sub /^refs\/heads\//, ''
442
+ branch.sub(/^refs\/heads\//, '')
433
443
  else
434
444
  nil
435
445
  end
@@ -1,9 +1,14 @@
1
1
  module LeapCli
2
2
  unless defined?(LeapCli::VERSION)
3
- VERSION = '1.8.1'
4
- COMPATIBLE_PLATFORM_VERSION = '0.8'..'0.8.99'
3
+ VERSION = '1.9'
4
+ COMPATIBLE_PLATFORM_VERSION = '0.9'..'0.99'
5
5
  SUMMARY = 'Command line interface to the LEAP platform'
6
6
  DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.'
7
- LOAD_PATHS = ['lib', 'vendor/certificate_authority/lib', 'vendor/rsync_command/lib']
7
+ LOAD_PATHS = ['lib',
8
+ 'vendor/certificate_authority/lib',
9
+ 'vendor/rsync_command/lib',
10
+ 'vendor/base32/lib',
11
+ 'vendor/acme-client/lib'
12
+ ]
8
13
  end
9
14
  end
@@ -0,0 +1 @@
1
+ require 'acme/client'
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'json'
5
+ require 'openssl'
6
+ require 'digest'
7
+ require 'forwardable'
8
+ require 'base64'
9
+ require 'time'
10
+
11
+ module Acme; end
12
+ class Acme::Client; end
13
+
14
+ require 'acme/client/version'
15
+ require 'acme/client/certificate'
16
+ require 'acme/client/certificate_request'
17
+ require 'acme/client/self_sign_certificate'
18
+ require 'acme/client/crypto'
19
+ require 'acme/client/resources'
20
+ require 'acme/client/faraday_middleware'
21
+ require 'acme/client/error'
22
+
23
+ class Acme::Client
24
+ DEFAULT_ENDPOINT = 'http://127.0.0.1:4000'.freeze
25
+ DIRECTORY_DEFAULT = {
26
+ 'new-authz' => '/acme/new-authz',
27
+ 'new-cert' => '/acme/new-cert',
28
+ 'new-reg' => '/acme/new-reg',
29
+ 'revoke-cert' => '/acme/revoke-cert'
30
+ }.freeze
31
+
32
+ def initialize(private_key:, endpoint: DEFAULT_ENDPOINT, directory_uri: nil, connection_options: {})
33
+ @endpoint, @private_key, @directory_uri, @connection_options = endpoint, private_key, directory_uri, connection_options
34
+ @nonces ||= []
35
+ load_directory!
36
+ end
37
+
38
+ attr_reader :private_key, :nonces, :operation_endpoints
39
+
40
+ def register(contact:)
41
+ payload = {
42
+ resource: 'new-reg', contact: Array(contact)
43
+ }
44
+
45
+ response = connection.post(@operation_endpoints.fetch('new-reg'), payload)
46
+ ::Acme::Client::Resources::Registration.new(self, response)
47
+ end
48
+
49
+ def authorize(domain:)
50
+ payload = {
51
+ resource: 'new-authz',
52
+ identifier: {
53
+ type: 'dns',
54
+ value: domain
55
+ }
56
+ }
57
+
58
+ response = connection.post(@operation_endpoints.fetch('new-authz'), payload)
59
+ ::Acme::Client::Resources::Authorization.new(self, response.headers['Location'], response)
60
+ end
61
+
62
+ def fetch_authorization(uri)
63
+ response = connection.get(uri)
64
+ ::Acme::Client::Resources::Authorization.new(self, uri, response)
65
+ end
66
+
67
+ def new_certificate(csr)
68
+ payload = {
69
+ resource: 'new-cert',
70
+ csr: Base64.urlsafe_encode64(csr.to_der)
71
+ }
72
+
73
+ response = connection.post(@operation_endpoints.fetch('new-cert'), payload)
74
+ ::Acme::Client::Certificate.new(OpenSSL::X509::Certificate.new(response.body), response.headers['location'], fetch_chain(response), csr)
75
+ end
76
+
77
+ def revoke_certificate(certificate)
78
+ payload = { resource: 'revoke-cert', certificate: Base64.urlsafe_encode64(certificate.to_der) }
79
+ endpoint = @operation_endpoints.fetch('revoke-cert')
80
+ response = connection.post(endpoint, payload)
81
+ response.success?
82
+ end
83
+
84
+ def self.revoke_certificate(certificate, *arguments)
85
+ client = new(*arguments)
86
+ client.revoke_certificate(certificate)
87
+ end
88
+
89
+ def connection
90
+ @connection ||= Faraday.new(@endpoint, **@connection_options) do |configuration|
91
+ configuration.use Acme::Client::FaradayMiddleware, client: self
92
+ configuration.adapter Faraday.default_adapter
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def fetch_chain(response, limit = 10)
99
+ links = response.headers['link']
100
+ if limit.zero? || links.nil? || links['up'].nil?
101
+ []
102
+ else
103
+ issuer = connection.get(links['up'])
104
+ [OpenSSL::X509::Certificate.new(issuer.body), *fetch_chain(issuer, limit - 1)]
105
+ end
106
+ end
107
+
108
+ def load_directory!
109
+ @operation_endpoints = if @directory_uri
110
+ response = connection.get(@directory_uri)
111
+ body = response.body
112
+ {
113
+ 'new-reg' => body.fetch('new-reg'),
114
+ 'new-authz' => body.fetch('new-authz'),
115
+ 'new-cert' => body.fetch('new-cert'),
116
+ 'revoke-cert' => body.fetch('revoke-cert'),
117
+ }
118
+ else
119
+ DIRECTORY_DEFAULT
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,30 @@
1
+ class Acme::Client::Certificate
2
+ extend Forwardable
3
+
4
+ attr_reader :x509, :x509_chain, :request, :private_key, :url
5
+
6
+ def_delegators :x509, :to_pem, :to_der
7
+
8
+ def initialize(certificate, url, chain, request)
9
+ @x509 = certificate
10
+ @url = url
11
+ @x509_chain = chain
12
+ @request = request
13
+ end
14
+
15
+ def chain_to_pem
16
+ x509_chain.map(&:to_pem).join
17
+ end
18
+
19
+ def x509_fullchain
20
+ [x509, *x509_chain]
21
+ end
22
+
23
+ def fullchain_to_pem
24
+ x509_fullchain.map(&:to_pem).join
25
+ end
26
+
27
+ def common_name
28
+ x509.subject.to_a.find { |name, _, _| name == 'CN' }[1]
29
+ end
30
+ end
@@ -0,0 +1,111 @@
1
+ class Acme::Client::CertificateRequest
2
+ extend Forwardable
3
+
4
+ DEFAULT_KEY_LENGTH = 2048
5
+ DEFAULT_DIGEST = OpenSSL::Digest::SHA256
6
+ SUBJECT_KEYS = {
7
+ common_name: 'CN',
8
+ country_name: 'C',
9
+ organization_name: 'O',
10
+ organizational_unit: 'OU',
11
+ state_or_province: 'ST',
12
+ locality_name: 'L'
13
+ }.freeze
14
+
15
+ SUBJECT_TYPES = {
16
+ 'CN' => OpenSSL::ASN1::UTF8STRING,
17
+ 'C' => OpenSSL::ASN1::UTF8STRING,
18
+ 'O' => OpenSSL::ASN1::UTF8STRING,
19
+ 'OU' => OpenSSL::ASN1::UTF8STRING,
20
+ 'ST' => OpenSSL::ASN1::UTF8STRING,
21
+ 'L' => OpenSSL::ASN1::UTF8STRING
22
+ }.freeze
23
+
24
+ attr_reader :private_key, :common_name, :names, :subject
25
+
26
+ def_delegators :csr, :to_pem, :to_der
27
+
28
+ def initialize(common_name: nil, names: [], private_key: generate_private_key, subject: {}, digest: DEFAULT_DIGEST.new)
29
+ @digest = digest
30
+ @private_key = private_key
31
+ @subject = normalize_subject(subject)
32
+ @common_name = common_name || @subject[SUBJECT_KEYS[:common_name]] || @subject[:common_name]
33
+ @names = names.to_a.dup
34
+ normalize_names
35
+ @subject[SUBJECT_KEYS[:common_name]] ||= @common_name
36
+ validate_subject
37
+ end
38
+
39
+ def csr
40
+ @csr ||= generate
41
+ end
42
+
43
+ private
44
+
45
+ def generate_private_key
46
+ OpenSSL::PKey::RSA.new(DEFAULT_KEY_LENGTH)
47
+ end
48
+
49
+ def normalize_subject(subject)
50
+ @subject = subject.each_with_object({}) do |(key, value), hash|
51
+ hash[SUBJECT_KEYS.fetch(key, key)] = value.to_s
52
+ end
53
+ end
54
+
55
+ def normalize_names
56
+ if @common_name
57
+ @names.unshift(@common_name) unless @names.include?(@common_name)
58
+ else
59
+ raise ArgumentError, 'No common name and no list of names given' if @names.empty?
60
+ @common_name = @names.first
61
+ end
62
+ end
63
+
64
+ def validate_subject
65
+ validate_subject_attributes
66
+ validate_subject_common_name
67
+ end
68
+
69
+ def validate_subject_attributes
70
+ extra_keys = @subject.keys - SUBJECT_KEYS.keys - SUBJECT_KEYS.values
71
+ return if extra_keys.empty?
72
+ raise ArgumentError, "Unexpected subject attributes given: #{extra_keys.inspect}"
73
+ end
74
+
75
+ def validate_subject_common_name
76
+ return if @common_name == @subject[SUBJECT_KEYS[:common_name]]
77
+ raise ArgumentError, 'Conflicting common name given in arguments and subject'
78
+ end
79
+
80
+ def generate
81
+ OpenSSL::X509::Request.new.tap do |csr|
82
+ csr.public_key = @private_key.public_key
83
+ csr.subject = generate_subject
84
+ csr.version = 2
85
+ add_extension(csr)
86
+ csr.sign @private_key, @digest
87
+ end
88
+ end
89
+
90
+ def generate_subject
91
+ OpenSSL::X509::Name.new(
92
+ @subject.map {|name, value|
93
+ [name, value, SUBJECT_TYPES[name]]
94
+ }
95
+ )
96
+ end
97
+
98
+ def add_extension(csr)
99
+ return if @names.size <= 1
100
+
101
+ extension = OpenSSL::X509::ExtensionFactory.new.create_extension(
102
+ 'subjectAltName', @names.map { |name| "DNS:#{name}" }.join(', '), false
103
+ )
104
+ csr.add_attribute(
105
+ OpenSSL::X509::Attribute.new(
106
+ 'extReq',
107
+ OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([extension])])
108
+ )
109
+ )
110
+ end
111
+ end