stitches 5.1.0.RC4 → 5.1.0.RC5
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/README.md +47 -43
- data/lib/stitches/calling_service_client.rb +3 -3
- data/lib/stitches/calling_service_middleware.rb +4 -5
- data/lib/stitches/calling_service_name.rb +1 -10
- data/lib/stitches/version.rb +1 -1
- data/spec/calling_service_middleware_spec.rb +2 -2
- data/spec/calling_service_name_spec.rb +2 -2
- 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: f589f4d3f2537a4f9b17328ad88ca1b89031bd80309eddc63657b2799fb7e0e9
|
|
4
|
+
data.tar.gz: 1c51e01a86ee6ad3fa70f7e7e69b9a700bfc2ea87040f8b9c059e52648a70632
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f5e2b42fa7a6609e8dc8e6bec1cf863e4c2e0e7d5bc47ae581187ce7ae2b15c672ec407fb20d07fe3829807e0ec976cbb7b56eeb47d47303352d3807ffd99b40
|
|
7
|
+
data.tar.gz: 766a73badd49a7aee8c3b16635230082dc0fb7e39cba5e20ef0e7acaaab42641874dd2348b233adcfa7f15aee883bef42965f82d3a7d46187f04799a1e632939
|
data/README.md
CHANGED
|
@@ -42,73 +42,77 @@ Stitches.configure do |config|
|
|
|
42
42
|
end
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
### Caller Identification
|
|
45
|
+
### Caller Identification
|
|
46
46
|
|
|
47
47
|
When API key auth is disabled, services lose the ability to identify which
|
|
48
|
-
internal service is calling them.
|
|
49
|
-
|
|
48
|
+
internal service is calling them. Stitches 5.1+ provides two mechanisms that
|
|
49
|
+
work together to restore this — one automatic, one opt-in.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
The calling service header name defaults to `X-StitchFix-Calling-Service` but
|
|
52
|
+
is configurable via `Stitches.configuration.calling_service_header`.
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
class Api::ApiController < ActionController::API
|
|
55
|
-
include Stitches::CallingServiceName
|
|
54
|
+
#### CallingServiceMiddleware (automatic)
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
`CallingServiceMiddleware` is registered via the railtie and runs after the
|
|
57
|
+
`ApiKey` middleware. When no auth middleware has populated the caller identity
|
|
58
|
+
env var (`Stitches.configuration.env_var_to_hold_api_client`), it reads the
|
|
59
|
+
calling service header and populates it with a `CallingServiceClient` struct.
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
**This means existing code that reads the caller identity object's `.name`
|
|
62
|
+
continues to work with no changes** — the value now comes from the header
|
|
63
|
+
instead of a DB lookup.
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```
|
|
65
|
+
`CallingServiceClient` implements:
|
|
66
|
+
- `.name` — the header value (e.g. "my-app")
|
|
67
|
+
- `.id` — nil
|
|
68
|
+
- `.key` — nil
|
|
70
69
|
|
|
71
|
-
**Resolution order:**
|
|
70
|
+
**Resolution order (for `request.env[env_var_to_hold_api_client]`):**
|
|
72
71
|
|
|
73
|
-
1. `
|
|
74
|
-
2.
|
|
75
|
-
3. `
|
|
72
|
+
1. If the `ApiKey` middleware authenticated a key → `ApiClient` record (has `.name`, `.id`, `.key`)
|
|
73
|
+
2. If JWT or other auth middleware set the env var → that object (e.g. a user)
|
|
74
|
+
3. If neither ran, but `X-StitchFix-Calling-Service` header is present → `CallingServiceClient` struct
|
|
75
|
+
4. If nothing → nil
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
stitches middleware still populates `api_client`, so the fallback returns the
|
|
79
|
-
authenticated name. When true, callers identify themselves via the header.
|
|
77
|
+
**Middleware ordering:** `ApiKey` → `CallingServiceMiddleware` → `ValidMimeType`
|
|
80
78
|
|
|
81
|
-
|
|
79
|
+
#### CallingServiceName concern (opt-in)
|
|
82
80
|
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
For cases where you want strictly the header value (e.g. metrics tags where
|
|
82
|
+
you never want a human user's name), include the concern:
|
|
85
83
|
|
|
86
84
|
```ruby
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
class Api::ApiController < ActionController::API
|
|
86
|
+
include Stitches::CallingServiceName
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns the header value only, empty string if absent:
|
|
90
|
+
calling_service_name # => "my-app" or ""
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
#### Configuration
|
|
94
|
+
|
|
95
|
+
The header name is configurable (defaults to `X-StitchFix-Calling-Service`):
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
Stitches.configure do |config|
|
|
99
|
+
config.calling_service_header = "X-My-Custom-Header"
|
|
100
|
+
end
|
|
101
|
+
```
|
|
95
102
|
|
|
96
103
|
#### Security considerations
|
|
97
104
|
|
|
98
105
|
`X-StitchFix-Calling-Service` is a **self-declared, unsigned header**. Any
|
|
99
106
|
caller that can reach your service can set it to any value. This means:
|
|
100
107
|
|
|
101
|
-
- **Do not use
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
can reach the endpoint.
|
|
108
|
+
- **Do not use it as a sole authorization mechanism** for sensitive operations
|
|
109
|
+
unless the network layer guarantees that only the legitimate service can
|
|
110
|
+
reach the endpoint.
|
|
105
111
|
- **Strip this header at public ingress points** (Traefik, ALB, API gateway)
|
|
106
112
|
to prevent external callers from spoofing internal service identities.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
This header provides identification only. If you need to verify the caller's
|
|
111
|
-
identity cryptographically, use mTLS or a service mesh AuthorizationPolicy.
|
|
113
|
+
- **This header replaces API key-based identity, not authorization.** It
|
|
114
|
+
provides identification only. If you need to verify the caller's identity
|
|
115
|
+
cryptographically, use mTLS or a service mesh AuthorizationPolicy.
|
|
112
116
|
- **Safe uses:** stats tagging, logging, `updated_by` audit fields, routing
|
|
113
117
|
hints, non-security-critical behavioral branching.
|
|
114
118
|
- **Unsafe without network enforcement:** access control decisions, privilege
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
module Stitches
|
|
2
2
|
# Lightweight stand-in for ApiClient when the caller is identified by
|
|
3
|
-
# the
|
|
4
|
-
#
|
|
5
|
-
#
|
|
3
|
+
# the calling service header rather than an API key lookup. Implements
|
|
4
|
+
# the same interface (.name, .id, .key) so existing code that reads
|
|
5
|
+
# the caller identity object continues to work.
|
|
6
6
|
CallingServiceClient = Struct.new(:name) do
|
|
7
7
|
def id
|
|
8
8
|
nil
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
require_relative 'calling_service_client'
|
|
2
2
|
|
|
3
3
|
module Stitches
|
|
4
|
-
# Rack middleware that populates the
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
4
|
+
# Rack middleware that populates the caller identity env var from the
|
|
5
|
+
# calling service header when no other auth middleware has already set
|
|
6
|
+
# it. This allows existing code that reads the caller identity object
|
|
7
|
+
# to work transparently after API keys are disabled.
|
|
8
8
|
class CallingServiceMiddleware
|
|
9
9
|
def initialize(app)
|
|
10
10
|
@app = app
|
|
@@ -15,7 +15,6 @@ module Stitches
|
|
|
15
15
|
|
|
16
16
|
unless env[client_key]
|
|
17
17
|
header_name = Stitches.configuration.calling_service_header
|
|
18
|
-
header_name = CallingServiceName::DEFAULT_CALLING_SERVICE_HEADER unless header_name.present?
|
|
19
18
|
rack_key = "HTTP_#{header_name.upcase.tr('-', '_')}"
|
|
20
19
|
|
|
21
20
|
if (name = env[rack_key]).present?
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
module Stitches
|
|
2
2
|
module CallingServiceName
|
|
3
|
-
DEFAULT_CALLING_SERVICE_HEADER = "X-StitchFix-Calling-Service"
|
|
4
|
-
|
|
5
3
|
def calling_service_name
|
|
6
4
|
@calling_service_name ||=
|
|
7
|
-
request.headers[
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
private
|
|
11
|
-
|
|
12
|
-
def calling_service_header_name
|
|
13
|
-
configured = Stitches.configuration.calling_service_header
|
|
14
|
-
configured.present? ? configured : DEFAULT_CALLING_SERVICE_HEADER
|
|
5
|
+
request.headers[Stitches.configuration.calling_service_header].presence || ""
|
|
15
6
|
end
|
|
16
7
|
end
|
|
17
8
|
end
|
data/lib/stitches/version.rb
CHANGED
|
@@ -9,14 +9,14 @@ describe Stitches::CallingServiceMiddleware do
|
|
|
9
9
|
|
|
10
10
|
describe "#call" do
|
|
11
11
|
context "when the header is present and env var is not set" do
|
|
12
|
-
before { env["HTTP_X_STITCHFIX_CALLING_SERVICE"] = "
|
|
12
|
+
before { env["HTTP_X_STITCHFIX_CALLING_SERVICE"] = "my-app" }
|
|
13
13
|
|
|
14
14
|
it "populates the env var with a CallingServiceClient" do
|
|
15
15
|
_status, result_env, _body = middleware.call(env)
|
|
16
16
|
client = result_env[client_key]
|
|
17
17
|
|
|
18
18
|
expect(client).to be_a(Stitches::CallingServiceClient)
|
|
19
|
-
expect(client.name).to eq("
|
|
19
|
+
expect(client.name).to eq("my-app")
|
|
20
20
|
expect(client.id).to be_nil
|
|
21
21
|
expect(client.key).to be_nil
|
|
22
22
|
end
|
|
@@ -13,10 +13,10 @@ describe Stitches::CallingServiceName do
|
|
|
13
13
|
|
|
14
14
|
describe "#calling_service_name" do
|
|
15
15
|
context "when the header is present" do
|
|
16
|
-
let(:headers) { {"X-StitchFix-Calling-Service" => "
|
|
16
|
+
let(:headers) { {"X-StitchFix-Calling-Service" => "my-app"} }
|
|
17
17
|
|
|
18
18
|
it "returns the header value" do
|
|
19
|
-
expect(fake_controller.calling_service_name).to eq("
|
|
19
|
+
expect(fake_controller.calling_service_name).to eq("my-app")
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|