bullion 0.11.5 → 0.11.6
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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +3 -1
- data/db/schema.rb +4 -1
- data/lib/bullion/acme/error.rb +5 -0
- data/lib/bullion/helpers/acme.rb +18 -1
- data/lib/bullion/services/ca.rb +14 -35
- data/lib/bullion/version.rb +1 -1
- data/lib/bullion.rb +13 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 44ad24a873adf3bab2e535688588282797d477c7f973835094b52d00cb9c6592
|
|
4
|
+
data.tar.gz: ee339845aaaca5001f5a33f21c75448c8d59b19893191bbc91055d41b13edc66
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f663b5f7aedb812648c9065fd324ab0ab70a49b4ecb58c2b1160e12c84c93a5e340512be14812ec1e9d6790cce41344182df20e942d4ade2383ff81cf686dae
|
|
7
|
+
data.tar.gz: 7145c2c82d0e710de29bfc97dbe481464696d74a5381ac4dd55e56bd06605dc225dc6bd2a0593df459413d962c9583f5b3e6d133757b1e5ed75654af880b271f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.11.6](https://github.com/jgnagy/bullion/compare/bullion/v0.11.5...bullion/v0.11.6) (2026-10-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* give each request fiber its own database connection ([c47a277](https://github.com/jgnagy/bullion/commit/c47a2779ab6b115bbb0c9af75559c17010353962))
|
|
9
|
+
* give each request fiber its own database connection ([cca3b31](https://github.com/jgnagy/bullion/commit/cca3b313a6adf04ef9680698ca64f53027b58d3b))
|
|
10
|
+
* return orderNotReady instead of badCSR for non-ready finalize ([2d5fc6f](https://github.com/jgnagy/bullion/commit/2d5fc6f15f98ba39253b294868674073d56e5f81))
|
|
11
|
+
|
|
3
12
|
## [0.11.5](https://github.com/jgnagy/bullion/compare/bullion/v0.11.4...bullion/v0.11.5) (2026-09-21)
|
|
4
13
|
|
|
5
14
|
|
data/README.md
CHANGED
|
@@ -160,7 +160,7 @@ Whether run locally or via Docker, the following environment variables configure
|
|
|
160
160
|
| `CA_CERT_PATH` | `$CA_DIR/tls.crt` | Public cert for Bullion. If Bullion is an intermediate CA, you'll want to include the root CA's public cert in this file as well the signed cert for Bullion. |
|
|
161
161
|
| `CA_DOMAINS` | `example.com` | A comma-delimited list of domains for which Bullion will sign certificate requests. Subdomains are automatically allowed. Certificates containing other domains will be rejected. |
|
|
162
162
|
| `CERT_VALIDITY_DURATION` | `7776000` | How long should issued certs be valid (in seconds)? Default is 90 days. |
|
|
163
|
-
| `DATABASE_URL` | _None_ | **(Required)** A shorthand for telling Bullion how to connect to a database. Acceptable URLs will either begin with `sqlite3:` or [`trilogy://`](https://github.com/trilogy-libraries/trilogy/tree/main/contrib/ruby). |
|
|
163
|
+
| `DATABASE_URL` | _None_ | **(Required)** A shorthand for telling Bullion how to connect to a database. Acceptable URLs will either begin with `sqlite3:` or [`trilogy://`](https://github.com/trilogy-libraries/trilogy/tree/main/contrib/ruby). ActiveRecord connection options may be passed as query parameters, e.g. `trilogy://user:pass@host:3306/bullion?max_connections=25&checkout_timeout=5`. |
|
|
164
164
|
| `DNS01_NAMESERVERS` | _None_ | A comma-delimited list of nameservers to use for resolving [DNS-01](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) challenges. Usually you'll want this to be set to your _internal_ nameservers so internal names resolve correctly. When not set, it'll use the host's DNS. |
|
|
165
165
|
| `LOG_LEVEL` | `warn` | Log level for Bullion. Supported levels (starting with the noisiest) are debug, info, warn, error, and fatal. |
|
|
166
166
|
| `BULLION_PORT` | `9292` | TCP port Bullion will listen on. |
|
|
@@ -169,6 +169,8 @@ Whether run locally or via Docker, the following environment variables configure
|
|
|
169
169
|
| `WORKER_MEMORY_LIMIT` | `1024**3` | [Itsi worker memory limit](https://itsi.fyi/options/worker_memory_limit/) for each worker process (in bytes). Default is 1GiB. |
|
|
170
170
|
| `RACK_ENV` | `production`* | When run via Docker, the default is `production`, when run via `rake local_demo` it is `development`. Used to tell Bullion if it is run in development mode or for testing. |
|
|
171
171
|
|
|
172
|
+
Because Bullion serves requests through Itsi's [fiber scheduler](https://itsi.fyi/options/fiber_scheduler/), each in-flight request leases its own database connection. ActiveRecord's connection pool defaults to 5 connections, so raise `max_connections` on `DATABASE_URL` if you expect more concurrent requests. `checkout_timeout` (default 5 seconds) controls how long a request waits for a connection before failing.
|
|
173
|
+
|
|
172
174
|
### Integrating
|
|
173
175
|
|
|
174
176
|
Any client that speaks the [ACMEv2](https://tools.ietf.org/html/rfc8555) protocol can be pointed at `/acme/directory` and everything else can be auto-discovered.
|
data/db/schema.rb
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
#
|
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
|
12
12
|
|
|
13
|
-
ActiveRecord::Schema[8.1].define(version:
|
|
13
|
+
ActiveRecord::Schema[8.1].define(version: 2026_07_16_000000) do
|
|
14
14
|
create_table "accounts", force: :cascade do |t|
|
|
15
15
|
t.text "contacts", null: false
|
|
16
16
|
t.datetime "created_at", null: false
|
|
@@ -40,6 +40,9 @@ ActiveRecord::Schema[8.1].define(version: 2025_08_23_073342) do
|
|
|
40
40
|
t.string "csr_fingerprint", null: false
|
|
41
41
|
t.text "data", null: false
|
|
42
42
|
t.string "requester"
|
|
43
|
+
t.integer "revocation_reason"
|
|
44
|
+
t.boolean "revoked", default: false, null: false
|
|
45
|
+
t.datetime "revoked_at"
|
|
43
46
|
t.bigint "serial", null: false
|
|
44
47
|
t.string "subject", null: false
|
|
45
48
|
t.datetime "updated_at", null: false
|
data/lib/bullion/acme/error.rb
CHANGED
|
@@ -64,6 +64,11 @@ module Bullion
|
|
|
64
64
|
def acme_type = "malformed"
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
# ACME exception for orders that aren't ready to be finalized
|
|
68
|
+
class OrderNotReady < Bullion::Acme::Error
|
|
69
|
+
def acme_type = "orderNotReady"
|
|
70
|
+
end
|
|
71
|
+
|
|
67
72
|
# ACME exception for unsupported contacts in accounts
|
|
68
73
|
class UnsupportedContact < Bullion::Acme::Error
|
|
69
74
|
def acme_type = "unsupportedContact"
|
data/lib/bullion/helpers/acme.rb
CHANGED
|
@@ -125,7 +125,6 @@ module Bullion
|
|
|
125
125
|
# Make sure the CSR has a valid public key
|
|
126
126
|
raise Bullion::Acme::Errors::BadCsr unless csr.verify(csr.public_key)
|
|
127
127
|
|
|
128
|
-
return false unless order.ready_status?
|
|
129
128
|
raise Bullion::Acme::Errors::BadCsr if csr_cn && !csr_domains.include?(csr_cn)
|
|
130
129
|
raise Bullion::Acme::Errors::BadCsr unless csr_domains.sort == order.domains.sort
|
|
131
130
|
|
|
@@ -192,6 +191,24 @@ module Bullion
|
|
|
192
191
|
Bullion.config.ca.domains.none? { domain["value"].end_with?(it) }
|
|
193
192
|
end
|
|
194
193
|
end
|
|
194
|
+
|
|
195
|
+
# Builds the ACMEv2 order resource representation
|
|
196
|
+
# @see https://tools.ietf.org/html/rfc8555#section-7.1.3
|
|
197
|
+
def order_data(order)
|
|
198
|
+
data = {
|
|
199
|
+
status: order.status,
|
|
200
|
+
expires: order.expires,
|
|
201
|
+
notBefore: order.not_before,
|
|
202
|
+
notAfter: order.not_after,
|
|
203
|
+
identifiers: order.identifiers,
|
|
204
|
+
authorizations: order.authorizations.map { uri("/authorizations/#{it.id}") },
|
|
205
|
+
finalize: uri("/orders/#{order.id}/finalize")
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
data[:certificate] = uri("/certificates/#{order.certificate.id}") if order.valid_status?
|
|
209
|
+
|
|
210
|
+
data
|
|
211
|
+
end
|
|
195
212
|
end
|
|
196
213
|
end
|
|
197
214
|
end
|
data/lib/bullion/services/ca.rb
CHANGED
|
@@ -214,15 +214,7 @@ module Bullion
|
|
|
214
214
|
content_type "application/json"
|
|
215
215
|
add_acme_headers @new_nonce, additional: { "Location" => uri("/orders/#{order.id}") }
|
|
216
216
|
|
|
217
|
-
halt 201,
|
|
218
|
-
status: order.status,
|
|
219
|
-
expires: order.expires,
|
|
220
|
-
notBefore: order.not_before,
|
|
221
|
-
notAfter: order.not_after,
|
|
222
|
-
identifiers: order.identifiers,
|
|
223
|
-
authorizations: order.authorizations.map { uri("/authorizations/#{it.id}") },
|
|
224
|
-
finalize: uri("/orders/#{order.id}/finalize")
|
|
225
|
-
}.to_json
|
|
217
|
+
halt 201, order_data(order).to_json
|
|
226
218
|
rescue Bullion::Acme::Error => e
|
|
227
219
|
content_type "application/problem+json"
|
|
228
220
|
halt 400, { type: e.acme_error, detail: e.message }.to_json
|
|
@@ -237,19 +229,7 @@ module Bullion
|
|
|
237
229
|
|
|
238
230
|
order = Models::Order.find(params[:id])
|
|
239
231
|
|
|
240
|
-
|
|
241
|
-
status: order.status,
|
|
242
|
-
expires: order.expires,
|
|
243
|
-
notBefore: order.not_before,
|
|
244
|
-
notAfter: order.not_after,
|
|
245
|
-
identifiers: order.identifiers,
|
|
246
|
-
authorizations: order.authorizations.map { uri("/authorizations/#{it.id}") },
|
|
247
|
-
finalize: uri("/orders/#{order.id}/finalize")
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
data[:certificate] = uri("/certificates/#{order.certificate.id}") if order.valid_status?
|
|
251
|
-
|
|
252
|
-
data.to_json
|
|
232
|
+
order_data(order).to_json
|
|
253
233
|
rescue Bullion::Acme::Error => e
|
|
254
234
|
content_type "application/problem+json"
|
|
255
235
|
halt 400, { type: e.acme_error, detail: e.message }.to_json
|
|
@@ -267,6 +247,17 @@ module Bullion
|
|
|
267
247
|
|
|
268
248
|
order_csr = Models::OrderCsr.from_acme_request(order, @payload_data["csr"])
|
|
269
249
|
|
|
250
|
+
unless order.ready_status?
|
|
251
|
+
# Finalize is idempotent for orders that have already been finalized
|
|
252
|
+
halt 200, order_data(order).to_json if %w[processing valid].include?(order.status)
|
|
253
|
+
|
|
254
|
+
content_type "application/problem+json"
|
|
255
|
+
halt 403, {
|
|
256
|
+
type: Bullion::Acme::Errors::OrderNotReady.new.acme_error,
|
|
257
|
+
detail: "Order is not ready to be finalized"
|
|
258
|
+
}.to_json
|
|
259
|
+
end
|
|
260
|
+
|
|
270
261
|
unless acme_csr_valid?(order_csr)
|
|
271
262
|
content_type "application/problem+json"
|
|
272
263
|
halt 400, {
|
|
@@ -281,19 +272,7 @@ module Bullion
|
|
|
281
272
|
order.status = "valid"
|
|
282
273
|
order.save
|
|
283
274
|
|
|
284
|
-
|
|
285
|
-
status: order.status,
|
|
286
|
-
expires: order.expires,
|
|
287
|
-
notBefore: order.not_before,
|
|
288
|
-
notAfter: order.not_after,
|
|
289
|
-
identifiers: order.identifiers,
|
|
290
|
-
authorizations: order.authorizations.map { uri("/authorizations/#{it.id}") },
|
|
291
|
-
finalize: uri("/orders/#{order.id}/finalize")
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
data[:certificate] = uri("/certificates/#{order.certificate.id}") if order.valid_status?
|
|
295
|
-
|
|
296
|
-
data.to_json
|
|
275
|
+
order_data(order).to_json
|
|
297
276
|
rescue Bullion::Acme::Error => e
|
|
298
277
|
content_type "application/problem+json"
|
|
299
278
|
halt 422, { type: e.acme_error, detail: e.message }.to_json
|
data/lib/bullion/version.rb
CHANGED
data/lib/bullion.rb
CHANGED
|
@@ -140,6 +140,19 @@ Bullion.configure do |config|
|
|
|
140
140
|
config.nameservers = nameservers if nameservers
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
+
# Itsi processes each request inside its own Fiber, but all fibers on a thread
|
|
144
|
+
# share an ActiveSupport execution context by default. ActiveRecord leases a
|
|
145
|
+
# database connection per execution context, so without fiber isolation every
|
|
146
|
+
# concurrent request fiber on a thread shares (and races on) the same
|
|
147
|
+
# connection. Trilogy detects this and raises Trilogy::SynchronizationError.
|
|
148
|
+
# Mirror Itsi's own scheduler_mode hook (which itsi-server 0.2.27 never loads)
|
|
149
|
+
# and the Rails 8.1+ default by opting into fiber isolation, honoring Itsi's
|
|
150
|
+
# opt-out.
|
|
151
|
+
# @see https://github.com/jgnagy/bullion/issues/116
|
|
152
|
+
unless ENV["ITSI_DISABLE_AS_AUTO_FIBER_ISOLATION_LEVEL"]
|
|
153
|
+
ActiveSupport::IsolatedExecutionState.isolation_level = :fiber
|
|
154
|
+
end
|
|
155
|
+
|
|
143
156
|
# Internal requirements
|
|
144
157
|
require "bullion/version"
|
|
145
158
|
require "bullion/acme/error"
|