securenow 8.11.2 → 8.12.0

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/firewall-cloud.js CHANGED
@@ -1,212 +1,212 @@
1
- 'use strict';
2
-
3
- /**
4
- * Layer 4: Cloud/Edge WAF integration.
5
- * Pushes the blocklist to the customer's cloud WAF provider so traffic is
6
- * blocked at the CDN/edge before it ever reaches the origin server.
7
- *
8
- * Supported providers: cloudflare, aws, gcp
9
- * Cloud SDKs are optional peer dependencies — only required when enabled.
10
- * Cloud credentials are never logged.
11
- */
12
-
13
- const https = require('https');
14
-
15
- let _options = null;
16
- let _active = false;
17
- let _provider = null;
18
- let _lastPushTime = 0;
19
-
20
- const MIN_PUSH_INTERVAL_MS = 30000;
21
-
22
- // ────── Cloudflare ──────
23
-
1
+ 'use strict';
2
+
3
+ /**
4
+ * Layer 4: Cloud/Edge WAF integration.
5
+ * Pushes the blocklist to the customer's cloud WAF provider so traffic is
6
+ * blocked at the CDN/edge before it ever reaches the origin server.
7
+ *
8
+ * Supported providers: cloudflare, aws, gcp
9
+ * Cloud SDKs are optional peer dependencies — only required when enabled.
10
+ * Cloud credentials are never logged.
11
+ */
12
+
13
+ const https = require('https');
14
+
15
+ let _options = null;
16
+ let _active = false;
17
+ let _provider = null;
18
+ let _lastPushTime = 0;
19
+
20
+ const MIN_PUSH_INTERVAL_MS = 30000;
21
+
22
+ // ────── Cloudflare ──────
23
+
24
24
  async function cloudflareSync(ips) {
25
25
  const token = _options?.cloudflare?.apiToken;
26
26
  const accountId = _options?.cloudflare?.accountId;
27
27
  if (!token || !accountId) throw new Error('Missing CLOUDFLARE_API_TOKEN or CLOUDFLARE_ACCOUNT_ID');
28
-
29
- const listName = 'securenow-blocklist';
30
-
31
- const lists = await cfApi('GET', `/accounts/${accountId}/rules/lists`, token);
32
- let list = lists.result?.find(l => l.name === listName);
33
-
34
- if (!list) {
35
- const created = await cfApi('POST', `/accounts/${accountId}/rules/lists`, token, {
36
- name: listName,
37
- kind: 'ip',
38
- description: 'Managed by SecureNow firewall SDK',
39
- });
40
- list = created.result;
41
- }
42
-
43
- const items = ips.map(ip => ({ ip: ip.includes('/') ? ip : ip + '/32' }));
44
-
45
- await cfApi('PUT', `/accounts/${accountId}/rules/lists/${list.id}/items`, token, items);
46
- }
47
-
48
- function cfApi(method, path, token, body) {
49
- return new Promise((resolve, reject) => {
50
- const data = body ? JSON.stringify(body) : null;
51
- const req = https.request({
52
- hostname: 'api.cloudflare.com',
53
- path: '/client/v4' + path,
54
- method,
55
- headers: {
56
- 'Authorization': `Bearer ${token}`,
57
- 'Content-Type': 'application/json',
58
- ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}),
59
- },
60
- timeout: 15000,
61
- }, (res) => {
62
- let chunks = '';
63
- res.on('data', c => chunks += c);
64
- res.on('end', () => {
65
- try { resolve(JSON.parse(chunks)); } catch (e) { reject(new Error(`CF parse error: ${chunks.slice(0, 200)}`)); }
66
- });
67
- });
68
- req.on('error', reject);
69
- req.on('timeout', () => { req.destroy(); reject(new Error('Cloudflare API timeout')); });
70
- if (data) req.write(data);
71
- req.end();
72
- });
73
- }
74
-
75
- // ────── AWS WAF ──────
76
-
77
- async function awsSync(ips) {
78
- let WAFV2;
79
- try {
80
- WAFV2 = require('@aws-sdk/client-wafv2');
81
- } catch {
82
- throw new Error('AWS WAF requires @aws-sdk/client-wafv2 — install it: npm i @aws-sdk/client-wafv2');
83
- }
84
-
28
+
29
+ const listName = 'securenow-blocklist';
30
+
31
+ const lists = await cfApi('GET', `/accounts/${accountId}/rules/lists`, token);
32
+ let list = lists.result?.find(l => l.name === listName);
33
+
34
+ if (!list) {
35
+ const created = await cfApi('POST', `/accounts/${accountId}/rules/lists`, token, {
36
+ name: listName,
37
+ kind: 'ip',
38
+ description: 'Managed by SecureNow firewall SDK',
39
+ });
40
+ list = created.result;
41
+ }
42
+
43
+ const items = ips.map(ip => ({ ip: ip.includes('/') ? ip : ip + '/32' }));
44
+
45
+ await cfApi('PUT', `/accounts/${accountId}/rules/lists/${list.id}/items`, token, items);
46
+ }
47
+
48
+ function cfApi(method, path, token, body) {
49
+ return new Promise((resolve, reject) => {
50
+ const data = body ? JSON.stringify(body) : null;
51
+ const req = https.request({
52
+ hostname: 'api.cloudflare.com',
53
+ path: '/client/v4' + path,
54
+ method,
55
+ headers: {
56
+ 'Authorization': `Bearer ${token}`,
57
+ 'Content-Type': 'application/json',
58
+ ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}),
59
+ },
60
+ timeout: 15000,
61
+ }, (res) => {
62
+ let chunks = '';
63
+ res.on('data', c => chunks += c);
64
+ res.on('end', () => {
65
+ try { resolve(JSON.parse(chunks)); } catch (e) { reject(new Error(`CF parse error: ${chunks.slice(0, 200)}`)); }
66
+ });
67
+ });
68
+ req.on('error', reject);
69
+ req.on('timeout', () => { req.destroy(); reject(new Error('Cloudflare API timeout')); });
70
+ if (data) req.write(data);
71
+ req.end();
72
+ });
73
+ }
74
+
75
+ // ────── AWS WAF ──────
76
+
77
+ async function awsSync(ips) {
78
+ let WAFV2;
79
+ try {
80
+ WAFV2 = require('@aws-sdk/client-wafv2');
81
+ } catch {
82
+ throw new Error('AWS WAF requires @aws-sdk/client-wafv2 — install it: npm i @aws-sdk/client-wafv2');
83
+ }
84
+
85
85
  const ipSetId = _options?.aws?.wafIpSetId;
86
86
  const ipSetName = _options?.aws?.wafIpSetName || 'securenow-blocklist';
87
87
  const scope = _options?.aws?.wafScope || 'REGIONAL';
88
- if (!ipSetId) throw new Error('Missing AWS_WAF_IP_SET_ID');
89
-
90
- const client = new WAFV2.WAFV2Client({});
91
-
92
- const { IPSet, LockToken } = await client.send(new WAFV2.GetIPSetCommand({
93
- Name: ipSetName,
94
- Scope: scope,
95
- Id: ipSetId,
96
- }));
97
-
98
- const addresses = ips.map(ip => ip.includes('/') ? ip : ip + '/32');
99
-
100
- await client.send(new WAFV2.UpdateIPSetCommand({
101
- Name: ipSetName,
102
- Scope: scope,
103
- Id: ipSetId,
104
- Addresses: addresses,
105
- LockToken,
106
- Description: 'Managed by SecureNow firewall SDK',
107
- }));
108
- }
109
-
110
- // ────── GCP Cloud Armor ──────
111
-
112
- async function gcpSync(ips) {
113
- let compute;
114
- try {
115
- compute = require('@google-cloud/compute');
116
- } catch {
117
- throw new Error('GCP Cloud Armor requires @google-cloud/compute — install it: npm i @google-cloud/compute');
118
- }
119
-
88
+ if (!ipSetId) throw new Error('Missing AWS_WAF_IP_SET_ID');
89
+
90
+ const client = new WAFV2.WAFV2Client({});
91
+
92
+ const { IPSet, LockToken } = await client.send(new WAFV2.GetIPSetCommand({
93
+ Name: ipSetName,
94
+ Scope: scope,
95
+ Id: ipSetId,
96
+ }));
97
+
98
+ const addresses = ips.map(ip => ip.includes('/') ? ip : ip + '/32');
99
+
100
+ await client.send(new WAFV2.UpdateIPSetCommand({
101
+ Name: ipSetName,
102
+ Scope: scope,
103
+ Id: ipSetId,
104
+ Addresses: addresses,
105
+ LockToken,
106
+ Description: 'Managed by SecureNow firewall SDK',
107
+ }));
108
+ }
109
+
110
+ // ────── GCP Cloud Armor ──────
111
+
112
+ async function gcpSync(ips) {
113
+ let compute;
114
+ try {
115
+ compute = require('@google-cloud/compute');
116
+ } catch {
117
+ throw new Error('GCP Cloud Armor requires @google-cloud/compute — install it: npm i @google-cloud/compute');
118
+ }
119
+
120
120
  const project = _options?.gcp?.projectId;
121
121
  const policyName = _options?.gcp?.securityPolicy;
122
- if (!project || !policyName) throw new Error('Missing GCP_PROJECT_ID or GCP_SECURITY_POLICY');
123
-
124
- const client = new compute.SecurityPoliciesClient();
125
- const rulePriority = 2000;
126
-
127
- const srcRanges = ips.map(ip => ip.includes('/') ? ip : ip + '/32');
128
-
129
- try {
130
- await client.patchRule({
131
- project,
132
- securityPolicy: policyName,
133
- priority: String(rulePriority),
134
- securityPolicyRuleResource: {
135
- priority: rulePriority,
136
- action: 'deny(403)',
137
- match: { versionedExpr: 'SRC_IPS_V1', config: { srcIpRanges: srcRanges } },
138
- description: 'Managed by SecureNow firewall SDK',
139
- },
140
- });
141
- } catch (e) {
142
- if (e.code === 404 || e.message?.includes('not found')) {
143
- await client.addRule({
144
- project,
145
- securityPolicy: policyName,
146
- securityPolicyRuleResource: {
147
- priority: rulePriority,
148
- action: 'deny(403)',
149
- match: { versionedExpr: 'SRC_IPS_V1', config: { srcIpRanges: srcRanges } },
150
- description: 'Managed by SecureNow firewall SDK',
151
- },
152
- });
153
- } else {
154
- throw e;
155
- }
156
- }
157
- }
158
-
159
- // ────── Provider dispatch ──────
160
-
161
- const PROVIDERS = {
162
- cloudflare: cloudflareSync,
163
- aws: awsSync,
164
- gcp: gcpSync,
165
- };
166
-
167
- // ────── Public API ──────
168
-
169
- function init(options) {
170
- _options = options;
171
- const provider = (options.cloud || '').toLowerCase();
172
-
173
- if (!PROVIDERS[provider]) {
174
- if (_options.log) console.warn('[securenow] Firewall cloud: unknown provider "%s", expected cloudflare|aws|gcp', provider);
175
- return;
176
- }
177
-
178
- _provider = provider;
179
- _active = true;
180
- }
181
-
182
- /**
183
- * Called by the firewall core after each successful blocklist sync.
184
- * Throttled to max 1 push per MIN_PUSH_INTERVAL_MS.
185
- * @param {string[]} ips - Array of IPs and CIDRs to push
186
- */
187
- async function sync(ips) {
188
- if (!_active || !_provider) return;
189
-
190
- const now = Date.now();
191
- if (now - _lastPushTime < MIN_PUSH_INTERVAL_MS) return;
192
- _lastPushTime = now;
193
-
122
+ if (!project || !policyName) throw new Error('Missing GCP_PROJECT_ID or GCP_SECURITY_POLICY');
123
+
124
+ const client = new compute.SecurityPoliciesClient();
125
+ const rulePriority = 2000;
126
+
127
+ const srcRanges = ips.map(ip => ip.includes('/') ? ip : ip + '/32');
128
+
129
+ try {
130
+ await client.patchRule({
131
+ project,
132
+ securityPolicy: policyName,
133
+ priority: String(rulePriority),
134
+ securityPolicyRuleResource: {
135
+ priority: rulePriority,
136
+ action: 'deny(403)',
137
+ match: { versionedExpr: 'SRC_IPS_V1', config: { srcIpRanges: srcRanges } },
138
+ description: 'Managed by SecureNow firewall SDK',
139
+ },
140
+ });
141
+ } catch (e) {
142
+ if (e.code === 404 || e.message?.includes('not found')) {
143
+ await client.addRule({
144
+ project,
145
+ securityPolicy: policyName,
146
+ securityPolicyRuleResource: {
147
+ priority: rulePriority,
148
+ action: 'deny(403)',
149
+ match: { versionedExpr: 'SRC_IPS_V1', config: { srcIpRanges: srcRanges } },
150
+ description: 'Managed by SecureNow firewall SDK',
151
+ },
152
+ });
153
+ } else {
154
+ throw e;
155
+ }
156
+ }
157
+ }
158
+
159
+ // ────── Provider dispatch ──────
160
+
161
+ const PROVIDERS = {
162
+ cloudflare: cloudflareSync,
163
+ aws: awsSync,
164
+ gcp: gcpSync,
165
+ };
166
+
167
+ // ────── Public API ──────
168
+
169
+ function init(options) {
170
+ _options = options;
171
+ const provider = (options.cloud || '').toLowerCase();
172
+
173
+ if (!PROVIDERS[provider]) {
174
+ if (_options.log) console.warn('[securenow] Firewall cloud: unknown provider "%s", expected cloudflare|aws|gcp', provider);
175
+ return;
176
+ }
177
+
178
+ _provider = provider;
179
+ _active = true;
180
+ }
181
+
182
+ /**
183
+ * Called by the firewall core after each successful blocklist sync.
184
+ * Throttled to max 1 push per MIN_PUSH_INTERVAL_MS.
185
+ * @param {string[]} ips - Array of IPs and CIDRs to push
186
+ */
187
+ async function sync(ips) {
188
+ if (!_active || !_provider) return;
189
+
190
+ const now = Date.now();
191
+ if (now - _lastPushTime < MIN_PUSH_INTERVAL_MS) return;
192
+ _lastPushTime = now;
193
+
194
194
  const dryRun = _options?.cloudDryRun === true;
195
- if (dryRun) {
196
- if (_options.log) console.log('[securenow] Firewall cloud: dry-run — would push %d IPs to %s', ips.length, _provider);
197
- return;
198
- }
199
-
200
- try {
201
- await PROVIDERS[_provider](ips);
202
- if (_options.log) console.log('[securenow] Firewall cloud: pushed %d IPs to %s', ips.length, _provider);
203
- } catch (e) {
204
- if (_options.log) console.warn('[securenow] Firewall cloud: push to %s failed:', _provider, e.message);
205
- }
206
- }
207
-
208
- function shutdown() {
209
- _active = false;
210
- }
211
-
212
- module.exports = { init, sync, shutdown };
195
+ if (dryRun) {
196
+ if (_options.log) console.log('[securenow] Firewall cloud: dry-run — would push %d IPs to %s', ips.length, _provider);
197
+ return;
198
+ }
199
+
200
+ try {
201
+ await PROVIDERS[_provider](ips);
202
+ if (_options.log) console.log('[securenow] Firewall cloud: pushed %d IPs to %s', ips.length, _provider);
203
+ } catch (e) {
204
+ if (_options.log) console.warn('[securenow] Firewall cloud: push to %s failed:', _provider, e.message);
205
+ }
206
+ }
207
+
208
+ function shutdown() {
209
+ _active = false;
210
+ }
211
+
212
+ module.exports = { init, sync, shutdown };