certmeister-rack 0.3.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.
- checksums.yaml +7 -0
- data/lib/certmeister/rack/app.rb +107 -0
- data/lib/certmeister/rack/symbolic_hash_accessor.rb +42 -0
- data/spec/certmeister/rack/app_spec.rb +373 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 50f3f6929f52a9500d3e1a1f1f983ed7de5a3fd7
|
4
|
+
data.tar.gz: c5b34e536ce55388c1937c8f25eef3bbd79f06c1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cd6d0dea0018201e2fb0e4e26a5ce1aa41f609ca6208d42e411ee3fd670cfa0a8cda1fd1fb820aa2ae02eae2951be134ecd47b8bd047a90a8385b04c1a0ebd22
|
7
|
+
data.tar.gz: a0b1d591d24dbf43d9a3dea978f7db4cabf1211130bf5ea14e9ad6e915570eb526c98a489da90448f2722e229bd83b5422734d5ca5644f29e9509110c7aa70b8
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
require 'certmeister/rack/symbolic_hash_accessor'
|
3
|
+
|
4
|
+
module Certmeister
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
|
8
|
+
class App
|
9
|
+
|
10
|
+
def initialize(ca)
|
11
|
+
@ca = ca
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
req = ::Rack::Request.new(env)
|
16
|
+
if req.path_info == '/ping'
|
17
|
+
if req.request_method == 'GET'
|
18
|
+
ok('PONG')
|
19
|
+
else
|
20
|
+
method_not_allowed
|
21
|
+
end
|
22
|
+
elsif req.path_info =~ %r{^/certificate/(.+)}
|
23
|
+
req.params['cn'] = $1
|
24
|
+
req.params['ip'] = req.ip
|
25
|
+
case req.request_method
|
26
|
+
when 'POST' then sign_action(req)
|
27
|
+
when 'GET' then fetch_action(req)
|
28
|
+
when 'DELETE' then remove_action(req)
|
29
|
+
else method_not_allowed
|
30
|
+
end
|
31
|
+
else
|
32
|
+
not_implemented
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def sign_action(req)
|
39
|
+
response = @ca.sign(SymbolicHashAccessor.new(req.params))
|
40
|
+
if response.hit?
|
41
|
+
redirect(req.path)
|
42
|
+
elsif response.denied?
|
43
|
+
forbidden(response.error)
|
44
|
+
else
|
45
|
+
internal_server_error(response.error)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def fetch_action(req)
|
50
|
+
response = @ca.fetch(SymbolicHashAccessor.new(req.params))
|
51
|
+
if response.hit?
|
52
|
+
ok(response.pem, 'application/x-pem-file')
|
53
|
+
elsif response.miss?
|
54
|
+
not_found
|
55
|
+
elsif response.denied?
|
56
|
+
forbidden(response.error)
|
57
|
+
else
|
58
|
+
internal_server_error(response.error)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def remove_action(req)
|
63
|
+
response = @ca.remove(SymbolicHashAccessor.new(req.params))
|
64
|
+
if response.hit?
|
65
|
+
ok
|
66
|
+
elsif response.miss?
|
67
|
+
not_found
|
68
|
+
elsif response.denied?
|
69
|
+
forbidden(response.error)
|
70
|
+
else
|
71
|
+
internal_server_error(response.error)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def ok(body = '200 OK', content_type = 'text/plain')
|
76
|
+
[200, {'Content-Type' => content_type}, [body]]
|
77
|
+
end
|
78
|
+
|
79
|
+
def redirect(location)
|
80
|
+
[303, {'Content-Type' => 'text/plain', 'Location' => location}, ['303 See Other']]
|
81
|
+
end
|
82
|
+
|
83
|
+
def not_found
|
84
|
+
[404, {'Content-Type' => 'text/plain'}, ["404 Not Found"]]
|
85
|
+
end
|
86
|
+
|
87
|
+
def forbidden(reason)
|
88
|
+
[403, {'Content-Type' => 'text/plain'}, ["403 Forbidden (#{reason})"]]
|
89
|
+
end
|
90
|
+
|
91
|
+
def method_not_allowed
|
92
|
+
[405, {'Content-Type' => 'text/plain'}, ['405 Method Not Allowed']]
|
93
|
+
end
|
94
|
+
|
95
|
+
def internal_server_error(reason)
|
96
|
+
[500, {'Content-Type' => 'text/plain'}, ["500 Internal Server Error (#{reason})"]]
|
97
|
+
end
|
98
|
+
|
99
|
+
def not_implemented
|
100
|
+
[501, {'Content-Type' => 'text/plain'}, ['501 Not Implemented']]
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
# Light-weight alternative to active_support's HashWithIndifferentAccess.
|
4
|
+
#
|
5
|
+
# The rack application must not symbolize params from the client, because
|
6
|
+
# symbols cannot be garbage-collected, and so provide a memory starvation
|
7
|
+
# attack.
|
8
|
+
#
|
9
|
+
# So instead we wrap the parameters with a symbolic accessor, so that
|
10
|
+
# Certmeister::Base and Certmeister::Policy::* can refer to parameters
|
11
|
+
# symbolically.
|
12
|
+
|
13
|
+
module Certmeister
|
14
|
+
|
15
|
+
module Rack
|
16
|
+
|
17
|
+
class SymbolicHashAccessor < SimpleDelegator
|
18
|
+
|
19
|
+
def initialize(hash)
|
20
|
+
@hash = hash
|
21
|
+
super(@hash)
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](key)
|
25
|
+
@hash[key.to_s]
|
26
|
+
end
|
27
|
+
|
28
|
+
def fetch(*args)
|
29
|
+
args[0] = args[0].to_s
|
30
|
+
@hash.fetch(*args)
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_key?(key)
|
34
|
+
@hash.has_key?(key.to_s)
|
35
|
+
end
|
36
|
+
alias_method :include?, :has_key?
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,373 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rack/test'
|
3
|
+
|
4
|
+
require 'certmeister'
|
5
|
+
require 'certmeister/rack/app'
|
6
|
+
|
7
|
+
describe Certmeister::Rack::App do
|
8
|
+
|
9
|
+
include Rack::Test::Methods
|
10
|
+
|
11
|
+
let(:response) { double(Certmeister::Response).as_null_object }
|
12
|
+
let(:ca) { double(Certmeister::Base, sign: response, fetch: response, remove: response) }
|
13
|
+
let(:app) { Certmeister::Rack::App.new(ca) }
|
14
|
+
|
15
|
+
it "GET /ping always PONGs (although one day we want a health check)" do
|
16
|
+
get "/ping"
|
17
|
+
expect(last_response.status).to eql 200
|
18
|
+
expect(last_response.headers['Content-Type']).to eql "text/plain"
|
19
|
+
expect(last_response.body).to eql "PONG"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "/ping returns 405 Method Not Allowed for other HTTP verbs" do
|
23
|
+
head "/ping"
|
24
|
+
expect(last_response.status).to eql 405
|
25
|
+
expect(last_response.headers['Content-Type']).to eql "text/plain"
|
26
|
+
expect(last_response.body).to eql "405 Method Not Allowed"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns 501 Not Implemented for an unknown URI" do
|
30
|
+
get "/nonexistent"
|
31
|
+
expect(last_response.status).to eql 501
|
32
|
+
expect(last_response.headers['Content-Type']).to eql "text/plain"
|
33
|
+
expect(last_response.body).to eql "501 Not Implemented"
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "POST /certificate/:cn" do
|
37
|
+
|
38
|
+
context "parameter handling" do
|
39
|
+
|
40
|
+
let (:response) { Certmeister::Response.hit("...crt...") }
|
41
|
+
before(:each) do
|
42
|
+
post "/certificate/axl.starjuice.net", {"csr" => "...csr..."}, {"REMOTE_ADDR" => "192.168.1.2"}
|
43
|
+
end
|
44
|
+
|
45
|
+
it "copies the cn into the params" do
|
46
|
+
expect(ca).to have_received(:sign).with hash_including(cn: "axl.starjuice.net")
|
47
|
+
end
|
48
|
+
|
49
|
+
it "copies the ip into the params" do
|
50
|
+
expect(ca).to have_received(:sign).with hash_including(ip: "192.168.1.2")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "passes the form params into the CA" do
|
54
|
+
expect(ca).to have_received(:sign).with hash_including(csr: "...csr...")
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
context "on hit" do
|
60
|
+
|
61
|
+
let (:response) { Certmeister::Response.hit("...crt...") }
|
62
|
+
before(:each) do
|
63
|
+
post "/certificate/axl.starjuice.net", {"csr" => "...csr..."}
|
64
|
+
end
|
65
|
+
|
66
|
+
it "returns 303 See Other" do
|
67
|
+
expect(last_response.status).to eql 303
|
68
|
+
end
|
69
|
+
|
70
|
+
it "offers the same resource URI as Location" do
|
71
|
+
expect(last_response.headers['Location']).to eql "/certificate/axl.starjuice.net"
|
72
|
+
end
|
73
|
+
|
74
|
+
it "describes the HTTP status in the body" do
|
75
|
+
expect(last_response.body).to eql '303 See Other'
|
76
|
+
end
|
77
|
+
|
78
|
+
it "describes the body as text/plain" do
|
79
|
+
expect(last_response.headers['Content-Type']).to eql 'text/plain'
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
context "on denied" do
|
85
|
+
|
86
|
+
let (:response) { Certmeister::Response.denied("not enough mojo") }
|
87
|
+
before(:each) do
|
88
|
+
post "/certificate/axl.starjuice.net", {"csr" => "...csr..."}
|
89
|
+
end
|
90
|
+
|
91
|
+
it "returns 403 Forbidden" do
|
92
|
+
expect(last_response.status).to eql 403
|
93
|
+
end
|
94
|
+
|
95
|
+
it "describes the HTTP status in the body" do
|
96
|
+
expect(last_response.body).to eql '403 Forbidden (not enough mojo)'
|
97
|
+
end
|
98
|
+
|
99
|
+
it "describes the body as text/plain" do
|
100
|
+
expect(last_response.headers['Content-Type']).to eql 'text/plain'
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
context "on error" do
|
106
|
+
|
107
|
+
let (:response) { Certmeister::Response.error("all is lost") }
|
108
|
+
|
109
|
+
before(:each) do
|
110
|
+
post "/certificate/axl.starjuice.net", {"csr" => "...csr..."}
|
111
|
+
end
|
112
|
+
|
113
|
+
it "returns 500 Internal Server Error" do
|
114
|
+
expect(last_response.status).to eql 500
|
115
|
+
end
|
116
|
+
|
117
|
+
it "describes the HTTP status in the body" do
|
118
|
+
expect(last_response.body).to eql "500 Internal Server Error (all is lost)"
|
119
|
+
end
|
120
|
+
|
121
|
+
it "describes the body as text/plain" do
|
122
|
+
expect(last_response.headers['Content-Type']).to eql 'text/plain'
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "GET /certificate/:cn" do
|
130
|
+
|
131
|
+
context "parameter handling" do
|
132
|
+
|
133
|
+
let (:response) { Certmeister::Response.hit("...crt...") }
|
134
|
+
before(:each) do
|
135
|
+
get "/certificate/axl.starjuice.net", {"psk" => "...secret..."}, {"REMOTE_ADDR" => "192.168.1.2"}
|
136
|
+
end
|
137
|
+
|
138
|
+
it "copies the cn into the params" do
|
139
|
+
expect(ca).to have_received(:fetch).with hash_including(cn: "axl.starjuice.net")
|
140
|
+
end
|
141
|
+
|
142
|
+
it "copies the ip into the params" do
|
143
|
+
expect(ca).to have_received(:fetch).with hash_including(ip: "192.168.1.2")
|
144
|
+
end
|
145
|
+
|
146
|
+
it "passes the form params into the CA" do
|
147
|
+
expect(ca).to have_received(:fetch).with hash_including(psk: "...secret...")
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
context "on hit" do
|
153
|
+
|
154
|
+
let (:response) { Certmeister::Response.hit("...crt...") }
|
155
|
+
before(:each) do
|
156
|
+
get "/certificate/axl.starjuice.net"
|
157
|
+
end
|
158
|
+
|
159
|
+
it "returns 200 OK" do
|
160
|
+
expect(last_response.status).to eql 200
|
161
|
+
end
|
162
|
+
|
163
|
+
it "provides the PEM-encoded X.509 certificate in the body" do
|
164
|
+
expect(last_response.body).to eql "...crt..."
|
165
|
+
end
|
166
|
+
|
167
|
+
it "describes the body as application/x-pem-file" do
|
168
|
+
expect(last_response.headers['Content-Type']).to eql 'application/x-pem-file'
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
context "on miss" do
|
174
|
+
|
175
|
+
let (:response) { Certmeister::Response.miss }
|
176
|
+
before(:each) do
|
177
|
+
get "/certificate/axl.starjuice.net"
|
178
|
+
end
|
179
|
+
|
180
|
+
it "returns 404 Not Found" do
|
181
|
+
expect(last_response.status).to eql 404
|
182
|
+
end
|
183
|
+
|
184
|
+
it "describes the HTTP status in the body" do
|
185
|
+
expect(last_response.body).to eql "404 Not Found"
|
186
|
+
end
|
187
|
+
|
188
|
+
it "describes the body as text/plain" do
|
189
|
+
expect(last_response.headers['Content-Type']).to eql "text/plain"
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
context "on denied" do
|
195
|
+
|
196
|
+
let (:response) { Certmeister::Response.denied("your fu is weak") }
|
197
|
+
before(:each) do
|
198
|
+
get "/certificate/axl.starjuice.net"
|
199
|
+
end
|
200
|
+
|
201
|
+
it "returns 403 Forbidden" do
|
202
|
+
expect(last_response.status).to eql 403
|
203
|
+
end
|
204
|
+
|
205
|
+
it "describes the HTTP status in the body" do
|
206
|
+
expect(last_response.body).to eql '403 Forbidden (your fu is weak)'
|
207
|
+
end
|
208
|
+
|
209
|
+
it "describes the body as text/plain" do
|
210
|
+
expect(last_response.headers['Content-Type']).to eql 'text/plain'
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
context "on error" do
|
216
|
+
|
217
|
+
let (:response) { Certmeister::Response.error("i have fallen") }
|
218
|
+
|
219
|
+
before(:each) do
|
220
|
+
get "/certificate/axl.starjuice.net"
|
221
|
+
end
|
222
|
+
|
223
|
+
it "returns 500 Internal Server Error" do
|
224
|
+
expect(last_response.status).to eql 500
|
225
|
+
end
|
226
|
+
|
227
|
+
it "describes the HTTP status in the body" do
|
228
|
+
expect(last_response.body).to eql "500 Internal Server Error (i have fallen)"
|
229
|
+
end
|
230
|
+
|
231
|
+
it "describes the body as text/plain" do
|
232
|
+
expect(last_response.headers['Content-Type']).to eql 'text/plain'
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
describe "DELETE /certificate/:cn" do
|
240
|
+
|
241
|
+
context "parameter handling" do
|
242
|
+
|
243
|
+
let (:response) { Certmeister::Response.hit }
|
244
|
+
before(:each) do
|
245
|
+
delete "/certificate/axl.starjuice.net", {"authoritah" => "...warhammer..."}, {"REMOTE_ADDR" => "192.168.1.2"}
|
246
|
+
end
|
247
|
+
|
248
|
+
it "copies the cn into the params" do
|
249
|
+
expect(ca).to have_received(:remove).with hash_including(cn: "axl.starjuice.net")
|
250
|
+
end
|
251
|
+
|
252
|
+
it "copies the ip into the params" do
|
253
|
+
expect(ca).to have_received(:remove).with hash_including(ip: "192.168.1.2")
|
254
|
+
end
|
255
|
+
|
256
|
+
it "passes the form params into the CA" do
|
257
|
+
expect(ca).to have_received(:remove).with hash_including(authoritah: "...warhammer...")
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
|
262
|
+
context "on hit" do
|
263
|
+
|
264
|
+
let (:response) { Certmeister::Response.hit }
|
265
|
+
before(:each) do
|
266
|
+
delete "/certificate/axl.starjuice.net"
|
267
|
+
end
|
268
|
+
|
269
|
+
it "returns 200 OK" do
|
270
|
+
expect(last_response.status).to eql 200
|
271
|
+
end
|
272
|
+
|
273
|
+
it "describes the HTTP status in the body" do
|
274
|
+
expect(last_response.body).to eql "200 OK"
|
275
|
+
end
|
276
|
+
|
277
|
+
it "describes the body as text/plain" do
|
278
|
+
expect(last_response.headers['Content-Type']).to eql 'text/plain'
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
|
283
|
+
context "on miss" do
|
284
|
+
|
285
|
+
let (:response) { Certmeister::Response.miss }
|
286
|
+
before(:each) do
|
287
|
+
delete "/certificate/axl.starjuice.net"
|
288
|
+
end
|
289
|
+
|
290
|
+
it "returns 404 Not Found" do
|
291
|
+
expect(last_response.status).to eql 404
|
292
|
+
end
|
293
|
+
|
294
|
+
it "describes the HTTP status in the body" do
|
295
|
+
expect(last_response.body).to eql "404 Not Found"
|
296
|
+
end
|
297
|
+
|
298
|
+
it "describes the body as text/plain" do
|
299
|
+
expect(last_response.headers['Content-Type']).to eql "text/plain"
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
|
304
|
+
context "on denied" do
|
305
|
+
|
306
|
+
let (:response) { Certmeister::Response.denied("y u no boss") }
|
307
|
+
before(:each) do
|
308
|
+
delete "/certificate/axl.starjuice.net"
|
309
|
+
end
|
310
|
+
|
311
|
+
it "returns 403 Forbidden" do
|
312
|
+
expect(last_response.status).to eql 403
|
313
|
+
end
|
314
|
+
|
315
|
+
it "describes the HTTP status in the body" do
|
316
|
+
expect(last_response.body).to eql '403 Forbidden (y u no boss)'
|
317
|
+
end
|
318
|
+
|
319
|
+
it "describes the body as text/plain" do
|
320
|
+
expect(last_response.headers['Content-Type']).to eql 'text/plain'
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
context "on error" do
|
326
|
+
|
327
|
+
let (:response) { Certmeister::Response.error("shuffled off this mortal coil") }
|
328
|
+
|
329
|
+
before(:each) do
|
330
|
+
delete "/certificate/axl.starjuice.net"
|
331
|
+
end
|
332
|
+
|
333
|
+
it "returns 500 Internal Server Error" do
|
334
|
+
expect(last_response.status).to eql 500
|
335
|
+
end
|
336
|
+
|
337
|
+
it "describes the HTTP status in the body" do
|
338
|
+
expect(last_response.body).to eql "500 Internal Server Error (shuffled off this mortal coil)"
|
339
|
+
end
|
340
|
+
|
341
|
+
it "describes the body as text/plain" do
|
342
|
+
expect(last_response.headers['Content-Type']).to eql 'text/plain'
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
347
|
+
end
|
348
|
+
|
349
|
+
describe "other verbs on /certificate/:cn" do
|
350
|
+
|
351
|
+
context "(e.g. HEAD)" do
|
352
|
+
|
353
|
+
before(:each) do
|
354
|
+
head "/certificate/axl.starjuice.net"
|
355
|
+
end
|
356
|
+
|
357
|
+
it "returns 405 Method Not Allowed" do
|
358
|
+
expect(last_response.status).to eql 405
|
359
|
+
end
|
360
|
+
|
361
|
+
it "describes the HTTP status in the body" do
|
362
|
+
expect(last_response.body).to eql '405 Method Not Allowed'
|
363
|
+
end
|
364
|
+
|
365
|
+
it "describes the body as text/plain" do
|
366
|
+
expect(last_response.headers['Content-Type']).to eql 'text/plain'
|
367
|
+
end
|
368
|
+
|
369
|
+
end
|
370
|
+
|
371
|
+
end
|
372
|
+
|
373
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: certmeister-rack
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sheldon Hearn
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: certmeister
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.3.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.3.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rack-test
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.6'
|
55
|
+
description: This gem provides a rack application to offer an HTTP service around
|
56
|
+
certmeister, the conditional autosigning certificate authority.
|
57
|
+
email:
|
58
|
+
- sheldonh@starjuice.net
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- lib/certmeister/rack/app.rb
|
64
|
+
- lib/certmeister/rack/symbolic_hash_accessor.rb
|
65
|
+
- spec/certmeister/rack/app_spec.rb
|
66
|
+
homepage: https://github.com/sheldonh/certmeister
|
67
|
+
licenses:
|
68
|
+
- MIT
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 2.2.1
|
87
|
+
signing_key:
|
88
|
+
specification_version: 4
|
89
|
+
summary: Rack application for certmeister
|
90
|
+
test_files:
|
91
|
+
- spec/certmeister/rack/app_spec.rb
|