s3db.js 13.5.1 → 13.6.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/README.md +25 -10
- package/dist/{s3db.cjs.js → s3db.cjs} +30323 -24958
- package/dist/s3db.cjs.map +1 -0
- package/dist/s3db.es.js +24026 -18654
- package/dist/s3db.es.js.map +1 -1
- package/package.json +216 -20
- package/src/concerns/id.js +90 -6
- package/src/concerns/index.js +2 -1
- package/src/concerns/password-hashing.js +150 -0
- package/src/database.class.js +4 -0
- package/src/plugins/api/auth/basic-auth.js +23 -1
- package/src/plugins/api/auth/index.js +49 -3
- package/src/plugins/api/auth/oauth2-auth.js +171 -0
- package/src/plugins/api/auth/oidc-auth.js +789 -0
- package/src/plugins/api/auth/oidc-client.js +462 -0
- package/src/plugins/api/auth/path-auth-matcher.js +284 -0
- package/src/plugins/api/concerns/event-emitter.js +134 -0
- package/src/plugins/api/concerns/failban-manager.js +651 -0
- package/src/plugins/api/concerns/guards-helpers.js +402 -0
- package/src/plugins/api/concerns/metrics-collector.js +346 -0
- package/src/plugins/api/index.js +503 -54
- package/src/plugins/api/middlewares/failban.js +305 -0
- package/src/plugins/api/middlewares/rate-limit.js +301 -0
- package/src/plugins/api/middlewares/request-id.js +74 -0
- package/src/plugins/api/middlewares/security-headers.js +120 -0
- package/src/plugins/api/middlewares/session-tracking.js +194 -0
- package/src/plugins/api/routes/auth-routes.js +23 -3
- package/src/plugins/api/routes/resource-routes.js +71 -29
- package/src/plugins/api/server.js +1017 -94
- package/src/plugins/api/utils/guards.js +213 -0
- package/src/plugins/api/utils/mime-types.js +154 -0
- package/src/plugins/api/utils/openapi-generator.js +44 -11
- package/src/plugins/api/utils/path-matcher.js +173 -0
- package/src/plugins/api/utils/static-filesystem.js +262 -0
- package/src/plugins/api/utils/static-s3.js +231 -0
- package/src/plugins/api/utils/template-engine.js +188 -0
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
- package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
- package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
- package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
- package/src/plugins/cloud-inventory/index.js +20 -0
- package/src/plugins/cloud-inventory/registry.js +146 -0
- package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
- package/src/plugins/cloud-inventory.plugin.js +1333 -0
- package/src/plugins/concerns/plugin-dependencies.js +61 -1
- package/src/plugins/eventual-consistency/analytics.js +1 -0
- package/src/plugins/identity/README.md +335 -0
- package/src/plugins/identity/concerns/mfa-manager.js +204 -0
- package/src/plugins/identity/concerns/password.js +138 -0
- package/src/plugins/identity/concerns/resource-schemas.js +273 -0
- package/src/plugins/identity/concerns/token-generator.js +172 -0
- package/src/plugins/identity/email-service.js +422 -0
- package/src/plugins/identity/index.js +1052 -0
- package/src/plugins/identity/oauth2-server.js +1033 -0
- package/src/plugins/identity/oidc-discovery.js +285 -0
- package/src/plugins/identity/rsa-keys.js +323 -0
- package/src/plugins/identity/server.js +500 -0
- package/src/plugins/identity/session-manager.js +453 -0
- package/src/plugins/identity/ui/layouts/base.js +251 -0
- package/src/plugins/identity/ui/middleware.js +135 -0
- package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
- package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
- package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
- package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
- package/src/plugins/identity/ui/pages/admin/users.js +263 -0
- package/src/plugins/identity/ui/pages/consent.js +262 -0
- package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
- package/src/plugins/identity/ui/pages/login.js +144 -0
- package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
- package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
- package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
- package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
- package/src/plugins/identity/ui/pages/profile.js +361 -0
- package/src/plugins/identity/ui/pages/register.js +226 -0
- package/src/plugins/identity/ui/pages/reset-password.js +128 -0
- package/src/plugins/identity/ui/pages/verify-email.js +172 -0
- package/src/plugins/identity/ui/routes.js +2541 -0
- package/src/plugins/identity/ui/styles/main.css +465 -0
- package/src/plugins/index.js +4 -1
- package/src/plugins/ml/base-model.class.js +32 -7
- package/src/plugins/ml/classification-model.class.js +1 -1
- package/src/plugins/ml/timeseries-model.class.js +3 -1
- package/src/plugins/ml.plugin.js +124 -32
- package/src/plugins/shared/error-handler.js +147 -0
- package/src/plugins/shared/index.js +9 -0
- package/src/plugins/shared/middlewares/compression.js +117 -0
- package/src/plugins/shared/middlewares/cors.js +49 -0
- package/src/plugins/shared/middlewares/index.js +11 -0
- package/src/plugins/shared/middlewares/logging.js +54 -0
- package/src/plugins/shared/middlewares/rate-limit.js +73 -0
- package/src/plugins/shared/middlewares/security.js +158 -0
- package/src/plugins/shared/response-formatter.js +264 -0
- package/src/resource.class.js +140 -12
- package/src/schema.class.js +30 -1
- package/src/validator.class.js +57 -6
- package/dist/s3db.cjs.js.map +0 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseCloudDriver - abstract class for cloud inventory drivers
|
|
3
|
+
*
|
|
4
|
+
* Concrete drivers must implement at least:
|
|
5
|
+
* - async initialize(): Perform any lazy connections or credential checks
|
|
6
|
+
* - async listResources(options): Return an iterable of discovered resources
|
|
7
|
+
*
|
|
8
|
+
* A discovered resource should follow the shape:
|
|
9
|
+
* {
|
|
10
|
+
* provider: 'aws' | 'gcp' | ...,
|
|
11
|
+
* accountId: string,
|
|
12
|
+
* subscriptionId?: string,
|
|
13
|
+
* organizationId?: string,
|
|
14
|
+
* projectId?: string,
|
|
15
|
+
* region?: string,
|
|
16
|
+
* service?: string,
|
|
17
|
+
* resourceType: string,
|
|
18
|
+
* resourceId: string,
|
|
19
|
+
* name?: string,
|
|
20
|
+
* tags?: Record<string,string>,
|
|
21
|
+
* labels?: Record<string,string>,
|
|
22
|
+
* attributes?: Record<string, unknown>,
|
|
23
|
+
* configuration: Record<string, unknown>,
|
|
24
|
+
* raw?: unknown
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* The plugin normalizes the payload, computes configuration digests and
|
|
28
|
+
* manages versioning/diffing.
|
|
29
|
+
*/
|
|
30
|
+
export class BaseCloudDriver {
|
|
31
|
+
/**
|
|
32
|
+
* @param {Object} options
|
|
33
|
+
* @param {string} options.id - Unique identifier for this cloud source
|
|
34
|
+
* @param {string} options.driver - Driver name (aws, gcp, do, ...)
|
|
35
|
+
* @param {Object} options.credentials - Authentication material
|
|
36
|
+
* @param {Object} options.config - Driver specific configuration
|
|
37
|
+
* @param {Object} options.globals - Global plugin options
|
|
38
|
+
* @param {Function} options.logger - Optional logger fn (level, msg, meta)
|
|
39
|
+
*/
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
const {
|
|
42
|
+
id,
|
|
43
|
+
driver,
|
|
44
|
+
credentials = {},
|
|
45
|
+
config = {},
|
|
46
|
+
globals = {},
|
|
47
|
+
logger = null
|
|
48
|
+
} = options;
|
|
49
|
+
|
|
50
|
+
if (!driver) {
|
|
51
|
+
throw new Error('Cloud driver requires a "driver" identifier');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.id = id || driver;
|
|
55
|
+
this.driver = driver;
|
|
56
|
+
this.credentials = credentials;
|
|
57
|
+
this.config = config;
|
|
58
|
+
this.globals = globals;
|
|
59
|
+
this.logger = typeof logger === 'function'
|
|
60
|
+
? logger
|
|
61
|
+
: () => {};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Perform driver bootstrapping (auth warm-up, SDK clients, etc).
|
|
66
|
+
* Default implementation is a no-op.
|
|
67
|
+
*/
|
|
68
|
+
async initialize() {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Fetch resources from the cloud API.
|
|
74
|
+
* Must be implemented by subclasses.
|
|
75
|
+
* @param {Object} options
|
|
76
|
+
* @returns {Promise<Array<Object>|AsyncIterable<Object>>}
|
|
77
|
+
*/
|
|
78
|
+
// eslint-disable-next-line no-unused-vars
|
|
79
|
+
async listResources(options = {}) {
|
|
80
|
+
throw new Error(`Driver "${this.driver}" does not implement listResources()`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Optional health check hook.
|
|
85
|
+
* @returns {Promise<{ok: boolean, details?: any}>}
|
|
86
|
+
*/
|
|
87
|
+
async healthCheck() {
|
|
88
|
+
return { ok: true };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Graceful shutdown hook for long-lived SDK clients.
|
|
93
|
+
*/
|
|
94
|
+
async destroy() {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export default BaseCloudDriver;
|
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import { BaseCloudDriver } from './base-driver.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Production-ready Cloudflare inventory driver using official cloudflare SDK.
|
|
5
|
+
*
|
|
6
|
+
* Covers 11+ services with 15+ edge computing resource types:
|
|
7
|
+
* - Edge Computing (Workers, Pages, Durable Objects)
|
|
8
|
+
* - Storage (R2 buckets, KV namespaces, D1 databases)
|
|
9
|
+
* - Networking (Zones, DNS records, Load Balancers)
|
|
10
|
+
* - Security (SSL/TLS Certificates, WAF Rulesets, Access Applications/Policies)
|
|
11
|
+
*
|
|
12
|
+
* @see https://developers.cloudflare.com/api/
|
|
13
|
+
* @see https://www.npmjs.com/package/cloudflare
|
|
14
|
+
*/
|
|
15
|
+
export class CloudflareInventoryDriver extends BaseCloudDriver {
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
super({ ...options, driver: options.driver || 'cloudflare' });
|
|
18
|
+
|
|
19
|
+
this._apiToken = null;
|
|
20
|
+
this._accountId = null;
|
|
21
|
+
this._client = null;
|
|
22
|
+
|
|
23
|
+
// Services to collect (can be filtered via config.services)
|
|
24
|
+
this._services = this.config?.services || [
|
|
25
|
+
'workers',
|
|
26
|
+
'r2',
|
|
27
|
+
'pages',
|
|
28
|
+
'd1',
|
|
29
|
+
'kv',
|
|
30
|
+
'durable-objects',
|
|
31
|
+
'zones',
|
|
32
|
+
'loadbalancers',
|
|
33
|
+
'certificates',
|
|
34
|
+
'waf',
|
|
35
|
+
'access'
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Initialize Cloudflare API client.
|
|
41
|
+
*/
|
|
42
|
+
async _initializeClient() {
|
|
43
|
+
if (this._client) return;
|
|
44
|
+
|
|
45
|
+
const credentials = this.credentials || {};
|
|
46
|
+
this._apiToken = credentials.apiToken || credentials.token || process.env.CLOUDFLARE_API_TOKEN;
|
|
47
|
+
this._accountId = credentials.accountId || this.config?.accountId || process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
48
|
+
|
|
49
|
+
if (!this._apiToken) {
|
|
50
|
+
throw new Error('Cloudflare API token is required. Provide via credentials.apiToken or CLOUDFLARE_API_TOKEN env var.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Lazy import
|
|
54
|
+
const Cloudflare = await import('cloudflare');
|
|
55
|
+
this._client = new Cloudflare.default({
|
|
56
|
+
apiToken: this._apiToken
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
this.logger('info', 'Cloudflare API client initialized', {
|
|
60
|
+
accountId: this._accountId,
|
|
61
|
+
services: this._services.length
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Main entry point - lists all resources from configured services.
|
|
67
|
+
*/
|
|
68
|
+
async *listResources(options = {}) {
|
|
69
|
+
await this._initializeClient();
|
|
70
|
+
|
|
71
|
+
const serviceCollectors = {
|
|
72
|
+
workers: () => this._collectWorkers(),
|
|
73
|
+
r2: () => this._collectR2(),
|
|
74
|
+
pages: () => this._collectPages(),
|
|
75
|
+
'd1': () => this._collectD1(),
|
|
76
|
+
kv: () => this._collectKV(),
|
|
77
|
+
'durable-objects': () => this._collectDurableObjects(),
|
|
78
|
+
zones: () => this._collectZones(),
|
|
79
|
+
loadbalancers: () => this._collectLoadBalancers(),
|
|
80
|
+
certificates: () => this._collectCertificates(),
|
|
81
|
+
waf: () => this._collectWAF(),
|
|
82
|
+
access: () => this._collectAccess()
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
for (const service of this._services) {
|
|
86
|
+
const collector = serviceCollectors[service];
|
|
87
|
+
if (!collector) {
|
|
88
|
+
this.logger('warn', `Unknown Cloudflare service: ${service}`, { service });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
this.logger('info', `Collecting Cloudflare ${service} resources`, { service });
|
|
94
|
+
yield* collector();
|
|
95
|
+
} catch (err) {
|
|
96
|
+
// Continue with next service instead of failing entire sync
|
|
97
|
+
this.logger('error', `Cloudflare service collection failed, skipping to next service`, {
|
|
98
|
+
service,
|
|
99
|
+
error: err.message,
|
|
100
|
+
errorName: err.name,
|
|
101
|
+
stack: err.stack
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Collect Workers scripts.
|
|
109
|
+
*/
|
|
110
|
+
async *_collectWorkers() {
|
|
111
|
+
try {
|
|
112
|
+
if (!this._accountId) {
|
|
113
|
+
this.logger('warn', 'Account ID required for Workers collection, skipping');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const scripts = await this._client.workers.scripts.list({ account_id: this._accountId });
|
|
118
|
+
|
|
119
|
+
for (const script of scripts) {
|
|
120
|
+
yield {
|
|
121
|
+
provider: 'cloudflare',
|
|
122
|
+
accountId: this._accountId,
|
|
123
|
+
region: 'global', // Workers are global/edge
|
|
124
|
+
service: 'workers',
|
|
125
|
+
resourceType: 'cloudflare.workers.script',
|
|
126
|
+
resourceId: script.id,
|
|
127
|
+
name: script.id,
|
|
128
|
+
tags: script.tags || [],
|
|
129
|
+
configuration: this._sanitize(script)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.logger('info', `Collected ${scripts.length || 0} Cloudflare Workers scripts`);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
this.logger('error', 'Failed to collect Cloudflare Workers', {
|
|
136
|
+
error: err.message,
|
|
137
|
+
stack: err.stack
|
|
138
|
+
});
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Collect R2 buckets.
|
|
145
|
+
*/
|
|
146
|
+
async *_collectR2() {
|
|
147
|
+
try {
|
|
148
|
+
if (!this._accountId) {
|
|
149
|
+
this.logger('warn', 'Account ID required for R2 collection, skipping');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const buckets = await this._client.r2.buckets.list({ account_id: this._accountId });
|
|
154
|
+
|
|
155
|
+
for (const bucket of buckets) {
|
|
156
|
+
yield {
|
|
157
|
+
provider: 'cloudflare',
|
|
158
|
+
accountId: this._accountId,
|
|
159
|
+
region: bucket.location || 'global',
|
|
160
|
+
service: 'r2',
|
|
161
|
+
resourceType: 'cloudflare.r2.bucket',
|
|
162
|
+
resourceId: bucket.name,
|
|
163
|
+
name: bucket.name,
|
|
164
|
+
tags: [],
|
|
165
|
+
configuration: this._sanitize(bucket)
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.logger('info', `Collected ${buckets.length || 0} Cloudflare R2 buckets`);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
this.logger('error', 'Failed to collect Cloudflare R2', {
|
|
172
|
+
error: err.message,
|
|
173
|
+
stack: err.stack
|
|
174
|
+
});
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Collect Pages projects.
|
|
181
|
+
*/
|
|
182
|
+
async *_collectPages() {
|
|
183
|
+
try {
|
|
184
|
+
if (!this._accountId) {
|
|
185
|
+
this.logger('warn', 'Account ID required for Pages collection, skipping');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const projects = await this._client.pages.projects.list({ account_id: this._accountId });
|
|
190
|
+
|
|
191
|
+
for (const project of projects) {
|
|
192
|
+
yield {
|
|
193
|
+
provider: 'cloudflare',
|
|
194
|
+
accountId: this._accountId,
|
|
195
|
+
region: 'global',
|
|
196
|
+
service: 'pages',
|
|
197
|
+
resourceType: 'cloudflare.pages.project',
|
|
198
|
+
resourceId: project.id || project.name,
|
|
199
|
+
name: project.name,
|
|
200
|
+
tags: [],
|
|
201
|
+
configuration: this._sanitize(project)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.logger('info', `Collected ${projects.length || 0} Cloudflare Pages projects`);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
this.logger('error', 'Failed to collect Cloudflare Pages', {
|
|
208
|
+
error: err.message,
|
|
209
|
+
stack: err.stack
|
|
210
|
+
});
|
|
211
|
+
throw err;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Collect D1 databases.
|
|
217
|
+
*/
|
|
218
|
+
async *_collectD1() {
|
|
219
|
+
try {
|
|
220
|
+
if (!this._accountId) {
|
|
221
|
+
this.logger('warn', 'Account ID required for D1 collection, skipping');
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const databases = await this._client.d1.database.list({ account_id: this._accountId });
|
|
226
|
+
|
|
227
|
+
for (const database of databases) {
|
|
228
|
+
yield {
|
|
229
|
+
provider: 'cloudflare',
|
|
230
|
+
accountId: this._accountId,
|
|
231
|
+
region: 'global',
|
|
232
|
+
service: 'd1',
|
|
233
|
+
resourceType: 'cloudflare.d1.database',
|
|
234
|
+
resourceId: database.uuid,
|
|
235
|
+
name: database.name,
|
|
236
|
+
tags: [],
|
|
237
|
+
configuration: this._sanitize(database)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.logger('info', `Collected ${databases.length || 0} Cloudflare D1 databases`);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
this.logger('error', 'Failed to collect Cloudflare D1', {
|
|
244
|
+
error: err.message,
|
|
245
|
+
stack: err.stack
|
|
246
|
+
});
|
|
247
|
+
throw err;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Collect KV namespaces.
|
|
253
|
+
*/
|
|
254
|
+
async *_collectKV() {
|
|
255
|
+
try {
|
|
256
|
+
if (!this._accountId) {
|
|
257
|
+
this.logger('warn', 'Account ID required for KV collection, skipping');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const namespaces = await this._client.kv.namespaces.list({ account_id: this._accountId });
|
|
262
|
+
|
|
263
|
+
for (const namespace of namespaces) {
|
|
264
|
+
yield {
|
|
265
|
+
provider: 'cloudflare',
|
|
266
|
+
accountId: this._accountId,
|
|
267
|
+
region: 'global',
|
|
268
|
+
service: 'kv',
|
|
269
|
+
resourceType: 'cloudflare.kv.namespace',
|
|
270
|
+
resourceId: namespace.id,
|
|
271
|
+
name: namespace.title,
|
|
272
|
+
tags: [],
|
|
273
|
+
configuration: this._sanitize(namespace)
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.logger('info', `Collected ${namespaces.length || 0} Cloudflare KV namespaces`);
|
|
278
|
+
} catch (err) {
|
|
279
|
+
this.logger('error', 'Failed to collect Cloudflare KV', {
|
|
280
|
+
error: err.message,
|
|
281
|
+
stack: err.stack
|
|
282
|
+
});
|
|
283
|
+
throw err;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Collect Durable Objects namespaces.
|
|
289
|
+
*/
|
|
290
|
+
async *_collectDurableObjects() {
|
|
291
|
+
try {
|
|
292
|
+
if (!this._accountId) {
|
|
293
|
+
this.logger('warn', 'Account ID required for Durable Objects collection, skipping');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const namespaces = await this._client.durableObjects.namespaces.list({ account_id: this._accountId });
|
|
298
|
+
|
|
299
|
+
for (const namespace of namespaces) {
|
|
300
|
+
yield {
|
|
301
|
+
provider: 'cloudflare',
|
|
302
|
+
accountId: this._accountId,
|
|
303
|
+
region: 'global',
|
|
304
|
+
service: 'durable-objects',
|
|
305
|
+
resourceType: 'cloudflare.durableobjects.namespace',
|
|
306
|
+
resourceId: namespace.id,
|
|
307
|
+
name: namespace.name,
|
|
308
|
+
tags: [],
|
|
309
|
+
configuration: this._sanitize(namespace)
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
this.logger('info', `Collected ${namespaces.length || 0} Cloudflare Durable Objects namespaces`);
|
|
314
|
+
} catch (err) {
|
|
315
|
+
this.logger('error', 'Failed to collect Cloudflare Durable Objects', {
|
|
316
|
+
error: err.message,
|
|
317
|
+
stack: err.stack
|
|
318
|
+
});
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Collect Zones (domains) and DNS records.
|
|
325
|
+
*/
|
|
326
|
+
async *_collectZones() {
|
|
327
|
+
try {
|
|
328
|
+
const zones = await this._client.zones.list();
|
|
329
|
+
|
|
330
|
+
for (const zone of zones) {
|
|
331
|
+
yield {
|
|
332
|
+
provider: 'cloudflare',
|
|
333
|
+
accountId: this._accountId || zone.account?.id,
|
|
334
|
+
region: 'global',
|
|
335
|
+
service: 'zones',
|
|
336
|
+
resourceType: 'cloudflare.zone',
|
|
337
|
+
resourceId: zone.id,
|
|
338
|
+
name: zone.name,
|
|
339
|
+
tags: [],
|
|
340
|
+
configuration: this._sanitize(zone)
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// Collect DNS records for this zone
|
|
344
|
+
try {
|
|
345
|
+
const records = await this._client.dns.records.list({ zone_id: zone.id });
|
|
346
|
+
|
|
347
|
+
for (const record of records) {
|
|
348
|
+
yield {
|
|
349
|
+
provider: 'cloudflare',
|
|
350
|
+
accountId: this._accountId || zone.account?.id,
|
|
351
|
+
region: 'global',
|
|
352
|
+
service: 'zones',
|
|
353
|
+
resourceType: 'cloudflare.dns.record',
|
|
354
|
+
resourceId: record.id,
|
|
355
|
+
name: `${record.name} (${record.type})`,
|
|
356
|
+
tags: record.tags || [],
|
|
357
|
+
metadata: { zoneId: zone.id, zoneName: zone.name },
|
|
358
|
+
configuration: this._sanitize(record)
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
} catch (recordErr) {
|
|
362
|
+
this.logger('warn', `Failed to collect DNS records for zone ${zone.name}`, {
|
|
363
|
+
zoneId: zone.id,
|
|
364
|
+
error: recordErr.message
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
this.logger('info', `Collected ${zones.length || 0} Cloudflare zones`);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
this.logger('error', 'Failed to collect Cloudflare zones', {
|
|
372
|
+
error: err.message,
|
|
373
|
+
stack: err.stack
|
|
374
|
+
});
|
|
375
|
+
throw err;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Collect Load Balancers.
|
|
381
|
+
*/
|
|
382
|
+
async *_collectLoadBalancers() {
|
|
383
|
+
try {
|
|
384
|
+
const zones = await this._client.zones.list();
|
|
385
|
+
|
|
386
|
+
for (const zone of zones) {
|
|
387
|
+
try {
|
|
388
|
+
const loadBalancers = await this._client.loadBalancers.list({ zone_id: zone.id });
|
|
389
|
+
|
|
390
|
+
for (const lb of loadBalancers) {
|
|
391
|
+
yield {
|
|
392
|
+
provider: 'cloudflare',
|
|
393
|
+
accountId: this._accountId || zone.account?.id,
|
|
394
|
+
region: 'global',
|
|
395
|
+
service: 'loadbalancers',
|
|
396
|
+
resourceType: 'cloudflare.loadbalancer',
|
|
397
|
+
resourceId: lb.id,
|
|
398
|
+
name: lb.name,
|
|
399
|
+
tags: [],
|
|
400
|
+
metadata: { zoneId: zone.id, zoneName: zone.name },
|
|
401
|
+
configuration: this._sanitize(lb)
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
} catch (lbErr) {
|
|
405
|
+
this.logger('debug', `No load balancers in zone ${zone.name}`, {
|
|
406
|
+
zoneId: zone.id,
|
|
407
|
+
error: lbErr.message
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
this.logger('info', `Collected Cloudflare load balancers`);
|
|
413
|
+
} catch (err) {
|
|
414
|
+
this.logger('error', 'Failed to collect Cloudflare load balancers', {
|
|
415
|
+
error: err.message,
|
|
416
|
+
stack: err.stack
|
|
417
|
+
});
|
|
418
|
+
throw err;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Collect SSL/TLS Certificates.
|
|
424
|
+
*/
|
|
425
|
+
async *_collectCertificates() {
|
|
426
|
+
try {
|
|
427
|
+
const zones = await this._client.zones.list();
|
|
428
|
+
|
|
429
|
+
for (const zone of zones) {
|
|
430
|
+
try {
|
|
431
|
+
// Collect SSL/TLS certificates for each zone
|
|
432
|
+
const certificates = await this._client.ssl.certificatePacks.list({ zone_id: zone.id });
|
|
433
|
+
|
|
434
|
+
for (const cert of certificates) {
|
|
435
|
+
yield {
|
|
436
|
+
provider: 'cloudflare',
|
|
437
|
+
accountId: this._accountId || zone.account?.id,
|
|
438
|
+
region: 'global',
|
|
439
|
+
service: 'certificates',
|
|
440
|
+
resourceType: 'cloudflare.ssl.certificate',
|
|
441
|
+
resourceId: cert.id,
|
|
442
|
+
name: `${zone.name} - ${cert.type}`,
|
|
443
|
+
tags: [],
|
|
444
|
+
metadata: {
|
|
445
|
+
zoneId: zone.id,
|
|
446
|
+
zoneName: zone.name,
|
|
447
|
+
type: cert.type,
|
|
448
|
+
status: cert.status
|
|
449
|
+
},
|
|
450
|
+
configuration: this._sanitize(cert)
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
} catch (certErr) {
|
|
454
|
+
this.logger('debug', `No certificates in zone ${zone.name}`, {
|
|
455
|
+
zoneId: zone.id,
|
|
456
|
+
error: certErr.message
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
this.logger('info', `Collected Cloudflare certificates`);
|
|
462
|
+
} catch (err) {
|
|
463
|
+
this.logger('error', 'Failed to collect Cloudflare certificates', {
|
|
464
|
+
error: err.message,
|
|
465
|
+
stack: err.stack
|
|
466
|
+
});
|
|
467
|
+
throw err;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Collect WAF (Web Application Firewall) Rulesets via new Rulesets API (2025).
|
|
473
|
+
*/
|
|
474
|
+
async *_collectWAF() {
|
|
475
|
+
try {
|
|
476
|
+
const zones = await this._client.zones.list();
|
|
477
|
+
|
|
478
|
+
for (const zone of zones) {
|
|
479
|
+
try {
|
|
480
|
+
// Collect WAF rulesets using the new Rulesets API
|
|
481
|
+
const rulesets = await this._client.rulesets.list({ zone_id: zone.id });
|
|
482
|
+
|
|
483
|
+
for (const ruleset of rulesets) {
|
|
484
|
+
// Only collect WAF-related rulesets
|
|
485
|
+
if (ruleset.phase && (ruleset.phase.includes('http_') || ruleset.phase.includes('firewall'))) {
|
|
486
|
+
yield {
|
|
487
|
+
provider: 'cloudflare',
|
|
488
|
+
accountId: this._accountId || zone.account?.id,
|
|
489
|
+
region: 'global',
|
|
490
|
+
service: 'waf',
|
|
491
|
+
resourceType: 'cloudflare.waf.ruleset',
|
|
492
|
+
resourceId: ruleset.id,
|
|
493
|
+
name: ruleset.name,
|
|
494
|
+
tags: [],
|
|
495
|
+
metadata: {
|
|
496
|
+
zoneId: zone.id,
|
|
497
|
+
zoneName: zone.name,
|
|
498
|
+
phase: ruleset.phase,
|
|
499
|
+
kind: ruleset.kind
|
|
500
|
+
},
|
|
501
|
+
configuration: this._sanitize(ruleset)
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} catch (wafErr) {
|
|
506
|
+
this.logger('debug', `No WAF rulesets in zone ${zone.name}`, {
|
|
507
|
+
zoneId: zone.id,
|
|
508
|
+
error: wafErr.message
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
this.logger('info', `Collected Cloudflare WAF rulesets`);
|
|
514
|
+
} catch (err) {
|
|
515
|
+
this.logger('error', 'Failed to collect Cloudflare WAF', {
|
|
516
|
+
error: err.message,
|
|
517
|
+
stack: err.stack
|
|
518
|
+
});
|
|
519
|
+
throw err;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Collect Cloudflare Access Applications (Zero Trust).
|
|
525
|
+
*/
|
|
526
|
+
async *_collectAccess() {
|
|
527
|
+
try {
|
|
528
|
+
if (!this._accountId) {
|
|
529
|
+
this.logger('warn', 'Account ID required for Access collection, skipping');
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Collect Access Applications
|
|
534
|
+
const applications = await this._client.access.applications.list({ account_id: this._accountId });
|
|
535
|
+
|
|
536
|
+
for (const app of applications) {
|
|
537
|
+
yield {
|
|
538
|
+
provider: 'cloudflare',
|
|
539
|
+
accountId: this._accountId,
|
|
540
|
+
region: 'global',
|
|
541
|
+
service: 'access',
|
|
542
|
+
resourceType: 'cloudflare.access.application',
|
|
543
|
+
resourceId: app.id,
|
|
544
|
+
name: app.name,
|
|
545
|
+
tags: [],
|
|
546
|
+
metadata: {
|
|
547
|
+
domain: app.domain,
|
|
548
|
+
type: app.type
|
|
549
|
+
},
|
|
550
|
+
configuration: this._sanitize(app)
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
// Collect Access Policies for this application
|
|
554
|
+
try {
|
|
555
|
+
const policies = await this._client.access.policies.list({
|
|
556
|
+
account_id: this._accountId,
|
|
557
|
+
application_id: app.id
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
for (const policy of policies) {
|
|
561
|
+
yield {
|
|
562
|
+
provider: 'cloudflare',
|
|
563
|
+
accountId: this._accountId,
|
|
564
|
+
region: 'global',
|
|
565
|
+
service: 'access',
|
|
566
|
+
resourceType: 'cloudflare.access.policy',
|
|
567
|
+
resourceId: policy.id,
|
|
568
|
+
name: policy.name,
|
|
569
|
+
tags: [],
|
|
570
|
+
metadata: {
|
|
571
|
+
applicationId: app.id,
|
|
572
|
+
applicationName: app.name,
|
|
573
|
+
decision: policy.decision
|
|
574
|
+
},
|
|
575
|
+
configuration: this._sanitize(policy)
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
} catch (policyErr) {
|
|
579
|
+
this.logger('warn', `Failed to collect policies for Access application ${app.name}`, {
|
|
580
|
+
applicationId: app.id,
|
|
581
|
+
error: policyErr.message
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
this.logger('info', `Collected Cloudflare Access applications`);
|
|
587
|
+
} catch (err) {
|
|
588
|
+
this.logger('error', 'Failed to collect Cloudflare Access', {
|
|
589
|
+
error: err.message,
|
|
590
|
+
stack: err.stack
|
|
591
|
+
});
|
|
592
|
+
throw err;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Sanitize configuration by removing sensitive data.
|
|
598
|
+
*/
|
|
599
|
+
_sanitize(config) {
|
|
600
|
+
if (!config || typeof config !== 'object') return config;
|
|
601
|
+
|
|
602
|
+
const sanitized = { ...config };
|
|
603
|
+
const sensitiveFields = [
|
|
604
|
+
'api_token',
|
|
605
|
+
'api_key',
|
|
606
|
+
'token',
|
|
607
|
+
'secret',
|
|
608
|
+
'password',
|
|
609
|
+
'private_key'
|
|
610
|
+
];
|
|
611
|
+
|
|
612
|
+
for (const field of sensitiveFields) {
|
|
613
|
+
if (field in sanitized) {
|
|
614
|
+
sanitized[field] = '***REDACTED***';
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return sanitized;
|
|
619
|
+
}
|
|
620
|
+
}
|