patchwork-rb 0.1.6 → 0.1.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 564b479edcfcbad47a6b170dc219eecc6a5cd633c066f10a070631dd12583860
4
- data.tar.gz: 0f0768a2e84d273f34eaebb0a6bef98f280dc6ff193f8ee72ded3ab6b3b9595a
3
+ metadata.gz: 966c3f0f6ac7395698a0f34d355813f86af4f8e685623197c0018b9c70ad53f7
4
+ data.tar.gz: 7919e2bd0ea6b8be6868f8bdc8972baaa727b9722d6be784458df7b702af1c96
5
5
  SHA512:
6
- metadata.gz: b4b7217e76620606f0b16b56d1e1828ebc0b36b86f2c99100ae0324adef914d772ca425017dc76a7e1c88f005c46586fd55be8ba5cb1abea895fb0dabf77e9d6
7
- data.tar.gz: 71b097330f26014ec0447a8fb90afa21d67400b6314bbb3783b6399a7ce1669d91773ce9054aba74ba76370b95b833e027a73ab5fd1c38f5c1ca31c4e83658d3
6
+ metadata.gz: b84168c956d15378261d722a48d3de3a32df59b6176d10b3ed5d6789db6bbaf024b8bafbc89579edf2a497c5e1fbf238c7d9d894eebd22dffcbece6d500e9aa3
7
+ data.tar.gz: 5507f4f8baaa7c454669a3ce71b611313d78d58ba369b28969bfeada6488332122b38606e32672e12db0c0ec98baa96d12af1ca6daa728d1db8e7b55f238ee1d
data/README.md CHANGED
@@ -238,7 +238,19 @@ A signature carries a label. `v1` covers the method, the path and the raw body.
238
238
 
239
239
  The gateway and the concern verify either label, and pass the query for you. If you verify with the primitives instead, pass `query:` — the raw query string, `request.query_string` in Rack and Rails. Leave it out and a `v2` value is checked against the bare path, which will not match.
240
240
 
241
- **A GET tool's arguments are still not protected yet.** Patchwork sends both labels while consumers upgrade, and `v1` does not cover the query, so altering a GET tool's arguments in flight still passes. That closes when Patchwork stops sending `v1`. Until then, give any tool whose arguments matter — ids, amounts, anything that writes — a `POST` binding, where the arguments travel in the signed body.
241
+ **By default a GET tool's arguments are not protected yet.** Patchwork sends both labels while consumers upgrade, and `v1` does not cover the query, so a header carrying both verifies through `v1` even if the query changed.
242
+
243
+ Two ways to close that:
244
+
245
+ ```ruby
246
+ use Patchwork::Gateway,
247
+ resolve: ...,
248
+ labels: [ Patchwork::Signature::V2 ]
249
+ ```
250
+
251
+ Requiring `v2` refuses any signature that does not cover the query. Patchwork sends `v2` on every tool call, the relay mint, the connection probe and webhook deliveries, so this is safe to turn on today. It will fail against a platform that has not started sending `v2`, which is why it is not the default.
252
+
253
+ Or give any tool whose arguments matter — ids, amounts, anything that writes — a `POST` binding, where the arguments travel in the signed body.
242
254
 
243
255
  The signed path is the one your app sees. If a proxy rewrites paths before your app does, mount the gateway where the original path is still intact.
244
256
 
@@ -33,7 +33,7 @@ module Patchwork
33
33
 
34
34
  def initialize(app, resolve:, subject: nil, mint_path: nil, health_check: true,
35
35
  health_path: HealthCheck::PATH, max_body_bytes: MAX_BODY_BYTES,
36
- replay_guard: nil)
36
+ replay_guard: nil, labels: Signature::LABELS)
37
37
  raise ArgumentError, "resolve must respond to #call" unless resolve.respond_to?(:call)
38
38
  raise ArgumentError, "subject must respond to #decode" if subject && !subject.respond_to?(:decode)
39
39
  raise ArgumentError, "replay_guard must respond to #call" if replay_guard && !replay_guard.respond_to?(:call)
@@ -48,6 +48,8 @@ module Patchwork
48
48
  @health_path = health_path.to_s
49
49
  @max_body_bytes = Integer(max_body_bytes)
50
50
  @replay_guard = replay_guard
51
+ @labels = Array(labels).map(&:to_s)
52
+ raise ArgumentError, "labels must name a signature version" if (@labels & Signature::LABELS).empty?
51
53
  end
52
54
 
53
55
  def call(env)
@@ -132,7 +134,8 @@ module Patchwork
132
134
  method: method,
133
135
  path: request.path,
134
136
  query: request.query_string,
135
- body: body
137
+ body: body,
138
+ labels: @labels
136
139
  )
137
140
  body
138
141
  end
@@ -69,8 +69,16 @@ module Patchwork
69
69
  # Accepts either label. During the migration Patchwork sends both, so a
70
70
  # reader that understands v2 must still accept v1 from a platform that has
71
71
  # not started sending it.
72
- def self.verify(secrets:, header:, method:, path:, body:, query: nil, skew: SKEW, now: Time.now.to_i)
72
+ # `labels:` narrows what counts. The default accepts either, which is what
73
+ # the migration needs. Pass [V2] to refuse a signature that does not cover
74
+ # the query — stronger, but it fails against a platform still sending v1
75
+ # alone.
76
+ def self.verify(secrets:, header:, method:, path:, body:, query: nil, labels: LABELS,
77
+ skew: SKEW, now: Time.now.to_i)
73
78
  secrets = usable!(secrets)
79
+ accepted = Array(labels).map(&:to_s) & LABELS
80
+ raise ArgumentError, "no known label to verify against" if accepted.empty?
81
+
74
82
  parsed = parse(header)
75
83
  return :bad if parsed.nil?
76
84
  return :stale unless fresh?(parsed, skew: skew, now: now)
@@ -78,7 +86,7 @@ module Patchwork
78
86
  # The body is hashed once however many candidates the header carries.
79
87
  body_digest = digest(body)
80
88
 
81
- matched = LABELS.any? do |label|
89
+ matched = accepted.any? do |label|
82
90
  candidates = parsed.signatures.fetch(label, []).select { |value| HEX_SHA256.match?(value) }
83
91
  next false if candidates.empty?
84
92
 
@@ -90,9 +98,10 @@ module Patchwork
90
98
  matched ? :ok : :bad
91
99
  end
92
100
 
93
- def self.verify!(secrets:, header:, method:, path:, body:, query: nil, skew: SKEW, now: Time.now.to_i)
101
+ def self.verify!(secrets:, header:, method:, path:, body:, query: nil, labels: LABELS,
102
+ skew: SKEW, now: Time.now.to_i)
94
103
  case verify(secrets: secrets, header: header, method: method, path: path, body: body,
95
- query: query, skew: skew, now: now)
104
+ query: query, labels: labels, skew: skew, now: now)
96
105
  when :stale then raise StaleSignature, "signature timestamp outside the #{skew}s window"
97
106
  when :bad then raise InvalidSignature, "signature did not match"
98
107
  else true
@@ -1,3 +1,3 @@
1
1
  module Patchwork
2
- VERSION = "0.1.6".freeze
2
+ VERSION = "0.1.7".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patchwork-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chromablue Labs