legionio 1.4.43 → 1.4.44
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/.rubocop.yml +1 -0
- data/CHANGELOG.md +6 -0
- data/lib/legion/api/auth_worker.rb +104 -0
- data/lib/legion/api/middleware/auth.rb +1 -1
- data/lib/legion/api.rb +2 -0
- data/lib/legion/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 16599e1c095434bfb65c5a5aa763cb821324e192f8e367cad1aff64fadec7e10
|
|
4
|
+
data.tar.gz: 2747877e468f15212ed3bbd19eaa287323d159a676a01e80e6b1ca11e0abed9b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e522c7f831673baa38cf887b1493e78e7133d930f3e857b82fefddbeeffbcc88877e4d741330a222e744170bd0e5876ec08deca5c8a00d8dc6cc520fbd0f54dc
|
|
7
|
+
data.tar.gz: 802b45c5a8d13d79f0c52ac872080a6fde0e5b64bc7ee0f46a21fbdb5be56c23ddad2f6b242b535b9a862e13b446735cb57c44ac50a9a0b021a728c620defe20
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.44] - 2026-03-17
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `POST /api/auth/worker-token`: Entra client credentials token exchange endpoint (validates client_credentials grant via JWKS, looks up worker by appid, issues scoped Legion worker JWT)
|
|
7
|
+
- Auth middleware SKIP_PATHS now includes `/api/auth/token` and `/api/auth/worker-token`
|
|
8
|
+
|
|
3
9
|
## [1.4.43] - 2026-03-17
|
|
4
10
|
|
|
5
11
|
### Fixed
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
class API < Sinatra::Base
|
|
5
|
+
module Routes
|
|
6
|
+
module AuthWorker
|
|
7
|
+
def self.registered(app)
|
|
8
|
+
register_worker_token_exchange(app)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.register_worker_token_exchange(app) # rubocop:disable Metrics/MethodLength
|
|
12
|
+
app.post '/api/auth/worker-token' do
|
|
13
|
+
body = parse_request_body
|
|
14
|
+
grant_type = body[:grant_type]
|
|
15
|
+
entra_token = body[:entra_token]
|
|
16
|
+
|
|
17
|
+
unless grant_type == 'client_credentials'
|
|
18
|
+
halt 400, json_error('unsupported_grant_type', 'grant_type must be client_credentials',
|
|
19
|
+
status_code: 400)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
halt 400, json_error('missing_entra_token', 'entra_token is required', status_code: 400) unless entra_token
|
|
23
|
+
|
|
24
|
+
unless defined?(Legion::Crypt::JWT) && Legion::Crypt::JWT.respond_to?(:verify_with_jwks)
|
|
25
|
+
halt 501, json_error('jwks_validation_not_available',
|
|
26
|
+
'JWKS validation is not available', status_code: 501)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
entra_settings = Routes::AuthWorker.resolve_entra_settings
|
|
30
|
+
tenant_id = entra_settings[:tenant_id]
|
|
31
|
+
unless tenant_id
|
|
32
|
+
halt 500, json_error('entra_tenant_not_configured',
|
|
33
|
+
'Entra tenant_id is not configured', status_code: 500)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
jwks_url = "https://login.microsoftonline.com/#{tenant_id}/discovery/v2.0/keys"
|
|
37
|
+
issuer = "https://login.microsoftonline.com/#{tenant_id}/v2.0"
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
claims = Legion::Crypt::JWT.verify_with_jwks(
|
|
41
|
+
entra_token, jwks_url: jwks_url, issuers: [issuer]
|
|
42
|
+
)
|
|
43
|
+
rescue Legion::Crypt::JWT::ExpiredTokenError
|
|
44
|
+
halt 401, json_error('token_expired', 'Entra token has expired', status_code: 401)
|
|
45
|
+
rescue Legion::Crypt::JWT::InvalidTokenError => e
|
|
46
|
+
halt 401, json_error('invalid_token', e.message, status_code: 401)
|
|
47
|
+
rescue Legion::Crypt::JWT::Error => e
|
|
48
|
+
halt 502, json_error('identity_provider_unavailable', e.message, status_code: 502)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
app_id = claims[:appid] || claims[:azp] || claims['appid'] || claims['azp']
|
|
52
|
+
halt 401, json_error('invalid_token', 'missing appid claim', status_code: 401) unless app_id
|
|
53
|
+
|
|
54
|
+
halt 503, json_error('data_unavailable', 'legion-data not connected', status_code: 503) unless defined?(Legion::Data::Model::DigitalWorker)
|
|
55
|
+
|
|
56
|
+
worker = Legion::Data::Model::DigitalWorker.first(entra_app_id: app_id)
|
|
57
|
+
unless worker
|
|
58
|
+
halt 404, json_error('worker_not_found',
|
|
59
|
+
"no worker registered for entra_app_id #{app_id}", status_code: 404)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
unless worker.lifecycle_state == 'active'
|
|
63
|
+
halt 403, json_error('worker_not_active',
|
|
64
|
+
"worker is in #{worker.lifecycle_state} state", status_code: 403)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
ttl = 3600
|
|
68
|
+
token = Legion::API::Token.issue_worker_token(
|
|
69
|
+
worker_id: worker.worker_id, owner_msid: worker.owner_msid, ttl: ttl
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
json_response({
|
|
73
|
+
access_token: token,
|
|
74
|
+
token_type: 'Bearer',
|
|
75
|
+
expires_in: ttl,
|
|
76
|
+
worker_id: worker.worker_id,
|
|
77
|
+
scope: 'worker'
|
|
78
|
+
})
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.resolve_entra_settings
|
|
83
|
+
return {} unless defined?(Legion::Settings)
|
|
84
|
+
|
|
85
|
+
identity = Legion::Settings[:identity]
|
|
86
|
+
entra = identity.is_a?(Hash) ? identity[:entra] : nil
|
|
87
|
+
return entra if entra.is_a?(Hash)
|
|
88
|
+
|
|
89
|
+
rbac = Legion::Settings[:rbac]
|
|
90
|
+
entra = rbac.is_a?(Hash) ? rbac[:entra] : nil
|
|
91
|
+
return entra if entra.is_a?(Hash)
|
|
92
|
+
|
|
93
|
+
{}
|
|
94
|
+
rescue StandardError
|
|
95
|
+
{}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class << self
|
|
99
|
+
private :register_worker_token_exchange
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -4,7 +4,7 @@ module Legion
|
|
|
4
4
|
class API < Sinatra::Base
|
|
5
5
|
module Middleware
|
|
6
6
|
class Auth
|
|
7
|
-
SKIP_PATHS = %w[/api/health /api/ready /api/openapi.json /metrics].freeze
|
|
7
|
+
SKIP_PATHS = %w[/api/health /api/ready /api/openapi.json /metrics /api/auth/token /api/auth/worker-token].freeze
|
|
8
8
|
AUTH_HEADER = 'HTTP_AUTHORIZATION'
|
|
9
9
|
BEARER_PATTERN = /\ABearer\s+(.+)\z/i
|
|
10
10
|
API_KEY_HEADER = 'HTTP_X_API_KEY'
|
data/lib/legion/api.rb
CHANGED
|
@@ -27,6 +27,7 @@ require_relative 'api/oauth'
|
|
|
27
27
|
require_relative 'api/openapi'
|
|
28
28
|
require_relative 'api/rbac'
|
|
29
29
|
require_relative 'api/auth'
|
|
30
|
+
require_relative 'api/auth_worker'
|
|
30
31
|
require_relative 'api/audit'
|
|
31
32
|
require_relative 'api/metrics'
|
|
32
33
|
|
|
@@ -98,6 +99,7 @@ module Legion
|
|
|
98
99
|
register Routes::OAuth
|
|
99
100
|
register Routes::Rbac
|
|
100
101
|
register Routes::Auth
|
|
102
|
+
register Routes::AuthWorker
|
|
101
103
|
register Routes::Audit
|
|
102
104
|
register Routes::Metrics
|
|
103
105
|
|
data/lib/legion/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legionio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.44
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -318,6 +318,7 @@ files:
|
|
|
318
318
|
- lib/legion/api.rb
|
|
319
319
|
- lib/legion/api/audit.rb
|
|
320
320
|
- lib/legion/api/auth.rb
|
|
321
|
+
- lib/legion/api/auth_worker.rb
|
|
321
322
|
- lib/legion/api/chains.rb
|
|
322
323
|
- lib/legion/api/coldstart.rb
|
|
323
324
|
- lib/legion/api/events.rb
|