verikloak-bff 0.1.2 → 0.2.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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +2 -1
- data/lib/verikloak/bff/configuration.rb +41 -6
- data/lib/verikloak/bff/consistency_checks.rb +4 -3
- data/lib/verikloak/bff/errors.rb +15 -0
- data/lib/verikloak/bff/forwarded_token.rb +6 -0
- data/lib/verikloak/bff/header_guard.rb +39 -4
- data/lib/verikloak/bff/proxy_trust.rb +4 -0
- data/lib/verikloak/bff/version.rb +1 -1
- data/lib/verikloak/header_sources.rb +60 -0
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 91439a22efdb87206de07fbff74c284cba33a05cb1c7392070c0b9f7c837734f
|
|
4
|
+
data.tar.gz: be08bb99047e72502cad67dbea7c41b916aef6d49fb103e3dbf53ab79082f6db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 135054bc3b1556597924c843a834d87c46c291da4d880ed63ef611cafb9705984694062f64d25a08f8d3f932f304cfb7277c4d25f25dfb3f35ad3d83f0768e47
|
|
7
|
+
data.tar.gz: 3358b1f9f3114e9a00a7d4edd283e5b2c81dd6166ec1a5718a581f5f83a7c1cf75d27f300da01d5dc25763f4569a50be2393203130c5a67adb00ffb1a42fc1d9
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2025-09-22
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `Verikloak::HeaderSources` module for shared header normalization (consumable by verikloak-rails and other adapters).
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `Configuration#token_header_priority=` now normalizes and deduplicates entries, reusing the shared helper and ignoring `HTTP_AUTHORIZATION` automatically.
|
|
17
|
+
- `forwarded_header_name` assignments trigger re-normalization of token priority lists to keep middleware aligned across gems.
|
|
18
|
+
|
|
10
19
|
## [0.1.2] - 2025-09-21
|
|
11
20
|
|
|
12
21
|
### Added
|
data/README.md
CHANGED
|
@@ -53,7 +53,7 @@ See `examples/rack.ru` for a tiny Rack app demo.
|
|
|
53
53
|
| `peer_preference` | Symbol (`:remote_then_xff`/`:xff_only`) | `:remote_then_xff` | Whether to prefer `REMOTE_ADDR` before falling back to XFF. |
|
|
54
54
|
| `clock_skew_leeway` | Integer (seconds) | `30` | Reserved for small exp/nbf skew handled by core verifier. |
|
|
55
55
|
| `logger` | `Logger` or `nil` | `nil` | Logger for audit tags (`rid`, `sub`, `kid`, `iss/aud`). |
|
|
56
|
-
| `token_header_priority` | Array[String] | `['HTTP_X_FORWARDED_ACCESS_TOKEN']` | When Authorization is empty and no token chosen, seed it from these env headers in order. `HTTP_AUTHORIZATION` is ignored as a source
|
|
56
|
+
| `token_header_priority` | Array[String] | `['HTTP_X_FORWARDED_ACCESS_TOKEN']` | When Authorization is empty and no token chosen, seed it from these env headers in order. Values are normalized via `Verikloak::HeaderSources`; `HTTP_AUTHORIZATION` is ignored as a source. |
|
|
57
57
|
| `forwarded_header_name` | String | `HTTP_X_FORWARDED_ACCESS_TOKEN` | Env key for forwarded access token. |
|
|
58
58
|
| `auth_request_headers` | Hash | see code | Mapping for `X-Auth-Request-*` env keys: `{ email, user, groups }`. |
|
|
59
59
|
|
|
@@ -82,6 +82,7 @@ For full reverse proxy examples (Nginx auth_request / oauth2-proxy), see [docs/r
|
|
|
82
82
|
- Authorization seeding from priority headers
|
|
83
83
|
- When no token is chosen and `HTTP_AUTHORIZATION` is empty, the middleware consults `token_header_priority` to seed Authorization.
|
|
84
84
|
- `HTTP_AUTHORIZATION` itself is never used as a source; forwarded headers are considered only from trusted peers.
|
|
85
|
+
- Other gems can `require 'verikloak/header_sources'` to reuse the same normalization helpers when sharing configuration defaults.
|
|
85
86
|
|
|
86
87
|
- Observability helpers
|
|
87
88
|
- Downstream can inspect `env['verikloak.bff.token']` (chosen token, unverified) and `env['verikloak.bff.selected_peer']` (peer IP selected for trust decisions).
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
# @!attribute [rw] logger
|
|
23
23
|
# @return [Logger, nil] optional logger for audit tags
|
|
24
24
|
|
|
25
|
+
require 'verikloak/header_sources'
|
|
26
|
+
|
|
25
27
|
module Verikloak
|
|
26
28
|
module BFF
|
|
27
29
|
# Configuration for Verikloak::BFF middleware (trusted proxies, header policies, logging, etc.).
|
|
@@ -29,12 +31,17 @@ module Verikloak
|
|
|
29
31
|
attr_accessor :trusted_proxies, :prefer_forwarded, :require_forwarded_header,
|
|
30
32
|
:enforce_header_consistency, :enforce_claims_consistency,
|
|
31
33
|
:strip_suspicious_headers, :xff_strategy, :clock_skew_leeway,
|
|
32
|
-
:logger, :
|
|
33
|
-
:forwarded_header_name, :auth_request_headers, :log_with,
|
|
34
|
+
:logger, :peer_preference, :auth_request_headers, :log_with,
|
|
34
35
|
:claims_consistency_mode
|
|
36
|
+
attr_reader :token_header_priority, :forwarded_header_name
|
|
35
37
|
|
|
36
38
|
# enforce_claims_consistency example:
|
|
37
39
|
# { email: :email, user: :sub, groups: :realm_roles }
|
|
40
|
+
#
|
|
41
|
+
# Initialize configuration with secure defaults for proxy trust, token
|
|
42
|
+
# handling, and logging behavior.
|
|
43
|
+
#
|
|
44
|
+
# @return [void]
|
|
38
45
|
def initialize
|
|
39
46
|
@trusted_proxies = []
|
|
40
47
|
@prefer_forwarded = true
|
|
@@ -48,7 +55,7 @@ module Verikloak
|
|
|
48
55
|
@clock_skew_leeway = 30
|
|
49
56
|
@logger = nil
|
|
50
57
|
@log_with = nil
|
|
51
|
-
|
|
58
|
+
self.forwarded_header_name = Verikloak::HeaderSources::DEFAULT_FORWARDED_HEADER
|
|
52
59
|
@auth_request_headers = {
|
|
53
60
|
email: 'HTTP_X_AUTH_REQUEST_EMAIL',
|
|
54
61
|
user: 'HTTP_X_AUTH_REQUEST_USER',
|
|
@@ -56,9 +63,37 @@ module Verikloak
|
|
|
56
63
|
}
|
|
57
64
|
# When Authorization is empty and no chosen token exists, try these env headers (in order)
|
|
58
65
|
# to seed Authorization, similar to verikloak-rails behavior. HTTP_AUTHORIZATION is always ignored as a source.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
self.token_header_priority = Verikloak::HeaderSources.default_priority(forwarded_header: @forwarded_header_name)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Override forwarded header name while re-normalizing the token priority list.
|
|
70
|
+
# When the forwarded header changes, downstream priority normalization must
|
|
71
|
+
# be refreshed because the forwarded value participates in that list.
|
|
72
|
+
#
|
|
73
|
+
# @param header [String, Symbol]
|
|
74
|
+
# @return [void]
|
|
75
|
+
def forwarded_header_name=(header)
|
|
76
|
+
@forwarded_header_name = Verikloak::HeaderSources.normalize_env_key(header)
|
|
77
|
+
renormalize_token_priority!
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Assign token header priority list using shared normalization logic.
|
|
81
|
+
#
|
|
82
|
+
# @param priority [Array<String, Symbol>, String, Symbol, nil]
|
|
83
|
+
# @return [void]
|
|
84
|
+
def token_header_priority=(priority)
|
|
85
|
+
normalized, = Verikloak::HeaderSources.normalize_priority(priority, forwarded_header: @forwarded_header_name)
|
|
86
|
+
@token_header_priority = normalized
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
# Re-apply token header normalization so it reflects the current forwarded header.
|
|
92
|
+
#
|
|
93
|
+
# @return [void]
|
|
94
|
+
def renormalize_token_priority!
|
|
95
|
+
# Refresh the normalized list so it reflects the new forwarded header name.
|
|
96
|
+
self.token_header_priority = @token_header_priority
|
|
62
97
|
end
|
|
63
98
|
end
|
|
64
99
|
end
|
|
@@ -15,12 +15,11 @@ module Verikloak
|
|
|
15
15
|
module ConsistencyChecks
|
|
16
16
|
module_function
|
|
17
17
|
|
|
18
|
-
#
|
|
19
|
-
#
|
|
18
|
+
# Decode the JWT payload without verifying the signature. Intended only
|
|
19
|
+
# for lightweight claim comparisons; full verification occurs downstream.
|
|
20
20
|
#
|
|
21
21
|
# @param token [String, nil]
|
|
22
22
|
# @return [Hash] claims or empty hash on error
|
|
23
|
-
|
|
24
23
|
def decode_claims(token)
|
|
25
24
|
return {} unless token
|
|
26
25
|
return {} if token.bytesize > Constants::MAX_TOKEN_BYTES
|
|
@@ -36,6 +35,7 @@ module Verikloak
|
|
|
36
35
|
# @param env [Hash]
|
|
37
36
|
# @param token [String, nil]
|
|
38
37
|
# @param mapping [Hash] e.g., { email: :email, user: :sub, groups: :realm_roles }
|
|
38
|
+
# @param headers_map [Hash{Symbol=>String}, nil] overrides for header keys
|
|
39
39
|
# @return [true, Array(:error, Symbol)] true or error tuple with failing field
|
|
40
40
|
def enforce!(env, token, mapping, headers_map = nil)
|
|
41
41
|
return true if mapping.nil? || mapping.empty?
|
|
@@ -63,6 +63,7 @@ module Verikloak
|
|
|
63
63
|
#
|
|
64
64
|
# @param env [Hash]
|
|
65
65
|
# @param key [Symbol]
|
|
66
|
+
# @param headers_map [Hash{Symbol=>String}, nil]
|
|
66
67
|
# @return [String, nil]
|
|
67
68
|
def extract_header_value(env, key, headers_map = nil)
|
|
68
69
|
return env[headers_map[key]] if headers_map && headers_map[key]
|
data/lib/verikloak/bff/errors.rb
CHANGED
|
@@ -12,6 +12,12 @@ module Verikloak
|
|
|
12
12
|
class Error < StandardError
|
|
13
13
|
attr_reader :code, :http_status
|
|
14
14
|
|
|
15
|
+
# Build a BFF error with a stable code and HTTP status.
|
|
16
|
+
#
|
|
17
|
+
# @param message [String, nil]
|
|
18
|
+
# @param code [String]
|
|
19
|
+
# @param http_status [Integer]
|
|
20
|
+
# @return [void]
|
|
15
21
|
def initialize(message = nil, code: 'bff_error', http_status: 401)
|
|
16
22
|
super(message || code)
|
|
17
23
|
@code = code
|
|
@@ -21,6 +27,8 @@ module Verikloak
|
|
|
21
27
|
|
|
22
28
|
# Raised when a request did not pass through a trusted proxy peer.
|
|
23
29
|
class UntrustedProxyError < Error
|
|
30
|
+
# @param msg [String]
|
|
31
|
+
# @return [void]
|
|
24
32
|
def initialize(msg = 'request did not pass through a trusted proxy')
|
|
25
33
|
super(msg, code: 'untrusted_proxy', http_status: 401)
|
|
26
34
|
end
|
|
@@ -28,6 +36,8 @@ module Verikloak
|
|
|
28
36
|
|
|
29
37
|
# Raised when require_forwarded_header is enabled but the forwarded token is absent.
|
|
30
38
|
class MissingForwardedTokenError < Error
|
|
39
|
+
# @param msg [String]
|
|
40
|
+
# @return [void]
|
|
31
41
|
def initialize(msg = 'missing X-Forwarded-Access-Token')
|
|
32
42
|
super(msg, code: 'missing_forwarded_token', http_status: 401)
|
|
33
43
|
end
|
|
@@ -35,6 +45,8 @@ module Verikloak
|
|
|
35
45
|
|
|
36
46
|
# Raised when Authorization and X-Forwarded-Access-Token both exist and differ.
|
|
37
47
|
class HeaderMismatchError < Error
|
|
48
|
+
# @param msg [String]
|
|
49
|
+
# @return [void]
|
|
38
50
|
def initialize(msg = 'authorization and forwarded token mismatch')
|
|
39
51
|
super(msg, code: 'header_mismatch', http_status: 401)
|
|
40
52
|
end
|
|
@@ -42,6 +54,9 @@ module Verikloak
|
|
|
42
54
|
|
|
43
55
|
# Raised when X-Auth-Request-* headers conflict with JWT claims.
|
|
44
56
|
class ClaimsMismatchError < Error
|
|
57
|
+
# @param field [Symbol, String]
|
|
58
|
+
# @param msg [String, nil]
|
|
59
|
+
# @return [void]
|
|
45
60
|
def initialize(field, msg = nil)
|
|
46
61
|
super(msg || "claims/header mismatch for #{field}", code: 'claims_mismatch', http_status: 403)
|
|
47
62
|
end
|
|
@@ -16,6 +16,8 @@ module Verikloak
|
|
|
16
16
|
# Extract normalized tokens from the Rack env.
|
|
17
17
|
#
|
|
18
18
|
# @param env [Hash]
|
|
19
|
+
# @param forwarded_header_name [String] Rack env key for forwarded header
|
|
20
|
+
# @param auth_header_name [String] Rack env key for Authorization header
|
|
19
21
|
# @return [Array(String, String)] [auth_token, forwarded_token]
|
|
20
22
|
def extract(env, forwarded_header_name = FORWARDED_HEADER, auth_header_name = AUTH_HEADER)
|
|
21
23
|
fwd_raw = env[forwarded_header_name]
|
|
@@ -54,6 +56,7 @@ module Verikloak
|
|
|
54
56
|
# - Detects scheme case-insensitively
|
|
55
57
|
# - Inserts a missing space (e.g., 'BearerXYZ' => 'Bearer XYZ')
|
|
56
58
|
# - Collapses multiple spaces/tabs after the scheme to a single space
|
|
59
|
+
#
|
|
57
60
|
# @param token [String]
|
|
58
61
|
# @return [String]
|
|
59
62
|
def ensure_bearer(token)
|
|
@@ -78,6 +81,7 @@ module Verikloak
|
|
|
78
81
|
#
|
|
79
82
|
# @param env [Hash]
|
|
80
83
|
# @param token [String]
|
|
84
|
+
# @return [void]
|
|
81
85
|
def set_authorization!(env, token)
|
|
82
86
|
existing = env[AUTH_HEADER].to_s
|
|
83
87
|
# Overwrite only if Authorization is empty or not a valid Bearer value
|
|
@@ -98,6 +102,8 @@ module Verikloak
|
|
|
98
102
|
# downstream when not emitted by a trusted proxy.
|
|
99
103
|
#
|
|
100
104
|
# @param env [Hash]
|
|
105
|
+
# @param headers [Hash{Symbol=>String}, nil] explicit headers to strip
|
|
106
|
+
# @return [void]
|
|
101
107
|
def strip_suspicious!(env, headers = nil)
|
|
102
108
|
if headers.is_a?(Hash)
|
|
103
109
|
headers.each_value { |h| env.delete(h) }
|
|
@@ -15,6 +15,7 @@ require 'rack/utils'
|
|
|
15
15
|
require 'json'
|
|
16
16
|
require 'jwt'
|
|
17
17
|
require 'digest'
|
|
18
|
+
require 'verikloak/header_sources'
|
|
18
19
|
require 'verikloak/bff/configuration'
|
|
19
20
|
require 'verikloak/bff/errors'
|
|
20
21
|
require 'verikloak/bff/proxy_trust'
|
|
@@ -30,6 +31,11 @@ module Verikloak
|
|
|
30
31
|
|
|
31
32
|
module_function
|
|
32
33
|
|
|
34
|
+
# Generate sanitized token metadata suitable for structured logging without
|
|
35
|
+
# verifying the signature.
|
|
36
|
+
#
|
|
37
|
+
# @param token [String, nil]
|
|
38
|
+
# @return [Hash{Symbol=>Object}] sanitized tags keyed by JWT claim/header
|
|
33
39
|
def token_tags(token)
|
|
34
40
|
return {} unless token
|
|
35
41
|
|
|
@@ -46,6 +52,11 @@ module Verikloak
|
|
|
46
52
|
{}
|
|
47
53
|
end
|
|
48
54
|
|
|
55
|
+
# Decode a JWT without verifying the signature while guarding against
|
|
56
|
+
# excessively large tokens.
|
|
57
|
+
#
|
|
58
|
+
# @param token [String, nil]
|
|
59
|
+
# @return [Array<Hash>] payload and header hashes
|
|
49
60
|
def decode_unverified(token)
|
|
50
61
|
return [{}, {}] if token.nil? || token.bytesize > Constants::MAX_TOKEN_BYTES
|
|
51
62
|
|
|
@@ -54,10 +65,18 @@ module Verikloak
|
|
|
54
65
|
[{}, {}]
|
|
55
66
|
end
|
|
56
67
|
|
|
68
|
+
# Remove unsafe characters from a structured logging payload.
|
|
69
|
+
#
|
|
70
|
+
# @param payload [Hash]
|
|
71
|
+
# @return [Hash] sanitized payload suitable for logging
|
|
57
72
|
def sanitize_payload(payload)
|
|
58
73
|
payload.transform_values { |value| sanitize_log_field(value) }.compact
|
|
59
74
|
end
|
|
60
75
|
|
|
76
|
+
# Sanitize an individual value destined for logs, pruning empty results.
|
|
77
|
+
#
|
|
78
|
+
# @param value [Object]
|
|
79
|
+
# @return [Object, nil] sanitized value or nil when the result is empty
|
|
61
80
|
def sanitize_log_field(value)
|
|
62
81
|
case value
|
|
63
82
|
when nil
|
|
@@ -74,6 +93,10 @@ module Verikloak
|
|
|
74
93
|
end
|
|
75
94
|
end
|
|
76
95
|
|
|
96
|
+
# Remove control characters and invalid UTF-8 from a string.
|
|
97
|
+
#
|
|
98
|
+
# @param value [#to_s]
|
|
99
|
+
# @return [String]
|
|
77
100
|
def sanitize_string(value)
|
|
78
101
|
value.to_s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '').gsub(LOG_CONTROL_CHARS, '')
|
|
79
102
|
end
|
|
@@ -128,6 +151,7 @@ module Verikloak
|
|
|
128
151
|
# Apply per-instance configuration overrides.
|
|
129
152
|
#
|
|
130
153
|
# @param opts [Hash]
|
|
154
|
+
# @return [void]
|
|
131
155
|
def apply_overrides!(opts)
|
|
132
156
|
cfg = @config
|
|
133
157
|
opts.each do |k, v|
|
|
@@ -182,6 +206,7 @@ module Verikloak
|
|
|
182
206
|
# @param env [Hash]
|
|
183
207
|
# @param kind [Symbol] :ok, :mismatch, :claims_mismatch, :error
|
|
184
208
|
# @param attrs [Hash]
|
|
209
|
+
# @return [void]
|
|
185
210
|
def log_event(env, kind, **attrs)
|
|
186
211
|
lg = logger(env)
|
|
187
212
|
payload = { event: 'bff.header_guard', kind: kind, rid: request_id(env) }.merge(attrs).compact
|
|
@@ -205,6 +230,7 @@ module Verikloak
|
|
|
205
230
|
# Raise when the request did not come through a trusted proxy.
|
|
206
231
|
#
|
|
207
232
|
# @param env [Hash]
|
|
233
|
+
# @raise [UntrustedProxyError]
|
|
208
234
|
def ensure_trusted_proxy!(env)
|
|
209
235
|
return if ProxyTrust.trusted?(env, @config.trusted_proxies, @config.xff_strategy)
|
|
210
236
|
|
|
@@ -214,6 +240,7 @@ module Verikloak
|
|
|
214
240
|
# Enforce presence of forwarded token when required.
|
|
215
241
|
#
|
|
216
242
|
# @param fwd_token [String, nil]
|
|
243
|
+
# @raise [MissingForwardedTokenError]
|
|
217
244
|
def ensure_forwarded_if_required!(fwd_token)
|
|
218
245
|
return unless @config.require_forwarded_header
|
|
219
246
|
raise MissingForwardedTokenError if fwd_token.nil? || fwd_token.to_s.strip.empty?
|
|
@@ -224,6 +251,7 @@ module Verikloak
|
|
|
224
251
|
# @param env [Hash]
|
|
225
252
|
# @param auth_token [String, nil]
|
|
226
253
|
# @param fwd_token [String, nil]
|
|
254
|
+
# @raise [HeaderMismatchError]
|
|
227
255
|
def enforce_header_consistency!(env, auth_token, fwd_token)
|
|
228
256
|
return unless @config.enforce_header_consistency
|
|
229
257
|
return unless auth_token && fwd_token
|
|
@@ -240,6 +268,8 @@ module Verikloak
|
|
|
240
268
|
#
|
|
241
269
|
# @param env [Hash]
|
|
242
270
|
# @param chosen [String, nil]
|
|
271
|
+
# @return [void]
|
|
272
|
+
# @raise [ClaimsMismatchError]
|
|
243
273
|
def enforce_claims_consistency!(env, chosen)
|
|
244
274
|
res = ConsistencyChecks.enforce!(env, chosen, @config.enforce_claims_consistency, @config.auth_request_headers)
|
|
245
275
|
return unless res.is_a?(Array) && res.first == :error
|
|
@@ -267,6 +297,7 @@ module Verikloak
|
|
|
267
297
|
# @param chosen [String, nil]
|
|
268
298
|
# @param auth_token [String, nil]
|
|
269
299
|
# @param fwd_token [String, nil]
|
|
300
|
+
# @return [void]
|
|
270
301
|
def normalize_authorization!(env, chosen, auth_token, fwd_token)
|
|
271
302
|
return unless chosen
|
|
272
303
|
|
|
@@ -289,19 +320,21 @@ module Verikloak
|
|
|
289
320
|
|
|
290
321
|
# Resolve the first env header from which to source a bearer token.
|
|
291
322
|
# Forwarded is considered only when the peer is trusted; HTTP_AUTHORIZATION is never a source.
|
|
323
|
+
#
|
|
292
324
|
# @param env [Hash]
|
|
293
325
|
# @return [String, nil]
|
|
294
326
|
def resolve_first_token_header(env)
|
|
295
327
|
candidates = Array(@config.token_header_priority).dup
|
|
296
|
-
candidates -= [
|
|
297
|
-
fwd_key =
|
|
328
|
+
candidates -= [Verikloak::HeaderSources::AUTHORIZATION_HEADER]
|
|
329
|
+
fwd_key = @config.forwarded_header_name || Verikloak::HeaderSources::DEFAULT_FORWARDED_HEADER
|
|
298
330
|
if candidates.include?(fwd_key) && !ProxyTrust.from_trusted_proxy?(env, @config.trusted_proxies)
|
|
299
331
|
candidates -= [fwd_key]
|
|
300
332
|
end
|
|
301
333
|
candidates.find { |k| (v = env[k]) && !v.to_s.empty? }
|
|
302
334
|
end
|
|
303
335
|
|
|
304
|
-
# Seed Authorization from priority headers if nothing chosen and empty Authorization
|
|
336
|
+
# Seed Authorization from priority headers if nothing chosen and empty Authorization.
|
|
337
|
+
#
|
|
305
338
|
# @param env [Hash]
|
|
306
339
|
# @param chosen [String, nil]
|
|
307
340
|
# @return [String, nil] possibly updated chosen token
|
|
@@ -317,9 +350,11 @@ module Verikloak
|
|
|
317
350
|
chosen
|
|
318
351
|
end
|
|
319
352
|
|
|
320
|
-
# Expose hints to downstream
|
|
353
|
+
# Expose hints to downstream middleware or apps.
|
|
354
|
+
#
|
|
321
355
|
# @param env [Hash]
|
|
322
356
|
# @param chosen [String, nil]
|
|
357
|
+
# @return [void]
|
|
323
358
|
def expose_env_hints(env, chosen)
|
|
324
359
|
env['verikloak.bff.token'] = chosen if chosen
|
|
325
360
|
env['verikloak.bff.selected_peer'] =
|
|
@@ -53,6 +53,7 @@ module Verikloak
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
# Return the selected peer IP according to preference and strategy.
|
|
56
|
+
#
|
|
56
57
|
# @param env [Hash]
|
|
57
58
|
# @param preference [Symbol] :remote_then_xff or :xff_only
|
|
58
59
|
# @param strategy [Symbol] :rightmost or :leftmost
|
|
@@ -70,6 +71,7 @@ module Verikloak
|
|
|
70
71
|
end
|
|
71
72
|
|
|
72
73
|
# Parse string to IPAddr or nil on failure.
|
|
74
|
+
#
|
|
73
75
|
# @param str [String]
|
|
74
76
|
# @return [IPAddr, nil]
|
|
75
77
|
def ip_or_nil(str)
|
|
@@ -79,6 +81,7 @@ module Verikloak
|
|
|
79
81
|
end
|
|
80
82
|
|
|
81
83
|
# Check whether a single rule trusts the selected remote.
|
|
84
|
+
#
|
|
82
85
|
# @param rule [String, Regexp, Proc]
|
|
83
86
|
# @param remote [String]
|
|
84
87
|
# @param remote_ip [IPAddr, nil]
|
|
@@ -104,6 +107,7 @@ module Verikloak
|
|
|
104
107
|
|
|
105
108
|
# Determine if the request originates from a trusted proxy subnet.
|
|
106
109
|
# Rails-aligned behavior: prefer REMOTE_ADDR, fallback to nearest (rightmost) X-Forwarded-For.
|
|
110
|
+
#
|
|
107
111
|
# @param env [Hash]
|
|
108
112
|
# @param trusted [Array<String, Regexp, Proc>, nil]
|
|
109
113
|
# @return [Boolean]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Helpers shared across verikloak middleware for normalizing Rack env header
|
|
4
|
+
# names and token source priority lists. Extracted to allow other gems (such as
|
|
5
|
+
# verikloak-rails) to consume the same normalization logic.
|
|
6
|
+
module Verikloak
|
|
7
|
+
# Provides normalization helpers for Rack env header keys and token priority lists.
|
|
8
|
+
module HeaderSources
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
DEFAULT_FORWARDED_HEADER = 'HTTP_X_FORWARDED_ACCESS_TOKEN'
|
|
12
|
+
AUTHORIZATION_HEADER = 'HTTP_AUTHORIZATION'
|
|
13
|
+
|
|
14
|
+
# Normalize a Rack env header key, accepting symbols, mixed case, or dash
|
|
15
|
+
# separated names and returning an upper-case HTTP_* variant.
|
|
16
|
+
#
|
|
17
|
+
# @param header [String, Symbol, nil]
|
|
18
|
+
# @return [String] normalized env key or empty string when blank
|
|
19
|
+
def normalize_env_key(header)
|
|
20
|
+
key = header.to_s.strip
|
|
21
|
+
return '' if key.empty?
|
|
22
|
+
|
|
23
|
+
key = key.tr('-', '_').upcase
|
|
24
|
+
key = "HTTP_#{key}" unless key.start_with?('HTTP_')
|
|
25
|
+
key
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Normalize a token priority list by stripping blanks, rejecting
|
|
29
|
+
# Authorization as a source, and deduplicating entries while preserving
|
|
30
|
+
# order.
|
|
31
|
+
#
|
|
32
|
+
# @param priority [Array<String, Symbol>, String, Symbol, nil]
|
|
33
|
+
# @param forwarded_header [String, Symbol]
|
|
34
|
+
# @param drop_authorization [Boolean]
|
|
35
|
+
# @return [Array(Array<String>, String)] normalized priority and forwarded key
|
|
36
|
+
def normalize_priority(priority, forwarded_header: DEFAULT_FORWARDED_HEADER, drop_authorization: true)
|
|
37
|
+
forwarded_env = normalize_env_key(forwarded_header)
|
|
38
|
+
items = Array(priority).flatten
|
|
39
|
+
|
|
40
|
+
normalized = items.map { |value| normalize_env_key(value) }.reject(&:empty?)
|
|
41
|
+
normalized = [forwarded_env] if normalized.empty?
|
|
42
|
+
normalized = normalized.reject { |key| drop_authorization && key == AUTHORIZATION_HEADER }
|
|
43
|
+
|
|
44
|
+
deduped = []
|
|
45
|
+
normalized.each do |key|
|
|
46
|
+
deduped << key unless deduped.include?(key)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
[deduped.freeze, forwarded_env]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Default priority list using the provided forwarded header name.
|
|
53
|
+
#
|
|
54
|
+
# @param forwarded_header [String, Symbol]
|
|
55
|
+
# @return [Array<String>] normalized default priority
|
|
56
|
+
def default_priority(forwarded_header: DEFAULT_FORWARDED_HEADER)
|
|
57
|
+
normalize_priority([forwarded_header], forwarded_header: forwarded_header).first
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: verikloak-bff
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- taiyaky
|
|
@@ -55,20 +55,20 @@ dependencies:
|
|
|
55
55
|
requirements:
|
|
56
56
|
- - ">="
|
|
57
57
|
- !ruby/object:Gem::Version
|
|
58
|
-
version: 0.1.
|
|
58
|
+
version: 0.1.5
|
|
59
59
|
- - "<"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
61
|
+
version: 1.0.0
|
|
62
62
|
type: :runtime
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
66
|
- - ">="
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: 0.1.
|
|
68
|
+
version: 0.1.5
|
|
69
69
|
- - "<"
|
|
70
70
|
- !ruby/object:Gem::Version
|
|
71
|
-
version:
|
|
71
|
+
version: 1.0.0
|
|
72
72
|
description: Framework-agnostic Rack middleware that normalizes forwarded tokens,
|
|
73
73
|
enforces trust boundaries, and checks header/claims consistency before verikloak.
|
|
74
74
|
executables: []
|
|
@@ -88,6 +88,7 @@ files:
|
|
|
88
88
|
- lib/verikloak/bff/header_guard.rb
|
|
89
89
|
- lib/verikloak/bff/proxy_trust.rb
|
|
90
90
|
- lib/verikloak/bff/version.rb
|
|
91
|
+
- lib/verikloak/header_sources.rb
|
|
91
92
|
homepage: https://github.com/taiyaky/verikloak-bff
|
|
92
93
|
licenses:
|
|
93
94
|
- MIT
|
|
@@ -95,7 +96,7 @@ metadata:
|
|
|
95
96
|
source_code_uri: https://github.com/taiyaky/verikloak-bff
|
|
96
97
|
changelog_uri: https://github.com/taiyaky/verikloak-bff/blob/main/CHANGELOG.md
|
|
97
98
|
bug_tracker_uri: https://github.com/taiyaky/verikloak-bff/issues
|
|
98
|
-
documentation_uri: https://rubydoc.info/gems/verikloak-bff/0.
|
|
99
|
+
documentation_uri: https://rubydoc.info/gems/verikloak-bff/0.2.0
|
|
99
100
|
rubygems_mfa_required: 'true'
|
|
100
101
|
rdoc_options: []
|
|
101
102
|
require_paths:
|