legion-crypt 1.4.0 → 1.4.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: 3bf2e0f0673b2c963e56d71c586d299a39c3711b0d3f49fe851375d78cebe07e
4
- data.tar.gz: 194d4c8b64922f5634dcf75607af87a0d83ba10efcb1c83421456bf7a3fddfc6
3
+ metadata.gz: 13bcc3fe80b97d57f54b1e5449f0319865ee5b05c5402ce9a5f3b3a30f89ad88
4
+ data.tar.gz: 1ba4f54c017ec83868defedd3fe4d7a3659b5d84f3afa030722dce9d9bb1211c
5
5
  SHA512:
6
- metadata.gz: 33e4ea2d0ca6413f24099181f5ccf3b61ced2c33f079dd11029ded41095ceaedaf419b315e097ede586560e5478cec5be946dbe627d0ab68763c5354baa3617f
7
- data.tar.gz: 8985763ca6ee45362527ec0368175125ed95d6001dc4d0f43759017fe1c7de33f7c194be7501e8c9c19a12fe72ac3fa1ce2aca5e6c5fde8d39c2bc76999ec925
6
+ metadata.gz: 6f120ed5f85a929fe22e750e482253550000888afbc6633c989df680c7acdc93673303014d911871642a48f66ab05e6899ab773319b1a195c64736eb052fc204
7
+ data.tar.gz: 8a4d154360dca522f05982d986eb28d25212038c7ca37e94253a4a1a89b03a9a51a15e46e2dedc77f382b5b7f84fcca3c8924332c9dae230858a3c154e3e8b15
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.4.1] - 2026-03-16
6
+
7
+ ### Added
8
+ - `Legion::Crypt::MockVault` in-memory Vault mock for local development mode
9
+
5
10
  ## [1.4.0] - 2026-03-16
6
11
 
7
12
  ### Added
data/CLAUDE.md CHANGED
@@ -34,8 +34,14 @@ Legion::Crypt (singleton module)
34
34
  ├── JWT # JSON Web Token operations
35
35
  │ ├── .issue # Create signed JWT (HS256 or RS256)
36
36
  │ ├── .verify # Verify and decode JWT
37
+ │ ├── .verify_with_jwks # Verify RS256 token via external JWKS endpoint (Entra ID, etc.)
37
38
  │ └── .decode # Decode without verification (inspection)
38
39
 
40
+ ├── JwksClient # External JWKS endpoint integration (thread-safe)
41
+ │ ├── .fetch_keys # Fetch and parse JWKS from a URL
42
+ │ ├── .find_key # Lookup key by kid (cache-first, re-fetch on miss)
43
+ │ └── .clear_cache # Clear the key cache
44
+
39
45
  ├── ClusterSecret # Cluster-wide shared secret management
40
46
  │ └── .cs # Generate/distribute cluster secret
41
47
 
@@ -46,6 +52,7 @@ Legion::Crypt (singleton module)
46
52
 
47
53
  ├── VaultRenewer # Background Vault token renewal thread
48
54
  ├── LeaseManager # Dynamic Vault lease lifecycle: fetch, cache, renew, rotate, push-back
55
+ ├── MockVault # In-memory Vault mock for local development mode
49
56
  ├── Settings # Default crypt config
50
57
  └── Version
51
58
  ```
@@ -57,6 +64,7 @@ Legion::Crypt (singleton module)
57
64
  - **Vault Conditional**: Vault module is only included if the `vault` gem is available
58
65
  - **Token Lifecycle**: VaultRenewer runs background thread for automatic token renewal
59
66
  - **JWT Dual Algorithm**: HS256 (symmetric, cluster secret) for intra-cluster tokens; RS256 (asymmetric, RSA keypair) for tokens verifiable without sharing the signing key
67
+ - **JWKS External Validation**: `JwksClient` fetches public keys from external identity provider JWKS endpoints (Entra ID, Bot Framework). Keys cached for 1 hour (CACHE_TTL=3600s), thread-safe via Mutex, automatic re-fetch on cache miss handles key rotation
60
68
 
61
69
  ## Default Settings
62
70
 
@@ -94,7 +102,8 @@ Dev dependencies: `legion-logging`, `legion-settings`
94
102
  |------|---------|
95
103
  | `lib/legion/crypt.rb` | Module entry, start/shutdown lifecycle |
96
104
  | `lib/legion/crypt/cipher.rb` | AES-256-CBC encrypt/decrypt, RSA key generation |
97
- | `lib/legion/crypt/jwt.rb` | JWT issue/verify/decode operations |
105
+ | `lib/legion/crypt/jwt.rb` | JWT issue/verify/decode/verify_with_jwks operations |
106
+ | `lib/legion/crypt/jwks_client.rb` | JWKS endpoint fetch, parse, cache (thread-safe, 1hr TTL) |
98
107
  | `lib/legion/crypt/vault.rb` | Vault read/write/connect/renew operations |
99
108
  | `lib/legion/crypt/cluster_secret.rb` | Cluster-wide shared secret management |
100
109
  | `lib/legion/crypt/vault_jwt_auth.rb` | Vault JWT auth backend: `.login`, `.login!`, `.worker_login`; raises `AuthError` on failure |
@@ -110,6 +119,7 @@ First service-level module initialized during `Legion::Service` startup (before
110
119
  2. Message encryption for `legion-transport` (optional `transport.messages.encrypt`)
111
120
  3. Cluster secret for inter-node encrypted communication
112
121
  4. JWT tokens for node authentication and task authorization
122
+ 5. External token verification for identity providers (Entra ID OIDC via JWKS)
113
123
 
114
124
  ### Vault JWT Auth Usage
115
125
 
@@ -144,6 +154,31 @@ decoded = Legion::Crypt::JWT.decode(token) # no verification, inspection only
144
154
  - `HS256` (default): Uses cluster secret. All cluster nodes can issue and verify.
145
155
  - `RS256`: Uses RSA keypair. Only the issuing node can sign; anyone with the public key can verify.
146
156
 
157
+ ### External Token Verification (JWKS)
158
+
159
+ Verify tokens from external identity providers using their public JWKS endpoints:
160
+
161
+ ```ruby
162
+ # Convenience method
163
+ claims = Legion::Crypt.verify_external_token(
164
+ token,
165
+ jwks_url: 'https://login.microsoftonline.com/TENANT/discovery/v2.0/keys',
166
+ issuers: ['https://login.microsoftonline.com/TENANT/v2.0'],
167
+ audience: 'app-client-id'
168
+ )
169
+
170
+ # Direct module usage
171
+ claims = Legion::Crypt::JWT.verify_with_jwks(token, jwks_url: jwks_url)
172
+ ```
173
+
174
+ **Flow:** decode JWT header (unverified) to extract `kid` -> `JwksClient.find_key` fetches the matching public key from cache or JWKS endpoint -> verify JWT signature with the public key.
175
+
176
+ **Options:** `issuers:` (array, multi-issuer support), `audience:` (string), `verify_expiration:` (bool, default true).
177
+
178
+ **Error hierarchy:** `ExpiredTokenError`, `InvalidTokenError` (bad signature, wrong issuer, wrong audience), `DecodeError` (malformed token) — all inherit from `Legion::Crypt::JWT::Error`.
179
+
180
+ **Used by:** `lex-identity` Entra runner for Digital Worker OIDC token validation.
181
+
147
182
  ---
148
183
 
149
184
  **Maintained By**: Matthew Iverson (@Esity)
data/README.md CHANGED
@@ -41,6 +41,25 @@ claims = Legion::Crypt.verify_token(token, algorithm: 'RS256')
41
41
  decoded = Legion::Crypt::JWT.decode(token)
42
42
  ```
43
43
 
44
+ ### External Token Verification (JWKS)
45
+
46
+ Verify tokens from external identity providers (Entra ID, Bot Framework) using their public JWKS endpoints:
47
+
48
+ ```ruby
49
+ # Verify an Entra ID OIDC token
50
+ claims = Legion::Crypt.verify_external_token(
51
+ token,
52
+ jwks_url: 'https://login.microsoftonline.com/TENANT/discovery/v2.0/keys',
53
+ issuers: ['https://login.microsoftonline.com/TENANT/v2.0'],
54
+ audience: 'app-client-id'
55
+ )
56
+
57
+ # Or use the JWT module directly
58
+ claims = Legion::Crypt::JWT.verify_with_jwks(token, jwks_url: jwks_url)
59
+ ```
60
+
61
+ Public keys are cached for 1 hour and automatically re-fetched on cache miss (handles key rotation).
62
+
44
63
  ## Configuration
45
64
 
46
65
  ```json
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Crypt
5
+ module MockVault
6
+ @store = {}
7
+ @mutex = Mutex.new
8
+
9
+ class << self
10
+ def read(path)
11
+ @mutex.synchronize { @store[path]&.dup }
12
+ end
13
+
14
+ def write(path, data)
15
+ @mutex.synchronize { @store[path] = data.dup }
16
+ true
17
+ end
18
+
19
+ def delete(path)
20
+ @mutex.synchronize { @store.delete(path) }
21
+ true
22
+ end
23
+
24
+ def list(prefix)
25
+ @mutex.synchronize do
26
+ @store.keys.select { |k| k.start_with?(prefix) }
27
+ end
28
+ end
29
+
30
+ def reset!
31
+ @mutex.synchronize { @store.clear }
32
+ end
33
+
34
+ def connected?
35
+ true
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Crypt
5
- VERSION = '1.4.0'
5
+ VERSION = '1.4.1'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-crypt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -62,6 +62,7 @@ files:
62
62
  - lib/legion/crypt/jwks_client.rb
63
63
  - lib/legion/crypt/jwt.rb
64
64
  - lib/legion/crypt/lease_manager.rb
65
+ - lib/legion/crypt/mock_vault.rb
65
66
  - lib/legion/crypt/settings.rb
66
67
  - lib/legion/crypt/vault.rb
67
68
  - lib/legion/crypt/vault_jwt_auth.rb