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 +4 -4
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +36 -1
- data/README.md +19 -0
- data/lib/legion/crypt/mock_vault.rb +40 -0
- data/lib/legion/crypt/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: 13bcc3fe80b97d57f54b1e5449f0319865ee5b05c5402ce9a5f3b3a30f89ad88
|
|
4
|
+
data.tar.gz: 1ba4f54c017ec83868defedd3fe4d7a3659b5d84f3afa030722dce9d9bb1211c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f120ed5f85a929fe22e750e482253550000888afbc6633c989df680c7acdc93673303014d911871642a48f66ab05e6899ab773319b1a195c64736eb052fc204
|
|
7
|
+
data.tar.gz: 8a4d154360dca522f05982d986eb28d25212038c7ca37e94253a4a1a89b03a9a51a15e46e2dedc77f382b5b7f84fcca3c8924332c9dae230858a3c154e3e8b15
|
data/CHANGELOG.md
CHANGED
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
|
data/lib/legion/crypt/version.rb
CHANGED
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.
|
|
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
|