s3db.js 13.6.1 → 14.0.2

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.
Files changed (189) hide show
  1. package/README.md +56 -15
  2. package/dist/s3db.cjs +72446 -39022
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72172 -38790
  5. package/dist/s3db.es.js.map +1 -1
  6. package/mcp/lib/base-handler.js +157 -0
  7. package/mcp/lib/handlers/connection-handler.js +280 -0
  8. package/mcp/lib/handlers/query-handler.js +533 -0
  9. package/mcp/lib/handlers/resource-handler.js +428 -0
  10. package/mcp/lib/tool-registry.js +336 -0
  11. package/mcp/lib/tools/connection-tools.js +161 -0
  12. package/mcp/lib/tools/query-tools.js +267 -0
  13. package/mcp/lib/tools/resource-tools.js +404 -0
  14. package/package.json +85 -50
  15. package/src/clients/memory-client.class.js +346 -191
  16. package/src/clients/memory-storage.class.js +300 -84
  17. package/src/clients/s3-client.class.js +7 -6
  18. package/src/concerns/geo-encoding.js +19 -2
  19. package/src/concerns/ip.js +59 -9
  20. package/src/concerns/money.js +8 -1
  21. package/src/concerns/password-hashing.js +49 -8
  22. package/src/concerns/plugin-storage.js +186 -18
  23. package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
  24. package/src/database.class.js +139 -29
  25. package/src/errors.js +332 -42
  26. package/src/plugins/api/auth/oidc-auth.js +66 -17
  27. package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
  28. package/src/plugins/api/auth/strategies/factory.class.js +63 -0
  29. package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
  30. package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
  31. package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
  32. package/src/plugins/api/concerns/failban-manager.js +106 -57
  33. package/src/plugins/api/concerns/route-context.js +601 -0
  34. package/src/plugins/api/index.js +168 -40
  35. package/src/plugins/api/routes/auth-routes.js +198 -30
  36. package/src/plugins/api/routes/resource-routes.js +19 -4
  37. package/src/plugins/api/server/health-manager.class.js +163 -0
  38. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  39. package/src/plugins/api/server/router.class.js +472 -0
  40. package/src/plugins/api/server.js +280 -1303
  41. package/src/plugins/api/utils/custom-routes.js +17 -5
  42. package/src/plugins/api/utils/guards.js +76 -17
  43. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  44. package/src/plugins/api/utils/openapi-generator.js +7 -6
  45. package/src/plugins/audit.plugin.js +30 -8
  46. package/src/plugins/backup.plugin.js +110 -14
  47. package/src/plugins/cache/cache.class.js +22 -5
  48. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  49. package/src/plugins/cache/memory-cache.class.js +211 -57
  50. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  51. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  52. package/src/plugins/cache/redis-cache.class.js +552 -0
  53. package/src/plugins/cache/s3-cache.class.js +17 -8
  54. package/src/plugins/cache.plugin.js +176 -61
  55. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  56. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  57. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  58. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  59. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  60. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  62. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/index.js +29 -8
  66. package/src/plugins/cloud-inventory/registry.js +64 -42
  67. package/src/plugins/cloud-inventory.plugin.js +240 -138
  68. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  69. package/src/plugins/concerns/resource-names.js +100 -0
  70. package/src/plugins/consumers/index.js +10 -2
  71. package/src/plugins/consumers/sqs-consumer.js +12 -2
  72. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  73. package/src/plugins/cookie-farm.errors.js +73 -0
  74. package/src/plugins/cookie-farm.plugin.js +869 -0
  75. package/src/plugins/costs.plugin.js +7 -1
  76. package/src/plugins/eventual-consistency/analytics.js +94 -19
  77. package/src/plugins/eventual-consistency/config.js +15 -7
  78. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  79. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  80. package/src/plugins/eventual-consistency/helpers.js +39 -14
  81. package/src/plugins/eventual-consistency/install.js +21 -2
  82. package/src/plugins/eventual-consistency/utils.js +32 -10
  83. package/src/plugins/fulltext.plugin.js +38 -11
  84. package/src/plugins/geo.plugin.js +61 -9
  85. package/src/plugins/identity/concerns/config.js +61 -0
  86. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  87. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  88. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  89. package/src/plugins/identity/concerns/token-generator.js +29 -4
  90. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  91. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  92. package/src/plugins/identity/drivers/index.js +18 -0
  93. package/src/plugins/identity/drivers/password-driver.js +122 -0
  94. package/src/plugins/identity/email-service.js +17 -2
  95. package/src/plugins/identity/index.js +413 -69
  96. package/src/plugins/identity/oauth2-server.js +413 -30
  97. package/src/plugins/identity/oidc-discovery.js +16 -8
  98. package/src/plugins/identity/rsa-keys.js +115 -35
  99. package/src/plugins/identity/server.js +166 -45
  100. package/src/plugins/identity/session-manager.js +53 -7
  101. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  102. package/src/plugins/identity/ui/routes.js +363 -255
  103. package/src/plugins/importer/index.js +153 -20
  104. package/src/plugins/index.js +9 -2
  105. package/src/plugins/kubernetes-inventory/index.js +6 -0
  106. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  107. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  108. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  109. package/src/plugins/metrics.plugin.js +64 -16
  110. package/src/plugins/ml/base-model.class.js +25 -15
  111. package/src/plugins/ml/regression-model.class.js +1 -1
  112. package/src/plugins/ml.errors.js +57 -25
  113. package/src/plugins/ml.plugin.js +28 -4
  114. package/src/plugins/namespace.js +210 -0
  115. package/src/plugins/plugin.class.js +180 -8
  116. package/src/plugins/puppeteer/console-monitor.js +729 -0
  117. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  118. package/src/plugins/puppeteer/network-monitor.js +816 -0
  119. package/src/plugins/puppeteer/performance-manager.js +746 -0
  120. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  121. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  122. package/src/plugins/puppeteer.errors.js +81 -0
  123. package/src/plugins/puppeteer.plugin.js +1327 -0
  124. package/src/plugins/queue-consumer.plugin.js +69 -14
  125. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  126. package/src/plugins/recon/concerns/command-runner.js +148 -0
  127. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  128. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  129. package/src/plugins/recon/concerns/process-manager.js +338 -0
  130. package/src/plugins/recon/concerns/report-generator.js +478 -0
  131. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  132. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  133. package/src/plugins/recon/config/defaults.js +321 -0
  134. package/src/plugins/recon/config/resources.js +370 -0
  135. package/src/plugins/recon/index.js +778 -0
  136. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  137. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  138. package/src/plugins/recon/managers/storage-manager.js +745 -0
  139. package/src/plugins/recon/managers/target-manager.js +274 -0
  140. package/src/plugins/recon/stages/asn-stage.js +314 -0
  141. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  142. package/src/plugins/recon/stages/dns-stage.js +107 -0
  143. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  144. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  145. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  146. package/src/plugins/recon/stages/http-stage.js +89 -0
  147. package/src/plugins/recon/stages/latency-stage.js +148 -0
  148. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  149. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  150. package/src/plugins/recon/stages/ports-stage.js +169 -0
  151. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  152. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  153. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  154. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  155. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  156. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  157. package/src/plugins/recon/stages/whois-stage.js +349 -0
  158. package/src/plugins/recon.plugin.js +75 -0
  159. package/src/plugins/recon.plugin.js.backup +2635 -0
  160. package/src/plugins/relation.errors.js +87 -14
  161. package/src/plugins/replicator.plugin.js +514 -137
  162. package/src/plugins/replicators/base-replicator.class.js +89 -1
  163. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  164. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  165. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  166. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  167. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  168. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  169. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  170. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  171. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  172. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  173. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  174. package/src/plugins/s3-queue.plugin.js +464 -65
  175. package/src/plugins/scheduler.plugin.js +20 -6
  176. package/src/plugins/state-machine.plugin.js +40 -9
  177. package/src/plugins/tfstate/base-driver.js +28 -4
  178. package/src/plugins/tfstate/errors.js +65 -10
  179. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  180. package/src/plugins/tfstate/index.js +163 -90
  181. package/src/plugins/tfstate/s3-driver.js +64 -6
  182. package/src/plugins/ttl.plugin.js +72 -17
  183. package/src/plugins/vector/distances.js +18 -12
  184. package/src/plugins/vector/kmeans.js +26 -4
  185. package/src/resource.class.js +115 -19
  186. package/src/testing/factory.class.js +20 -3
  187. package/src/testing/seeder.class.js +7 -1
  188. package/src/clients/memory-client.md +0 -917
  189. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -0,0 +1,867 @@
1
+ /**
2
+ * Kubernetes Driver for Inventory Plugin
3
+ *
4
+ * Connects to Kubernetes cluster and lists all resources using @kubernetes/client-node.
5
+ * Supports kubeconfig, in-cluster auth, and custom configurations.
6
+ */
7
+
8
+ import { requirePluginDependency } from '../concerns/plugin-dependencies.js';
9
+ import {
10
+ ALL_STANDARD_RESOURCE_TYPES,
11
+ formatResourceTypeId,
12
+ CORE_RESOURCE_TYPES,
13
+ APPS_RESOURCE_TYPES,
14
+ BATCH_RESOURCE_TYPES,
15
+ NETWORKING_RESOURCE_TYPES,
16
+ STORAGE_RESOURCE_TYPES,
17
+ RBAC_RESOURCE_TYPES,
18
+ POLICY_RESOURCE_TYPES,
19
+ AUTOSCALING_RESOURCE_TYPES,
20
+ SCHEDULING_RESOURCE_TYPES,
21
+ NODE_RESOURCE_TYPES,
22
+ CERTIFICATES_RESOURCE_TYPES,
23
+ COORDINATION_RESOURCE_TYPES,
24
+ DISCOVERY_RESOURCE_TYPES,
25
+ EVENTS_RESOURCE_TYPES,
26
+ ADMISSION_RESOURCE_TYPES,
27
+ API_REGISTRATION_RESOURCE_TYPES,
28
+ FLOWCONTROL_RESOURCE_TYPES,
29
+ } from './resource-types.js';
30
+
31
+ export class KubernetesDriver {
32
+ constructor(options = {}) {
33
+ this.options = options;
34
+ this.clusterId = options.id;
35
+ this.clusterName = options.name || options.id;
36
+
37
+ // Kubernetes client objects
38
+ this.kubeConfig = null;
39
+ this.apiClients = {};
40
+
41
+ // CRD discovery cache
42
+ this.crdCache = null;
43
+ this.crdCacheTime = 0;
44
+ this.crdCacheTTL = options.discovery?.crdCacheTTL || 300000; // 5 minutes
45
+
46
+ // Discovery options
47
+ this.discovery = {
48
+ includeSecrets: options.discovery?.includeSecrets ?? false,
49
+ includeConfigMaps: options.discovery?.includeConfigMaps ?? true,
50
+ includeCRDs: options.discovery?.includeCRDs ?? true,
51
+ coreResources: options.discovery?.coreResources ?? true,
52
+ appsResources: options.discovery?.appsResources ?? true,
53
+ batchResources: options.discovery?.batchResources ?? true,
54
+ networkingResources: options.discovery?.networkingResources ?? true,
55
+ storageResources: options.discovery?.storageResources ?? true,
56
+ rbacResources: options.discovery?.rbacResources ?? true,
57
+ namespaces: options.discovery?.namespaces || null, // null = all
58
+ excludeNamespaces: options.discovery?.excludeNamespaces || [],
59
+ ...options.discovery,
60
+ };
61
+
62
+ // Performance options
63
+ this.concurrency = options.discovery?.concurrency || 5;
64
+ this.pagination = options.discovery?.pagination || { enabled: true, pageSize: 100 };
65
+
66
+ // Retries
67
+ this.retries = options.retries || {
68
+ maxRetries: 5,
69
+ backoffBase: 1000,
70
+ retryOn429: true,
71
+ retryOn5xx: true,
72
+ };
73
+
74
+ // Sanitization
75
+ this.sanitization = options.sanitization || {
76
+ removeSecrets: true,
77
+ removeManagedFields: true,
78
+ removeResourceVersion: false,
79
+ };
80
+
81
+ // Logger
82
+ this.logger = options.logger || (() => {});
83
+ this.verbose = options.verbose ?? false;
84
+
85
+ // Metadata
86
+ this.tags = options.tags || {};
87
+ this.metadata = options.metadata || {};
88
+ }
89
+
90
+ /**
91
+ * Initialize the Kubernetes client
92
+ */
93
+ async initialize() {
94
+ // Require @kubernetes/client-node as peer dependency
95
+ const k8s = requirePluginDependency('@kubernetes/client-node', 'KubernetesInventoryPlugin');
96
+
97
+ this.k8s = k8s;
98
+ this.kubeConfig = new k8s.KubeConfig();
99
+
100
+ // Load kubeconfig based on options with fallback chain
101
+ await this._loadKubeConfig();
102
+
103
+ // Create API clients
104
+ this._createApiClients();
105
+
106
+ // Test connection
107
+ await this._testConnection();
108
+
109
+ this.log('info', `Kubernetes driver initialized for cluster: ${this.clusterName}`);
110
+ }
111
+
112
+ /**
113
+ * Load kubeconfig from multiple sources with priority order
114
+ */
115
+ async _loadKubeConfig() {
116
+ // Priority 1: In-cluster service account
117
+ if (this.options.inCluster) {
118
+ this.log('info', 'Loading in-cluster configuration');
119
+ this.kubeConfig.loadFromCluster();
120
+ return;
121
+ }
122
+
123
+ // Priority 2: Direct connection object (manual configuration)
124
+ if (this.options.connection) {
125
+ this.log('info', 'Loading kubeconfig from connection object');
126
+ this._loadFromConnectionObject();
127
+ return;
128
+ }
129
+
130
+ // Priority 3: Kubeconfig content as string (from environment variable or direct)
131
+ const kubeconfigContent = this._resolveKubeconfigContent();
132
+ if (kubeconfigContent) {
133
+ this.log('info', 'Loading kubeconfig from content string');
134
+ this.kubeConfig.loadFromString(kubeconfigContent);
135
+
136
+ // Apply context if specified
137
+ if (this.options.context) {
138
+ this.log('info', `Switching to context: ${this.options.context}`);
139
+ this.kubeConfig.setCurrentContext(this.options.context);
140
+ }
141
+ return;
142
+ }
143
+
144
+ // Priority 4: Kubeconfig file path (from option or environment variable)
145
+ const kubeconfigPath = this._resolveKubeconfigPath();
146
+ if (kubeconfigPath) {
147
+ this.log('info', `Loading kubeconfig from file: ${kubeconfigPath}`);
148
+ this.kubeConfig.loadFromFile(kubeconfigPath);
149
+
150
+ // Apply context if specified
151
+ if (this.options.context) {
152
+ this.log('info', `Switching to context: ${this.options.context}`);
153
+ this.kubeConfig.setCurrentContext(this.options.context);
154
+ }
155
+ return;
156
+ }
157
+
158
+ // Priority 5: Context only (use default kubeconfig with specific context)
159
+ if (this.options.context) {
160
+ this.log('info', `Loading default kubeconfig with context: ${this.options.context}`);
161
+ this.kubeConfig.loadFromDefault();
162
+ this.kubeConfig.setCurrentContext(this.options.context);
163
+ return;
164
+ }
165
+
166
+ // Priority 6: Default kubeconfig (~/.kube/config or KUBECONFIG env var)
167
+ this.log('info', 'Loading default kubeconfig');
168
+ this.kubeConfig.loadFromDefault();
169
+ }
170
+
171
+ /**
172
+ * Resolve kubeconfig content from multiple sources
173
+ * Priority: options.kubeconfigContent > env var
174
+ */
175
+ _resolveKubeconfigContent() {
176
+ // Direct content from options
177
+ if (this.options.kubeconfigContent) {
178
+ return this.options.kubeconfigContent;
179
+ }
180
+
181
+ // Environment variable: KUBECONFIG_CONTENT (base64 or plain text)
182
+ const envContent = process.env.KUBECONFIG_CONTENT;
183
+ if (envContent) {
184
+ this.log('debug', 'Found KUBECONFIG_CONTENT environment variable');
185
+
186
+ // Try to decode as base64 first
187
+ try {
188
+ const decoded = Buffer.from(envContent, 'base64').toString('utf-8');
189
+ // Check if it looks like YAML/JSON
190
+ if (decoded.includes('apiVersion') || decoded.includes('clusters')) {
191
+ this.log('debug', 'KUBECONFIG_CONTENT is base64-encoded');
192
+ return decoded;
193
+ }
194
+ } catch (error) {
195
+ // Not base64, use as-is
196
+ }
197
+
198
+ // Use as plain text
199
+ return envContent;
200
+ }
201
+
202
+ // Cluster-specific environment variable: KUBECONFIG_CONTENT_<CLUSTER_ID>
203
+ const clusterEnvKey = `KUBECONFIG_CONTENT_${this.clusterId.toUpperCase().replace(/-/g, '_')}`;
204
+ const clusterEnvContent = process.env[clusterEnvKey];
205
+ if (clusterEnvContent) {
206
+ this.log('debug', `Found ${clusterEnvKey} environment variable`);
207
+
208
+ // Try base64 decode
209
+ try {
210
+ const decoded = Buffer.from(clusterEnvContent, 'base64').toString('utf-8');
211
+ if (decoded.includes('apiVersion') || decoded.includes('clusters')) {
212
+ return decoded;
213
+ }
214
+ } catch (error) {
215
+ // Not base64
216
+ }
217
+
218
+ return clusterEnvContent;
219
+ }
220
+
221
+ return null;
222
+ }
223
+
224
+ /**
225
+ * Resolve kubeconfig file path from multiple sources
226
+ * Priority: options.kubeconfig > cluster-specific env var > KUBECONFIG env var
227
+ */
228
+ _resolveKubeconfigPath() {
229
+ // Direct path from options
230
+ if (this.options.kubeconfig) {
231
+ return this._expandPath(this.options.kubeconfig);
232
+ }
233
+
234
+ // Cluster-specific environment variable: KUBECONFIG_<CLUSTER_ID>
235
+ const clusterEnvKey = `KUBECONFIG_${this.clusterId.toUpperCase().replace(/-/g, '_')}`;
236
+ const clusterEnvPath = process.env[clusterEnvKey];
237
+ if (clusterEnvPath) {
238
+ this.log('debug', `Found ${clusterEnvKey} environment variable`);
239
+ return this._expandPath(clusterEnvPath);
240
+ }
241
+
242
+ // Standard KUBECONFIG environment variable
243
+ // Note: loadFromDefault() already handles this, so we return null here
244
+ // to let it fall through to default behavior
245
+
246
+ return null;
247
+ }
248
+
249
+ /**
250
+ * Expand path with home directory (~) and environment variables
251
+ */
252
+ _expandPath(path) {
253
+ if (!path) return path;
254
+
255
+ // Expand ~ to home directory
256
+ let expandedPath = path.replace(/^~/, process.env.HOME || '');
257
+
258
+ // Expand environment variables: ${VAR} or $VAR
259
+ expandedPath = expandedPath.replace(/\$\{([^}]+)\}/g, (_, varName) => {
260
+ return process.env[varName] || '';
261
+ });
262
+ expandedPath = expandedPath.replace(/\$([A-Z_][A-Z0-9_]*)/gi, (_, varName) => {
263
+ return process.env[varName] || '';
264
+ });
265
+
266
+ return expandedPath;
267
+ }
268
+
269
+ /**
270
+ * Load from connection object (manual configuration)
271
+ */
272
+ _loadFromConnectionObject() {
273
+ const conn = this.options.connection;
274
+
275
+ this.kubeConfig.loadFromOptions({
276
+ clusters: [{
277
+ name: this.clusterId,
278
+ server: conn.server,
279
+ caData: conn.caData,
280
+ skipTLSVerify: conn.skipTLSVerify,
281
+ }],
282
+ users: [{
283
+ name: this.clusterId,
284
+ token: conn.token,
285
+ certData: conn.certData,
286
+ keyData: conn.keyData,
287
+ }],
288
+ contexts: [{
289
+ name: this.clusterId,
290
+ cluster: this.clusterId,
291
+ user: this.clusterId,
292
+ }],
293
+ currentContext: this.clusterId,
294
+ });
295
+ }
296
+
297
+ /**
298
+ * Create all necessary API clients
299
+ */
300
+ _createApiClients() {
301
+ const { k8s, kubeConfig } = this;
302
+
303
+ this.apiClients = {
304
+ core: kubeConfig.makeApiClient(k8s.CoreV1Api),
305
+ apps: kubeConfig.makeApiClient(k8s.AppsV1Api),
306
+ batch: kubeConfig.makeApiClient(k8s.BatchV1Api),
307
+ networking: kubeConfig.makeApiClient(k8s.NetworkingV1Api),
308
+ storage: kubeConfig.makeApiClient(k8s.StorageV1Api),
309
+ rbac: kubeConfig.makeApiClient(k8s.RbacAuthorizationV1Api),
310
+ policy: kubeConfig.makeApiClient(k8s.PolicyV1Api),
311
+ autoscalingV1: kubeConfig.makeApiClient(k8s.AutoscalingV1Api),
312
+ autoscalingV2: kubeConfig.makeApiClient(k8s.AutoscalingV2Api),
313
+ scheduling: kubeConfig.makeApiClient(k8s.SchedulingV1Api),
314
+ node: kubeConfig.makeApiClient(k8s.NodeV1Api),
315
+ certificates: kubeConfig.makeApiClient(k8s.CertificatesV1Api),
316
+ coordination: kubeConfig.makeApiClient(k8s.CoordinationV1Api),
317
+ discovery: kubeConfig.makeApiClient(k8s.DiscoveryV1Api),
318
+ events: kubeConfig.makeApiClient(k8s.EventsV1Api),
319
+ admission: kubeConfig.makeApiClient(k8s.AdmissionregistrationV1Api),
320
+ apiRegistration: kubeConfig.makeApiClient(k8s.ApiregistrationV1Api),
321
+ apiExtensions: kubeConfig.makeApiClient(k8s.ApiextensionsV1Api),
322
+ customObjects: kubeConfig.makeApiClient(k8s.CustomObjectsApi),
323
+ };
324
+ }
325
+
326
+ /**
327
+ * Test connection to cluster
328
+ */
329
+ async _testConnection() {
330
+ try {
331
+ const response = await this._retryOperation(async () => {
332
+ return await this.apiClients.core.listNamespace();
333
+ });
334
+
335
+ const namespaceCount = response.body.items.length;
336
+ this.log('info', `Successfully connected to cluster. Found ${namespaceCount} namespaces.`);
337
+ } catch (error) {
338
+ throw new Error(`Failed to connect to Kubernetes cluster: ${error.message}`);
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Discover all available resource types (including CRDs)
344
+ * @param {Object} options - Discovery options
345
+ * @returns {Promise<Array>} Array of resource type definitions
346
+ */
347
+ async discoverResourceTypes(options = {}) {
348
+ const resourceTypes = [];
349
+
350
+ // Add standard resource types based on discovery options
351
+ if (this.discovery.coreResources) {
352
+ resourceTypes.push(...this._filterSecrets(CORE_RESOURCE_TYPES));
353
+ }
354
+ if (this.discovery.appsResources) {
355
+ resourceTypes.push(...APPS_RESOURCE_TYPES);
356
+ }
357
+ if (this.discovery.batchResources) {
358
+ resourceTypes.push(...BATCH_RESOURCE_TYPES);
359
+ }
360
+ if (this.discovery.networkingResources) {
361
+ resourceTypes.push(...NETWORKING_RESOURCE_TYPES);
362
+ }
363
+ if (this.discovery.storageResources) {
364
+ resourceTypes.push(...STORAGE_RESOURCE_TYPES);
365
+ }
366
+ if (this.discovery.rbacResources) {
367
+ resourceTypes.push(...RBAC_RESOURCE_TYPES);
368
+ }
369
+
370
+ // Add other standard types
371
+ resourceTypes.push(
372
+ ...POLICY_RESOURCE_TYPES,
373
+ ...AUTOSCALING_RESOURCE_TYPES,
374
+ ...SCHEDULING_RESOURCE_TYPES,
375
+ ...NODE_RESOURCE_TYPES,
376
+ ...CERTIFICATES_RESOURCE_TYPES,
377
+ ...COORDINATION_RESOURCE_TYPES,
378
+ ...DISCOVERY_RESOURCE_TYPES,
379
+ ...EVENTS_RESOURCE_TYPES,
380
+ ...ADMISSION_RESOURCE_TYPES,
381
+ ...API_REGISTRATION_RESOURCE_TYPES,
382
+ ...FLOWCONTROL_RESOURCE_TYPES
383
+ );
384
+
385
+ // Discover CRDs if enabled
386
+ if (this.discovery.includeCRDs) {
387
+ const crds = await this._discoverCRDs(options.force);
388
+ resourceTypes.push(...crds);
389
+ }
390
+
391
+ return resourceTypes;
392
+ }
393
+
394
+ /**
395
+ * Filter out Secrets if includeSecrets is false
396
+ */
397
+ _filterSecrets(resourceTypes) {
398
+ if (this.discovery.includeSecrets) {
399
+ return resourceTypes;
400
+ }
401
+ return resourceTypes.filter(rt => rt.kind !== 'Secret');
402
+ }
403
+
404
+ /**
405
+ * Discover Custom Resource Definitions (CRDs)
406
+ * @param {boolean} force - Force refresh cache
407
+ * @returns {Promise<Array>} Array of CRD resource types
408
+ */
409
+ async _discoverCRDs(force = false) {
410
+ const now = Date.now();
411
+
412
+ // Return cached CRDs if still valid
413
+ if (!force && this.crdCache && (now - this.crdCacheTime < this.crdCacheTTL)) {
414
+ this.log('debug', `Using cached CRDs (${this.crdCache.length} types)`);
415
+ return this.crdCache;
416
+ }
417
+
418
+ this.log('info', 'Discovering Custom Resource Definitions...');
419
+
420
+ try {
421
+ const response = await this._retryOperation(async () => {
422
+ return await this.apiClients.apiExtensions.listCustomResourceDefinition();
423
+ });
424
+
425
+ const crds = response.body.items.map(crd => {
426
+ // Find the storage version or use the first version
427
+ const storageVersion = crd.spec.versions.find(v => v.storage);
428
+ const version = storageVersion || crd.spec.versions[0];
429
+
430
+ return {
431
+ group: crd.spec.group,
432
+ version: version.name,
433
+ kind: crd.spec.names.kind,
434
+ plural: crd.spec.names.plural,
435
+ namespaced: crd.spec.scope === 'Namespaced',
436
+ isCRD: true,
437
+ category: 'custom',
438
+ crdName: crd.metadata.name,
439
+ };
440
+ });
441
+
442
+ this.crdCache = crds;
443
+ this.crdCacheTime = now;
444
+
445
+ this.log('info', `Discovered ${crds.length} Custom Resource Definitions`);
446
+
447
+ return crds;
448
+ } catch (error) {
449
+ this.log('warn', `Failed to discover CRDs: ${error.message}`);
450
+ return [];
451
+ }
452
+ }
453
+
454
+ /**
455
+ * List all resources from the Kubernetes cluster
456
+ * @param {Object} options - Discovery options
457
+ * @returns {AsyncIterator} Yields normalized resources
458
+ */
459
+ async *listResources(options = {}) {
460
+ const { runtime } = options;
461
+
462
+ // Discover resource types
463
+ const resourceTypes = await this.discoverResourceTypes(options);
464
+ this.log('info', `Discovering ${resourceTypes.length} resource types from cluster ${this.clusterName}`);
465
+
466
+ // Emit progress
467
+ runtime?.emitProgress?.({
468
+ stage: 'discovery',
469
+ clusterId: this.clusterId,
470
+ totalTypes: resourceTypes.length,
471
+ processedTypes: 0,
472
+ });
473
+
474
+ let processedTypes = 0;
475
+
476
+ // Fetch resources for each type
477
+ for (const resourceType of resourceTypes) {
478
+ const resourceTypeId = formatResourceTypeId(resourceType);
479
+
480
+ try {
481
+ this.log('debug', `Fetching resources of type: ${resourceTypeId}`);
482
+
483
+ // Fetch resources based on type
484
+ const resources = await this._fetchResourceType(resourceType);
485
+
486
+ // Yield normalized resources
487
+ for (const resource of resources) {
488
+ yield this._normalizeResource(resourceType, resource);
489
+ }
490
+
491
+ processedTypes++;
492
+
493
+ // Emit progress
494
+ runtime?.emitProgress?.({
495
+ stage: 'discovery',
496
+ clusterId: this.clusterId,
497
+ totalTypes: resourceTypes.length,
498
+ processedTypes,
499
+ currentType: resourceTypeId,
500
+ });
501
+
502
+ this.log('debug', `Fetched ${resources.length} resources of type: ${resourceTypeId}`);
503
+ } catch (error) {
504
+ this.log('warn', `Failed to fetch resource type ${resourceTypeId}: ${error.message}`);
505
+
506
+ // Continue with next resource type (don't fail entire discovery)
507
+ runtime?.emitProgress?.({
508
+ stage: 'error',
509
+ clusterId: this.clusterId,
510
+ resourceType: resourceTypeId,
511
+ error: error.message,
512
+ });
513
+ }
514
+ }
515
+
516
+ this.log('info', `Completed discovery for cluster ${this.clusterName}`);
517
+ }
518
+
519
+ /**
520
+ * Fetch all resources of a specific type
521
+ * @param {Object} resourceType - Resource type definition
522
+ * @returns {Promise<Array>} Array of Kubernetes resources
523
+ */
524
+ async _fetchResourceType(resourceType) {
525
+ const resources = [];
526
+
527
+ // Determine if resource is namespaced
528
+ if (resourceType.namespaced) {
529
+ // Fetch namespaced resources
530
+ const namespaces = await this._getNamespaces();
531
+
532
+ for (const namespace of namespaces) {
533
+ const namespaceResources = await this._fetchNamespacedResources(resourceType, namespace);
534
+ resources.push(...namespaceResources);
535
+ }
536
+ } else {
537
+ // Fetch cluster-scoped resources
538
+ const clusterResources = await this._fetchClusterResources(resourceType);
539
+ resources.push(...clusterResources);
540
+ }
541
+
542
+ return resources;
543
+ }
544
+
545
+ /**
546
+ * Get list of namespaces to query
547
+ * @returns {Promise<Array>} Array of namespace names
548
+ */
549
+ async _getNamespaces() {
550
+ // If specific namespaces are configured, use those
551
+ if (this.discovery.namespaces && this.discovery.namespaces.length > 0) {
552
+ return this.discovery.namespaces.filter(
553
+ ns => !this.discovery.excludeNamespaces.includes(ns)
554
+ );
555
+ }
556
+
557
+ // Otherwise, fetch all namespaces
558
+ try {
559
+ const response = await this._retryOperation(async () => {
560
+ return await this.apiClients.core.listNamespace();
561
+ });
562
+
563
+ return response.body.items
564
+ .map(ns => ns.metadata.name)
565
+ .filter(ns => !this.discovery.excludeNamespaces.includes(ns));
566
+ } catch (error) {
567
+ this.log('warn', `Failed to list namespaces: ${error.message}`);
568
+ return ['default']; // Fallback to default namespace
569
+ }
570
+ }
571
+
572
+ /**
573
+ * Fetch namespaced resources
574
+ * @param {Object} resourceType - Resource type definition
575
+ * @param {string} namespace - Namespace name
576
+ * @returns {Promise<Array>} Array of resources
577
+ */
578
+ async _fetchNamespacedResources(resourceType, namespace) {
579
+ try {
580
+ if (resourceType.isCRD) {
581
+ // Fetch custom resources
582
+ return await this._fetchCustomResources(resourceType, namespace);
583
+ } else {
584
+ // Fetch standard namespaced resources
585
+ return await this._fetchStandardNamespacedResources(resourceType, namespace);
586
+ }
587
+ } catch (error) {
588
+ // 404 means resource type not found in this cluster (e.g., old K8s version)
589
+ if (error.response?.statusCode === 404 || error.statusCode === 404) {
590
+ this.log('debug', `Resource type ${formatResourceTypeId(resourceType)} not found in cluster`);
591
+ return [];
592
+ }
593
+
594
+ // 403 means permission denied
595
+ if (error.response?.statusCode === 403 || error.statusCode === 403) {
596
+ this.log('warn', `Permission denied for resource type ${formatResourceTypeId(resourceType)} in namespace ${namespace}`);
597
+ return [];
598
+ }
599
+
600
+ throw error;
601
+ }
602
+ }
603
+
604
+ /**
605
+ * Fetch cluster-scoped resources
606
+ * @param {Object} resourceType - Resource type definition
607
+ * @returns {Promise<Array>} Array of resources
608
+ */
609
+ async _fetchClusterResources(resourceType) {
610
+ try {
611
+ if (resourceType.isCRD) {
612
+ // Fetch custom resources (cluster-scoped)
613
+ return await this._fetchCustomResources(resourceType, null);
614
+ } else {
615
+ // Fetch standard cluster-scoped resources
616
+ return await this._fetchStandardClusterResources(resourceType);
617
+ }
618
+ } catch (error) {
619
+ // 404 means resource type not found
620
+ if (error.response?.statusCode === 404 || error.statusCode === 404) {
621
+ this.log('debug', `Resource type ${formatResourceTypeId(resourceType)} not found in cluster`);
622
+ return [];
623
+ }
624
+
625
+ // 403 means permission denied
626
+ if (error.response?.statusCode === 403 || error.statusCode === 403) {
627
+ this.log('warn', `Permission denied for resource type ${formatResourceTypeId(resourceType)}`);
628
+ return [];
629
+ }
630
+
631
+ throw error;
632
+ }
633
+ }
634
+
635
+ /**
636
+ * Fetch standard namespaced resources
637
+ */
638
+ async _fetchStandardNamespacedResources(resourceType, namespace) {
639
+ const apiClient = this._getApiClient(resourceType);
640
+ const methodName = `listNamespaced${resourceType.kind}`;
641
+
642
+ if (!apiClient || !apiClient[methodName]) {
643
+ this.log('warn', `API method not found: ${methodName}`);
644
+ return [];
645
+ }
646
+
647
+ const response = await this._retryOperation(async () => {
648
+ return await apiClient[methodName](namespace);
649
+ });
650
+
651
+ return response.body.items || [];
652
+ }
653
+
654
+ /**
655
+ * Fetch standard cluster-scoped resources
656
+ */
657
+ async _fetchStandardClusterResources(resourceType) {
658
+ const apiClient = this._getApiClient(resourceType);
659
+ const methodName = `list${resourceType.kind}`;
660
+
661
+ if (!apiClient || !apiClient[methodName]) {
662
+ this.log('warn', `API method not found: ${methodName}`);
663
+ return [];
664
+ }
665
+
666
+ const response = await this._retryOperation(async () => {
667
+ return await apiClient[methodName]();
668
+ });
669
+
670
+ return response.body.items || [];
671
+ }
672
+
673
+ /**
674
+ * Fetch custom resources (CRDs)
675
+ */
676
+ async _fetchCustomResources(resourceType, namespace) {
677
+ const { customObjects } = this.apiClients;
678
+
679
+ const response = await this._retryOperation(async () => {
680
+ if (namespace) {
681
+ return await customObjects.listNamespacedCustomObject(
682
+ resourceType.group,
683
+ resourceType.version,
684
+ namespace,
685
+ resourceType.plural
686
+ );
687
+ } else {
688
+ return await customObjects.listClusterCustomObject(
689
+ resourceType.group,
690
+ resourceType.version,
691
+ resourceType.plural
692
+ );
693
+ }
694
+ });
695
+
696
+ return response.body.items || [];
697
+ }
698
+
699
+ /**
700
+ * Get appropriate API client for resource type
701
+ */
702
+ _getApiClient(resourceType) {
703
+ const { group } = resourceType;
704
+
705
+ if (!group || group === '') return this.apiClients.core;
706
+ if (group === 'apps') return this.apiClients.apps;
707
+ if (group === 'batch') return this.apiClients.batch;
708
+ if (group === 'networking.k8s.io') return this.apiClients.networking;
709
+ if (group === 'storage.k8s.io') return this.apiClients.storage;
710
+ if (group === 'rbac.authorization.k8s.io') return this.apiClients.rbac;
711
+ if (group === 'policy') return this.apiClients.policy;
712
+ if (group === 'autoscaling' && resourceType.version === 'v1') return this.apiClients.autoscalingV1;
713
+ if (group === 'autoscaling' && resourceType.version === 'v2') return this.apiClients.autoscalingV2;
714
+ if (group === 'scheduling.k8s.io') return this.apiClients.scheduling;
715
+ if (group === 'node.k8s.io') return this.apiClients.node;
716
+ if (group === 'certificates.k8s.io') return this.apiClients.certificates;
717
+ if (group === 'coordination.k8s.io') return this.apiClients.coordination;
718
+ if (group === 'discovery.k8s.io') return this.apiClients.discovery;
719
+ if (group === 'events.k8s.io') return this.apiClients.events;
720
+ if (group === 'admissionregistration.k8s.io') return this.apiClients.admission;
721
+ if (group === 'apiregistration.k8s.io') return this.apiClients.apiRegistration;
722
+
723
+ return null;
724
+ }
725
+
726
+ /**
727
+ * Normalize Kubernetes resource to standard format
728
+ */
729
+ _normalizeResource(resourceType, resource) {
730
+ const metadata = resource.metadata || {};
731
+
732
+ // Sanitize configuration
733
+ const configuration = this._sanitizeConfiguration(resource);
734
+
735
+ return {
736
+ // IDENTITY
737
+ provider: 'kubernetes',
738
+ clusterId: this.clusterId,
739
+ clusterName: this.clusterName,
740
+ namespace: metadata.namespace || null,
741
+ resourceType: formatResourceTypeId(resourceType),
742
+ resourceId: metadata.name,
743
+ uid: metadata.uid,
744
+
745
+ // METADATA
746
+ apiVersion: resource.apiVersion,
747
+ kind: resource.kind,
748
+ name: metadata.name,
749
+ labels: metadata.labels || {},
750
+ annotations: metadata.annotations || {},
751
+ creationTimestamp: metadata.creationTimestamp,
752
+ resourceVersion: this.sanitization.removeResourceVersion ? undefined : metadata.resourceVersion,
753
+
754
+ // CONFIGURATION
755
+ configuration,
756
+
757
+ // CONTEXT
758
+ tags: this.tags,
759
+ metadata: this.metadata,
760
+ raw: this.sanitization.removeRaw ? undefined : resource,
761
+ };
762
+ }
763
+
764
+ /**
765
+ * Sanitize resource configuration
766
+ */
767
+ _sanitizeConfiguration(resource) {
768
+ const config = { ...resource };
769
+
770
+ // Remove metadata from configuration (already extracted)
771
+ delete config.metadata;
772
+ delete config.apiVersion;
773
+ delete config.kind;
774
+
775
+ // Remove managed fields (reduce size)
776
+ if (this.sanitization.removeManagedFields && config.metadata?.managedFields) {
777
+ delete config.metadata.managedFields;
778
+ }
779
+
780
+ // Remove secret data
781
+ if (this.sanitization.removeSecrets && resource.kind === 'Secret' && config.data) {
782
+ config.data = Object.keys(config.data).reduce((acc, key) => {
783
+ acc[key] = '[REDACTED]';
784
+ return acc;
785
+ }, {});
786
+ }
787
+
788
+ // Custom sanitizer
789
+ if (this.sanitization.customSanitizer) {
790
+ return this.sanitization.customSanitizer(config);
791
+ }
792
+
793
+ return config;
794
+ }
795
+
796
+ /**
797
+ * Retry operation with exponential backoff
798
+ */
799
+ async _retryOperation(operation, attempt = 1) {
800
+ try {
801
+ return await operation();
802
+ } catch (error) {
803
+ const shouldRetry = this._shouldRetry(error, attempt);
804
+
805
+ if (shouldRetry && attempt < this.retries.maxRetries) {
806
+ const delay = Math.pow(2, attempt - 1) * this.retries.backoffBase;
807
+ this.log('debug', `Retrying operation (attempt ${attempt + 1}/${this.retries.maxRetries}) after ${delay}ms`);
808
+
809
+ await new Promise(resolve => setTimeout(resolve, delay));
810
+ return this._retryOperation(operation, attempt + 1);
811
+ }
812
+
813
+ throw error;
814
+ }
815
+ }
816
+
817
+ /**
818
+ * Determine if error should be retried
819
+ */
820
+ _shouldRetry(error, attempt) {
821
+ if (attempt >= this.retries.maxRetries) {
822
+ return false;
823
+ }
824
+
825
+ const statusCode = error.response?.statusCode || error.statusCode;
826
+
827
+ // Retry on 429 (rate limit)
828
+ if (statusCode === 429 && this.retries.retryOn429) {
829
+ return true;
830
+ }
831
+
832
+ // Retry on 5xx (server errors)
833
+ if (statusCode >= 500 && statusCode < 600 && this.retries.retryOn5xx) {
834
+ return true;
835
+ }
836
+
837
+ // Retry on network errors
838
+ if (!statusCode && error.code === 'ECONNRESET') {
839
+ return true;
840
+ }
841
+
842
+ return false;
843
+ }
844
+
845
+ /**
846
+ * Cleanup driver resources
847
+ */
848
+ async destroy() {
849
+ this.log('info', `Destroying Kubernetes driver for cluster: ${this.clusterName}`);
850
+ this.kubeConfig = null;
851
+ this.apiClients = {};
852
+ this.crdCache = null;
853
+ }
854
+
855
+ /**
856
+ * Internal logger
857
+ */
858
+ log(level, message, meta = {}) {
859
+ if (this.logger) {
860
+ this.logger(level, message, { driver: 'kubernetes', clusterId: this.clusterId, ...meta });
861
+ }
862
+
863
+ if (this.verbose && level !== 'debug') {
864
+ console.log(`[${level.toUpperCase()}] [k8s:${this.clusterId}] ${message}`);
865
+ }
866
+ }
867
+ }