secure-gateway-sdk 1.0.1 → 1.2.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.
- package/README.md +82 -51
- package/index.js +37 -5
- package/lib/enclave-client.js +60 -19
- package/middleware/mls.js +10 -5
- package/middleware/rateLimit.js +108 -0
- package/middleware/rbac.js +54 -0
- package/package.json +5 -3
- package/server.js +55 -6
package/README.md
CHANGED
|
@@ -1,70 +1,101 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Secure Gateway SDK - Proxy Library
|
|
2
2
|
|
|
3
|
-
This package is a
|
|
3
|
+
This package is a modular API Gateway router library designed to secure and route traffic to a microservice ecosystem (Auth, Enclave, and Audit endpoints).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 1. Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Install the package via the npm registry:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm install secure-gateway-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 2. Setup Requirements
|
|
16
|
+
|
|
17
|
+
To use this library, your Node.js/Express application must have:
|
|
18
|
+
* **Express** installed as a peer dependency.
|
|
19
|
+
* **JSON Parsing Middleware** (`express.json()`) mounted before the gateway.
|
|
20
|
+
* An active **JWT Token Secret** to decode and verify client identities.
|
|
10
21
|
|
|
11
|
-
|
|
12
|
-
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 3. Integration Code Example
|
|
25
|
+
|
|
26
|
+
Create your gateway server (e.g., `gateway.js`) and mount the middleware as follows:
|
|
13
27
|
|
|
14
|
-
### 2. Integration Example (Your main app)
|
|
15
28
|
```javascript
|
|
16
29
|
const express = require('express');
|
|
17
|
-
const { SecurityGateway } = require('
|
|
30
|
+
const { SecurityGateway } = require('secure-gateway-sdk');
|
|
18
31
|
|
|
19
32
|
const app = express();
|
|
20
33
|
|
|
21
|
-
//
|
|
34
|
+
// Required to parse JSON payloads for secure TEE channels
|
|
35
|
+
app.use(express.json());
|
|
36
|
+
|
|
37
|
+
// Initialize and mount the gateway router
|
|
22
38
|
app.use('/api', SecurityGateway({
|
|
23
|
-
jwtSecret:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
jwtSecret: process.env.JWT_SECRET || 'your_secret_signing_key',
|
|
40
|
+
routes: [
|
|
41
|
+
{
|
|
42
|
+
path: '/auth',
|
|
43
|
+
target: 'http://<auth-service-host>:<port>',
|
|
44
|
+
protected: false
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
path: '/enclave',
|
|
48
|
+
target: 'http://<enclave-service-host>:<port>',
|
|
49
|
+
protected: true,
|
|
50
|
+
requiredClearance: 'TS',
|
|
51
|
+
useSecureChannel: true,
|
|
52
|
+
mrenclave: 'YOUR_EXPECTED_MRENCLAVE_HASH',
|
|
53
|
+
blsPrivateKey: process.env.BLS_PRIVATE_KEY || 'your_bls_private_key',
|
|
54
|
+
auditUrl: 'http://<audit-service-host>:<port>',
|
|
55
|
+
actionName: 'SECURE_COMPUTE'
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
rbacPolicies: {
|
|
59
|
+
admin: { resources: ['*'], actions: ['*'] },
|
|
60
|
+
user: { resources: ['/enclave'], actions: ['GET'] }
|
|
61
|
+
},
|
|
62
|
+
mlsLattice: {
|
|
63
|
+
'U': 10,
|
|
64
|
+
'S': 30,
|
|
65
|
+
'TS': 40
|
|
66
|
+
}
|
|
27
67
|
}));
|
|
28
68
|
|
|
29
|
-
|
|
30
|
-
|
|
69
|
+
const PORT = process.env.PORT || 3000;
|
|
70
|
+
app.listen(PORT, () => {
|
|
71
|
+
console.log(`Security Gateway active on port ${PORT}`);
|
|
31
72
|
});
|
|
32
73
|
```
|
|
33
74
|
|
|
34
|
-
### Routing Rules Under the Hood
|
|
35
|
-
Once mounted (for example at `/api`), the library automatically protects traffic:
|
|
36
|
-
* `POST /api/auth/register` -> Publicly proxies to `authTarget`
|
|
37
|
-
* `GET /api/enclave/data` -> Instantly intercepted! Extracts JWT, validates against `jwtSecret`, and passes to `enclaveTarget` only if valid.
|
|
38
|
-
|
|
39
75
|
---
|
|
40
76
|
|
|
41
|
-
##
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
### 4. Access the Enclave (Protected Route - Failure)
|
|
67
|
-
If you try to access the enclave without attaching the token header, the SDK will proactively block you.
|
|
68
|
-
```bash
|
|
69
|
-
curl -X GET http://localhost:7001/api/enclave
|
|
70
|
-
```
|
|
77
|
+
## 4. Configuration Reference
|
|
78
|
+
|
|
79
|
+
### SecurityGateway Options
|
|
80
|
+
|
|
81
|
+
| Property | Type | Required | Description |
|
|
82
|
+
| :--- | :--- | :--- | :--- |
|
|
83
|
+
| `jwtSecret` | String | Yes | Secret key used to verify client JWT signatures. |
|
|
84
|
+
| `routes` | Array | Yes | List of route definitions to protect and proxy. |
|
|
85
|
+
| `rbacPolicies` | Object | No | Map of roles to allowed resources and request methods. |
|
|
86
|
+
| `mlsLattice` | Object | No | Clearance level hierarchy definitions (e.g., `U`, `S`, `TS`). |
|
|
87
|
+
| `rateLimit` | Object | No | Custom intervals and maximum requests for the rate limiters. |
|
|
88
|
+
|
|
89
|
+
### Route Object Schema
|
|
90
|
+
|
|
91
|
+
| Property | Type | Required | Description |
|
|
92
|
+
| :--- | :--- | :--- | :--- |
|
|
93
|
+
| `path` | String | Yes | Route mount path (e.g., `/enclave`). |
|
|
94
|
+
| `target` | String | Yes | Microservice downstream target URL. |
|
|
95
|
+
| `protected` | Boolean | Yes | Whether requests require JWT authentication and RBAC validation. |
|
|
96
|
+
| `requiredClearance`| String | No | Level needed to pass Bell-LaPadula clearance validation. |
|
|
97
|
+
| `useSecureChannel` | Boolean | No | Toggles automated TEE Handshake (ECDH key exchange). |
|
|
98
|
+
| `mrenclave` | String | No | Expected hardware enclave hash identity. |
|
|
99
|
+
| `blsPrivateKey` | String | No | Private key used to sign transaction aggregate proofs. |
|
|
100
|
+
| `auditUrl` | String | No | Endpoint of the centralized Cryptographic Audit Log Service. |
|
|
101
|
+
| `actionName` | String | No | Identifier label for the audited action type. |
|
package/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
|
+
const fetch = require('node-fetch');
|
|
2
3
|
const { createProxyMiddleware } = require('http-proxy-middleware');
|
|
3
4
|
const { createSecurityMiddleware } = require('./middleware/security');
|
|
4
5
|
const { createMLSMiddleware } = require('./middleware/mls');
|
|
6
|
+
const { createRBACMiddleware } = require('./middleware/rbac');
|
|
7
|
+
const { createGlobalRateLimit, createAuthRateLimit, createSensitiveRateLimit } = require('./middleware/rateLimit');
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Universal Security Gateway Library (Publishable SDK)
|
|
@@ -13,25 +16,49 @@ function SecurityGateway(options) {
|
|
|
13
16
|
throw new Error('[SecurityGateway] Fatal: "jwtSecret" must be provided in configuration options.');
|
|
14
17
|
}
|
|
15
18
|
|
|
19
|
+
// --- Availability: Rate Limiting & DDoS Protection ---
|
|
20
|
+
const globalLimiter = createGlobalRateLimit(options.rateLimit?.global);
|
|
21
|
+
const authLimiter = createAuthRateLimit(options.rateLimit?.auth);
|
|
22
|
+
const sensitiveLimiter = createSensitiveRateLimit(options.rateLimit?.sensitive);
|
|
23
|
+
|
|
24
|
+
// Apply global rate limit to ALL requests through the gateway
|
|
25
|
+
router.use(globalLimiter);
|
|
26
|
+
console.log('[SecurityGateway] DDoS Protection: Global rate limiter active');
|
|
27
|
+
|
|
16
28
|
const verifyToken = createSecurityMiddleware(options.jwtSecret);
|
|
29
|
+
const rbacMiddleware = createRBACMiddleware(options.rbacPolicies);
|
|
30
|
+
const enclaveClients = new Map(); // Cache to maintain persistent secure sessions
|
|
31
|
+
const blsAuditInstances = new Map(); // Cache to maintain aggregate signatures per route
|
|
17
32
|
|
|
18
33
|
if (options.routes && Array.isArray(options.routes)) {
|
|
19
34
|
options.routes.forEach(route => {
|
|
20
35
|
const middlewares = [];
|
|
21
36
|
|
|
22
37
|
if (route.protected) {
|
|
38
|
+
// Stricter rate limit on sensitive/protected endpoints
|
|
39
|
+
middlewares.push(sensitiveLimiter);
|
|
23
40
|
middlewares.push(verifyToken);
|
|
41
|
+
middlewares.push(rbacMiddleware);
|
|
24
42
|
if (route.requiredClearance) {
|
|
25
|
-
middlewares.push(createMLSMiddleware(route.requiredClearance));
|
|
43
|
+
middlewares.push(createMLSMiddleware(route.requiredClearance, options.mlsLattice));
|
|
26
44
|
}
|
|
27
45
|
}
|
|
28
46
|
|
|
29
47
|
if (route.useSecureChannel) {
|
|
30
48
|
const EnclaveClient = require('./lib/enclave-client');
|
|
31
|
-
// Security params are now pulled directly from the ROUTE config!
|
|
32
|
-
const enclaveClient = new EnclaveClient(route.target, route.mrenclave);
|
|
33
49
|
const BLSAuditSim = require('./lib/bls');
|
|
34
|
-
|
|
50
|
+
|
|
51
|
+
// Get or Create a persistent client for this specific target
|
|
52
|
+
if (!enclaveClients.has(route.target)) {
|
|
53
|
+
enclaveClients.set(route.target, new EnclaveClient(route.target, route.mrenclave));
|
|
54
|
+
}
|
|
55
|
+
const enclaveClient = enclaveClients.get(route.target);
|
|
56
|
+
|
|
57
|
+
// Get or Create a persistent BLS instance to maintain aggregate signatures
|
|
58
|
+
if (!blsAuditInstances.has(route.path)) {
|
|
59
|
+
blsAuditInstances.set(route.path, new BLSAuditSim(route.blsPrivateKey));
|
|
60
|
+
}
|
|
61
|
+
const blsAudit = blsAuditInstances.get(route.path);
|
|
35
62
|
|
|
36
63
|
middlewares.push(express.json());
|
|
37
64
|
middlewares.push(async (req, res) => {
|
|
@@ -68,6 +95,8 @@ function SecurityGateway(options) {
|
|
|
68
95
|
}
|
|
69
96
|
});
|
|
70
97
|
} else {
|
|
98
|
+
// Auth endpoints get brute-force protection
|
|
99
|
+
middlewares.push(authLimiter);
|
|
71
100
|
const proxyConfig = {
|
|
72
101
|
target: route.target,
|
|
73
102
|
changeOrigin: true
|
|
@@ -90,5 +119,8 @@ module.exports = {
|
|
|
90
119
|
SecurityGateway,
|
|
91
120
|
createSecurityMiddleware,
|
|
92
121
|
createMLSMiddleware,
|
|
93
|
-
SecureChannel
|
|
122
|
+
SecureChannel,
|
|
123
|
+
createGlobalRateLimit,
|
|
124
|
+
createAuthRateLimit,
|
|
125
|
+
createSensitiveRateLimit
|
|
94
126
|
};
|
package/lib/enclave-client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { SecureChannel } = require('./crypto');
|
|
2
2
|
const crypto = require('crypto');
|
|
3
|
+
const fetch = require('node-fetch');
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* EnclaveClient acts as the secure intermediary between the Proxy and the TEE.
|
|
@@ -46,33 +47,73 @@ class EnclaveClient {
|
|
|
46
47
|
console.log('[Proxy->Enclave] Attestation successful. ECDH secure channel established.');
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
async seal(payload, label, token) {
|
|
51
|
+
if (!this.isAttested) await this.attestAndHandshake();
|
|
52
|
+
const encrypted = this.channel.encrypt(payload);
|
|
53
|
+
const response = await fetch(`${this.enclaveUrl}/seal`, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
|
56
|
+
body: JSON.stringify({ ...encrypted, label })
|
|
57
|
+
});
|
|
58
|
+
return response.json();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async unseal(label, token) {
|
|
62
|
+
if (!this.isAttested) await this.attestAndHandshake();
|
|
63
|
+
const response = await fetch(`${this.enclaveUrl}/unseal`, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
|
66
|
+
body: JSON.stringify({ label })
|
|
67
|
+
});
|
|
68
|
+
const data = await response.json();
|
|
69
|
+
if (!response.ok) throw new Error(data.error || 'Unseal failed');
|
|
70
|
+
return this.channel.decrypt(data);
|
|
71
|
+
}
|
|
72
|
+
|
|
49
73
|
async execute(payload, token) {
|
|
50
74
|
// If we haven't verified the enclave yet, do it now
|
|
51
75
|
if (!this.isAttested) {
|
|
52
76
|
await this.attestAndHandshake();
|
|
53
77
|
}
|
|
54
78
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
79
|
+
try {
|
|
80
|
+
// Encrypt the payload using AES-256-GCM
|
|
81
|
+
const encrypted = this.channel.encrypt(payload);
|
|
82
|
+
|
|
83
|
+
// Send to Enclave
|
|
84
|
+
const response = await fetch(`${this.enclaveUrl}/execute`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
'Authorization': `Bearer ${token}`
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify(encrypted)
|
|
91
|
+
});
|
|
67
92
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
93
|
+
const data = await response.json();
|
|
94
|
+
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
// AUTO-RETRY LOGIC: If enclave restarted, re-attest once
|
|
97
|
+
if (data.error && data.error.includes("Shared secret not computed")) {
|
|
98
|
+
console.warn('[Proxy] Enclave session lost. Re-attesting and retrying...');
|
|
99
|
+
this.isAttested = false;
|
|
100
|
+
await this.attestAndHandshake();
|
|
101
|
+
return this.execute(payload, token); // Recursive retry
|
|
102
|
+
}
|
|
103
|
+
throw new Error(data.error || 'Execution failed');
|
|
104
|
+
}
|
|
73
105
|
|
|
74
|
-
|
|
75
|
-
|
|
106
|
+
// Decrypt the response
|
|
107
|
+
return this.channel.decrypt(data);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
// If encryption failed because secret was somehow null (concurrency), retry
|
|
110
|
+
if (error.message.includes("not computed")) {
|
|
111
|
+
this.isAttested = false;
|
|
112
|
+
await this.attestAndHandshake();
|
|
113
|
+
return this.execute(payload, token);
|
|
114
|
+
}
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
76
117
|
}
|
|
77
118
|
}
|
|
78
119
|
|
package/middleware/mls.js
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
* Implements Bell-LaPadula Lattice logic for the SDK.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const DEFAULT_LATTICE = {
|
|
7
7
|
'U': 10, // Unclassified
|
|
8
8
|
'C': 20, // Confidential
|
|
9
9
|
'S': 30, // Secret
|
|
10
10
|
'TS': 40 // Top Secret
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
function createMLSMiddleware(routeRequiredClearance) {
|
|
14
|
-
const
|
|
13
|
+
function createMLSMiddleware(routeRequiredClearance, customLattice) {
|
|
14
|
+
const lattice = customLattice || DEFAULT_LATTICE;
|
|
15
|
+
const routeLevel = lattice[routeRequiredClearance?.toUpperCase()];
|
|
15
16
|
|
|
16
17
|
if (!routeLevel) {
|
|
17
18
|
throw new Error(`[MLS Engine] Fatal: Invalid clearance level provided: ${routeRequiredClearance}. Must be U, C, S, or TS.`);
|
|
@@ -24,12 +25,16 @@ function createMLSMiddleware(routeRequiredClearance) {
|
|
|
24
25
|
return res.status(403).json({ error: 'MLS Blocked: User clearance level is not defined.' });
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
const userLevel =
|
|
28
|
+
const userLevel = lattice[req.user.clearance.toUpperCase()];
|
|
28
29
|
if (!userLevel) {
|
|
29
30
|
return res.status(403).json({ error: `MLS Blocked: Unknown user clearance header '${req.user.clearance}'` });
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
// Secure execution or TEE retrieval endpoints are functionally reads (requesting sensitive output)
|
|
34
|
+
const isRead = ['GET', 'HEAD', 'OPTIONS'].includes(req.method) ||
|
|
35
|
+
req.path.includes('/enclave') ||
|
|
36
|
+
req.path.includes('/execute') ||
|
|
37
|
+
req.path.includes('/unseal');
|
|
33
38
|
const isWrite = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method);
|
|
34
39
|
|
|
35
40
|
// ----------------------------------------------------
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const rateLimit = require('express-rate-limit');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Rate Limiting & DDoS Protection Middleware
|
|
5
|
+
*
|
|
6
|
+
* Provides the Availability property of the CIA triad by preventing
|
|
7
|
+
* resource exhaustion through excessive requests.
|
|
8
|
+
*
|
|
9
|
+
* Three tiers of protection:
|
|
10
|
+
* 1. Global rate limit — caps total requests per IP
|
|
11
|
+
* 2. Auth rate limit — stricter limit on login/register (brute-force protection)
|
|
12
|
+
* 3. Sensitive route rate limit — strictest limit on protected endpoints
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a global rate limiter for all gateway traffic.
|
|
19
|
+
* Default: 100 requests per 15 minutes per IP.
|
|
20
|
+
* In development, window is 15 seconds to allow fast simulator resets.
|
|
21
|
+
*/
|
|
22
|
+
function createGlobalRateLimit(options = {}) {
|
|
23
|
+
const windowMs = process.env.RATE_LIMIT_GLOBAL_WINDOW_MS
|
|
24
|
+
? parseInt(process.env.RATE_LIMIT_GLOBAL_WINDOW_MS)
|
|
25
|
+
: (options.windowMs || (isDev ? 15 * 1000 : 15 * 60 * 1000));
|
|
26
|
+
const max = process.env.RATE_LIMIT_GLOBAL_MAX
|
|
27
|
+
? parseInt(process.env.RATE_LIMIT_GLOBAL_MAX)
|
|
28
|
+
: (options.max || 100);
|
|
29
|
+
|
|
30
|
+
return rateLimit({
|
|
31
|
+
windowMs,
|
|
32
|
+
max,
|
|
33
|
+
standardHeaders: true, // Return rate limit info in headers
|
|
34
|
+
legacyHeaders: false,
|
|
35
|
+
message: {
|
|
36
|
+
error: 'Too many requests from this IP. Please try again later.',
|
|
37
|
+
retryAfterMs: windowMs
|
|
38
|
+
},
|
|
39
|
+
handler: (req, res, next, options) => {
|
|
40
|
+
console.warn(`[DDoS Protection] Global rate limit exceeded for IP: ${req.ip}`);
|
|
41
|
+
res.status(429).json(options.message);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Creates a strict rate limiter for authentication endpoints.
|
|
48
|
+
* Default: 10 requests per 15 minutes per IP (brute-force protection).
|
|
49
|
+
* In development, window is 15 seconds to allow fast simulator resets.
|
|
50
|
+
*/
|
|
51
|
+
function createAuthRateLimit(options = {}) {
|
|
52
|
+
const windowMs = process.env.RATE_LIMIT_AUTH_WINDOW_MS
|
|
53
|
+
? parseInt(process.env.RATE_LIMIT_AUTH_WINDOW_MS)
|
|
54
|
+
: (options.windowMs || (isDev ? 15 * 1000 : 15 * 60 * 1000));
|
|
55
|
+
const max = process.env.RATE_LIMIT_AUTH_MAX
|
|
56
|
+
? parseInt(process.env.RATE_LIMIT_AUTH_MAX)
|
|
57
|
+
: (options.max || 10);
|
|
58
|
+
|
|
59
|
+
return rateLimit({
|
|
60
|
+
windowMs,
|
|
61
|
+
max,
|
|
62
|
+
standardHeaders: true,
|
|
63
|
+
legacyHeaders: false,
|
|
64
|
+
message: {
|
|
65
|
+
error: 'Too many authentication attempts. Account protection triggered.',
|
|
66
|
+
retryAfterMs: windowMs
|
|
67
|
+
},
|
|
68
|
+
handler: (req, res, next, options) => {
|
|
69
|
+
console.warn(`[DDoS Protection] Auth rate limit exceeded for IP: ${req.ip} on ${req.path}`);
|
|
70
|
+
res.status(429).json(options.message);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Creates a strict rate limiter for sensitive/protected routes (e.g., Enclave access).
|
|
77
|
+
* Default: 20 requests per 15 minutes per IP.
|
|
78
|
+
* In development, window is 15 seconds and max is 10 to guarantee simulator triggers and resets.
|
|
79
|
+
*/
|
|
80
|
+
function createSensitiveRateLimit(options = {}) {
|
|
81
|
+
const windowMs = process.env.RATE_LIMIT_SENSITIVE_WINDOW_MS
|
|
82
|
+
? parseInt(process.env.RATE_LIMIT_SENSITIVE_WINDOW_MS)
|
|
83
|
+
: (options.windowMs || (isDev ? 15 * 1000 : 15 * 60 * 1000));
|
|
84
|
+
const max = process.env.RATE_LIMIT_SENSITIVE_MAX
|
|
85
|
+
? parseInt(process.env.RATE_LIMIT_SENSITIVE_MAX)
|
|
86
|
+
: (options.max || (isDev ? 10 : 20));
|
|
87
|
+
|
|
88
|
+
return rateLimit({
|
|
89
|
+
windowMs,
|
|
90
|
+
max,
|
|
91
|
+
standardHeaders: true,
|
|
92
|
+
legacyHeaders: false,
|
|
93
|
+
message: {
|
|
94
|
+
error: 'Too many requests to protected resource. Access temporarily restricted.',
|
|
95
|
+
retryAfterMs: windowMs
|
|
96
|
+
},
|
|
97
|
+
handler: (req, res, next, options) => {
|
|
98
|
+
console.warn(`[DDoS Protection] Sensitive route rate limit exceeded for IP: ${req.ip}`);
|
|
99
|
+
res.status(429).json(options.message);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
createGlobalRateLimit,
|
|
106
|
+
createAuthRateLimit,
|
|
107
|
+
createSensitiveRateLimit
|
|
108
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RBAC Engine (Role-Based Access Control)
|
|
3
|
+
* Enforces policies provided at initialization.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function createRBACMiddleware(policies) {
|
|
7
|
+
if (!policies) {
|
|
8
|
+
console.warn('[RBAC Engine] Warning: No policies provided. All access will be denied.');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return (req, res, next) => {
|
|
12
|
+
// verifyToken MUST run before this middleware so req.user exists
|
|
13
|
+
if (!req.user || !req.user.role) {
|
|
14
|
+
console.error('[RBAC Engine] Missing role from JWT Payload!');
|
|
15
|
+
return res.status(403).json({ error: 'RBAC Blocked: User role is not defined.' });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const userRole = req.user.role.toLowerCase();
|
|
19
|
+
const policy = policies ? policies[userRole] : null;
|
|
20
|
+
|
|
21
|
+
if (!policy) {
|
|
22
|
+
return res.status(403).json({ error: `RBAC Blocked: No policy defined for role '${userRole}'` });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { resources, actions } = policy;
|
|
26
|
+
const requestPath = req.path;
|
|
27
|
+
const requestMethod = req.method.toUpperCase();
|
|
28
|
+
|
|
29
|
+
// 1. Check Action Permission
|
|
30
|
+
if (!actions.includes('*') && !actions.includes(requestMethod)) {
|
|
31
|
+
console.warn(`[RBAC] Blocked Action: Role(${userRole}) cannot perform ${requestMethod}`);
|
|
32
|
+
return res.status(403).json({ error: `RBAC Violation: Your role (${userRole}) is not permitted to perform ${requestMethod} operations.` });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Check Resource Permission (Simple glob-like matching)
|
|
36
|
+
const hasAccess = resources.some(pattern => {
|
|
37
|
+
if (pattern === '*') return true;
|
|
38
|
+
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
|
|
39
|
+
return regex.test(requestPath);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!hasAccess) {
|
|
43
|
+
console.warn(`[RBAC] Blocked Resource: Role(${userRole}) cannot access ${requestPath}`);
|
|
44
|
+
return res.status(403).json({ error: `RBAC Violation: Your role (${userRole}) does not have access to the resource ${requestPath}.` });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`[RBAC Engine] Passed policy checks for user ${req.user.sub} (Role: ${userRole})`);
|
|
48
|
+
next();
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
createRBACMiddleware
|
|
54
|
+
};
|
package/package.json
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "secure-gateway-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"scripts": {
|
|
5
|
-
"dev": "
|
|
5
|
+
"dev": "nodemon server.js"
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"cors": "^2.8.6",
|
|
9
9
|
"dotenv": "^16.6.1",
|
|
10
10
|
"express": "^5.2.1",
|
|
11
|
+
"express-rate-limit": "^8.5.2",
|
|
11
12
|
"http-proxy-middleware": "^3.0.5",
|
|
12
|
-
"jsonwebtoken": "^9.0.3"
|
|
13
|
+
"jsonwebtoken": "^9.0.3",
|
|
14
|
+
"node-fetch": "^2.7.0"
|
|
13
15
|
},
|
|
14
16
|
"description": "This package is a drop-in API Gateway library designed to secure and route traffic to a microservice ecosystem containing Auth, Enclave, and Audit endpoints.",
|
|
15
17
|
"main": "index.js",
|
package/server.js
CHANGED
|
@@ -1,20 +1,69 @@
|
|
|
1
|
-
require('dotenv')
|
|
1
|
+
const dotenv = require('dotenv');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
// Robust workspace-aware dotenv loading
|
|
6
|
+
if (fs.existsSync(path.join(process.cwd(), '.env'))) {
|
|
7
|
+
dotenv.config({ path: path.join(process.cwd(), '.env') });
|
|
8
|
+
} else if (fs.existsSync(path.join(process.cwd(), '..', '.env'))) {
|
|
9
|
+
dotenv.config({ path: path.join(process.cwd(), '..', '.env') });
|
|
10
|
+
} else {
|
|
11
|
+
dotenv.config();
|
|
12
|
+
}
|
|
2
13
|
const express = require('express');
|
|
14
|
+
const cors = require('cors');
|
|
3
15
|
const { SecurityGateway } = require('./index');
|
|
4
16
|
|
|
5
17
|
const app = express();
|
|
18
|
+
app.use(cors());
|
|
19
|
+
|
|
6
20
|
const PORT = process.env.PROXY_PORT || 3000;
|
|
7
21
|
|
|
8
|
-
|
|
22
|
+
/**
|
|
23
|
+
* PROJECT-AGNOSTIC CONFIG LOADER
|
|
24
|
+
* The proxy no longer reaches out to '../config'.
|
|
25
|
+
* It looks for a 'gateway-config.json' in the current working directory
|
|
26
|
+
* or a path specified by GATEWAY_CONFIG_PATH.
|
|
27
|
+
*/
|
|
28
|
+
let configPath = process.env.GATEWAY_CONFIG_PATH
|
|
29
|
+
? path.resolve(process.env.GATEWAY_CONFIG_PATH)
|
|
30
|
+
: path.join(process.cwd(), 'gateway-config.json');
|
|
31
|
+
|
|
32
|
+
// Robust fallback: check parent directory if not found in current working directory
|
|
33
|
+
if (!fs.existsSync(configPath)) {
|
|
34
|
+
const parentConfigPath = path.join(process.cwd(), '..', 'gateway-config.json');
|
|
35
|
+
if (fs.existsSync(parentConfigPath)) {
|
|
36
|
+
configPath = parentConfigPath;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let gatewayConfig = { routes: [], rbacPolicies: {}, mlsLattice: {} };
|
|
41
|
+
|
|
42
|
+
if (fs.existsSync(configPath)) {
|
|
43
|
+
try {
|
|
44
|
+
gatewayConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
45
|
+
console.log(`[Proxy] Configuration loaded from: ${configPath}`);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error(`[Proxy] Failed to parse config at ${configPath}:`, err.message);
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
console.warn(`[Proxy] Warning: No config file found at ${configPath}. Using defaults.`);
|
|
51
|
+
}
|
|
9
52
|
|
|
10
|
-
// Initialize the Security Gateway
|
|
53
|
+
// Initialize the Security Gateway using the injected configuration
|
|
11
54
|
app.use('/api', SecurityGateway({
|
|
12
|
-
jwtSecret: process.env.JWT_SECRET,
|
|
13
|
-
routes:
|
|
55
|
+
jwtSecret: process.env.JWT_SECRET || 'fallback_secret',
|
|
56
|
+
routes: gatewayConfig.routes || [],
|
|
57
|
+
rbacPolicies: gatewayConfig.rbacPolicies || {},
|
|
58
|
+
mlsLattice: gatewayConfig.mlsLattice || {}
|
|
14
59
|
}));
|
|
15
60
|
|
|
16
61
|
app.get('/', (req, res) => {
|
|
17
|
-
res.json({
|
|
62
|
+
res.json({
|
|
63
|
+
message: 'Security Gateway Standalone Server is running.',
|
|
64
|
+
config_source: configPath,
|
|
65
|
+
active_routes: (gatewayConfig.routes || []).length
|
|
66
|
+
});
|
|
18
67
|
});
|
|
19
68
|
|
|
20
69
|
app.listen(PORT, () => {
|