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
@@ -0,0 +1,98 @@
1
+ class Acme::Client::Crypto
2
+ attr_reader :private_key
3
+
4
+ def initialize(private_key)
5
+ @private_key = private_key
6
+ end
7
+
8
+ def generate_signed_jws(header:, payload:)
9
+ header = { typ: 'JWT', alg: jws_alg, jwk: jwk }.merge(header)
10
+
11
+ encoded_header = urlsafe_base64(header.to_json)
12
+ encoded_payload = urlsafe_base64(payload.to_json)
13
+ signature_data = "#{encoded_header}.#{encoded_payload}"
14
+
15
+ signature = private_key.sign digest, signature_data
16
+ encoded_signature = urlsafe_base64(signature)
17
+
18
+ {
19
+ protected: encoded_header,
20
+ payload: encoded_payload,
21
+ signature: encoded_signature
22
+ }.to_json
23
+ end
24
+
25
+ def thumbprint
26
+ urlsafe_base64 digest.digest(jwk.to_json)
27
+ end
28
+
29
+ def digest
30
+ OpenSSL::Digest::SHA256.new
31
+ end
32
+
33
+ def urlsafe_base64(data)
34
+ Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '')
35
+ end
36
+
37
+ private
38
+
39
+ def jws_alg
40
+ { 'RSA' => 'RS256', 'EC' => 'ES256' }.fetch(jwk[:kty])
41
+ end
42
+
43
+ def jwk
44
+ @jwk ||= case private_key
45
+ when OpenSSL::PKey::RSA
46
+ rsa_jwk
47
+ when OpenSSL::PKey::EC
48
+ ec_jwk
49
+ else
50
+ raise ArgumentError, "Can't handle #{private_key} as private key, only OpenSSL::PKey::RSA and OpenSSL::PKey::EC"
51
+ end
52
+ end
53
+
54
+ def rsa_jwk
55
+ {
56
+ e: urlsafe_base64(public_key.e.to_s(2)),
57
+ kty: 'RSA',
58
+ n: urlsafe_base64(public_key.n.to_s(2))
59
+ }
60
+ end
61
+
62
+ def ec_jwk
63
+ {
64
+ crv: curve_name,
65
+ kty: 'EC',
66
+ x: urlsafe_base64(coordinates[:x].to_s(2)),
67
+ y: urlsafe_base64(coordinates[:y].to_s(2))
68
+ }
69
+ end
70
+
71
+ def curve_name
72
+ {
73
+ 'prime256v1' => 'P-256',
74
+ 'secp384r1' => 'P-384',
75
+ 'secp521r1' => 'P-521'
76
+ }.fetch(private_key.group.curve_name) { raise ArgumentError, 'Unknown EC curve' }
77
+ end
78
+
79
+ # rubocop:disable Metrics/AbcSize
80
+ def coordinates
81
+ @coordinates ||= begin
82
+ hex = public_key.to_bn.to_s(16)
83
+ data_len = hex.length - 2
84
+ hex_x = hex[2, data_len / 2]
85
+ hex_y = hex[2 + data_len / 2, data_len / 2]
86
+
87
+ {
88
+ x: OpenSSL::BN.new([hex_x].pack('H*'), 2),
89
+ y: OpenSSL::BN.new([hex_y].pack('H*'), 2)
90
+ }
91
+ end
92
+ end
93
+ # rubocop:enable Metrics/AbcSize
94
+
95
+ def public_key
96
+ @public_key ||= private_key.public_key
97
+ end
98
+ end
@@ -0,0 +1,16 @@
1
+ class Acme::Client::Error < StandardError
2
+ class NotFound < Acme::Client::Error; end
3
+ class BadCSR < Acme::Client::Error; end
4
+ class BadNonce < Acme::Client::Error; end
5
+ class Connection < Acme::Client::Error; end
6
+ class Dnssec < Acme::Client::Error; end
7
+ class Malformed < Acme::Client::Error; end
8
+ class ServerInternal < Acme::Client::Error; end
9
+ class Acme::Tls < Acme::Client::Error; end
10
+ class Unauthorized < Acme::Client::Error; end
11
+ class UnknownHost < Acme::Client::Error; end
12
+ class Timeout < Acme::Client::Error; end
13
+ class RateLimited < Acme::Client::Error; end
14
+ class RejectedIdentifier < Acme::Client::Error; end
15
+ class UnsupportedIdentifier < Acme::Client::Error; end
16
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Acme::Client::FaradayMiddleware < Faraday::Middleware
4
+ attr_reader :env, :response, :client
5
+
6
+ repo_url = 'https://github.com/unixcharles/acme-client'
7
+ USER_AGENT = "Acme::Client v#{Acme::Client::VERSION} (#{repo_url})".freeze
8
+
9
+ def initialize(app, client:)
10
+ super(app)
11
+ @client = client
12
+ end
13
+
14
+ def call(env)
15
+ @env = env
16
+ @env[:request_headers]['User-Agent'] = USER_AGENT
17
+ @env.body = crypto.generate_signed_jws(header: { nonce: pop_nonce }, payload: env.body)
18
+ @app.call(env).on_complete { |response_env| on_complete(response_env) }
19
+ rescue Faraday::TimeoutError
20
+ raise Acme::Client::Error::Timeout
21
+ end
22
+
23
+ def on_complete(env)
24
+ @env = env
25
+
26
+ raise_on_not_found!
27
+ store_nonce
28
+ env.body = decode_body
29
+ env.response_headers['Link'] = decode_link_headers
30
+
31
+ return if env.success?
32
+
33
+ raise_on_error!
34
+ end
35
+
36
+ private
37
+
38
+ def raise_on_not_found!
39
+ raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404
40
+ end
41
+
42
+ def raise_on_error!
43
+ raise error_class, error_message
44
+ end
45
+
46
+ def error_message
47
+ if env.body.is_a? Hash
48
+ env.body['detail']
49
+ else
50
+ "Error message: #{env.body}"
51
+ end
52
+ end
53
+
54
+ def error_class
55
+ if error_name && !error_name.empty? && Acme::Client::Error.const_defined?(error_name)
56
+ Object.const_get("Acme::Client::Error::#{error_name}")
57
+ else
58
+ Acme::Client::Error
59
+ end
60
+ end
61
+
62
+ def error_name
63
+ @error_name ||= begin
64
+ return unless env.body.is_a?(Hash)
65
+ return unless env.body.key?('type')
66
+
67
+ env.body['type'].gsub('urn:acme:error:', '').split(/[_-]/).map(&:capitalize).join
68
+ end
69
+ end
70
+
71
+ def decode_body
72
+ content_type = env.response_headers['Content-Type']
73
+
74
+ if content_type == 'application/json' || content_type == 'application/problem+json'
75
+ JSON.load(env.body)
76
+ else
77
+ env.body
78
+ end
79
+ end
80
+
81
+ LINK_MATCH = /<(.*?)>;rel="([\w-]+)"/
82
+
83
+ def decode_link_headers
84
+ return unless env.response_headers.key?('Link')
85
+ link_header = env.response_headers['Link']
86
+
87
+ links = link_header.split(', ').map { |entry|
88
+ _, link, name = *entry.match(LINK_MATCH)
89
+ [name, link]
90
+ }
91
+
92
+ Hash[*links.flatten]
93
+ end
94
+
95
+ def store_nonce
96
+ nonces << env.response_headers['replay-nonce']
97
+ end
98
+
99
+ def pop_nonce
100
+ if nonces.empty?
101
+ get_nonce
102
+ else
103
+ nonces.pop
104
+ end
105
+ end
106
+
107
+ def get_nonce
108
+ response = Faraday.head(env.url, nil, 'User-Agent' => USER_AGENT)
109
+ response.headers['replay-nonce']
110
+ end
111
+
112
+ def nonces
113
+ client.nonces
114
+ end
115
+
116
+ def private_key
117
+ client.private_key
118
+ end
119
+
120
+ def crypto
121
+ @crypto ||= Acme::Client::Crypto.new(private_key)
122
+ end
123
+ end
@@ -0,0 +1,5 @@
1
+ module Acme::Client::Resources; end
2
+
3
+ require 'acme/client/resources/registration'
4
+ require 'acme/client/resources/challenges'
5
+ require 'acme/client/resources/authorization'
@@ -0,0 +1,44 @@
1
+ class Acme::Client::Resources::Authorization
2
+ HTTP01 = Acme::Client::Resources::Challenges::HTTP01
3
+ DNS01 = Acme::Client::Resources::Challenges::DNS01
4
+ TLSSNI01 = Acme::Client::Resources::Challenges::TLSSNI01
5
+
6
+ attr_reader :client, :uri, :domain, :status, :expires, :http01, :dns01, :tls_sni01
7
+
8
+ def initialize(client, uri, response)
9
+ @client = client
10
+ @uri = uri
11
+ assign_attributes(response.body)
12
+ end
13
+
14
+ def verify_status
15
+ response = @client.connection.get(@uri)
16
+
17
+ assign_attributes(response.body)
18
+ status
19
+ end
20
+
21
+ private
22
+
23
+ def assign_attributes(body)
24
+ @expires = Time.iso8601(body['expires']) if body.key? 'expires'
25
+ @domain = body['identifier']['value']
26
+ @status = body['status']
27
+ assign_challenges(body['challenges'])
28
+ end
29
+
30
+ def assign_challenges(challenges)
31
+ challenges.each do |attributes|
32
+ challenge = case attributes.fetch('type')
33
+ when 'http-01'
34
+ @http01 ||= HTTP01.new(self)
35
+ when 'dns-01'
36
+ @dns01 ||= DNS01.new(self)
37
+ when 'tls-sni-01'
38
+ @tls_sni01 ||= TLSSNI01.new(self)
39
+ end
40
+
41
+ challenge.assign_attributes(attributes) if challenge
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,6 @@
1
+ module Acme::Client::Resources::Challenges; end
2
+
3
+ require 'acme/client/resources/challenges/base'
4
+ require 'acme/client/resources/challenges/http01'
5
+ require 'acme/client/resources/challenges/dns01'
6
+ require 'acme/client/resources/challenges/tls_sni01'
@@ -0,0 +1,43 @@
1
+ class Acme::Client::Resources::Challenges::Base
2
+ attr_reader :authorization, :status, :uri, :token, :error
3
+
4
+ def initialize(authorization)
5
+ @authorization = authorization
6
+ end
7
+
8
+ def client
9
+ authorization.client
10
+ end
11
+
12
+ def verify_status
13
+ authorization.verify_status
14
+
15
+ status
16
+ end
17
+
18
+ def request_verification
19
+ response = client.connection.post(@uri, resource: 'challenge', type: challenge_type, keyAuthorization: authorization_key)
20
+ response.success?
21
+ end
22
+
23
+ def assign_attributes(attributes)
24
+ @status = attributes.fetch('status', 'pending')
25
+ @uri = attributes.fetch('uri')
26
+ @token = attributes.fetch('token')
27
+ @error = attributes['error']
28
+ end
29
+
30
+ private
31
+
32
+ def challenge_type
33
+ self.class::CHALLENGE_TYPE
34
+ end
35
+
36
+ def authorization_key
37
+ "#{token}.#{crypto.thumbprint}"
38
+ end
39
+
40
+ def crypto
41
+ @crypto ||= Acme::Client::Crypto.new(client.private_key)
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Acme::Client::Resources::Challenges::DNS01 < Acme::Client::Resources::Challenges::Base
4
+ CHALLENGE_TYPE = 'dns-01'.freeze
5
+ RECORD_NAME = '_acme-challenge'.freeze
6
+ RECORD_TYPE = 'TXT'.freeze
7
+
8
+ def record_name
9
+ RECORD_NAME
10
+ end
11
+
12
+ def record_type
13
+ RECORD_TYPE
14
+ end
15
+
16
+ def record_content
17
+ crypto.urlsafe_base64(crypto.digest.digest(authorization_key))
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Acme::Client::Resources::Challenges::HTTP01 < Acme::Client::Resources::Challenges::Base
4
+ CHALLENGE_TYPE = 'http-01'.freeze
5
+ CONTENT_TYPE = 'text/plain'.freeze
6
+
7
+ def content_type
8
+ CONTENT_TYPE
9
+ end
10
+
11
+ def file_content
12
+ authorization_key
13
+ end
14
+
15
+ def filename
16
+ ".well-known/acme-challenge/#{token}"
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Acme::Client::Resources::Challenges::TLSSNI01 < Acme::Client::Resources::Challenges::Base
4
+ CHALLENGE_TYPE = 'tls-sni-01'.freeze
5
+
6
+ def hostname
7
+ digest = crypto.digest.hexdigest(authorization_key)
8
+ "#{digest[0..31]}.#{digest[32..64]}.acme.invalid"
9
+ end
10
+
11
+ def certificate
12
+ self_sign_certificate.certificate
13
+ end
14
+
15
+ def private_key
16
+ self_sign_certificate.private_key
17
+ end
18
+
19
+ private
20
+
21
+ def self_sign_certificate
22
+ @self_sign_certificate ||= Acme::Client::SelfSignCertificate.new(subject_alt_names: [hostname])
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ class Acme::Client::Resources::Registration
2
+ attr_reader :id, :key, :contact, :uri, :next_uri, :recover_uri, :term_of_service_uri
3
+
4
+ def initialize(client, response)
5
+ @client = client
6
+ @uri = response.headers['location']
7
+ assign_links(response.headers['Link'])
8
+ assign_attributes(response.body)
9
+ end
10
+
11
+ def get_terms
12
+ return unless @term_of_service_uri
13
+
14
+ @client.connection.get(@term_of_service_uri).body
15
+ end
16
+
17
+ def agree_terms
18
+ return true unless @term_of_service_uri
19
+
20
+ response = @client.connection.post(@uri, resource: 'reg', agreement: @term_of_service_uri)
21
+ response.success?
22
+ end
23
+
24
+ private
25
+
26
+ def assign_links(links)
27
+ @next_uri = links['next']
28
+ @recover_uri = links['recover']
29
+ @term_of_service_uri = links['terms-of-service']
30
+ end
31
+
32
+ def assign_attributes(body)
33
+ @id = body['id']
34
+ @key = body['key']
35
+ @contact = body['contact']
36
+ end
37
+ end