rack-acme 0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b9df198961605f27428650808b9e8ebf7a41fb02
4
+ data.tar.gz: '03123841beb1d4c992445f256b9a0d3730dc269f'
5
+ SHA512:
6
+ metadata.gz: 7eceb0a3822e15990141aef6db70308428d947f892323d568470bab4d5a490e6e3851d25a1a5c18d55878e6187d2041fd26bf77bf26467d0251becf944e77f19
7
+ data.tar.gz: 58bae57f3bc4ecfa359a4a059cdf05464f093d60fdffe681f1ad7880e10bdea55996d347bd81787d1b9f505321392408f9e7fca53b8ed4b6a521e4cdb18a6ebd
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+ require "rack"
3
+ require "acme-client"
4
+ require "openssl"
5
+ require "rack/acme/endpoint"
6
+ require "rack/acme/file_cache"
7
+
8
+ module Rack::Acme
9
+ class << self
10
+ attr_accessor :contact
11
+
12
+ def configure
13
+ yield self if block_given?
14
+ end
15
+
16
+ def cache
17
+ path = "./tokens"
18
+ @cache ||= FileCache.new(path)
19
+ end
20
+ attr_writer :cache
21
+
22
+ def issue(domain)
23
+ raise ArgumentError, "contact is nil" if contact.nil?
24
+
25
+ authorization = client.authorize(domain: domain)
26
+
27
+ case authorization.status
28
+ when "pending"
29
+ challenge = authorization.http01
30
+
31
+ token = challenge.token
32
+ challenge_content = challenge.file_content
33
+
34
+ cache[token] = challenge_content
35
+
36
+ challenge.request_verification
37
+
38
+ sleep 1
39
+
40
+ csr = ::Acme::Client::CertificateRequest.new(names: [domain])
41
+ certificate = client.new_certificate(csr)
42
+
43
+ certificate_handler.call(certificate) if certificate_handler.respond_to?(:call)
44
+ restart_handler.call if restart_handler.respond_to?(:call)
45
+
46
+ certificate
47
+
48
+ when "valid"
49
+ csr = Acme::Client::CertificateRequest.new(names: [domain])
50
+ certificate = client.new_certificate(csr)
51
+
52
+ certificate_handler.call(certificate) if certificate_handler.respond_to?(:call)
53
+ restart_handler.call if restart_handler.respond_to?(:call)
54
+
55
+ certificate
56
+ end
57
+ end
58
+
59
+ def certificate_handler
60
+ @certificate_handler || proc {}
61
+ end
62
+ attr_writer :certificate_handler
63
+
64
+ def restart_handler
65
+ @restart_handler || proc {}
66
+ end
67
+ attr_writer :restart_handler
68
+
69
+ def client
70
+ @client ||= build_client
71
+ end
72
+
73
+ def build_client
74
+ client = ::Acme::Client.new(
75
+ private_key: private_key,
76
+ endpoint: endpoint,
77
+ connection_options: connection_options
78
+ )
79
+
80
+ registration = client.register(contact: "mailto:#{contact}")
81
+ registration.agree_terms
82
+
83
+ client
84
+ end
85
+
86
+ def endpoint
87
+ @endpoint ||= default_endpoint
88
+ end
89
+ attr_writer :endpoint
90
+
91
+ def default_endpoint
92
+ "https://acme-staging.api.letsencrypt.org/"
93
+ end
94
+
95
+ def private_key
96
+ @private_key ||= build_private_key
97
+ end
98
+
99
+ def build_private_key
100
+ OpenSSL::PKey::RSA.new(4096)
101
+ end
102
+
103
+ def connection_options
104
+ @connection_options ||= { request: { open_timeout: 5, timeout: 5 } }
105
+ end
106
+ attr_writer :connection_options
107
+ end
108
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module Rack::Acme
3
+ class Endpoint
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ request = ::Rack::Request.new(env)
10
+
11
+ if (m = request.fullpath.match(%r{\A/.well-known/acme-challenge/(.+)\z}))
12
+ token = m[1]
13
+ content = ::Rack::Acme.cache[token]
14
+
15
+ if content
16
+ return [200, {}, [content]]
17
+ else
18
+ return [404, {}, ["No match for #{token}"]]
19
+ end
20
+ else
21
+ @app.call(env)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack::Acme
4
+ class FileCache
5
+ attr_accessor :path
6
+
7
+ def initialize(path)
8
+ @path = path
9
+ end
10
+
11
+ def [](token)
12
+ dest = File.expand_path(token, path)
13
+
14
+ File.read(dest) if File.exists?(dest)
15
+ end
16
+
17
+ def []=(token, value)
18
+ dest = File.expand_path(token, path)
19
+
20
+ File.write(dest, value)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ module Acme
3
+ VERSION = "0.1.0".freeze
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-acme
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Daniels
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: acme-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubygems-tasks
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.2'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.2'
83
+ description:
84
+ email:
85
+ - adam@mediadrive.ca
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - lib/rack/acme.rb
91
+ - lib/rack/acme/endpoint.rb
92
+ - lib/rack/acme/file_cache.rb
93
+ - lib/rack/acme/version.rb
94
+ homepage: https://github.com/adam12/rack-acme
95
+ licenses:
96
+ - MIT
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 2.5.2
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: ''
118
+ test_files: []