leap_cli 1.8.1 → 1.9

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 (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