@etcsec-com/etc-collector 1.4.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.
Files changed (617) hide show
  1. package/.env.example +60 -0
  2. package/.env.test.example +33 -0
  3. package/.github/workflows/ci.yml +83 -0
  4. package/.github/workflows/release.yml +246 -0
  5. package/.prettierrc.json +10 -0
  6. package/CHANGELOG.md +15 -0
  7. package/Dockerfile +57 -0
  8. package/LICENSE +190 -0
  9. package/README.md +194 -0
  10. package/dist/api/controllers/audit.controller.d.ts +21 -0
  11. package/dist/api/controllers/audit.controller.d.ts.map +1 -0
  12. package/dist/api/controllers/audit.controller.js +179 -0
  13. package/dist/api/controllers/audit.controller.js.map +1 -0
  14. package/dist/api/controllers/auth.controller.d.ts +16 -0
  15. package/dist/api/controllers/auth.controller.d.ts.map +1 -0
  16. package/dist/api/controllers/auth.controller.js +146 -0
  17. package/dist/api/controllers/auth.controller.js.map +1 -0
  18. package/dist/api/controllers/export.controller.d.ts +27 -0
  19. package/dist/api/controllers/export.controller.d.ts.map +1 -0
  20. package/dist/api/controllers/export.controller.js +80 -0
  21. package/dist/api/controllers/export.controller.js.map +1 -0
  22. package/dist/api/controllers/health.controller.d.ts +5 -0
  23. package/dist/api/controllers/health.controller.d.ts.map +1 -0
  24. package/dist/api/controllers/health.controller.js +16 -0
  25. package/dist/api/controllers/health.controller.js.map +1 -0
  26. package/dist/api/controllers/jobs.controller.d.ts +13 -0
  27. package/dist/api/controllers/jobs.controller.d.ts.map +1 -0
  28. package/dist/api/controllers/jobs.controller.js +125 -0
  29. package/dist/api/controllers/jobs.controller.js.map +1 -0
  30. package/dist/api/controllers/providers.controller.d.ts +15 -0
  31. package/dist/api/controllers/providers.controller.d.ts.map +1 -0
  32. package/dist/api/controllers/providers.controller.js +112 -0
  33. package/dist/api/controllers/providers.controller.js.map +1 -0
  34. package/dist/api/dto/AuditRequest.dto.d.ts +6 -0
  35. package/dist/api/dto/AuditRequest.dto.d.ts.map +1 -0
  36. package/dist/api/dto/AuditRequest.dto.js +3 -0
  37. package/dist/api/dto/AuditRequest.dto.js.map +1 -0
  38. package/dist/api/dto/AuditResponse.dto.d.ts +17 -0
  39. package/dist/api/dto/AuditResponse.dto.d.ts.map +1 -0
  40. package/dist/api/dto/AuditResponse.dto.js +3 -0
  41. package/dist/api/dto/AuditResponse.dto.js.map +1 -0
  42. package/dist/api/dto/TokenRequest.dto.d.ts +6 -0
  43. package/dist/api/dto/TokenRequest.dto.d.ts.map +1 -0
  44. package/dist/api/dto/TokenRequest.dto.js +3 -0
  45. package/dist/api/dto/TokenRequest.dto.js.map +1 -0
  46. package/dist/api/dto/TokenResponse.dto.d.ts +12 -0
  47. package/dist/api/dto/TokenResponse.dto.d.ts.map +1 -0
  48. package/dist/api/dto/TokenResponse.dto.js +3 -0
  49. package/dist/api/dto/TokenResponse.dto.js.map +1 -0
  50. package/dist/api/middlewares/authenticate.d.ts +12 -0
  51. package/dist/api/middlewares/authenticate.d.ts.map +1 -0
  52. package/dist/api/middlewares/authenticate.js +141 -0
  53. package/dist/api/middlewares/authenticate.js.map +1 -0
  54. package/dist/api/middlewares/errorHandler.d.ts +3 -0
  55. package/dist/api/middlewares/errorHandler.d.ts.map +1 -0
  56. package/dist/api/middlewares/errorHandler.js +30 -0
  57. package/dist/api/middlewares/errorHandler.js.map +1 -0
  58. package/dist/api/middlewares/rateLimit.d.ts +3 -0
  59. package/dist/api/middlewares/rateLimit.d.ts.map +1 -0
  60. package/dist/api/middlewares/rateLimit.js +34 -0
  61. package/dist/api/middlewares/rateLimit.js.map +1 -0
  62. package/dist/api/middlewares/validate.d.ts +4 -0
  63. package/dist/api/middlewares/validate.d.ts.map +1 -0
  64. package/dist/api/middlewares/validate.js +31 -0
  65. package/dist/api/middlewares/validate.js.map +1 -0
  66. package/dist/api/routes/audit.routes.d.ts +5 -0
  67. package/dist/api/routes/audit.routes.d.ts.map +1 -0
  68. package/dist/api/routes/audit.routes.js +24 -0
  69. package/dist/api/routes/audit.routes.js.map +1 -0
  70. package/dist/api/routes/auth.routes.d.ts +6 -0
  71. package/dist/api/routes/auth.routes.d.ts.map +1 -0
  72. package/dist/api/routes/auth.routes.js +22 -0
  73. package/dist/api/routes/auth.routes.js.map +1 -0
  74. package/dist/api/routes/export.routes.d.ts +5 -0
  75. package/dist/api/routes/export.routes.d.ts.map +1 -0
  76. package/dist/api/routes/export.routes.js +16 -0
  77. package/dist/api/routes/export.routes.js.map +1 -0
  78. package/dist/api/routes/health.routes.d.ts +4 -0
  79. package/dist/api/routes/health.routes.d.ts.map +1 -0
  80. package/dist/api/routes/health.routes.js +11 -0
  81. package/dist/api/routes/health.routes.js.map +1 -0
  82. package/dist/api/routes/index.d.ts +10 -0
  83. package/dist/api/routes/index.d.ts.map +1 -0
  84. package/dist/api/routes/index.js +20 -0
  85. package/dist/api/routes/index.js.map +1 -0
  86. package/dist/api/routes/providers.routes.d.ts +5 -0
  87. package/dist/api/routes/providers.routes.d.ts.map +1 -0
  88. package/dist/api/routes/providers.routes.js +13 -0
  89. package/dist/api/routes/providers.routes.js.map +1 -0
  90. package/dist/api/validators/audit.schemas.d.ts +60 -0
  91. package/dist/api/validators/audit.schemas.d.ts.map +1 -0
  92. package/dist/api/validators/audit.schemas.js +55 -0
  93. package/dist/api/validators/audit.schemas.js.map +1 -0
  94. package/dist/api/validators/auth.schemas.d.ts +17 -0
  95. package/dist/api/validators/auth.schemas.d.ts.map +1 -0
  96. package/dist/api/validators/auth.schemas.js +21 -0
  97. package/dist/api/validators/auth.schemas.js.map +1 -0
  98. package/dist/app.d.ts +3 -0
  99. package/dist/app.d.ts.map +1 -0
  100. package/dist/app.js +62 -0
  101. package/dist/app.js.map +1 -0
  102. package/dist/config/config.schema.d.ts +65 -0
  103. package/dist/config/config.schema.d.ts.map +1 -0
  104. package/dist/config/config.schema.js +95 -0
  105. package/dist/config/config.schema.js.map +1 -0
  106. package/dist/config/index.d.ts +4 -0
  107. package/dist/config/index.d.ts.map +1 -0
  108. package/dist/config/index.js +75 -0
  109. package/dist/config/index.js.map +1 -0
  110. package/dist/container.d.ts +47 -0
  111. package/dist/container.d.ts.map +1 -0
  112. package/dist/container.js +137 -0
  113. package/dist/container.js.map +1 -0
  114. package/dist/data/database.d.ts +13 -0
  115. package/dist/data/database.d.ts.map +1 -0
  116. package/dist/data/database.js +68 -0
  117. package/dist/data/database.js.map +1 -0
  118. package/dist/data/jobs/token-cleanup.job.d.ts +23 -0
  119. package/dist/data/jobs/token-cleanup.job.d.ts.map +1 -0
  120. package/dist/data/jobs/token-cleanup.job.js +96 -0
  121. package/dist/data/jobs/token-cleanup.job.js.map +1 -0
  122. package/dist/data/migrations/migration.runner.d.ts +13 -0
  123. package/dist/data/migrations/migration.runner.d.ts.map +1 -0
  124. package/dist/data/migrations/migration.runner.js +136 -0
  125. package/dist/data/migrations/migration.runner.js.map +1 -0
  126. package/dist/data/models/Token.model.d.ts +30 -0
  127. package/dist/data/models/Token.model.d.ts.map +1 -0
  128. package/dist/data/models/Token.model.js +3 -0
  129. package/dist/data/models/Token.model.js.map +1 -0
  130. package/dist/data/repositories/token.repository.d.ts +16 -0
  131. package/dist/data/repositories/token.repository.d.ts.map +1 -0
  132. package/dist/data/repositories/token.repository.js +97 -0
  133. package/dist/data/repositories/token.repository.js.map +1 -0
  134. package/dist/providers/azure/auth.provider.d.ts +5 -0
  135. package/dist/providers/azure/auth.provider.d.ts.map +1 -0
  136. package/dist/providers/azure/auth.provider.js +13 -0
  137. package/dist/providers/azure/auth.provider.js.map +1 -0
  138. package/dist/providers/azure/azure-errors.d.ts +40 -0
  139. package/dist/providers/azure/azure-errors.d.ts.map +1 -0
  140. package/dist/providers/azure/azure-errors.js +121 -0
  141. package/dist/providers/azure/azure-errors.js.map +1 -0
  142. package/dist/providers/azure/azure-retry.d.ts +41 -0
  143. package/dist/providers/azure/azure-retry.d.ts.map +1 -0
  144. package/dist/providers/azure/azure-retry.js +85 -0
  145. package/dist/providers/azure/azure-retry.js.map +1 -0
  146. package/dist/providers/azure/graph-client.d.ts +26 -0
  147. package/dist/providers/azure/graph-client.d.ts.map +1 -0
  148. package/dist/providers/azure/graph-client.js +146 -0
  149. package/dist/providers/azure/graph-client.js.map +1 -0
  150. package/dist/providers/azure/graph.provider.d.ts +23 -0
  151. package/dist/providers/azure/graph.provider.d.ts.map +1 -0
  152. package/dist/providers/azure/graph.provider.js +161 -0
  153. package/dist/providers/azure/graph.provider.js.map +1 -0
  154. package/dist/providers/azure/queries/app.queries.d.ts +6 -0
  155. package/dist/providers/azure/queries/app.queries.d.ts.map +1 -0
  156. package/dist/providers/azure/queries/app.queries.js +9 -0
  157. package/dist/providers/azure/queries/app.queries.js.map +1 -0
  158. package/dist/providers/azure/queries/policy.queries.d.ts +6 -0
  159. package/dist/providers/azure/queries/policy.queries.d.ts.map +1 -0
  160. package/dist/providers/azure/queries/policy.queries.js +9 -0
  161. package/dist/providers/azure/queries/policy.queries.js.map +1 -0
  162. package/dist/providers/azure/queries/user.queries.d.ts +7 -0
  163. package/dist/providers/azure/queries/user.queries.d.ts.map +1 -0
  164. package/dist/providers/azure/queries/user.queries.js +10 -0
  165. package/dist/providers/azure/queries/user.queries.js.map +1 -0
  166. package/dist/providers/interfaces/IGraphProvider.d.ts +31 -0
  167. package/dist/providers/interfaces/IGraphProvider.d.ts.map +1 -0
  168. package/dist/providers/interfaces/IGraphProvider.js +3 -0
  169. package/dist/providers/interfaces/IGraphProvider.js.map +1 -0
  170. package/dist/providers/interfaces/ILDAPProvider.d.ts +37 -0
  171. package/dist/providers/interfaces/ILDAPProvider.d.ts.map +1 -0
  172. package/dist/providers/interfaces/ILDAPProvider.js +3 -0
  173. package/dist/providers/interfaces/ILDAPProvider.js.map +1 -0
  174. package/dist/providers/ldap/acl-parser.d.ts +8 -0
  175. package/dist/providers/ldap/acl-parser.d.ts.map +1 -0
  176. package/dist/providers/ldap/acl-parser.js +157 -0
  177. package/dist/providers/ldap/acl-parser.js.map +1 -0
  178. package/dist/providers/ldap/ad-mappers.d.ts +8 -0
  179. package/dist/providers/ldap/ad-mappers.d.ts.map +1 -0
  180. package/dist/providers/ldap/ad-mappers.js +162 -0
  181. package/dist/providers/ldap/ad-mappers.js.map +1 -0
  182. package/dist/providers/ldap/ldap-client.d.ts +33 -0
  183. package/dist/providers/ldap/ldap-client.d.ts.map +1 -0
  184. package/dist/providers/ldap/ldap-client.js +195 -0
  185. package/dist/providers/ldap/ldap-client.js.map +1 -0
  186. package/dist/providers/ldap/ldap-errors.d.ts +48 -0
  187. package/dist/providers/ldap/ldap-errors.d.ts.map +1 -0
  188. package/dist/providers/ldap/ldap-errors.js +120 -0
  189. package/dist/providers/ldap/ldap-errors.js.map +1 -0
  190. package/dist/providers/ldap/ldap-retry.d.ts +14 -0
  191. package/dist/providers/ldap/ldap-retry.d.ts.map +1 -0
  192. package/dist/providers/ldap/ldap-retry.js +102 -0
  193. package/dist/providers/ldap/ldap-retry.js.map +1 -0
  194. package/dist/providers/ldap/ldap-sanitizer.d.ts +12 -0
  195. package/dist/providers/ldap/ldap-sanitizer.d.ts.map +1 -0
  196. package/dist/providers/ldap/ldap-sanitizer.js +104 -0
  197. package/dist/providers/ldap/ldap-sanitizer.js.map +1 -0
  198. package/dist/providers/ldap/ldap.provider.d.ts +21 -0
  199. package/dist/providers/ldap/ldap.provider.d.ts.map +1 -0
  200. package/dist/providers/ldap/ldap.provider.js +165 -0
  201. package/dist/providers/ldap/ldap.provider.js.map +1 -0
  202. package/dist/providers/ldap/queries/computer.queries.d.ts +6 -0
  203. package/dist/providers/ldap/queries/computer.queries.d.ts.map +1 -0
  204. package/dist/providers/ldap/queries/computer.queries.js +9 -0
  205. package/dist/providers/ldap/queries/computer.queries.js.map +1 -0
  206. package/dist/providers/ldap/queries/group.queries.d.ts +6 -0
  207. package/dist/providers/ldap/queries/group.queries.d.ts.map +1 -0
  208. package/dist/providers/ldap/queries/group.queries.js +9 -0
  209. package/dist/providers/ldap/queries/group.queries.js.map +1 -0
  210. package/dist/providers/ldap/queries/user.queries.d.ts +7 -0
  211. package/dist/providers/ldap/queries/user.queries.d.ts.map +1 -0
  212. package/dist/providers/ldap/queries/user.queries.js +10 -0
  213. package/dist/providers/ldap/queries/user.queries.js.map +1 -0
  214. package/dist/providers/smb/smb.provider.d.ts +68 -0
  215. package/dist/providers/smb/smb.provider.d.ts.map +1 -0
  216. package/dist/providers/smb/smb.provider.js +382 -0
  217. package/dist/providers/smb/smb.provider.js.map +1 -0
  218. package/dist/server.d.ts +2 -0
  219. package/dist/server.d.ts.map +1 -0
  220. package/dist/server.js +44 -0
  221. package/dist/server.js.map +1 -0
  222. package/dist/services/audit/ad-audit.service.d.ts +70 -0
  223. package/dist/services/audit/ad-audit.service.d.ts.map +1 -0
  224. package/dist/services/audit/ad-audit.service.js +1019 -0
  225. package/dist/services/audit/ad-audit.service.js.map +1 -0
  226. package/dist/services/audit/attack-graph.service.d.ts +62 -0
  227. package/dist/services/audit/attack-graph.service.d.ts.map +1 -0
  228. package/dist/services/audit/attack-graph.service.js +702 -0
  229. package/dist/services/audit/attack-graph.service.js.map +1 -0
  230. package/dist/services/audit/audit.service.d.ts +4 -0
  231. package/dist/services/audit/audit.service.d.ts.map +1 -0
  232. package/dist/services/audit/audit.service.js +10 -0
  233. package/dist/services/audit/audit.service.js.map +1 -0
  234. package/dist/services/audit/azure-audit.service.d.ts +37 -0
  235. package/dist/services/audit/azure-audit.service.d.ts.map +1 -0
  236. package/dist/services/audit/azure-audit.service.js +153 -0
  237. package/dist/services/audit/azure-audit.service.js.map +1 -0
  238. package/dist/services/audit/detectors/ad/accounts.detector.d.ts +37 -0
  239. package/dist/services/audit/detectors/ad/accounts.detector.d.ts.map +1 -0
  240. package/dist/services/audit/detectors/ad/accounts.detector.js +881 -0
  241. package/dist/services/audit/detectors/ad/accounts.detector.js.map +1 -0
  242. package/dist/services/audit/detectors/ad/adcs.detector.d.ts +21 -0
  243. package/dist/services/audit/detectors/ad/adcs.detector.d.ts.map +1 -0
  244. package/dist/services/audit/detectors/ad/adcs.detector.js +227 -0
  245. package/dist/services/audit/detectors/ad/adcs.detector.js.map +1 -0
  246. package/dist/services/audit/detectors/ad/advanced.detector.d.ts +63 -0
  247. package/dist/services/audit/detectors/ad/advanced.detector.d.ts.map +1 -0
  248. package/dist/services/audit/detectors/ad/advanced.detector.js +867 -0
  249. package/dist/services/audit/detectors/ad/advanced.detector.js.map +1 -0
  250. package/dist/services/audit/detectors/ad/attack-paths.detector.d.ts +16 -0
  251. package/dist/services/audit/detectors/ad/attack-paths.detector.d.ts.map +1 -0
  252. package/dist/services/audit/detectors/ad/attack-paths.detector.js +369 -0
  253. package/dist/services/audit/detectors/ad/attack-paths.detector.js.map +1 -0
  254. package/dist/services/audit/detectors/ad/compliance.detector.d.ts +28 -0
  255. package/dist/services/audit/detectors/ad/compliance.detector.d.ts.map +1 -0
  256. package/dist/services/audit/detectors/ad/compliance.detector.js +896 -0
  257. package/dist/services/audit/detectors/ad/compliance.detector.js.map +1 -0
  258. package/dist/services/audit/detectors/ad/computers.detector.d.ts +30 -0
  259. package/dist/services/audit/detectors/ad/computers.detector.d.ts.map +1 -0
  260. package/dist/services/audit/detectors/ad/computers.detector.js +799 -0
  261. package/dist/services/audit/detectors/ad/computers.detector.js.map +1 -0
  262. package/dist/services/audit/detectors/ad/gpo.detector.d.ts +17 -0
  263. package/dist/services/audit/detectors/ad/gpo.detector.d.ts.map +1 -0
  264. package/dist/services/audit/detectors/ad/gpo.detector.js +257 -0
  265. package/dist/services/audit/detectors/ad/gpo.detector.js.map +1 -0
  266. package/dist/services/audit/detectors/ad/groups.detector.d.ts +19 -0
  267. package/dist/services/audit/detectors/ad/groups.detector.d.ts.map +1 -0
  268. package/dist/services/audit/detectors/ad/groups.detector.js +488 -0
  269. package/dist/services/audit/detectors/ad/groups.detector.js.map +1 -0
  270. package/dist/services/audit/detectors/ad/index.d.ts +15 -0
  271. package/dist/services/audit/detectors/ad/index.d.ts.map +1 -0
  272. package/dist/services/audit/detectors/ad/index.js +51 -0
  273. package/dist/services/audit/detectors/ad/index.js.map +1 -0
  274. package/dist/services/audit/detectors/ad/kerberos.detector.d.ts +17 -0
  275. package/dist/services/audit/detectors/ad/kerberos.detector.d.ts.map +1 -0
  276. package/dist/services/audit/detectors/ad/kerberos.detector.js +293 -0
  277. package/dist/services/audit/detectors/ad/kerberos.detector.js.map +1 -0
  278. package/dist/services/audit/detectors/ad/monitoring.detector.d.ts +23 -0
  279. package/dist/services/audit/detectors/ad/monitoring.detector.d.ts.map +1 -0
  280. package/dist/services/audit/detectors/ad/monitoring.detector.js +328 -0
  281. package/dist/services/audit/detectors/ad/monitoring.detector.js.map +1 -0
  282. package/dist/services/audit/detectors/ad/network.detector.d.ts +39 -0
  283. package/dist/services/audit/detectors/ad/network.detector.d.ts.map +1 -0
  284. package/dist/services/audit/detectors/ad/network.detector.js +257 -0
  285. package/dist/services/audit/detectors/ad/network.detector.js.map +1 -0
  286. package/dist/services/audit/detectors/ad/password.detector.d.ts +14 -0
  287. package/dist/services/audit/detectors/ad/password.detector.d.ts.map +1 -0
  288. package/dist/services/audit/detectors/ad/password.detector.js +235 -0
  289. package/dist/services/audit/detectors/ad/password.detector.js.map +1 -0
  290. package/dist/services/audit/detectors/ad/permissions.detector.d.ts +20 -0
  291. package/dist/services/audit/detectors/ad/permissions.detector.d.ts.map +1 -0
  292. package/dist/services/audit/detectors/ad/permissions.detector.js +392 -0
  293. package/dist/services/audit/detectors/ad/permissions.detector.js.map +1 -0
  294. package/dist/services/audit/detectors/ad/trusts.detector.d.ts +11 -0
  295. package/dist/services/audit/detectors/ad/trusts.detector.d.ts.map +1 -0
  296. package/dist/services/audit/detectors/ad/trusts.detector.js +186 -0
  297. package/dist/services/audit/detectors/ad/trusts.detector.js.map +1 -0
  298. package/dist/services/audit/detectors/azure/app-security.detector.d.ts +11 -0
  299. package/dist/services/audit/detectors/azure/app-security.detector.d.ts.map +1 -0
  300. package/dist/services/audit/detectors/azure/app-security.detector.js +184 -0
  301. package/dist/services/audit/detectors/azure/app-security.detector.js.map +1 -0
  302. package/dist/services/audit/detectors/azure/conditional-access.detector.d.ts +10 -0
  303. package/dist/services/audit/detectors/azure/conditional-access.detector.d.ts.map +1 -0
  304. package/dist/services/audit/detectors/azure/conditional-access.detector.js +130 -0
  305. package/dist/services/audit/detectors/azure/conditional-access.detector.js.map +1 -0
  306. package/dist/services/audit/detectors/azure/privilege-security.detector.d.ts +8 -0
  307. package/dist/services/audit/detectors/azure/privilege-security.detector.d.ts.map +1 -0
  308. package/dist/services/audit/detectors/azure/privilege-security.detector.js +113 -0
  309. package/dist/services/audit/detectors/azure/privilege-security.detector.js.map +1 -0
  310. package/dist/services/audit/detectors/azure/user-security.detector.d.ts +14 -0
  311. package/dist/services/audit/detectors/azure/user-security.detector.d.ts.map +1 -0
  312. package/dist/services/audit/detectors/azure/user-security.detector.js +198 -0
  313. package/dist/services/audit/detectors/azure/user-security.detector.js.map +1 -0
  314. package/dist/services/audit/detectors/index.d.ts +2 -0
  315. package/dist/services/audit/detectors/index.d.ts.map +1 -0
  316. package/dist/services/audit/detectors/index.js +38 -0
  317. package/dist/services/audit/detectors/index.js.map +1 -0
  318. package/dist/services/audit/response-formatter.d.ts +176 -0
  319. package/dist/services/audit/response-formatter.d.ts.map +1 -0
  320. package/dist/services/audit/response-formatter.js +240 -0
  321. package/dist/services/audit/response-formatter.js.map +1 -0
  322. package/dist/services/audit/scoring.service.d.ts +15 -0
  323. package/dist/services/audit/scoring.service.d.ts.map +1 -0
  324. package/dist/services/audit/scoring.service.js +139 -0
  325. package/dist/services/audit/scoring.service.js.map +1 -0
  326. package/dist/services/auth/crypto.service.d.ts +19 -0
  327. package/dist/services/auth/crypto.service.d.ts.map +1 -0
  328. package/dist/services/auth/crypto.service.js +135 -0
  329. package/dist/services/auth/crypto.service.js.map +1 -0
  330. package/dist/services/auth/errors.d.ts +19 -0
  331. package/dist/services/auth/errors.d.ts.map +1 -0
  332. package/dist/services/auth/errors.js +46 -0
  333. package/dist/services/auth/errors.js.map +1 -0
  334. package/dist/services/auth/token.service.d.ts +41 -0
  335. package/dist/services/auth/token.service.d.ts.map +1 -0
  336. package/dist/services/auth/token.service.js +208 -0
  337. package/dist/services/auth/token.service.js.map +1 -0
  338. package/dist/services/config/config.service.d.ts +6 -0
  339. package/dist/services/config/config.service.d.ts.map +1 -0
  340. package/dist/services/config/config.service.js +64 -0
  341. package/dist/services/config/config.service.js.map +1 -0
  342. package/dist/services/export/export.service.d.ts +28 -0
  343. package/dist/services/export/export.service.d.ts.map +1 -0
  344. package/dist/services/export/export.service.js +28 -0
  345. package/dist/services/export/export.service.js.map +1 -0
  346. package/dist/services/export/formatters/csv.formatter.d.ts +8 -0
  347. package/dist/services/export/formatters/csv.formatter.d.ts.map +1 -0
  348. package/dist/services/export/formatters/csv.formatter.js +46 -0
  349. package/dist/services/export/formatters/csv.formatter.js.map +1 -0
  350. package/dist/services/export/formatters/json.formatter.d.ts +40 -0
  351. package/dist/services/export/formatters/json.formatter.d.ts.map +1 -0
  352. package/dist/services/export/formatters/json.formatter.js +58 -0
  353. package/dist/services/export/formatters/json.formatter.js.map +1 -0
  354. package/dist/services/jobs/azure-job-runner.d.ts +38 -0
  355. package/dist/services/jobs/azure-job-runner.d.ts.map +1 -0
  356. package/dist/services/jobs/azure-job-runner.js +199 -0
  357. package/dist/services/jobs/azure-job-runner.js.map +1 -0
  358. package/dist/services/jobs/index.d.ts +4 -0
  359. package/dist/services/jobs/index.d.ts.map +1 -0
  360. package/dist/services/jobs/index.js +20 -0
  361. package/dist/services/jobs/index.js.map +1 -0
  362. package/dist/services/jobs/job-runner.d.ts +64 -0
  363. package/dist/services/jobs/job-runner.d.ts.map +1 -0
  364. package/dist/services/jobs/job-runner.js +952 -0
  365. package/dist/services/jobs/job-runner.js.map +1 -0
  366. package/dist/services/jobs/job-store.d.ts +27 -0
  367. package/dist/services/jobs/job-store.d.ts.map +1 -0
  368. package/dist/services/jobs/job-store.js +261 -0
  369. package/dist/services/jobs/job-store.js.map +1 -0
  370. package/dist/services/jobs/job.types.d.ts +67 -0
  371. package/dist/services/jobs/job.types.d.ts.map +1 -0
  372. package/dist/services/jobs/job.types.js +36 -0
  373. package/dist/services/jobs/job.types.js.map +1 -0
  374. package/dist/types/ad.types.d.ts +74 -0
  375. package/dist/types/ad.types.d.ts.map +1 -0
  376. package/dist/types/ad.types.js +3 -0
  377. package/dist/types/ad.types.js.map +1 -0
  378. package/dist/types/adcs.types.d.ts +58 -0
  379. package/dist/types/adcs.types.d.ts.map +1 -0
  380. package/dist/types/adcs.types.js +38 -0
  381. package/dist/types/adcs.types.js.map +1 -0
  382. package/dist/types/attack-graph.types.d.ts +135 -0
  383. package/dist/types/attack-graph.types.d.ts.map +1 -0
  384. package/dist/types/attack-graph.types.js +58 -0
  385. package/dist/types/attack-graph.types.js.map +1 -0
  386. package/dist/types/audit.types.d.ts +34 -0
  387. package/dist/types/audit.types.d.ts.map +1 -0
  388. package/dist/types/audit.types.js +3 -0
  389. package/dist/types/audit.types.js.map +1 -0
  390. package/dist/types/azure.types.d.ts +61 -0
  391. package/dist/types/azure.types.d.ts.map +1 -0
  392. package/dist/types/azure.types.js +3 -0
  393. package/dist/types/azure.types.js.map +1 -0
  394. package/dist/types/config.types.d.ts +63 -0
  395. package/dist/types/config.types.d.ts.map +1 -0
  396. package/dist/types/config.types.js +3 -0
  397. package/dist/types/config.types.js.map +1 -0
  398. package/dist/types/error.types.d.ts +33 -0
  399. package/dist/types/error.types.d.ts.map +1 -0
  400. package/dist/types/error.types.js +70 -0
  401. package/dist/types/error.types.js.map +1 -0
  402. package/dist/types/finding.types.d.ts +133 -0
  403. package/dist/types/finding.types.d.ts.map +1 -0
  404. package/dist/types/finding.types.js +3 -0
  405. package/dist/types/finding.types.js.map +1 -0
  406. package/dist/types/gpo.types.d.ts +39 -0
  407. package/dist/types/gpo.types.d.ts.map +1 -0
  408. package/dist/types/gpo.types.js +15 -0
  409. package/dist/types/gpo.types.js.map +1 -0
  410. package/dist/types/token.types.d.ts +26 -0
  411. package/dist/types/token.types.d.ts.map +1 -0
  412. package/dist/types/token.types.js +3 -0
  413. package/dist/types/token.types.js.map +1 -0
  414. package/dist/types/trust.types.d.ts +45 -0
  415. package/dist/types/trust.types.d.ts.map +1 -0
  416. package/dist/types/trust.types.js +71 -0
  417. package/dist/types/trust.types.js.map +1 -0
  418. package/dist/utils/entity-converter.d.ts +17 -0
  419. package/dist/utils/entity-converter.d.ts.map +1 -0
  420. package/dist/utils/entity-converter.js +285 -0
  421. package/dist/utils/entity-converter.js.map +1 -0
  422. package/dist/utils/graph.util.d.ts +66 -0
  423. package/dist/utils/graph.util.d.ts.map +1 -0
  424. package/dist/utils/graph.util.js +382 -0
  425. package/dist/utils/graph.util.js.map +1 -0
  426. package/dist/utils/logger.d.ts +7 -0
  427. package/dist/utils/logger.d.ts.map +1 -0
  428. package/dist/utils/logger.js +86 -0
  429. package/dist/utils/logger.js.map +1 -0
  430. package/dist/utils/type-name-normalizer.d.ts +5 -0
  431. package/dist/utils/type-name-normalizer.d.ts.map +1 -0
  432. package/dist/utils/type-name-normalizer.js +218 -0
  433. package/dist/utils/type-name-normalizer.js.map +1 -0
  434. package/docker-compose.yml +26 -0
  435. package/docs/api/README.md +178 -0
  436. package/docs/api/openapi.yaml +1524 -0
  437. package/eslint.config.js +54 -0
  438. package/jest.config.js +38 -0
  439. package/package.json +97 -0
  440. package/scripts/fetch-ad-cert.sh +142 -0
  441. package/src/.gitkeep +0 -0
  442. package/src/api/.gitkeep +0 -0
  443. package/src/api/controllers/.gitkeep +0 -0
  444. package/src/api/controllers/audit.controller.ts +313 -0
  445. package/src/api/controllers/auth.controller.ts +258 -0
  446. package/src/api/controllers/export.controller.ts +153 -0
  447. package/src/api/controllers/health.controller.ts +16 -0
  448. package/src/api/controllers/jobs.controller.ts +187 -0
  449. package/src/api/controllers/providers.controller.ts +165 -0
  450. package/src/api/dto/.gitkeep +0 -0
  451. package/src/api/dto/AuditRequest.dto.ts +8 -0
  452. package/src/api/dto/AuditResponse.dto.ts +19 -0
  453. package/src/api/dto/TokenRequest.dto.ts +8 -0
  454. package/src/api/dto/TokenResponse.dto.ts +14 -0
  455. package/src/api/middlewares/.gitkeep +0 -0
  456. package/src/api/middlewares/authenticate.ts +203 -0
  457. package/src/api/middlewares/errorHandler.ts +54 -0
  458. package/src/api/middlewares/rateLimit.ts +35 -0
  459. package/src/api/middlewares/validate.ts +32 -0
  460. package/src/api/routes/.gitkeep +0 -0
  461. package/src/api/routes/audit.routes.ts +77 -0
  462. package/src/api/routes/auth.routes.ts +71 -0
  463. package/src/api/routes/export.routes.ts +34 -0
  464. package/src/api/routes/health.routes.ts +14 -0
  465. package/src/api/routes/index.ts +40 -0
  466. package/src/api/routes/providers.routes.ts +24 -0
  467. package/src/api/validators/.gitkeep +0 -0
  468. package/src/api/validators/audit.schemas.ts +59 -0
  469. package/src/api/validators/auth.schemas.ts +59 -0
  470. package/src/app.ts +87 -0
  471. package/src/config/.gitkeep +0 -0
  472. package/src/config/config.schema.ts +108 -0
  473. package/src/config/index.ts +82 -0
  474. package/src/container.ts +221 -0
  475. package/src/data/.gitkeep +0 -0
  476. package/src/data/database.ts +78 -0
  477. package/src/data/jobs/token-cleanup.job.ts +166 -0
  478. package/src/data/migrations/.gitkeep +0 -0
  479. package/src/data/migrations/001_initial_schema.sql +47 -0
  480. package/src/data/migrations/migration.runner.ts +125 -0
  481. package/src/data/models/.gitkeep +0 -0
  482. package/src/data/models/Token.model.ts +35 -0
  483. package/src/data/repositories/.gitkeep +0 -0
  484. package/src/data/repositories/token.repository.ts +160 -0
  485. package/src/providers/.gitkeep +0 -0
  486. package/src/providers/azure/.gitkeep +0 -0
  487. package/src/providers/azure/auth.provider.ts +14 -0
  488. package/src/providers/azure/azure-errors.ts +189 -0
  489. package/src/providers/azure/azure-retry.ts +168 -0
  490. package/src/providers/azure/graph-client.ts +315 -0
  491. package/src/providers/azure/graph.provider.ts +294 -0
  492. package/src/providers/azure/queries/app.queries.ts +9 -0
  493. package/src/providers/azure/queries/policy.queries.ts +9 -0
  494. package/src/providers/azure/queries/user.queries.ts +10 -0
  495. package/src/providers/interfaces/.gitkeep +0 -0
  496. package/src/providers/interfaces/IGraphProvider.ts +117 -0
  497. package/src/providers/interfaces/ILDAPProvider.ts +142 -0
  498. package/src/providers/ldap/.gitkeep +0 -0
  499. package/src/providers/ldap/acl-parser.ts +231 -0
  500. package/src/providers/ldap/ad-mappers.ts +280 -0
  501. package/src/providers/ldap/ldap-client.ts +259 -0
  502. package/src/providers/ldap/ldap-errors.ts +188 -0
  503. package/src/providers/ldap/ldap-retry.ts +267 -0
  504. package/src/providers/ldap/ldap-sanitizer.ts +273 -0
  505. package/src/providers/ldap/ldap.provider.ts +293 -0
  506. package/src/providers/ldap/queries/computer.queries.ts +9 -0
  507. package/src/providers/ldap/queries/group.queries.ts +9 -0
  508. package/src/providers/ldap/queries/user.queries.ts +10 -0
  509. package/src/providers/smb/smb.provider.ts +653 -0
  510. package/src/server.ts +60 -0
  511. package/src/services/.gitkeep +0 -0
  512. package/src/services/audit/.gitkeep +0 -0
  513. package/src/services/audit/ad-audit.service.ts +1481 -0
  514. package/src/services/audit/attack-graph.service.ts +1104 -0
  515. package/src/services/audit/audit.service.ts +12 -0
  516. package/src/services/audit/azure-audit.service.ts +286 -0
  517. package/src/services/audit/detectors/ad/accounts.detector.ts +1232 -0
  518. package/src/services/audit/detectors/ad/adcs.detector.ts +449 -0
  519. package/src/services/audit/detectors/ad/advanced.detector.ts +1270 -0
  520. package/src/services/audit/detectors/ad/attack-paths.detector.ts +600 -0
  521. package/src/services/audit/detectors/ad/compliance.detector.ts +1421 -0
  522. package/src/services/audit/detectors/ad/computers.detector.ts +1188 -0
  523. package/src/services/audit/detectors/ad/gpo.detector.ts +485 -0
  524. package/src/services/audit/detectors/ad/groups.detector.ts +685 -0
  525. package/src/services/audit/detectors/ad/index.ts +84 -0
  526. package/src/services/audit/detectors/ad/kerberos.detector.ts +424 -0
  527. package/src/services/audit/detectors/ad/monitoring.detector.ts +501 -0
  528. package/src/services/audit/detectors/ad/network.detector.ts +538 -0
  529. package/src/services/audit/detectors/ad/password.detector.ts +324 -0
  530. package/src/services/audit/detectors/ad/permissions.detector.ts +637 -0
  531. package/src/services/audit/detectors/ad/trusts.detector.ts +315 -0
  532. package/src/services/audit/detectors/azure/app-security.detector.ts +246 -0
  533. package/src/services/audit/detectors/azure/conditional-access.detector.ts +186 -0
  534. package/src/services/audit/detectors/azure/privilege-security.detector.ts +176 -0
  535. package/src/services/audit/detectors/azure/user-security.detector.ts +280 -0
  536. package/src/services/audit/detectors/index.ts +18 -0
  537. package/src/services/audit/response-formatter.ts +604 -0
  538. package/src/services/audit/scoring.service.ts +234 -0
  539. package/src/services/auth/.gitkeep +0 -0
  540. package/src/services/auth/crypto.service.ts +230 -0
  541. package/src/services/auth/errors.ts +47 -0
  542. package/src/services/auth/token.service.ts +420 -0
  543. package/src/services/config/.gitkeep +0 -0
  544. package/src/services/config/config.service.ts +75 -0
  545. package/src/services/export/.gitkeep +0 -0
  546. package/src/services/export/export.service.ts +99 -0
  547. package/src/services/export/formatters/csv.formatter.ts +124 -0
  548. package/src/services/export/formatters/json.formatter.ts +160 -0
  549. package/src/services/jobs/azure-job-runner.ts +312 -0
  550. package/src/services/jobs/index.ts +9 -0
  551. package/src/services/jobs/job-runner.ts +1280 -0
  552. package/src/services/jobs/job-store.ts +384 -0
  553. package/src/services/jobs/job.types.ts +182 -0
  554. package/src/types/.gitkeep +0 -0
  555. package/src/types/ad.types.ts +91 -0
  556. package/src/types/adcs.types.ts +107 -0
  557. package/src/types/attack-graph.types.ts +260 -0
  558. package/src/types/audit.types.ts +42 -0
  559. package/src/types/azure.types.ts +68 -0
  560. package/src/types/config.types.ts +79 -0
  561. package/src/types/error.types.ts +69 -0
  562. package/src/types/finding.types.ts +284 -0
  563. package/src/types/gpo.types.ts +72 -0
  564. package/src/types/smb2.d.ts +73 -0
  565. package/src/types/token.types.ts +32 -0
  566. package/src/types/trust.types.ts +140 -0
  567. package/src/utils/.gitkeep +0 -0
  568. package/src/utils/entity-converter.ts +453 -0
  569. package/src/utils/graph.util.ts +609 -0
  570. package/src/utils/logger.ts +111 -0
  571. package/src/utils/type-name-normalizer.ts +302 -0
  572. package/tests/.gitkeep +0 -0
  573. package/tests/e2e/.gitkeep +0 -0
  574. package/tests/fixtures/.gitkeep +0 -0
  575. package/tests/integration/.gitkeep +0 -0
  576. package/tests/integration/README.md +156 -0
  577. package/tests/integration/ad-audit.integration.test.ts +216 -0
  578. package/tests/integration/api/.gitkeep +0 -0
  579. package/tests/integration/api/endpoints.integration.test.ts +431 -0
  580. package/tests/integration/auth/jwt-authentication.integration.test.ts +358 -0
  581. package/tests/integration/providers/.gitkeep +0 -0
  582. package/tests/integration/providers/azure-basic.integration.test.ts +167 -0
  583. package/tests/integration/providers/ldap-basic.integration.test.ts +152 -0
  584. package/tests/integration/providers/ldap-connectivity.test.ts +44 -0
  585. package/tests/integration/providers/ldap-provider.integration.test.ts +347 -0
  586. package/tests/mocks/.gitkeep +0 -0
  587. package/tests/setup.ts +16 -0
  588. package/tests/unit/.gitkeep +0 -0
  589. package/tests/unit/api/middlewares/authenticate.test.ts +446 -0
  590. package/tests/unit/providers/.gitkeep +0 -0
  591. package/tests/unit/providers/azure/azure-errors.test.ts +193 -0
  592. package/tests/unit/providers/azure/azure-retry.test.ts +254 -0
  593. package/tests/unit/providers/azure/graph-provider.test.ts +313 -0
  594. package/tests/unit/providers/ldap/ad-mappers.test.ts +392 -0
  595. package/tests/unit/providers/ldap/ldap-provider.test.ts +376 -0
  596. package/tests/unit/providers/ldap/ldap-retry.test.ts +377 -0
  597. package/tests/unit/providers/ldap/ldap-sanitizer.test.ts +301 -0
  598. package/tests/unit/sample.test.ts +19 -0
  599. package/tests/unit/services/.gitkeep +0 -0
  600. package/tests/unit/services/audit/detectors/ad/accounts.detector.test.ts +393 -0
  601. package/tests/unit/services/audit/detectors/ad/advanced.detector.test.ts +380 -0
  602. package/tests/unit/services/audit/detectors/ad/computers.detector.test.ts +440 -0
  603. package/tests/unit/services/audit/detectors/ad/groups.detector.test.ts +276 -0
  604. package/tests/unit/services/audit/detectors/ad/kerberos.detector.test.ts +215 -0
  605. package/tests/unit/services/audit/detectors/ad/password.detector.test.ts +226 -0
  606. package/tests/unit/services/audit/detectors/ad/permissions.detector.test.ts +244 -0
  607. package/tests/unit/services/audit/detectors/azure/app-security.detector.test.ts +349 -0
  608. package/tests/unit/services/audit/detectors/azure/conditional-access.detector.test.ts +374 -0
  609. package/tests/unit/services/audit/detectors/azure/privilege-security.detector.test.ts +374 -0
  610. package/tests/unit/services/audit/detectors/azure/user-security.detector.test.ts +297 -0
  611. package/tests/unit/services/auth/crypto.service.test.ts +296 -0
  612. package/tests/unit/services/auth/token.service.test.ts +579 -0
  613. package/tests/unit/services/export/export.service.test.ts +241 -0
  614. package/tests/unit/services/export/formatters/csv.formatter.test.ts +270 -0
  615. package/tests/unit/services/export/formatters/json.formatter.test.ts +258 -0
  616. package/tests/unit/utils/.gitkeep +0 -0
  617. package/tsconfig.json +50 -0
@@ -0,0 +1,1481 @@
1
+ /**
2
+ * Active Directory Audit Service
3
+ *
4
+ * Orchestrates AD vulnerability detection across 85 checks in 7 categories
5
+ * Story 1.7: AD Vulnerability Detection Engine
6
+ *
7
+ * Architecture:
8
+ * 1. Collect AD data (users, groups, computers, domain, templates, CAs, ACLs)
9
+ * 2. Run all detector categories in parallel
10
+ * 3. Aggregate findings
11
+ * 4. Calculate security score
12
+ * 5. Return audit results
13
+ */
14
+
15
+ import { LDAPProvider } from '../../providers/ldap/ldap.provider';
16
+ import { ADUser, ADGroup, ADComputer, ADDomain, AclEntry } from '../../types/ad.types';
17
+ import { LDAPControl } from '../../providers/interfaces/ILDAPProvider';
18
+ import { parseSecurityDescriptor, resetParseStats } from '../../providers/ldap/acl-parser';
19
+ import { Finding } from '../../types/finding.types';
20
+ import { calculateSecurityScore, SecurityScore } from './scoring.service';
21
+ import { DomainConfig } from './response-formatter';
22
+ import { logger } from '../../utils/logger';
23
+ import { SMBProvider, formatKerberosPolicy, getDefaultKerberosPolicy, SMBConfig, FormattedKerberosPolicy, GpoSecuritySettings } from '../../providers/smb/smb.provider';
24
+ import { Client as LDAPClient } from 'ldapts';
25
+ import { SMBConfig as AppSMBConfig, LDAPConfig } from '../../types/config.types';
26
+
27
+ // Import all AD detectors
28
+ import {
29
+ detectPasswordVulnerabilities,
30
+ detectKerberosVulnerabilities,
31
+ detectAccountsVulnerabilities,
32
+ detectGroupsVulnerabilities,
33
+ detectComputersVulnerabilities,
34
+ detectAdvancedVulnerabilities,
35
+ detectPermissionsVulnerabilities,
36
+ detectAdcsVulnerabilities,
37
+ detectGpoVulnerabilities,
38
+ detectTrustVulnerabilities,
39
+ detectAttackPathVulnerabilities,
40
+ detectMonitoringVulnerabilities,
41
+ } from './detectors/ad';
42
+
43
+ // Import new types
44
+ import { ADCSCertificateTemplate, ADCSCertificateAuthority } from '../../types/adcs.types';
45
+ import { ADGPO, GPOLink } from '../../types/gpo.types';
46
+ import { ADTrustExtended, parseTrustAttributes, parseTrustDirection, parseTrustType } from '../../types/trust.types';
47
+ import { computeAttackGraph } from './attack-graph.service';
48
+ import { AttackGraphExport } from '../../types/attack-graph.types';
49
+
50
+ /**
51
+ * Create LDAP_SERVER_SD_FLAGS_OID control for reading Security Descriptors
52
+ *
53
+ * This control is required to read ntSecurityDescriptor attribute.
54
+ * Flags specify which parts of the SD to return:
55
+ * - OWNER_SECURITY_INFORMATION (0x00000001)
56
+ * - GROUP_SECURITY_INFORMATION (0x00000002)
57
+ * - DACL_SECURITY_INFORMATION (0x00000004)
58
+ * - SACL_SECURITY_INFORMATION (0x00000008)
59
+ */
60
+ function createSDFlagsControl(): LDAPControl {
61
+ const LDAP_SERVER_SD_FLAGS_OID = '1.2.840.113556.1.4.801';
62
+
63
+ // Request OWNER + GROUP + DACL (0x07)
64
+ // We don't request SACL (0x08) as it requires special permissions
65
+ const SD_FLAGS = 0x00000007;
66
+
67
+ // Encode as BER INTEGER: 02 01 07
68
+ // Tag: 0x02 (INTEGER), Length: 0x01 (1 byte), Value: 0x07
69
+ const buffer = Buffer.from([0x02, 0x01, SD_FLAGS]);
70
+
71
+ return {
72
+ oid: LDAP_SERVER_SD_FLAGS_OID,
73
+ critical: false,
74
+ value: buffer,
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Audit options
80
+ */
81
+ export interface AuditOptions {
82
+ /**
83
+ * Include detailed entity DNs in findings
84
+ */
85
+ includeDetails?: boolean;
86
+
87
+ /**
88
+ * Maximum number of users to fetch (for testing)
89
+ */
90
+ maxUsers?: number;
91
+
92
+ /**
93
+ * Maximum number of groups to fetch (for testing)
94
+ */
95
+ maxGroups?: number;
96
+
97
+ /**
98
+ * Maximum number of computers to fetch (for testing)
99
+ */
100
+ maxComputers?: number;
101
+ }
102
+
103
+ /**
104
+ * Audit result
105
+ */
106
+ export interface AuditResult {
107
+ /**
108
+ * Overall security score
109
+ */
110
+ score: SecurityScore;
111
+
112
+ /**
113
+ * All vulnerability findings
114
+ */
115
+ findings: Finding[];
116
+
117
+ /**
118
+ * Statistics
119
+ */
120
+ stats: {
121
+ totalUsers: number;
122
+ enabledUsers: number;
123
+ disabledUsers: number;
124
+ totalGroups: number;
125
+ totalComputers: number;
126
+ enabledComputers: number;
127
+ disabledComputers: number;
128
+ totalOUs: number;
129
+ totalFindings: number;
130
+ executionTimeMs: number;
131
+ ldapUrl?: string; // For debugging which DC we connected to
132
+ };
133
+
134
+ /**
135
+ * Timestamp
136
+ */
137
+ timestamp: Date;
138
+
139
+ /**
140
+ * Domain configuration (password policy, domain info, trusts, GPOs)
141
+ */
142
+ domainConfig?: DomainConfig;
143
+
144
+ /**
145
+ * Attack graph data for visualization
146
+ */
147
+ attackGraph?: AttackGraphExport;
148
+ }
149
+
150
+ /**
151
+ * Convert Windows FILETIME to JavaScript Date
152
+ * FILETIME is a 64-bit value representing 100-nanosecond intervals since January 1, 1601 UTC
153
+ *
154
+ * @param filetime Windows FILETIME value (string or number)
155
+ * @returns JavaScript Date object or undefined
156
+ */
157
+ function convertFiletimeToDate(filetime: any): Date | undefined {
158
+ if (!filetime) return undefined;
159
+
160
+ // Parse the filetime value
161
+ let filetimeNum: number;
162
+ if (typeof filetime === 'string') {
163
+ filetimeNum = parseInt(filetime, 10);
164
+ } else if (typeof filetime === 'number') {
165
+ filetimeNum = filetime;
166
+ } else {
167
+ return undefined;
168
+ }
169
+
170
+ // Check for invalid values
171
+ // 0 or very large values (like 0x7FFFFFFFFFFFFFFF) indicate "never" or invalid
172
+ if (filetimeNum === 0 || filetimeNum >= 9223372036854775807) {
173
+ return undefined;
174
+ }
175
+
176
+ // Convert from 100-nanosecond intervals to milliseconds
177
+ const milliseconds = filetimeNum / 10000;
178
+
179
+ // Offset between Windows epoch (1601) and Unix epoch (1970) in milliseconds
180
+ const epochOffset = 11644473600000;
181
+
182
+ // Calculate Unix timestamp and create Date
183
+ const unixTimestamp = milliseconds - epochOffset;
184
+
185
+ // Validate the timestamp is reasonable (not negative, not too far in future)
186
+ if (unixTimestamp < 0 || unixTimestamp > Date.now() + 100 * 365 * 24 * 60 * 60 * 1000) {
187
+ return undefined;
188
+ }
189
+
190
+ return new Date(unixTimestamp);
191
+ }
192
+
193
+ /**
194
+ * Active Directory Audit Service
195
+ */
196
+ export class ADAuditService {
197
+ private smbConfig?: { smb: AppSMBConfig; ldap: LDAPConfig };
198
+
199
+ constructor(private ldapProvider: LDAPProvider, smbConfig?: { smb: AppSMBConfig; ldap: LDAPConfig }) {
200
+ this.smbConfig = smbConfig;
201
+ }
202
+
203
+ /**
204
+ * Run full AD security audit
205
+ *
206
+ * @param options Audit options
207
+ * @returns Audit results with findings and security score
208
+ */
209
+ async runAudit(options: AuditOptions = {}): Promise<AuditResult> {
210
+ const startTime = Date.now();
211
+ const { includeDetails = false, maxUsers, maxGroups, maxComputers } = options;
212
+ const baseDN = this.ldapProvider.getBaseDN();
213
+
214
+ // 1. Collect AD data
215
+ const [users, groups, computers, domain, ouCount] = await Promise.all([
216
+ this.fetchUsers(maxUsers),
217
+ this.fetchGroups(maxGroups),
218
+ this.fetchComputers(maxComputers),
219
+ this.fetchDomain(),
220
+ this.fetchOUCount(),
221
+ ]);
222
+
223
+ // 2. Collect additional data for advanced detectors
224
+ // Fetch ADCS, GPO, and Trust data in parallel
225
+ const [certTemplates, certAuthorities, gpoData, trustsExtended, aclEntries, anonymousAccessAllowed, gpoSecuritySettings] = await Promise.all([
226
+ this.fetchCertificateTemplates(),
227
+ this.fetchCertificateAuthorities(),
228
+ this.fetchGPOsWithAcls(),
229
+ this.fetchTrustsExtended(),
230
+ this.fetchAcls(users, groups, computers),
231
+ this.testAnonymousLdapAccess(),
232
+ this.fetchGpoSecuritySettings(),
233
+ ]);
234
+
235
+ // Legacy placeholders for advanced detector (kept for backward compatibility)
236
+ const templates: any[] = certTemplates; // ADCS certificate templates
237
+ const cas: any[] = certAuthorities; // ADCS Certificate Authorities
238
+ const fsps: any[] = []; // Foreign Security Principals
239
+
240
+ // 3. Run all detector categories
241
+ const findings: Finding[] = [];
242
+
243
+ // Run detectors in parallel for better performance
244
+ const detectorResults = await Promise.all([
245
+ Promise.resolve(detectPasswordVulnerabilities(users, includeDetails)),
246
+ Promise.resolve(detectKerberosVulnerabilities(users, includeDetails)),
247
+ Promise.resolve(detectAccountsVulnerabilities(users, includeDetails)),
248
+ Promise.resolve(detectGroupsVulnerabilities(users, groups, includeDetails)),
249
+ Promise.resolve(detectComputersVulnerabilities(computers, includeDetails)),
250
+ Promise.resolve(
251
+ detectAdvancedVulnerabilities(users, computers, domain, templates, cas, fsps, includeDetails, {
252
+ gpoSettings: gpoSecuritySettings,
253
+ anonymousAccessAllowed,
254
+ })
255
+ ),
256
+ Promise.resolve(detectPermissionsVulnerabilities(aclEntries, includeDetails, computers.map((c) => c.dn))),
257
+ // New detectors: ADCS, GPO, Trusts
258
+ Promise.resolve(detectAdcsVulnerabilities(certTemplates, certAuthorities, includeDetails)),
259
+ Promise.resolve(
260
+ detectGpoVulnerabilities(
261
+ gpoData.gpos,
262
+ gpoData.links,
263
+ domain ? { minPasswordLength: domain['minPwdLength'] as number | undefined } : null,
264
+ includeDetails,
265
+ gpoData.gpoAcls
266
+ )
267
+ ),
268
+ Promise.resolve(detectTrustVulnerabilities(trustsExtended, includeDetails)),
269
+ // Attack Paths detector (Phase 2A)
270
+ Promise.resolve(
271
+ detectAttackPathVulnerabilities(
272
+ users,
273
+ groups,
274
+ computers,
275
+ aclEntries,
276
+ gpoData.gpos,
277
+ trustsExtended,
278
+ certTemplates,
279
+ includeDetails
280
+ )
281
+ ),
282
+ // Monitoring detector (Phase 2B)
283
+ Promise.resolve(
284
+ detectMonitoringVulnerabilities(users, groups, domain, includeDetails, {
285
+ gpoSettings: gpoSecuritySettings,
286
+ })
287
+ ),
288
+ ]);
289
+
290
+ // Flatten all findings
291
+ detectorResults.forEach((categoryFindings) => {
292
+ findings.push(...categoryFindings);
293
+ });
294
+
295
+ // 4. Calculate security score
296
+ const score = calculateSecurityScore(findings, users.length);
297
+
298
+ // 5. Fetch domain configuration
299
+ const domainConfig = await this.fetchDomainConfig(domain);
300
+
301
+ // 6. Compute attack graph
302
+ logger.info('Computing attack graph...');
303
+ const attackGraphStartTime = Date.now();
304
+ const attackGraph = computeAttackGraph(
305
+ users,
306
+ groups,
307
+ computers,
308
+ aclEntries,
309
+ certTemplates,
310
+ gpoData.gpos,
311
+ {
312
+ name: domainConfig?.domainInfo?.domainName || this.extractDomainNameFromDN(baseDN),
313
+ sid: undefined, // Will be auto-detected
314
+ },
315
+ 500 // maxPaths
316
+ );
317
+ logger.info(`Attack graph computed in ${Date.now() - attackGraphStartTime}ms: ${attackGraph.paths.length} paths found`);
318
+
319
+ // 7. Build result
320
+ const executionTimeMs = Date.now() - startTime;
321
+
322
+ // Calculate enabled/disabled user counts (UAC flag 0x2 = ACCOUNTDISABLE)
323
+ const disabledUsers = users.filter((u) => ((u.userAccountControl ?? 0) & 0x2) !== 0).length;
324
+ const enabledUsers = users.length - disabledUsers;
325
+
326
+ // Calculate enabled/disabled computer counts
327
+ const disabledComputers = computers.filter((c) => !c.enabled).length;
328
+ const enabledComputers = computers.length - disabledComputers;
329
+
330
+ return {
331
+ score,
332
+ findings,
333
+ stats: {
334
+ totalUsers: users.length,
335
+ enabledUsers,
336
+ disabledUsers,
337
+ totalGroups: groups.length,
338
+ totalComputers: computers.length,
339
+ enabledComputers,
340
+ disabledComputers,
341
+ totalOUs: ouCount,
342
+ totalFindings: findings.length,
343
+ executionTimeMs,
344
+ ldapUrl: this.ldapProvider.getUrl(),
345
+ },
346
+ timestamp: new Date(),
347
+ domainConfig,
348
+ attackGraph,
349
+ };
350
+ }
351
+
352
+ /**
353
+ * Test LDAP connection
354
+ *
355
+ * @returns Connection test result
356
+ */
357
+ async testConnection(): Promise<{ success: boolean; message: string }> {
358
+ try {
359
+ return await this.ldapProvider.testConnection();
360
+ } catch (error) {
361
+ return {
362
+ success: false,
363
+ message: error instanceof Error ? error.message : 'Unknown connection error',
364
+ };
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Test if anonymous LDAP access is allowed
370
+ * Attempts an anonymous bind to the LDAP server
371
+ *
372
+ * @returns true if anonymous access is allowed
373
+ */
374
+ private async testAnonymousLdapAccess(): Promise<boolean> {
375
+ if (!this.smbConfig?.ldap) {
376
+ return false;
377
+ }
378
+
379
+ const { ldap } = this.smbConfig;
380
+ // Extract host and port from LDAP URL
381
+ const urlMatch = ldap.url.match(/ldaps?:\/\/([^:/]+)(?::(\d+))?/i);
382
+ if (!urlMatch || !urlMatch[1]) {
383
+ return false;
384
+ }
385
+
386
+ const host = urlMatch[1];
387
+ const isLdaps = ldap.url.toLowerCase().startsWith('ldaps://');
388
+ const defaultPort = isLdaps ? 636 : 389;
389
+ const port = urlMatch[2] ? parseInt(urlMatch[2], 10) : defaultPort;
390
+
391
+ try {
392
+ // Create a separate client for anonymous bind test
393
+ const anonClient = new LDAPClient({
394
+ url: `ldap://${host}:${port}`,
395
+ timeout: 5000,
396
+ connectTimeout: 5000,
397
+ });
398
+
399
+ // Attempt anonymous bind (empty DN and password)
400
+ await anonClient.bind('', '');
401
+
402
+ // Try to search for something - if successful, anonymous access is truly allowed
403
+ try {
404
+ await anonClient.search(ldap.baseDN, {
405
+ filter: '(objectClass=domain)',
406
+ attributes: ['dn'],
407
+ scope: 'base',
408
+ sizeLimit: 1,
409
+ });
410
+ } catch {
411
+ // Search failed but bind succeeded - limited anonymous access
412
+ }
413
+
414
+ await anonClient.unbind();
415
+ return true;
416
+ } catch {
417
+ // Anonymous bind failed - anonymous access not allowed
418
+ return false;
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Fetch users from AD
424
+ */
425
+ private async fetchUsers(maxUsers?: number): Promise<ADUser[]> {
426
+ const baseDN = this.ldapProvider.getBaseDN();
427
+ const filter = '(&(objectClass=user)(objectCategory=person))';
428
+ const attributes = [
429
+ // Identity
430
+ 'dn',
431
+ 'sAMAccountName',
432
+ 'userPrincipalName',
433
+ 'displayName',
434
+ 'mail',
435
+ // Organization
436
+ 'title',
437
+ 'department',
438
+ 'company',
439
+ 'manager',
440
+ 'physicalDeliveryOfficeName',
441
+ 'description',
442
+ 'employeeID',
443
+ 'telephoneNumber',
444
+ // Dates
445
+ 'whenCreated',
446
+ 'whenChanged',
447
+ 'lastLogon',
448
+ 'pwdLastSet',
449
+ 'passwordLastSet',
450
+ 'accountExpires',
451
+ // Security
452
+ 'badPwdCount',
453
+ 'lockoutTime',
454
+ 'adminCount',
455
+ 'memberOf',
456
+ 'userAccountControl',
457
+ // Technical (for detection)
458
+ 'servicePrincipalName',
459
+ 'msDS-SupportedEncryptionTypes',
460
+ 'sIDHistory',
461
+ 'msDS-KeyCredentialLink',
462
+ 'msDS-AllowedToActOnBehalfOfOtherIdentity',
463
+ 'msDS-AllowedToDelegateTo',
464
+ // Unix password attributes (cleartext risk)
465
+ 'unixUserPassword',
466
+ 'userPassword',
467
+ ];
468
+
469
+ const results = await this.ldapProvider.search<any>(baseDN, {
470
+ filter,
471
+ attributes,
472
+ scope: 'sub',
473
+ sizeLimit: maxUsers,
474
+ paged: true, // Enable pagination to fetch all users beyond 1000 limit
475
+ });
476
+
477
+ return results.map((entry: any) => ({
478
+ ...entry, // Spread all properties first
479
+ dn: entry.dn,
480
+ sAMAccountName: entry.sAMAccountName as string,
481
+ userPrincipalName: entry.userPrincipalName as string,
482
+ displayName: entry.displayName as string,
483
+ enabled: !((entry.userAccountControl as number) & 0x2), // ACCOUNTDISABLE flag
484
+ passwordLastSet: convertFiletimeToDate(entry.passwordLastSet), // Override with converted dates
485
+ lastLogon: convertFiletimeToDate(entry.lastLogon), // Override with converted dates
486
+ accountExpires: convertFiletimeToDate(entry.accountExpires), // Override with converted dates
487
+ adminCount: entry.adminCount as number,
488
+ // Ensure array fields are always arrays (LDAP returns single value as string)
489
+ memberOf: !entry.memberOf ? [] : Array.isArray(entry.memberOf) ? entry.memberOf : [entry.memberOf],
490
+ servicePrincipalName: !entry.servicePrincipalName
491
+ ? []
492
+ : Array.isArray(entry.servicePrincipalName)
493
+ ? entry.servicePrincipalName
494
+ : [entry.servicePrincipalName],
495
+ userAccountControl: entry.userAccountControl as number,
496
+ }));
497
+ }
498
+
499
+ /**
500
+ * Fetch groups from AD
501
+ */
502
+ private async fetchGroups(maxGroups?: number): Promise<ADGroup[]> {
503
+ const baseDN = this.ldapProvider.getBaseDN();
504
+ const filter = '(objectClass=group)';
505
+ const attributes = ['dn', 'sAMAccountName', 'displayName', 'groupType', 'memberOf', 'member'];
506
+
507
+ const results = await this.ldapProvider.search<any>(baseDN, {
508
+ filter,
509
+ attributes,
510
+ scope: 'sub',
511
+ sizeLimit: maxGroups,
512
+ paged: true, // Enable pagination to fetch all groups beyond 1000 limit
513
+ });
514
+
515
+ return results.map((entry: any) => ({
516
+ ...entry, // Spread all properties first
517
+ dn: entry.dn,
518
+ sAMAccountName: entry.sAMAccountName as string,
519
+ displayName: entry.displayName as string,
520
+ groupType: entry.groupType as number,
521
+ // Ensure array fields are always arrays (LDAP returns single value as string)
522
+ memberOf: !entry.memberOf ? [] : Array.isArray(entry.memberOf) ? entry.memberOf : [entry.memberOf],
523
+ member: !entry.member ? [] : Array.isArray(entry.member) ? entry.member : [entry.member],
524
+ }));
525
+ }
526
+
527
+ /**
528
+ * Fetch computers from AD
529
+ */
530
+ private async fetchComputers(maxComputers?: number): Promise<ADComputer[]> {
531
+ const baseDN = this.ldapProvider.getBaseDN();
532
+ const filter = '(objectClass=computer)';
533
+ const attributes = [
534
+ 'dn',
535
+ 'sAMAccountName',
536
+ 'dNSHostName',
537
+ 'operatingSystem',
538
+ 'operatingSystemVersion',
539
+ 'lastLogon',
540
+ 'userAccountControl',
541
+ 'pwdLastSet',
542
+ 'servicePrincipalName',
543
+ 'ms-Mcs-AdmPwd',
544
+ 'ms-Mcs-AdmPwdExpirationTime', // LAPS expiration (readable even without password rights)
545
+ 'msLAPS-Password',
546
+ 'msLAPS-PasswordExpirationTime', // Windows LAPS expiration
547
+ 'lastLogonTimestamp', // Replicated version of lastLogon
548
+ 'msDS-AllowedToDelegateTo',
549
+ 'msDS-AllowedToActOnBehalfOfOtherIdentity',
550
+ 'msDS-SupportedEncryptionTypes',
551
+ 'memberOf',
552
+ 'description',
553
+ 'whenCreated', // For debugging date issues
554
+ 'whenChanged',
555
+ 'adminCount',
556
+ ];
557
+
558
+ const results = await this.ldapProvider.search<any>(baseDN, {
559
+ filter,
560
+ attributes,
561
+ scope: 'sub',
562
+ sizeLimit: maxComputers,
563
+ paged: true, // Enable pagination to fetch all computers beyond 1000 limit
564
+ });
565
+
566
+ return results.map((entry: any) => ({
567
+ ...entry, // Spread all properties first
568
+ dn: entry.dn,
569
+ sAMAccountName: entry.sAMAccountName as string,
570
+ dNSHostName: entry.dNSHostName as string,
571
+ operatingSystem: entry.operatingSystem as string,
572
+ operatingSystemVersion: entry.operatingSystemVersion as string,
573
+ lastLogon: convertFiletimeToDate(entry.lastLogon), // Convert date
574
+ pwdLastSet: convertFiletimeToDate(entry.pwdLastSet), // Convert date
575
+ enabled: !((entry.userAccountControl as number) & 0x2), // ACCOUNTDISABLE flag
576
+ // Ensure array fields are always arrays (LDAP returns single value as string)
577
+ memberOf: !entry.memberOf ? [] : Array.isArray(entry.memberOf) ? entry.memberOf : [entry.memberOf],
578
+ servicePrincipalName: !entry.servicePrincipalName
579
+ ? []
580
+ : Array.isArray(entry.servicePrincipalName)
581
+ ? entry.servicePrincipalName
582
+ : [entry.servicePrincipalName],
583
+ }));
584
+ }
585
+
586
+ /**
587
+ * Fetch domain configuration
588
+ */
589
+ private async fetchDomain(): Promise<ADDomain | null> {
590
+ try {
591
+ const baseDN = this.ldapProvider.getBaseDN();
592
+ const filter = '(objectClass=domain)';
593
+ const attributes = [
594
+ 'dn',
595
+ 'name',
596
+ 'msDS-Behavior-Version', // Domain functional level
597
+ 'minPwdLength',
598
+ 'maxPwdAge',
599
+ 'pwdHistoryLength',
600
+ 'ms-DS-MachineAccountQuota',
601
+ ];
602
+
603
+ const results = await this.ldapProvider.search<any>(baseDN, {
604
+ filter,
605
+ attributes,
606
+ scope: 'base',
607
+ });
608
+
609
+ if (results.length === 0) return null;
610
+
611
+ const entry: any = results[0];
612
+ const domainFunctionalLevel = entry['msDS-Behavior-Version'] !== undefined
613
+ ? parseInt(entry['msDS-Behavior-Version'], 10)
614
+ : undefined;
615
+
616
+ // Fetch forest functional level from Configuration partition
617
+ let forestFunctionalLevel: number | undefined;
618
+ try {
619
+ const configDN = `CN=Partitions,CN=Configuration,${baseDN}`;
620
+ const forestResults = await this.ldapProvider.search<any>(configDN, {
621
+ filter: '(objectClass=crossRefContainer)',
622
+ attributes: ['msDS-Behavior-Version'],
623
+ scope: 'base',
624
+ });
625
+ if (forestResults.length > 0 && forestResults[0]['msDS-Behavior-Version'] !== undefined) {
626
+ forestFunctionalLevel = parseInt(forestResults[0]['msDS-Behavior-Version'], 10);
627
+ }
628
+ } catch (e) {
629
+ // Forest level fetch failed, continue without it
630
+ }
631
+
632
+ // Check if AD Recycle Bin is enabled
633
+ let recycleBinEnabled = false;
634
+ try {
635
+ const optionalFeaturesDN = `CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,${baseDN}`;
636
+ const recycleBinResults = await this.ldapProvider.search<any>(optionalFeaturesDN, {
637
+ filter: '(cn=Recycle Bin Feature)',
638
+ attributes: ['msDS-EnabledFeatureBL'],
639
+ scope: 'sub',
640
+ });
641
+ if (recycleBinResults.length > 0) {
642
+ const enabledFeatureBL = recycleBinResults[0]['msDS-EnabledFeatureBL'];
643
+ // If msDS-EnabledFeatureBL has entries, the feature is enabled for those partitions
644
+ recycleBinEnabled = enabledFeatureBL && (Array.isArray(enabledFeatureBL) ? enabledFeatureBL.length > 0 : true);
645
+ }
646
+ } catch (e) {
647
+ // Recycle Bin check failed, assume not enabled
648
+ }
649
+
650
+ return {
651
+ dn: entry.dn,
652
+ name: entry.name as string,
653
+ domainFunctionalLevel,
654
+ forestFunctionalLevel,
655
+ recycleBinEnabled,
656
+ ...entry,
657
+ };
658
+ } catch (error) {
659
+ console.error('Failed to fetch domain:', error);
660
+ return null;
661
+ }
662
+ }
663
+
664
+ /**
665
+ * Fetch GPO security settings from SYSVOL via SMB
666
+ * Returns LDAP signing, channel binding, SMBv1, audit policy, PS logging settings
667
+ */
668
+ private async fetchGpoSecuritySettings(): Promise<GpoSecuritySettings | null> {
669
+ // Check if SMB is enabled and we have config
670
+ if (!this.smbConfig || !this.smbConfig.smb.enabled) {
671
+ logger.debug('SMB is disabled, skipping GPO security settings fetch');
672
+ return null;
673
+ }
674
+
675
+ const { smb, ldap } = this.smbConfig;
676
+ const baseDN = this.ldapProvider.getBaseDN();
677
+ const domainDnsName = this.extractDomainNameFromDN(baseDN);
678
+
679
+ // Get DC hostname - we need a DC to read SYSVOL
680
+ let dcHostname: string | null = null;
681
+ try {
682
+ const dcResults = await this.ldapProvider.search<any>(baseDN, {
683
+ filter: '(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))',
684
+ attributes: ['dNSHostName'],
685
+ scope: 'sub',
686
+ sizeLimit: 1,
687
+ });
688
+ if (dcResults.length > 0 && dcResults[0].dNSHostName) {
689
+ dcHostname = dcResults[0].dNSHostName;
690
+ }
691
+ } catch (e) {
692
+ logger.debug('Failed to find DC for GPO settings fetch', { error: e });
693
+ }
694
+
695
+ if (!dcHostname) {
696
+ // Try extracting from LDAP URL
697
+ const urlMatch = ldap.url.match(/ldaps?:\/\/([^:/]+)/i);
698
+ if (urlMatch && urlMatch[1]) {
699
+ dcHostname = urlMatch[1];
700
+ }
701
+ }
702
+
703
+ if (!dcHostname) {
704
+ logger.debug('No DC hostname available for GPO settings fetch');
705
+ return null;
706
+ }
707
+
708
+ // Use SMB credentials or fall back to LDAP credentials
709
+ const username = smb.username || ldap.bindDN.split(',')[0]?.replace(/^CN=/i, '') || '';
710
+ const password = smb.password || ldap.bindPassword;
711
+
712
+ const baseDNMatch = ldap.baseDN.match(/DC=([^,]+)/i);
713
+ const smbProviderConfig: SMBConfig = {
714
+ host: dcHostname,
715
+ share: 'SYSVOL',
716
+ domain: baseDNMatch?.[1] || '',
717
+ username,
718
+ password,
719
+ timeout: smb.timeout,
720
+ };
721
+
722
+ const smbProvider = new SMBProvider(smbProviderConfig);
723
+
724
+ try {
725
+ await smbProvider.connect();
726
+ const settings = await smbProvider.readGpoSecuritySettings(domainDnsName);
727
+
728
+ if (settings) {
729
+ logger.debug('Successfully fetched GPO security settings from SYSVOL', {
730
+ domainDnsName,
731
+ hasLdapSigning: settings.ldapServerIntegrity !== undefined,
732
+ hasAuditPolicy: settings.auditPolicies !== undefined,
733
+ hasPsLogging: settings.powershellLogging !== undefined,
734
+ });
735
+ }
736
+
737
+ return settings;
738
+ } catch (error) {
739
+ logger.warn('Failed to fetch GPO security settings via SMB', { error, dcHostname });
740
+ return null;
741
+ } finally {
742
+ await smbProvider.disconnect();
743
+ }
744
+ }
745
+
746
+ /**
747
+ * Fetch OU count from AD
748
+ */
749
+ private async fetchOUCount(): Promise<number> {
750
+ try {
751
+ const baseDN = this.ldapProvider.getBaseDN();
752
+ const filter = '(objectClass=organizationalUnit)';
753
+
754
+ const results = await this.ldapProvider.search<any>(baseDN, {
755
+ filter,
756
+ attributes: ['dn'],
757
+ scope: 'sub',
758
+ paged: true,
759
+ });
760
+
761
+ return results.length;
762
+ } catch (error) {
763
+ logger.warn('Failed to fetch OU count', { error });
764
+ return 0;
765
+ }
766
+ }
767
+
768
+ /**
769
+ * Fetch ACLs from sensitive AD objects
770
+ *
771
+ * Retrieves ntSecurityDescriptor from:
772
+ * - All users, groups, computers (for delegation/privilege analysis)
773
+ * - Critical system objects (AdminSDHolder, GPOs, Domain root)
774
+ */
775
+ private async fetchAcls(
776
+ users: ADUser[],
777
+ groups: ADGroup[],
778
+ computers: ADComputer[]
779
+ ): Promise<AclEntry[]> {
780
+ const allAclEntries: AclEntry[] = [];
781
+ const baseDN = this.ldapProvider.getBaseDN();
782
+
783
+ try {
784
+ resetParseStats(); // Reset stats at start
785
+
786
+ // 1. Fetch ACLs for all users
787
+ const userDns = users.map((u) => u.dn);
788
+ const userAcls = await this.fetchAclsForObjects(userDns);
789
+ // Use for loop to avoid stack overflow with large arrays
790
+ for (const entry of userAcls) {
791
+ allAclEntries.push(entry);
792
+ }
793
+
794
+ // 2. Fetch ACLs for all groups
795
+ const groupDns = groups.map((g) => g.dn);
796
+ const groupAcls = await this.fetchAclsForObjects(groupDns);
797
+ // Use for loop to avoid stack overflow with large arrays
798
+ for (const entry of groupAcls) {
799
+ allAclEntries.push(entry);
800
+ }
801
+
802
+ // 3. Fetch ACLs for all computers
803
+ const computerDns = computers.map((c) => c.dn);
804
+ const computerAcls = await this.fetchAclsForObjects(computerDns);
805
+ for (const entry of computerAcls) {
806
+ allAclEntries.push(entry);
807
+ }
808
+
809
+ // 4. Fetch ACLs for critical system objects
810
+ const systemObjects = [
811
+ baseDN, // Domain root
812
+ `CN=AdminSDHolder,CN=System,${baseDN}`,
813
+ `CN=Policies,CN=System,${baseDN}`, // GPO container
814
+ ];
815
+ const systemAcls = await this.fetchAclsForObjects(systemObjects);
816
+ for (const entry of systemAcls) {
817
+ allAclEntries.push(entry);
818
+ }
819
+
820
+ return allAclEntries;
821
+ } catch (error) {
822
+ // Silently fail if permissions insufficient
823
+ logger.warn('Failed to fetch ACL entries - insufficient permissions or unsupported configuration');
824
+ return [];
825
+ }
826
+ }
827
+
828
+ /**
829
+ * Fetch ACLs for a list of object DNs
830
+ */
831
+ private async fetchAclsForObjects(objectDns: string[]): Promise<AclEntry[]> {
832
+ const allAclEntries: AclEntry[] = [];
833
+ let _successCount = 0;
834
+
835
+ // Batch requests to avoid overwhelming LDAP server
836
+ const BATCH_SIZE = 100;
837
+ for (let i = 0; i < objectDns.length; i += BATCH_SIZE) {
838
+ const batch = objectDns.slice(i, i + BATCH_SIZE);
839
+
840
+ // Query each object for its ntSecurityDescriptor
841
+ const batchPromises = batch.map(async (dn) => {
842
+ try {
843
+ const results = await this.ldapProvider.search<any>(dn, {
844
+ filter: '(objectClass=*)',
845
+ attributes: ['nTSecurityDescriptor'], // Note: Capital T - that's the official AD schema name
846
+ scope: 'base',
847
+ controls: [createSDFlagsControl()], // Required to read security descriptors
848
+ });
849
+
850
+ if (results.length > 0 && results[0].nTSecurityDescriptor) {
851
+ _successCount++;
852
+ const secDescriptor = results[0].nTSecurityDescriptor;
853
+
854
+ // Convert to Buffer if needed (ldapts may return as Buffer or string)
855
+ const buffer = Buffer.isBuffer(secDescriptor)
856
+ ? secDescriptor
857
+ : Buffer.from(secDescriptor, 'binary');
858
+ const entries = parseSecurityDescriptor(buffer, dn);
859
+ return entries;
860
+ }
861
+ } catch (error) {
862
+ // Silently skip objects we can't read
863
+ return [];
864
+ }
865
+ return [];
866
+ });
867
+
868
+ const batchResults = await Promise.all(batchPromises);
869
+ batchResults.forEach((entries) => allAclEntries.push(...entries));
870
+ }
871
+
872
+ return allAclEntries;
873
+ }
874
+
875
+ /**
876
+ * Fetch ADCS Certificate Templates from Configuration partition
877
+ */
878
+ private async fetchCertificateTemplates(): Promise<ADCSCertificateTemplate[]> {
879
+ try {
880
+ const baseDN = this.ldapProvider.getBaseDN();
881
+ const templatesDN = `CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,${baseDN}`;
882
+
883
+ const results = await this.ldapProvider.search<any>(templatesDN, {
884
+ filter: '(objectClass=pKICertificateTemplate)',
885
+ attributes: [
886
+ 'dn',
887
+ 'cn',
888
+ 'name',
889
+ 'displayName',
890
+ 'msPKI-Certificate-Name-Flag',
891
+ 'msPKI-Enrollment-Flag',
892
+ 'pKIExtendedKeyUsage',
893
+ 'nTSecurityDescriptor',
894
+ ],
895
+ scope: 'one',
896
+ controls: [createSDFlagsControl()],
897
+ });
898
+
899
+ return results.map((entry: any) => ({
900
+ dn: entry.dn,
901
+ cn: entry.cn || entry.name,
902
+ name: entry.name || entry.cn,
903
+ displayName: entry.displayName,
904
+ 'msPKI-Certificate-Name-Flag': entry['msPKI-Certificate-Name-Flag']
905
+ ? parseInt(entry['msPKI-Certificate-Name-Flag'], 10)
906
+ : 0,
907
+ 'msPKI-Enrollment-Flag': entry['msPKI-Enrollment-Flag']
908
+ ? parseInt(entry['msPKI-Enrollment-Flag'], 10)
909
+ : 0,
910
+ pKIExtendedKeyUsage: !entry.pKIExtendedKeyUsage
911
+ ? []
912
+ : Array.isArray(entry.pKIExtendedKeyUsage)
913
+ ? entry.pKIExtendedKeyUsage
914
+ : [entry.pKIExtendedKeyUsage],
915
+ nTSecurityDescriptor: entry.nTSecurityDescriptor,
916
+ }));
917
+ } catch (error) {
918
+ logger.debug('Could not fetch certificate templates (ADCS may not be configured)', { error });
919
+ return [];
920
+ }
921
+ }
922
+
923
+ /**
924
+ * Fetch ADCS Certificate Authorities from Configuration partition
925
+ */
926
+ private async fetchCertificateAuthorities(): Promise<ADCSCertificateAuthority[]> {
927
+ try {
928
+ const baseDN = this.ldapProvider.getBaseDN();
929
+ const enrollmentDN = `CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,${baseDN}`;
930
+
931
+ const results = await this.ldapProvider.search<any>(enrollmentDN, {
932
+ filter: '(objectClass=pKIEnrollmentService)',
933
+ attributes: [
934
+ 'dn',
935
+ 'cn',
936
+ 'name',
937
+ 'dNSHostName',
938
+ 'certificateTemplates',
939
+ 'nTSecurityDescriptor',
940
+ ],
941
+ scope: 'one',
942
+ controls: [createSDFlagsControl()],
943
+ });
944
+
945
+ return results.map((entry: any) => ({
946
+ dn: entry.dn,
947
+ cn: entry.cn || entry.name,
948
+ name: entry.name || entry.cn,
949
+ dNSHostName: entry.dNSHostName,
950
+ certificateTemplates: !entry.certificateTemplates
951
+ ? []
952
+ : Array.isArray(entry.certificateTemplates)
953
+ ? entry.certificateTemplates
954
+ : [entry.certificateTemplates],
955
+ nTSecurityDescriptor: entry.nTSecurityDescriptor,
956
+ }));
957
+ } catch (error) {
958
+ logger.debug('Could not fetch certificate authorities (ADCS may not be configured)', { error });
959
+ return [];
960
+ }
961
+ }
962
+
963
+ /**
964
+ * Fetch GPOs with ACLs and their links
965
+ */
966
+ private async fetchGPOsWithAcls(): Promise<{ gpos: ADGPO[]; links: GPOLink[]; gpoAcls: import('../../types/gpo.types').GPOAclEntry[] }> {
967
+ const baseDN = this.ldapProvider.getBaseDN();
968
+ const gpos: ADGPO[] = [];
969
+ const links: GPOLink[] = [];
970
+ const gpoAcls: import('../../types/gpo.types').GPOAclEntry[] = [];
971
+
972
+ try {
973
+ // 1. Fetch all GPOs
974
+ const gpoResults = await this.ldapProvider.search<any>(`CN=Policies,CN=System,${baseDN}`, {
975
+ filter: '(objectClass=groupPolicyContainer)',
976
+ attributes: [
977
+ 'dn',
978
+ 'cn',
979
+ 'displayName',
980
+ 'gPCFileSysPath',
981
+ 'flags',
982
+ 'gPCMachineExtensionNames',
983
+ 'nTSecurityDescriptor',
984
+ ],
985
+ scope: 'one',
986
+ controls: [createSDFlagsControl()],
987
+ });
988
+
989
+ for (const entry of gpoResults) {
990
+ const flags = entry.flags ? parseInt(entry.flags, 10) : 0;
991
+ gpos.push({
992
+ dn: entry.dn,
993
+ cn: entry.cn,
994
+ displayName: entry.displayName,
995
+ gPCFileSysPath: entry.gPCFileSysPath,
996
+ flags,
997
+ gPCMachineExtensionNames: entry.gPCMachineExtensionNames,
998
+ nTSecurityDescriptor: entry.nTSecurityDescriptor,
999
+ });
1000
+
1001
+ // Parse GPO security descriptor to extract ACLs
1002
+ if (entry.nTSecurityDescriptor) {
1003
+ try {
1004
+ const buffer = Buffer.isBuffer(entry.nTSecurityDescriptor)
1005
+ ? entry.nTSecurityDescriptor
1006
+ : Buffer.from(entry.nTSecurityDescriptor, 'binary');
1007
+ const aclEntries = parseSecurityDescriptor(buffer, entry.dn);
1008
+ for (const acl of aclEntries) {
1009
+ gpoAcls.push({
1010
+ gpoDn: entry.dn,
1011
+ gpoName: entry.displayName || entry.cn,
1012
+ trustee: acl.trustee,
1013
+ trusteeSid: acl.trustee, // trustee is already the SID
1014
+ accessMask: acl.accessMask,
1015
+ aceType: typeof acl.aceType === 'string' ? parseInt(acl.aceType, 10) : acl.aceType,
1016
+ });
1017
+ }
1018
+ } catch (e) {
1019
+ // Skip GPOs we can't parse ACLs for
1020
+ logger.debug('Could not parse GPO ACLs for', { gpo: entry.displayName, error: e });
1021
+ }
1022
+ }
1023
+ }
1024
+
1025
+ // 2. Fetch GPO links from OUs, domain, and sites
1026
+ const linkResults = await this.ldapProvider.search<any>(baseDN, {
1027
+ filter: '(gPLink=*)',
1028
+ attributes: ['dn', 'gPLink'],
1029
+ scope: 'sub',
1030
+ sizeLimit: 1000,
1031
+ });
1032
+
1033
+ for (const entry of linkResults) {
1034
+ const gpLink = entry.gPLink;
1035
+ if (!gpLink) continue;
1036
+
1037
+ // Parse gPLink format: [LDAP://cn={GUID},...;options][...]
1038
+ const linkMatches = gpLink.matchAll(/\[LDAP:\/\/([^\]]+);(\d+)\]/gi);
1039
+ for (const match of linkMatches) {
1040
+ const gpoDn = match[1];
1041
+ const options = parseInt(match[2], 10);
1042
+
1043
+ // Extract GUID from DN
1044
+ const guidMatch = gpoDn.match(/cn=(\{[^}]+\})/i);
1045
+ if (guidMatch) {
1046
+ links.push({
1047
+ gpoGuid: guidMatch[1],
1048
+ linkedTo: entry.dn,
1049
+ enforced: (options & 2) !== 0, // GPO_FLAG_ENFORCED
1050
+ disabled: (options & 1) !== 0, // GPO_FLAG_DISABLED
1051
+ });
1052
+ }
1053
+ }
1054
+ }
1055
+ } catch (error) {
1056
+ logger.debug('Could not fetch GPOs with ACLs', { error });
1057
+ }
1058
+
1059
+ return { gpos, links, gpoAcls };
1060
+ }
1061
+
1062
+ /**
1063
+ * Fetch extended trust information with security attributes
1064
+ */
1065
+ private async fetchTrustsExtended(): Promise<ADTrustExtended[]> {
1066
+ try {
1067
+ const baseDN = this.ldapProvider.getBaseDN();
1068
+
1069
+ const results = await this.ldapProvider.search<any>(`CN=System,${baseDN}`, {
1070
+ filter: '(objectClass=trustedDomain)',
1071
+ attributes: [
1072
+ 'dn',
1073
+ 'name',
1074
+ 'trustDirection',
1075
+ 'trustType',
1076
+ 'trustAttributes',
1077
+ 'flatName',
1078
+ 'securityIdentifier',
1079
+ ],
1080
+ scope: 'one',
1081
+ });
1082
+
1083
+ return results.map((entry: any) => {
1084
+ const trustDirection = parseInt(entry.trustDirection || '0', 10);
1085
+ const trustType = parseInt(entry.trustType || '0', 10);
1086
+ const trustAttributes = parseInt(entry.trustAttributes || '0', 10);
1087
+
1088
+ // Parse and enrich trust data
1089
+ const parsed = parseTrustAttributes(trustAttributes);
1090
+
1091
+ return {
1092
+ dn: entry.dn,
1093
+ name: entry.name,
1094
+ flatName: entry.flatName,
1095
+ trustDirection,
1096
+ trustType,
1097
+ trustAttributes,
1098
+ direction: parseTrustDirection(trustDirection),
1099
+ type: parseTrustType(trustType, trustAttributes),
1100
+ ...parsed, // sidFilteringEnabled, selectiveAuthEnabled, isTransitive
1101
+ };
1102
+ });
1103
+ } catch (error) {
1104
+ logger.debug('Could not fetch extended trust information', { error });
1105
+ return [];
1106
+ }
1107
+ }
1108
+
1109
+ /**
1110
+ * Fetch domain configuration (password policy, domain info, trusts, GPOs)
1111
+ */
1112
+ private async fetchDomainConfig(domain: ADDomain | null): Promise<DomainConfig> {
1113
+ const baseDN = this.ldapProvider.getBaseDN();
1114
+
1115
+ // Default config
1116
+ const config: DomainConfig = {
1117
+ passwordPolicy: {
1118
+ minPasswordLength: 0,
1119
+ passwordHistoryLength: 0,
1120
+ maxPasswordAge: 'Not configured',
1121
+ minPasswordAge: 'Not configured',
1122
+ lockoutThreshold: 0,
1123
+ lockoutDuration: 'Not configured',
1124
+ lockoutObservationWindow: 'Not configured',
1125
+ complexity: false,
1126
+ },
1127
+ kerberosPolicy: getDefaultKerberosPolicy(),
1128
+ domainInfo: {
1129
+ forestName: '',
1130
+ domainName: '',
1131
+ domainMode: 'Unknown',
1132
+ forestMode: 'Unknown',
1133
+ domainControllers: [],
1134
+ fsmoRoles: {},
1135
+ },
1136
+ trusts: [],
1137
+ gpoSummary: {
1138
+ totalGPOs: 0,
1139
+ linkedGPOs: 0,
1140
+ },
1141
+ };
1142
+
1143
+ try {
1144
+ // 1. Fetch password policy from domain
1145
+ const policyResults = await this.ldapProvider.search<any>(baseDN, {
1146
+ filter: '(objectClass=domain)',
1147
+ attributes: [
1148
+ 'minPwdLength',
1149
+ 'pwdHistoryLength',
1150
+ 'maxPwdAge',
1151
+ 'minPwdAge',
1152
+ 'lockoutThreshold',
1153
+ 'lockoutDuration',
1154
+ 'lockOutObservationWindow',
1155
+ 'pwdProperties',
1156
+ 'name',
1157
+ ],
1158
+ scope: 'base',
1159
+ });
1160
+
1161
+ if (policyResults.length > 0) {
1162
+ const policy = policyResults[0];
1163
+ config.passwordPolicy.minPasswordLength = parseInt(policy.minPwdLength || '0', 10);
1164
+ config.passwordPolicy.passwordHistoryLength = parseInt(policy.pwdHistoryLength || '0', 10);
1165
+ config.passwordPolicy.maxPasswordAge = this.formatFiletimeDuration(policy.maxPwdAge);
1166
+ config.passwordPolicy.minPasswordAge = this.formatFiletimeDuration(policy.minPwdAge);
1167
+ config.passwordPolicy.lockoutThreshold = parseInt(policy.lockoutThreshold || '0', 10);
1168
+ config.passwordPolicy.lockoutDuration = this.formatFiletimeDuration(policy.lockoutDuration);
1169
+ config.passwordPolicy.lockoutObservationWindow = this.formatFiletimeDuration(
1170
+ policy.lockOutObservationWindow
1171
+ );
1172
+ // pwdProperties bit 1 = DOMAIN_PASSWORD_COMPLEX
1173
+ config.passwordPolicy.complexity = (parseInt(policy.pwdProperties || '0', 10) & 1) === 1;
1174
+ config.domainInfo.domainName = policy.name || '';
1175
+ }
1176
+
1177
+ // 2. Fetch domain functional levels
1178
+ if (domain) {
1179
+ config.domainInfo.domainMode = this.getDomainModeName(domain.domainFunctionalLevel);
1180
+ config.domainInfo.forestMode = this.getDomainModeName(domain.forestFunctionalLevel);
1181
+ config.domainInfo.forestName = this.extractDomainNameFromDN(baseDN);
1182
+ config.domainInfo.domainName = this.extractDomainNameFromDN(baseDN);
1183
+ }
1184
+
1185
+ // 3. Fetch domain controllers
1186
+ const dcResults = await this.ldapProvider.search<any>(baseDN, {
1187
+ filter: '(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))',
1188
+ attributes: ['dNSHostName', 'name'],
1189
+ scope: 'sub',
1190
+ });
1191
+
1192
+ config.domainInfo.domainControllers = dcResults
1193
+ .map((dc: any) => dc.dNSHostName || dc.name)
1194
+ .filter((name: string) => name);
1195
+
1196
+ // 4. Fetch FSMO role holders
1197
+ await this.fetchFSMORoles(config, baseDN);
1198
+
1199
+ // 5. Fetch trust relationships
1200
+ await this.fetchTrusts(config, baseDN);
1201
+
1202
+ // 6. Fetch GPO count
1203
+ await this.fetchGPOCount(config, baseDN);
1204
+
1205
+ // 7. Fetch Kerberos policy via SMB (or use defaults)
1206
+ const dcHostname = config.domainInfo.domainControllers[0];
1207
+ if (dcHostname) {
1208
+ const domainDnsName = this.extractDomainNameFromDN(baseDN);
1209
+ config.kerberosPolicy = await this.fetchKerberosPolicyViaSMB(domainDnsName, dcHostname);
1210
+ } else {
1211
+ // No DC found, use defaults
1212
+ config.kerberosPolicy = getDefaultKerberosPolicy();
1213
+ }
1214
+ } catch (error) {
1215
+ logger.warn('Failed to fetch some domain configuration', { error });
1216
+ }
1217
+
1218
+ return config;
1219
+ }
1220
+
1221
+ /**
1222
+ * Format Windows FILETIME duration to human-readable string
1223
+ * FILETIME durations are negative 100-nanosecond intervals
1224
+ */
1225
+ private formatFiletimeDuration(filetime: any): string {
1226
+ if (!filetime) return 'Not configured';
1227
+
1228
+ const value = typeof filetime === 'string' ? parseInt(filetime, 10) : filetime;
1229
+ if (value === 0 || isNaN(value)) return 'Not configured';
1230
+
1231
+ // Convert from negative 100-ns intervals to positive minutes
1232
+ const minutes = Math.abs(value) / (10000000 * 60);
1233
+
1234
+ if (minutes < 60) {
1235
+ return `${Math.round(minutes)} min`;
1236
+ } else if (minutes < 1440) {
1237
+ const hours = Math.round(minutes / 60);
1238
+ return `${hours} hour${hours > 1 ? 's' : ''}`;
1239
+ } else {
1240
+ const days = Math.round(minutes / 1440);
1241
+ return `${days} day${days > 1 ? 's' : ''}`;
1242
+ }
1243
+ }
1244
+
1245
+ /**
1246
+ * Get human-readable domain functional level name
1247
+ */
1248
+ private getDomainModeName(level: number | undefined): string {
1249
+ if (level === undefined) return 'Unknown';
1250
+
1251
+ const levels: Record<number, string> = {
1252
+ 0: 'Windows2000Domain',
1253
+ 1: 'Windows2003InterimDomain',
1254
+ 2: 'Windows2003Domain',
1255
+ 3: 'Windows2008Domain',
1256
+ 4: 'Windows2008R2Domain',
1257
+ 5: 'Windows2012Domain',
1258
+ 6: 'Windows2012R2Domain',
1259
+ 7: 'Windows2016Domain',
1260
+ };
1261
+
1262
+ return levels[level] || `Unknown (${level})`;
1263
+ }
1264
+
1265
+ /**
1266
+ * Extract domain name from DN
1267
+ */
1268
+ private extractDomainNameFromDN(dn: string): string {
1269
+ const parts = dn.match(/DC=([^,]+)/gi);
1270
+ if (!parts) return dn;
1271
+ return parts.map((p) => p.replace(/DC=/i, '')).join('.');
1272
+ }
1273
+
1274
+ /**
1275
+ * Fetch FSMO role holders
1276
+ */
1277
+ private async fetchFSMORoles(config: DomainConfig, baseDN: string): Promise<void> {
1278
+ try {
1279
+ // PDC Emulator, RID Master, Infrastructure Master are on domain
1280
+ const domainRoles = await this.ldapProvider.search<any>(baseDN, {
1281
+ filter: '(objectClass=domain)',
1282
+ attributes: ['fSMORoleOwner'],
1283
+ scope: 'base',
1284
+ });
1285
+
1286
+ if (domainRoles.length > 0 && domainRoles[0].fSMORoleOwner) {
1287
+ config.domainInfo.fsmoRoles.pdcEmulator = this.extractServerFromDN(domainRoles[0].fSMORoleOwner);
1288
+ }
1289
+
1290
+ // RID Master
1291
+ const ridResults = await this.ldapProvider.search<any>(`CN=RID Manager$,CN=System,${baseDN}`, {
1292
+ filter: '(objectClass=*)',
1293
+ attributes: ['fSMORoleOwner'],
1294
+ scope: 'base',
1295
+ });
1296
+
1297
+ if (ridResults.length > 0 && ridResults[0].fSMORoleOwner) {
1298
+ config.domainInfo.fsmoRoles.ridMaster = this.extractServerFromDN(ridResults[0].fSMORoleOwner);
1299
+ }
1300
+
1301
+ // Infrastructure Master
1302
+ const infraResults = await this.ldapProvider.search<any>(
1303
+ `CN=Infrastructure,${baseDN}`,
1304
+ {
1305
+ filter: '(objectClass=*)',
1306
+ attributes: ['fSMORoleOwner'],
1307
+ scope: 'base',
1308
+ }
1309
+ );
1310
+
1311
+ if (infraResults.length > 0 && infraResults[0].fSMORoleOwner) {
1312
+ config.domainInfo.fsmoRoles.infrastructureMaster = this.extractServerFromDN(
1313
+ infraResults[0].fSMORoleOwner
1314
+ );
1315
+ }
1316
+ } catch (error) {
1317
+ logger.debug('Could not fetch all FSMO roles', { error });
1318
+ }
1319
+ }
1320
+
1321
+ /**
1322
+ * Extract server name from FSMO role owner DN
1323
+ */
1324
+ private extractServerFromDN(dn: string): string {
1325
+ // DN format: CN=NTDS Settings,CN=DC01,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,...
1326
+ const match = dn.match(/CN=NTDS Settings,CN=([^,]+)/i);
1327
+ return match && match[1] ? match[1] : dn;
1328
+ }
1329
+
1330
+ /**
1331
+ * Fetch trust relationships
1332
+ */
1333
+ private async fetchTrusts(config: DomainConfig, baseDN: string): Promise<void> {
1334
+ try {
1335
+ const trustResults = await this.ldapProvider.search<any>(`CN=System,${baseDN}`, {
1336
+ filter: '(objectClass=trustedDomain)',
1337
+ attributes: ['name', 'trustDirection', 'trustType', 'trustAttributes'],
1338
+ scope: 'one',
1339
+ });
1340
+
1341
+ config.trusts = trustResults.map((trust: any) => {
1342
+ const direction = parseInt(trust.trustDirection || '0', 10);
1343
+ const trustType = parseInt(trust.trustType || '0', 10);
1344
+ const attributes = parseInt(trust.trustAttributes || '0', 10);
1345
+
1346
+ return {
1347
+ name: trust.name || 'Unknown',
1348
+ direction: this.getTrustDirection(direction),
1349
+ type: this.getTrustType(trustType),
1350
+ transitive: (attributes & 1) === 1, // TRUST_ATTRIBUTE_NON_TRANSITIVE = 0x1 (inverted)
1351
+ };
1352
+ });
1353
+ } catch (error) {
1354
+ logger.debug('Could not fetch trust relationships', { error });
1355
+ }
1356
+ }
1357
+
1358
+ /**
1359
+ * Get trust direction name
1360
+ */
1361
+ private getTrustDirection(direction: number): 'inbound' | 'outbound' | 'bidirectional' {
1362
+ switch (direction) {
1363
+ case 1:
1364
+ return 'inbound';
1365
+ case 2:
1366
+ return 'outbound';
1367
+ case 3:
1368
+ return 'bidirectional';
1369
+ default:
1370
+ return 'inbound';
1371
+ }
1372
+ }
1373
+
1374
+ /**
1375
+ * Get trust type name
1376
+ */
1377
+ private getTrustType(type: number): 'forest' | 'external' | 'realm' | 'shortcut' {
1378
+ switch (type) {
1379
+ case 1:
1380
+ return 'external'; // TRUST_TYPE_DOWNLEVEL
1381
+ case 2:
1382
+ return 'external'; // TRUST_TYPE_UPLEVEL (AD domain)
1383
+ case 3:
1384
+ return 'realm'; // TRUST_TYPE_MIT (Kerberos realm)
1385
+ case 4:
1386
+ return 'external'; // TRUST_TYPE_DCE
1387
+ default:
1388
+ return 'external';
1389
+ }
1390
+ }
1391
+
1392
+ /**
1393
+ * Fetch GPO count
1394
+ */
1395
+ private async fetchGPOCount(config: DomainConfig, baseDN: string): Promise<void> {
1396
+ try {
1397
+ const gpoResults = await this.ldapProvider.search<any>(`CN=Policies,CN=System,${baseDN}`, {
1398
+ filter: '(objectClass=groupPolicyContainer)',
1399
+ attributes: ['cn', 'gPCFileSysPath'],
1400
+ scope: 'one',
1401
+ });
1402
+
1403
+ config.gpoSummary.totalGPOs = gpoResults.length;
1404
+
1405
+ // Count linked GPOs by searching for gPLink attributes
1406
+ const linkedResults = await this.ldapProvider.search<any>(baseDN, {
1407
+ filter: '(gPLink=*)',
1408
+ attributes: ['gPLink'],
1409
+ scope: 'sub',
1410
+ sizeLimit: 1000,
1411
+ });
1412
+
1413
+ // Count unique GPO links
1414
+ const linkedGPOs = new Set<string>();
1415
+ linkedResults.forEach((obj: any) => {
1416
+ const gpLink = obj.gPLink;
1417
+ if (gpLink) {
1418
+ // gPLink format: [LDAP://cn={GUID},cn=policies,cn=system,DC=domain,DC=com;0]
1419
+ const matches = gpLink.match(/cn=\{[^}]+\}/gi);
1420
+ if (matches) {
1421
+ matches.forEach((m: string) => linkedGPOs.add(m.toLowerCase()));
1422
+ }
1423
+ }
1424
+ });
1425
+
1426
+ config.gpoSummary.linkedGPOs = linkedGPOs.size;
1427
+ } catch (error) {
1428
+ logger.debug('Could not fetch GPO count', { error });
1429
+ }
1430
+ }
1431
+
1432
+ /**
1433
+ * Fetch Kerberos policy from SYSVOL via SMB
1434
+ * Returns formatted policy with isDefault flag
1435
+ * Returns default Windows values if file not found or SMB disabled
1436
+ */
1437
+ private async fetchKerberosPolicyViaSMB(domainDnsName: string, dcHostname: string): Promise<FormattedKerberosPolicy> {
1438
+ // Check if SMB is enabled and we have config
1439
+ if (!this.smbConfig || !this.smbConfig.smb.enabled) {
1440
+ logger.debug('SMB is disabled, returning default Kerberos policy values');
1441
+ return getDefaultKerberosPolicy();
1442
+ }
1443
+
1444
+ const { smb, ldap } = this.smbConfig!;
1445
+
1446
+ // Use SMB credentials or fall back to LDAP credentials
1447
+ const username = smb.username || ldap.bindDN.split(',')[0]?.replace(/^CN=/i, '') || '';
1448
+ const password = smb.password || ldap.bindPassword;
1449
+
1450
+ const baseDNMatch = ldap.baseDN.match(/DC=([^,]+)/i);
1451
+ const smbProviderConfig: SMBConfig = {
1452
+ host: dcHostname,
1453
+ share: 'SYSVOL',
1454
+ domain: baseDNMatch?.[1] || '',
1455
+ username,
1456
+ password,
1457
+ timeout: smb.timeout,
1458
+ };
1459
+
1460
+ const smbProvider = new SMBProvider(smbProviderConfig);
1461
+
1462
+ try {
1463
+ await smbProvider.connect();
1464
+ const kerberosPolicy = await smbProvider.readKerberosPolicy(domainDnsName);
1465
+
1466
+ if (kerberosPolicy) {
1467
+ logger.debug('Successfully fetched Kerberos policy from SYSVOL', { domainDnsName });
1468
+ return formatKerberosPolicy(kerberosPolicy, false);
1469
+ }
1470
+
1471
+ // File not found - return Windows defaults
1472
+ logger.debug('GptTmpl.inf not found, using Windows default Kerberos policy values');
1473
+ return getDefaultKerberosPolicy();
1474
+ } catch (error) {
1475
+ logger.warn('Failed to fetch Kerberos policy via SMB, using defaults', { error, dcHostname });
1476
+ return getDefaultKerberosPolicy();
1477
+ } finally {
1478
+ await smbProvider.disconnect();
1479
+ }
1480
+ }
1481
+ }