rack-acme 0.1.0

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