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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3034b92c69f7147be681038b8f91a5a0c4ec1d577144a4372cad33b6645adb3a
4
- data.tar.gz: a52b1ccc320fee7d5526f612fa509218a5b2232ce678d6d6e03a42d5c0122df0
3
+ metadata.gz: 19452cfc021fd5ffde923f55f294aafb99c4b80506047d0ef195233d019c5adb
4
+ data.tar.gz: 3211ddb4779f6bbe1b079f6c83b018bb5b7b881bb267d253f94ffaaa20006711
5
5
  SHA512:
6
- metadata.gz: a9e31d538da66f06b1f18412b41520a3c1a5f0f5e3adf88af19f4aa68451420717002c11dc639a2f1dbc9441b64b68b74de2729d2ef8a5af805d862b7568dd75
7
- data.tar.gz: cb2f118b465d7cb4a64f0aa9eb77cb81ec0bd5a82ae2b33c1e9992b1b518e5690dee84f7c5ae69189b83e0394de784d559c57daef41154c24f396390cb0d8bab
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] | `[]` | Allowlist for proxy peers (by IP/CIDR/regex/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] | see code | 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. |
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
- parts = token.split('.')
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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Verikloak
4
+ module BFF
5
+ module Constants
6
+ MAX_TOKEN_BYTES = 4096
7
+ end
8
+ end
9
+ end
@@ -45,7 +45,7 @@ module Verikloak
45
45
  return nil unless raw
46
46
 
47
47
  token = raw.to_s.strip
48
- return ::Regexp.last_match(1) if token =~ /^Bearer\s+(.+)$/i
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.to_s.strip
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
- parts = token.to_s.split('.')
126
- return [{}, {}] unless parts.size >= 2
133
+ return [{}, {}] if token.nil? || token.bytesize > Constants::MAX_TOKEN_BYTES
127
134
 
128
- payload = begin
129
- JSON.parse(::JWT::Base64.url_decode(parts[1]))
130
- rescue StandardError
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
- return if auth_token == fwd_token
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 true if trusted.nil? || trusted.empty?
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 true if trusted.nil? || trusted.empty?
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']
@@ -5,6 +5,6 @@
5
5
  # @return [String]
6
6
  module Verikloak
7
7
  module BFF
8
- VERSION = '0.1.0'
8
+ VERSION = '0.1.1'
9
9
  end
10
10
  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.1.0
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.0
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: