verikloak-bff 0.1.0 → 0.1.1
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 +11 -0
- data/README.md +2 -2
- data/lib/verikloak/bff/configuration.rb +1 -2
- data/lib/verikloak/bff/consistency_checks.rb +4 -5
- data/lib/verikloak/bff/constants.rb +9 -0
- data/lib/verikloak/bff/forwarded_token.rb +10 -2
- data/lib/verikloak/bff/header_guard.rb +16 -14
- data/lib/verikloak/bff/proxy_trust.rb +2 -2
- data/lib/verikloak/bff/version.rb +1 -1
- metadata +11 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 19452cfc021fd5ffde923f55f294aafb99c4b80506047d0ef195233d019c5adb
|
|
4
|
+
data.tar.gz: 3211ddb4779f6bbe1b079f6c83b018bb5b7b881bb267d253f94ffaaa20006711
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0890fcfe4ac2af3ce8bdbc34ee0753744a3a56459a90da3441247a7d36779b28db68ae7998b349968f5ecfd937121d3fad96e2f21a092662da2cbedeb61a358c'
|
|
7
|
+
data.tar.gz: 7a4e7888b2f2caafc0bd1345760d3ef5e049f860cc9f36dba028c2a2afebaf8625719b6b0dbad9f7a21c01db957368b5d10d73c3533f19ce8fdc9acb4513e3f0
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.1.1] - 2025-09-15
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Centralize `MAX_TOKEN_BYTES` in `Verikloak::BFF::Constants` and refactor usages in `HeaderGuard` and `ConsistencyChecks` to avoid duplication.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Preserve full token content when forwarded header includes control characters (e.g., `Bearer tok\r\nmal`) by adjusting Bearer parsing in `ForwardedToken.normalize_forwarded`; combined with existing sanitization, Authorization now normalizes to `Bearer tokmal`.
|
|
17
|
+
|
|
18
|
+
### Tests
|
|
19
|
+
- Add boundary tests for token size limits in `ConsistencyChecks` and `HeaderGuard`.
|
|
20
|
+
|
|
10
21
|
## [0.1.0] - 2025-09-14
|
|
11
22
|
|
|
12
23
|
### Added
|
data/README.md
CHANGED
|
@@ -42,7 +42,7 @@ See `examples/rack.ru` for a tiny Rack app demo.
|
|
|
42
42
|
|
|
43
43
|
| Key | Type | Default | Description |
|
|
44
44
|
|----------------------------- |--------------------------------------|--------------|-------------|
|
|
45
|
-
| `trusted_proxies` | Array[String/Regexp/Proc] |
|
|
45
|
+
| `trusted_proxies` | Array[String/Regexp/Proc] | *(required)* | Allowlist for proxy peers (by IP/CIDR/regex/proc). |
|
|
46
46
|
| `prefer_forwarded` | Boolean | `true` | Prefer `X-Forwarded-Access-Token` over `Authorization`. |
|
|
47
47
|
| `require_forwarded_header` | Boolean | `false` | Reject when no `X-Forwarded-Access-Token` (blocks direct access). |
|
|
48
48
|
| `enforce_header_consistency` | Boolean | `true` | If both headers exist, require identical token values. |
|
|
@@ -52,7 +52,7 @@ See `examples/rack.ru` for a tiny Rack app demo.
|
|
|
52
52
|
| `peer_preference` | Symbol (`:remote_then_xff`/`:xff_only`) | `:remote_then_xff` | Whether to prefer `REMOTE_ADDR` before falling back to XFF. |
|
|
53
53
|
| `clock_skew_leeway` | Integer (seconds) | `30` | Reserved for small exp/nbf skew handled by core verifier. |
|
|
54
54
|
| `logger` | `Logger` or `nil` | `nil` | Logger for audit tags (`rid`, `sub`, `kid`, `iss/aud`). |
|
|
55
|
-
| `token_header_priority` | Array[String] |
|
|
55
|
+
| `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; forwarded header is considered only from trusted peers. |
|
|
56
56
|
| `forwarded_header_name` | String | `HTTP_X_FORWARDED_ACCESS_TOKEN` | Env key for forwarded access token. |
|
|
57
57
|
| `auth_request_headers` | Hash | see code | Mapping for `X-Auth-Request-*` env keys: `{ email, user, groups }`. |
|
|
58
58
|
|
|
@@ -53,10 +53,9 @@ module Verikloak
|
|
|
53
53
|
groups: 'HTTP_X_AUTH_REQUEST_GROUPS'
|
|
54
54
|
}
|
|
55
55
|
# When Authorization is empty and no chosen token exists, try these env headers (in order)
|
|
56
|
-
# to seed Authorization, similar to verikloak-rails behavior. HTTP_AUTHORIZATION is ignored as a source.
|
|
56
|
+
# to seed Authorization, similar to verikloak-rails behavior. HTTP_AUTHORIZATION is always ignored as a source.
|
|
57
57
|
@token_header_priority = %w[
|
|
58
58
|
HTTP_X_FORWARDED_ACCESS_TOKEN
|
|
59
|
-
HTTP_AUTHORIZATION
|
|
60
59
|
]
|
|
61
60
|
end
|
|
62
61
|
end
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
require 'json'
|
|
9
9
|
require 'jwt' # used only to parse segments safely without verify
|
|
10
|
+
require 'verikloak/bff/constants'
|
|
10
11
|
|
|
11
12
|
module Verikloak
|
|
12
13
|
module BFF
|
|
@@ -19,14 +20,12 @@ module Verikloak
|
|
|
19
20
|
#
|
|
20
21
|
# @param token [String, nil]
|
|
21
22
|
# @return [Hash] claims or empty hash on error
|
|
23
|
+
|
|
22
24
|
def decode_claims(token)
|
|
23
25
|
return {} unless token
|
|
26
|
+
return {} if token.bytesize > Constants::MAX_TOKEN_BYTES
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
return {} unless parts.size >= 2
|
|
27
|
-
|
|
28
|
-
payload = JWT::Base64.url_decode(parts[1])
|
|
29
|
-
JSON.parse(payload)
|
|
28
|
+
JWT.decode(token, nil, false).first
|
|
30
29
|
rescue StandardError
|
|
31
30
|
{}
|
|
32
31
|
end
|
|
@@ -45,7 +45,7 @@ module Verikloak
|
|
|
45
45
|
return nil unless raw
|
|
46
46
|
|
|
47
47
|
token = raw.to_s.strip
|
|
48
|
-
|
|
48
|
+
token = token.sub(/^Bearer\s+/i, '') if token =~ /^Bearer\s+/i
|
|
49
49
|
|
|
50
50
|
token.empty? ? nil : token
|
|
51
51
|
end
|
|
@@ -57,7 +57,7 @@ module Verikloak
|
|
|
57
57
|
# @param token [String]
|
|
58
58
|
# @return [String]
|
|
59
59
|
def ensure_bearer(token)
|
|
60
|
-
s = token
|
|
60
|
+
s = sanitize(token)
|
|
61
61
|
# Case-insensitive 'Bearer' with spaces/tabs after
|
|
62
62
|
if s =~ /\ABearer[ \t]+/i
|
|
63
63
|
rest = s.sub(/\ABearer[ \t]+/i, '')
|
|
@@ -86,6 +86,14 @@ module Verikloak
|
|
|
86
86
|
env[AUTH_HEADER] = ensure_bearer(token)
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
+
# Remove CRLF and other control characters to prevent header injection.
|
|
90
|
+
#
|
|
91
|
+
# @param token [String]
|
|
92
|
+
# @return [String]
|
|
93
|
+
def sanitize(token)
|
|
94
|
+
token.to_s.gsub(/[[:cntrl:]]/, '').strip
|
|
95
|
+
end
|
|
96
|
+
|
|
89
97
|
# Remove potentially forged X-Auth-Request-* headers before passing
|
|
90
98
|
# downstream when not emitted by a trusted proxy.
|
|
91
99
|
#
|
|
@@ -11,13 +11,16 @@
|
|
|
11
11
|
# headers against JWT claims, and normalizes the request into
|
|
12
12
|
# `HTTP_AUTHORIZATION: Bearer <token>` for the downstream verifier.
|
|
13
13
|
require 'rack'
|
|
14
|
+
require 'rack/utils'
|
|
14
15
|
require 'json'
|
|
15
16
|
require 'jwt'
|
|
17
|
+
require 'digest'
|
|
16
18
|
require 'verikloak/bff/configuration'
|
|
17
19
|
require 'verikloak/bff/errors'
|
|
18
20
|
require 'verikloak/bff/proxy_trust'
|
|
19
21
|
require 'verikloak/bff/forwarded_token'
|
|
20
22
|
require 'verikloak/bff/consistency_checks'
|
|
23
|
+
require 'verikloak/bff/constants'
|
|
21
24
|
|
|
22
25
|
module Verikloak
|
|
23
26
|
module BFF
|
|
@@ -38,6 +41,10 @@ module Verikloak
|
|
|
38
41
|
combined.merge!(opts) if opts.is_a?(Hash)
|
|
39
42
|
combined.merge!(opts_kw) if opts_kw && !opts_kw.empty?
|
|
40
43
|
apply_overrides!(combined)
|
|
44
|
+
|
|
45
|
+
return unless @config.trusted_proxies.nil? || @config.trusted_proxies.empty?
|
|
46
|
+
|
|
47
|
+
raise ArgumentError, 'trusted_proxies must be configured'
|
|
41
48
|
end
|
|
42
49
|
|
|
43
50
|
# Process a Rack request.
|
|
@@ -121,21 +128,13 @@ module Verikloak
|
|
|
121
128
|
#
|
|
122
129
|
# @param token [String]
|
|
123
130
|
# @return [Array(Hash, Hash)] [payload, header]
|
|
131
|
+
|
|
124
132
|
def decode_unverified(token)
|
|
125
|
-
|
|
126
|
-
return [{}, {}] unless parts.size >= 2
|
|
133
|
+
return [{}, {}] if token.nil? || token.bytesize > Constants::MAX_TOKEN_BYTES
|
|
127
134
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
{}
|
|
132
|
-
end
|
|
133
|
-
header = begin
|
|
134
|
-
JSON.parse(::JWT::Base64.url_decode(parts[0]))
|
|
135
|
-
rescue StandardError
|
|
136
|
-
{}
|
|
137
|
-
end
|
|
138
|
-
[payload, header]
|
|
135
|
+
JWT.decode(token, nil, false)
|
|
136
|
+
rescue StandardError
|
|
137
|
+
[{}, {}]
|
|
139
138
|
end
|
|
140
139
|
|
|
141
140
|
# Extract request id for logging from common headers.
|
|
@@ -203,7 +202,10 @@ module Verikloak
|
|
|
203
202
|
def enforce_header_consistency!(env, auth_token, fwd_token)
|
|
204
203
|
return unless @config.enforce_header_consistency
|
|
205
204
|
return unless auth_token && fwd_token
|
|
206
|
-
|
|
205
|
+
|
|
206
|
+
digest_a = ::Digest::SHA256.hexdigest(auth_token)
|
|
207
|
+
digest_b = ::Digest::SHA256.hexdigest(fwd_token)
|
|
208
|
+
return if Rack::Utils.secure_compare(digest_a, digest_b)
|
|
207
209
|
|
|
208
210
|
log_event(env, :mismatch, reason: 'authorization_vs_forwarded')
|
|
209
211
|
raise HeaderMismatchError
|
|
@@ -23,7 +23,7 @@ module Verikloak
|
|
|
23
23
|
# @example CIDR + Regex allowlist
|
|
24
24
|
# ProxyTrust.trusted?(env, ["10.0.0.0/8", /^192\.168\./], :rightmost)
|
|
25
25
|
def trusted?(env, trusted, strategy = :rightmost)
|
|
26
|
-
return
|
|
26
|
+
return false if trusted.nil? || trusted.empty?
|
|
27
27
|
|
|
28
28
|
# Rails-aligned: prefer REMOTE_ADDR; fallback to nearest XFF entry
|
|
29
29
|
remote = (env['REMOTE_ADDR'] || '').to_s.strip
|
|
@@ -108,7 +108,7 @@ module Verikloak
|
|
|
108
108
|
# @param trusted [Array<String, Regexp, Proc>, nil]
|
|
109
109
|
# @return [Boolean]
|
|
110
110
|
def self.from_trusted_proxy?(env, trusted)
|
|
111
|
-
return
|
|
111
|
+
return false if trusted.nil? || trusted.empty?
|
|
112
112
|
|
|
113
113
|
ip = (env['REMOTE_ADDR'] || '').to_s.strip
|
|
114
114
|
ip = env['HTTP_X_FORWARDED_FOR'].to_s.split(',').last.to_s.strip if ip.empty? && env['HTTP_X_FORWARDED_FOR']
|
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.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- taiyaky
|
|
@@ -13,16 +13,22 @@ dependencies:
|
|
|
13
13
|
name: jwt
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
18
|
version: '2.7'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '4.0'
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
25
|
requirements:
|
|
23
|
-
- - "
|
|
26
|
+
- - ">="
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
28
|
version: '2.7'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '4.0'
|
|
26
32
|
- !ruby/object:Gem::Dependency
|
|
27
33
|
name: rack
|
|
28
34
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -76,6 +82,7 @@ files:
|
|
|
76
82
|
- lib/verikloak/bff.rb
|
|
77
83
|
- lib/verikloak/bff/configuration.rb
|
|
78
84
|
- lib/verikloak/bff/consistency_checks.rb
|
|
85
|
+
- lib/verikloak/bff/constants.rb
|
|
79
86
|
- lib/verikloak/bff/errors.rb
|
|
80
87
|
- lib/verikloak/bff/forwarded_token.rb
|
|
81
88
|
- lib/verikloak/bff/header_guard.rb
|
|
@@ -88,7 +95,7 @@ metadata:
|
|
|
88
95
|
source_code_uri: https://github.com/taiyaky/verikloak-bff
|
|
89
96
|
changelog_uri: https://github.com/taiyaky/verikloak-bff/blob/main/CHANGELOG.md
|
|
90
97
|
bug_tracker_uri: https://github.com/taiyaky/verikloak-bff/issues
|
|
91
|
-
documentation_uri: https://rubydoc.info/gems/verikloak-bff/0.1.
|
|
98
|
+
documentation_uri: https://rubydoc.info/gems/verikloak-bff/0.1.1
|
|
92
99
|
rubygems_mfa_required: 'true'
|
|
93
100
|
rdoc_options: []
|
|
94
101
|
require_paths:
|