@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.
- package/.env.example +60 -0
- package/.env.test.example +33 -0
- package/.github/workflows/ci.yml +83 -0
- package/.github/workflows/release.yml +246 -0
- package/.prettierrc.json +10 -0
- package/CHANGELOG.md +15 -0
- package/Dockerfile +57 -0
- package/LICENSE +190 -0
- package/README.md +194 -0
- package/dist/api/controllers/audit.controller.d.ts +21 -0
- package/dist/api/controllers/audit.controller.d.ts.map +1 -0
- package/dist/api/controllers/audit.controller.js +179 -0
- package/dist/api/controllers/audit.controller.js.map +1 -0
- package/dist/api/controllers/auth.controller.d.ts +16 -0
- package/dist/api/controllers/auth.controller.d.ts.map +1 -0
- package/dist/api/controllers/auth.controller.js +146 -0
- package/dist/api/controllers/auth.controller.js.map +1 -0
- package/dist/api/controllers/export.controller.d.ts +27 -0
- package/dist/api/controllers/export.controller.d.ts.map +1 -0
- package/dist/api/controllers/export.controller.js +80 -0
- package/dist/api/controllers/export.controller.js.map +1 -0
- package/dist/api/controllers/health.controller.d.ts +5 -0
- package/dist/api/controllers/health.controller.d.ts.map +1 -0
- package/dist/api/controllers/health.controller.js +16 -0
- package/dist/api/controllers/health.controller.js.map +1 -0
- package/dist/api/controllers/jobs.controller.d.ts +13 -0
- package/dist/api/controllers/jobs.controller.d.ts.map +1 -0
- package/dist/api/controllers/jobs.controller.js +125 -0
- package/dist/api/controllers/jobs.controller.js.map +1 -0
- package/dist/api/controllers/providers.controller.d.ts +15 -0
- package/dist/api/controllers/providers.controller.d.ts.map +1 -0
- package/dist/api/controllers/providers.controller.js +112 -0
- package/dist/api/controllers/providers.controller.js.map +1 -0
- package/dist/api/dto/AuditRequest.dto.d.ts +6 -0
- package/dist/api/dto/AuditRequest.dto.d.ts.map +1 -0
- package/dist/api/dto/AuditRequest.dto.js +3 -0
- package/dist/api/dto/AuditRequest.dto.js.map +1 -0
- package/dist/api/dto/AuditResponse.dto.d.ts +17 -0
- package/dist/api/dto/AuditResponse.dto.d.ts.map +1 -0
- package/dist/api/dto/AuditResponse.dto.js +3 -0
- package/dist/api/dto/AuditResponse.dto.js.map +1 -0
- package/dist/api/dto/TokenRequest.dto.d.ts +6 -0
- package/dist/api/dto/TokenRequest.dto.d.ts.map +1 -0
- package/dist/api/dto/TokenRequest.dto.js +3 -0
- package/dist/api/dto/TokenRequest.dto.js.map +1 -0
- package/dist/api/dto/TokenResponse.dto.d.ts +12 -0
- package/dist/api/dto/TokenResponse.dto.d.ts.map +1 -0
- package/dist/api/dto/TokenResponse.dto.js +3 -0
- package/dist/api/dto/TokenResponse.dto.js.map +1 -0
- package/dist/api/middlewares/authenticate.d.ts +12 -0
- package/dist/api/middlewares/authenticate.d.ts.map +1 -0
- package/dist/api/middlewares/authenticate.js +141 -0
- package/dist/api/middlewares/authenticate.js.map +1 -0
- package/dist/api/middlewares/errorHandler.d.ts +3 -0
- package/dist/api/middlewares/errorHandler.d.ts.map +1 -0
- package/dist/api/middlewares/errorHandler.js +30 -0
- package/dist/api/middlewares/errorHandler.js.map +1 -0
- package/dist/api/middlewares/rateLimit.d.ts +3 -0
- package/dist/api/middlewares/rateLimit.d.ts.map +1 -0
- package/dist/api/middlewares/rateLimit.js +34 -0
- package/dist/api/middlewares/rateLimit.js.map +1 -0
- package/dist/api/middlewares/validate.d.ts +4 -0
- package/dist/api/middlewares/validate.d.ts.map +1 -0
- package/dist/api/middlewares/validate.js +31 -0
- package/dist/api/middlewares/validate.js.map +1 -0
- package/dist/api/routes/audit.routes.d.ts +5 -0
- package/dist/api/routes/audit.routes.d.ts.map +1 -0
- package/dist/api/routes/audit.routes.js +24 -0
- package/dist/api/routes/audit.routes.js.map +1 -0
- package/dist/api/routes/auth.routes.d.ts +6 -0
- package/dist/api/routes/auth.routes.d.ts.map +1 -0
- package/dist/api/routes/auth.routes.js +22 -0
- package/dist/api/routes/auth.routes.js.map +1 -0
- package/dist/api/routes/export.routes.d.ts +5 -0
- package/dist/api/routes/export.routes.d.ts.map +1 -0
- package/dist/api/routes/export.routes.js +16 -0
- package/dist/api/routes/export.routes.js.map +1 -0
- package/dist/api/routes/health.routes.d.ts +4 -0
- package/dist/api/routes/health.routes.d.ts.map +1 -0
- package/dist/api/routes/health.routes.js +11 -0
- package/dist/api/routes/health.routes.js.map +1 -0
- package/dist/api/routes/index.d.ts +10 -0
- package/dist/api/routes/index.d.ts.map +1 -0
- package/dist/api/routes/index.js +20 -0
- package/dist/api/routes/index.js.map +1 -0
- package/dist/api/routes/providers.routes.d.ts +5 -0
- package/dist/api/routes/providers.routes.d.ts.map +1 -0
- package/dist/api/routes/providers.routes.js +13 -0
- package/dist/api/routes/providers.routes.js.map +1 -0
- package/dist/api/validators/audit.schemas.d.ts +60 -0
- package/dist/api/validators/audit.schemas.d.ts.map +1 -0
- package/dist/api/validators/audit.schemas.js +55 -0
- package/dist/api/validators/audit.schemas.js.map +1 -0
- package/dist/api/validators/auth.schemas.d.ts +17 -0
- package/dist/api/validators/auth.schemas.d.ts.map +1 -0
- package/dist/api/validators/auth.schemas.js +21 -0
- package/dist/api/validators/auth.schemas.js.map +1 -0
- package/dist/app.d.ts +3 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +62 -0
- package/dist/app.js.map +1 -0
- package/dist/config/config.schema.d.ts +65 -0
- package/dist/config/config.schema.d.ts.map +1 -0
- package/dist/config/config.schema.js +95 -0
- package/dist/config/config.schema.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +75 -0
- package/dist/config/index.js.map +1 -0
- package/dist/container.d.ts +47 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +137 -0
- package/dist/container.js.map +1 -0
- package/dist/data/database.d.ts +13 -0
- package/dist/data/database.d.ts.map +1 -0
- package/dist/data/database.js +68 -0
- package/dist/data/database.js.map +1 -0
- package/dist/data/jobs/token-cleanup.job.d.ts +23 -0
- package/dist/data/jobs/token-cleanup.job.d.ts.map +1 -0
- package/dist/data/jobs/token-cleanup.job.js +96 -0
- package/dist/data/jobs/token-cleanup.job.js.map +1 -0
- package/dist/data/migrations/migration.runner.d.ts +13 -0
- package/dist/data/migrations/migration.runner.d.ts.map +1 -0
- package/dist/data/migrations/migration.runner.js +136 -0
- package/dist/data/migrations/migration.runner.js.map +1 -0
- package/dist/data/models/Token.model.d.ts +30 -0
- package/dist/data/models/Token.model.d.ts.map +1 -0
- package/dist/data/models/Token.model.js +3 -0
- package/dist/data/models/Token.model.js.map +1 -0
- package/dist/data/repositories/token.repository.d.ts +16 -0
- package/dist/data/repositories/token.repository.d.ts.map +1 -0
- package/dist/data/repositories/token.repository.js +97 -0
- package/dist/data/repositories/token.repository.js.map +1 -0
- package/dist/providers/azure/auth.provider.d.ts +5 -0
- package/dist/providers/azure/auth.provider.d.ts.map +1 -0
- package/dist/providers/azure/auth.provider.js +13 -0
- package/dist/providers/azure/auth.provider.js.map +1 -0
- package/dist/providers/azure/azure-errors.d.ts +40 -0
- package/dist/providers/azure/azure-errors.d.ts.map +1 -0
- package/dist/providers/azure/azure-errors.js +121 -0
- package/dist/providers/azure/azure-errors.js.map +1 -0
- package/dist/providers/azure/azure-retry.d.ts +41 -0
- package/dist/providers/azure/azure-retry.d.ts.map +1 -0
- package/dist/providers/azure/azure-retry.js +85 -0
- package/dist/providers/azure/azure-retry.js.map +1 -0
- package/dist/providers/azure/graph-client.d.ts +26 -0
- package/dist/providers/azure/graph-client.d.ts.map +1 -0
- package/dist/providers/azure/graph-client.js +146 -0
- package/dist/providers/azure/graph-client.js.map +1 -0
- package/dist/providers/azure/graph.provider.d.ts +23 -0
- package/dist/providers/azure/graph.provider.d.ts.map +1 -0
- package/dist/providers/azure/graph.provider.js +161 -0
- package/dist/providers/azure/graph.provider.js.map +1 -0
- package/dist/providers/azure/queries/app.queries.d.ts +6 -0
- package/dist/providers/azure/queries/app.queries.d.ts.map +1 -0
- package/dist/providers/azure/queries/app.queries.js +9 -0
- package/dist/providers/azure/queries/app.queries.js.map +1 -0
- package/dist/providers/azure/queries/policy.queries.d.ts +6 -0
- package/dist/providers/azure/queries/policy.queries.d.ts.map +1 -0
- package/dist/providers/azure/queries/policy.queries.js +9 -0
- package/dist/providers/azure/queries/policy.queries.js.map +1 -0
- package/dist/providers/azure/queries/user.queries.d.ts +7 -0
- package/dist/providers/azure/queries/user.queries.d.ts.map +1 -0
- package/dist/providers/azure/queries/user.queries.js +10 -0
- package/dist/providers/azure/queries/user.queries.js.map +1 -0
- package/dist/providers/interfaces/IGraphProvider.d.ts +31 -0
- package/dist/providers/interfaces/IGraphProvider.d.ts.map +1 -0
- package/dist/providers/interfaces/IGraphProvider.js +3 -0
- package/dist/providers/interfaces/IGraphProvider.js.map +1 -0
- package/dist/providers/interfaces/ILDAPProvider.d.ts +37 -0
- package/dist/providers/interfaces/ILDAPProvider.d.ts.map +1 -0
- package/dist/providers/interfaces/ILDAPProvider.js +3 -0
- package/dist/providers/interfaces/ILDAPProvider.js.map +1 -0
- package/dist/providers/ldap/acl-parser.d.ts +8 -0
- package/dist/providers/ldap/acl-parser.d.ts.map +1 -0
- package/dist/providers/ldap/acl-parser.js +157 -0
- package/dist/providers/ldap/acl-parser.js.map +1 -0
- package/dist/providers/ldap/ad-mappers.d.ts +8 -0
- package/dist/providers/ldap/ad-mappers.d.ts.map +1 -0
- package/dist/providers/ldap/ad-mappers.js +162 -0
- package/dist/providers/ldap/ad-mappers.js.map +1 -0
- package/dist/providers/ldap/ldap-client.d.ts +33 -0
- package/dist/providers/ldap/ldap-client.d.ts.map +1 -0
- package/dist/providers/ldap/ldap-client.js +195 -0
- package/dist/providers/ldap/ldap-client.js.map +1 -0
- package/dist/providers/ldap/ldap-errors.d.ts +48 -0
- package/dist/providers/ldap/ldap-errors.d.ts.map +1 -0
- package/dist/providers/ldap/ldap-errors.js +120 -0
- package/dist/providers/ldap/ldap-errors.js.map +1 -0
- package/dist/providers/ldap/ldap-retry.d.ts +14 -0
- package/dist/providers/ldap/ldap-retry.d.ts.map +1 -0
- package/dist/providers/ldap/ldap-retry.js +102 -0
- package/dist/providers/ldap/ldap-retry.js.map +1 -0
- package/dist/providers/ldap/ldap-sanitizer.d.ts +12 -0
- package/dist/providers/ldap/ldap-sanitizer.d.ts.map +1 -0
- package/dist/providers/ldap/ldap-sanitizer.js +104 -0
- package/dist/providers/ldap/ldap-sanitizer.js.map +1 -0
- package/dist/providers/ldap/ldap.provider.d.ts +21 -0
- package/dist/providers/ldap/ldap.provider.d.ts.map +1 -0
- package/dist/providers/ldap/ldap.provider.js +165 -0
- package/dist/providers/ldap/ldap.provider.js.map +1 -0
- package/dist/providers/ldap/queries/computer.queries.d.ts +6 -0
- package/dist/providers/ldap/queries/computer.queries.d.ts.map +1 -0
- package/dist/providers/ldap/queries/computer.queries.js +9 -0
- package/dist/providers/ldap/queries/computer.queries.js.map +1 -0
- package/dist/providers/ldap/queries/group.queries.d.ts +6 -0
- package/dist/providers/ldap/queries/group.queries.d.ts.map +1 -0
- package/dist/providers/ldap/queries/group.queries.js +9 -0
- package/dist/providers/ldap/queries/group.queries.js.map +1 -0
- package/dist/providers/ldap/queries/user.queries.d.ts +7 -0
- package/dist/providers/ldap/queries/user.queries.d.ts.map +1 -0
- package/dist/providers/ldap/queries/user.queries.js +10 -0
- package/dist/providers/ldap/queries/user.queries.js.map +1 -0
- package/dist/providers/smb/smb.provider.d.ts +68 -0
- package/dist/providers/smb/smb.provider.d.ts.map +1 -0
- package/dist/providers/smb/smb.provider.js +382 -0
- package/dist/providers/smb/smb.provider.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +44 -0
- package/dist/server.js.map +1 -0
- package/dist/services/audit/ad-audit.service.d.ts +70 -0
- package/dist/services/audit/ad-audit.service.d.ts.map +1 -0
- package/dist/services/audit/ad-audit.service.js +1019 -0
- package/dist/services/audit/ad-audit.service.js.map +1 -0
- package/dist/services/audit/attack-graph.service.d.ts +62 -0
- package/dist/services/audit/attack-graph.service.d.ts.map +1 -0
- package/dist/services/audit/attack-graph.service.js +702 -0
- package/dist/services/audit/attack-graph.service.js.map +1 -0
- package/dist/services/audit/audit.service.d.ts +4 -0
- package/dist/services/audit/audit.service.d.ts.map +1 -0
- package/dist/services/audit/audit.service.js +10 -0
- package/dist/services/audit/audit.service.js.map +1 -0
- package/dist/services/audit/azure-audit.service.d.ts +37 -0
- package/dist/services/audit/azure-audit.service.d.ts.map +1 -0
- package/dist/services/audit/azure-audit.service.js +153 -0
- package/dist/services/audit/azure-audit.service.js.map +1 -0
- package/dist/services/audit/detectors/ad/accounts.detector.d.ts +37 -0
- package/dist/services/audit/detectors/ad/accounts.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/accounts.detector.js +881 -0
- package/dist/services/audit/detectors/ad/accounts.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/adcs.detector.d.ts +21 -0
- package/dist/services/audit/detectors/ad/adcs.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/adcs.detector.js +227 -0
- package/dist/services/audit/detectors/ad/adcs.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/advanced.detector.d.ts +63 -0
- package/dist/services/audit/detectors/ad/advanced.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/advanced.detector.js +867 -0
- package/dist/services/audit/detectors/ad/advanced.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/attack-paths.detector.d.ts +16 -0
- package/dist/services/audit/detectors/ad/attack-paths.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/attack-paths.detector.js +369 -0
- package/dist/services/audit/detectors/ad/attack-paths.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/compliance.detector.d.ts +28 -0
- package/dist/services/audit/detectors/ad/compliance.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/compliance.detector.js +896 -0
- package/dist/services/audit/detectors/ad/compliance.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/computers.detector.d.ts +30 -0
- package/dist/services/audit/detectors/ad/computers.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/computers.detector.js +799 -0
- package/dist/services/audit/detectors/ad/computers.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/gpo.detector.d.ts +17 -0
- package/dist/services/audit/detectors/ad/gpo.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/gpo.detector.js +257 -0
- package/dist/services/audit/detectors/ad/gpo.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/groups.detector.d.ts +19 -0
- package/dist/services/audit/detectors/ad/groups.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/groups.detector.js +488 -0
- package/dist/services/audit/detectors/ad/groups.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/index.d.ts +15 -0
- package/dist/services/audit/detectors/ad/index.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/index.js +51 -0
- package/dist/services/audit/detectors/ad/index.js.map +1 -0
- package/dist/services/audit/detectors/ad/kerberos.detector.d.ts +17 -0
- package/dist/services/audit/detectors/ad/kerberos.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/kerberos.detector.js +293 -0
- package/dist/services/audit/detectors/ad/kerberos.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/monitoring.detector.d.ts +23 -0
- package/dist/services/audit/detectors/ad/monitoring.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/monitoring.detector.js +328 -0
- package/dist/services/audit/detectors/ad/monitoring.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/network.detector.d.ts +39 -0
- package/dist/services/audit/detectors/ad/network.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/network.detector.js +257 -0
- package/dist/services/audit/detectors/ad/network.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/password.detector.d.ts +14 -0
- package/dist/services/audit/detectors/ad/password.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/password.detector.js +235 -0
- package/dist/services/audit/detectors/ad/password.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/permissions.detector.d.ts +20 -0
- package/dist/services/audit/detectors/ad/permissions.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/permissions.detector.js +392 -0
- package/dist/services/audit/detectors/ad/permissions.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/trusts.detector.d.ts +11 -0
- package/dist/services/audit/detectors/ad/trusts.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/trusts.detector.js +186 -0
- package/dist/services/audit/detectors/ad/trusts.detector.js.map +1 -0
- package/dist/services/audit/detectors/azure/app-security.detector.d.ts +11 -0
- package/dist/services/audit/detectors/azure/app-security.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/azure/app-security.detector.js +184 -0
- package/dist/services/audit/detectors/azure/app-security.detector.js.map +1 -0
- package/dist/services/audit/detectors/azure/conditional-access.detector.d.ts +10 -0
- package/dist/services/audit/detectors/azure/conditional-access.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/azure/conditional-access.detector.js +130 -0
- package/dist/services/audit/detectors/azure/conditional-access.detector.js.map +1 -0
- package/dist/services/audit/detectors/azure/privilege-security.detector.d.ts +8 -0
- package/dist/services/audit/detectors/azure/privilege-security.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/azure/privilege-security.detector.js +113 -0
- package/dist/services/audit/detectors/azure/privilege-security.detector.js.map +1 -0
- package/dist/services/audit/detectors/azure/user-security.detector.d.ts +14 -0
- package/dist/services/audit/detectors/azure/user-security.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/azure/user-security.detector.js +198 -0
- package/dist/services/audit/detectors/azure/user-security.detector.js.map +1 -0
- package/dist/services/audit/detectors/index.d.ts +2 -0
- package/dist/services/audit/detectors/index.d.ts.map +1 -0
- package/dist/services/audit/detectors/index.js +38 -0
- package/dist/services/audit/detectors/index.js.map +1 -0
- package/dist/services/audit/response-formatter.d.ts +176 -0
- package/dist/services/audit/response-formatter.d.ts.map +1 -0
- package/dist/services/audit/response-formatter.js +240 -0
- package/dist/services/audit/response-formatter.js.map +1 -0
- package/dist/services/audit/scoring.service.d.ts +15 -0
- package/dist/services/audit/scoring.service.d.ts.map +1 -0
- package/dist/services/audit/scoring.service.js +139 -0
- package/dist/services/audit/scoring.service.js.map +1 -0
- package/dist/services/auth/crypto.service.d.ts +19 -0
- package/dist/services/auth/crypto.service.d.ts.map +1 -0
- package/dist/services/auth/crypto.service.js +135 -0
- package/dist/services/auth/crypto.service.js.map +1 -0
- package/dist/services/auth/errors.d.ts +19 -0
- package/dist/services/auth/errors.d.ts.map +1 -0
- package/dist/services/auth/errors.js +46 -0
- package/dist/services/auth/errors.js.map +1 -0
- package/dist/services/auth/token.service.d.ts +41 -0
- package/dist/services/auth/token.service.d.ts.map +1 -0
- package/dist/services/auth/token.service.js +208 -0
- package/dist/services/auth/token.service.js.map +1 -0
- package/dist/services/config/config.service.d.ts +6 -0
- package/dist/services/config/config.service.d.ts.map +1 -0
- package/dist/services/config/config.service.js +64 -0
- package/dist/services/config/config.service.js.map +1 -0
- package/dist/services/export/export.service.d.ts +28 -0
- package/dist/services/export/export.service.d.ts.map +1 -0
- package/dist/services/export/export.service.js +28 -0
- package/dist/services/export/export.service.js.map +1 -0
- package/dist/services/export/formatters/csv.formatter.d.ts +8 -0
- package/dist/services/export/formatters/csv.formatter.d.ts.map +1 -0
- package/dist/services/export/formatters/csv.formatter.js +46 -0
- package/dist/services/export/formatters/csv.formatter.js.map +1 -0
- package/dist/services/export/formatters/json.formatter.d.ts +40 -0
- package/dist/services/export/formatters/json.formatter.d.ts.map +1 -0
- package/dist/services/export/formatters/json.formatter.js +58 -0
- package/dist/services/export/formatters/json.formatter.js.map +1 -0
- package/dist/services/jobs/azure-job-runner.d.ts +38 -0
- package/dist/services/jobs/azure-job-runner.d.ts.map +1 -0
- package/dist/services/jobs/azure-job-runner.js +199 -0
- package/dist/services/jobs/azure-job-runner.js.map +1 -0
- package/dist/services/jobs/index.d.ts +4 -0
- package/dist/services/jobs/index.d.ts.map +1 -0
- package/dist/services/jobs/index.js +20 -0
- package/dist/services/jobs/index.js.map +1 -0
- package/dist/services/jobs/job-runner.d.ts +64 -0
- package/dist/services/jobs/job-runner.d.ts.map +1 -0
- package/dist/services/jobs/job-runner.js +952 -0
- package/dist/services/jobs/job-runner.js.map +1 -0
- package/dist/services/jobs/job-store.d.ts +27 -0
- package/dist/services/jobs/job-store.d.ts.map +1 -0
- package/dist/services/jobs/job-store.js +261 -0
- package/dist/services/jobs/job-store.js.map +1 -0
- package/dist/services/jobs/job.types.d.ts +67 -0
- package/dist/services/jobs/job.types.d.ts.map +1 -0
- package/dist/services/jobs/job.types.js +36 -0
- package/dist/services/jobs/job.types.js.map +1 -0
- package/dist/types/ad.types.d.ts +74 -0
- package/dist/types/ad.types.d.ts.map +1 -0
- package/dist/types/ad.types.js +3 -0
- package/dist/types/ad.types.js.map +1 -0
- package/dist/types/adcs.types.d.ts +58 -0
- package/dist/types/adcs.types.d.ts.map +1 -0
- package/dist/types/adcs.types.js +38 -0
- package/dist/types/adcs.types.js.map +1 -0
- package/dist/types/attack-graph.types.d.ts +135 -0
- package/dist/types/attack-graph.types.d.ts.map +1 -0
- package/dist/types/attack-graph.types.js +58 -0
- package/dist/types/attack-graph.types.js.map +1 -0
- package/dist/types/audit.types.d.ts +34 -0
- package/dist/types/audit.types.d.ts.map +1 -0
- package/dist/types/audit.types.js +3 -0
- package/dist/types/audit.types.js.map +1 -0
- package/dist/types/azure.types.d.ts +61 -0
- package/dist/types/azure.types.d.ts.map +1 -0
- package/dist/types/azure.types.js +3 -0
- package/dist/types/azure.types.js.map +1 -0
- package/dist/types/config.types.d.ts +63 -0
- package/dist/types/config.types.d.ts.map +1 -0
- package/dist/types/config.types.js +3 -0
- package/dist/types/config.types.js.map +1 -0
- package/dist/types/error.types.d.ts +33 -0
- package/dist/types/error.types.d.ts.map +1 -0
- package/dist/types/error.types.js +70 -0
- package/dist/types/error.types.js.map +1 -0
- package/dist/types/finding.types.d.ts +133 -0
- package/dist/types/finding.types.d.ts.map +1 -0
- package/dist/types/finding.types.js +3 -0
- package/dist/types/finding.types.js.map +1 -0
- package/dist/types/gpo.types.d.ts +39 -0
- package/dist/types/gpo.types.d.ts.map +1 -0
- package/dist/types/gpo.types.js +15 -0
- package/dist/types/gpo.types.js.map +1 -0
- package/dist/types/token.types.d.ts +26 -0
- package/dist/types/token.types.d.ts.map +1 -0
- package/dist/types/token.types.js +3 -0
- package/dist/types/token.types.js.map +1 -0
- package/dist/types/trust.types.d.ts +45 -0
- package/dist/types/trust.types.d.ts.map +1 -0
- package/dist/types/trust.types.js +71 -0
- package/dist/types/trust.types.js.map +1 -0
- package/dist/utils/entity-converter.d.ts +17 -0
- package/dist/utils/entity-converter.d.ts.map +1 -0
- package/dist/utils/entity-converter.js +285 -0
- package/dist/utils/entity-converter.js.map +1 -0
- package/dist/utils/graph.util.d.ts +66 -0
- package/dist/utils/graph.util.d.ts.map +1 -0
- package/dist/utils/graph.util.js +382 -0
- package/dist/utils/graph.util.js.map +1 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +86 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/type-name-normalizer.d.ts +5 -0
- package/dist/utils/type-name-normalizer.d.ts.map +1 -0
- package/dist/utils/type-name-normalizer.js +218 -0
- package/dist/utils/type-name-normalizer.js.map +1 -0
- package/docker-compose.yml +26 -0
- package/docs/api/README.md +178 -0
- package/docs/api/openapi.yaml +1524 -0
- package/eslint.config.js +54 -0
- package/jest.config.js +38 -0
- package/package.json +97 -0
- package/scripts/fetch-ad-cert.sh +142 -0
- package/src/.gitkeep +0 -0
- package/src/api/.gitkeep +0 -0
- package/src/api/controllers/.gitkeep +0 -0
- package/src/api/controllers/audit.controller.ts +313 -0
- package/src/api/controllers/auth.controller.ts +258 -0
- package/src/api/controllers/export.controller.ts +153 -0
- package/src/api/controllers/health.controller.ts +16 -0
- package/src/api/controllers/jobs.controller.ts +187 -0
- package/src/api/controllers/providers.controller.ts +165 -0
- package/src/api/dto/.gitkeep +0 -0
- package/src/api/dto/AuditRequest.dto.ts +8 -0
- package/src/api/dto/AuditResponse.dto.ts +19 -0
- package/src/api/dto/TokenRequest.dto.ts +8 -0
- package/src/api/dto/TokenResponse.dto.ts +14 -0
- package/src/api/middlewares/.gitkeep +0 -0
- package/src/api/middlewares/authenticate.ts +203 -0
- package/src/api/middlewares/errorHandler.ts +54 -0
- package/src/api/middlewares/rateLimit.ts +35 -0
- package/src/api/middlewares/validate.ts +32 -0
- package/src/api/routes/.gitkeep +0 -0
- package/src/api/routes/audit.routes.ts +77 -0
- package/src/api/routes/auth.routes.ts +71 -0
- package/src/api/routes/export.routes.ts +34 -0
- package/src/api/routes/health.routes.ts +14 -0
- package/src/api/routes/index.ts +40 -0
- package/src/api/routes/providers.routes.ts +24 -0
- package/src/api/validators/.gitkeep +0 -0
- package/src/api/validators/audit.schemas.ts +59 -0
- package/src/api/validators/auth.schemas.ts +59 -0
- package/src/app.ts +87 -0
- package/src/config/.gitkeep +0 -0
- package/src/config/config.schema.ts +108 -0
- package/src/config/index.ts +82 -0
- package/src/container.ts +221 -0
- package/src/data/.gitkeep +0 -0
- package/src/data/database.ts +78 -0
- package/src/data/jobs/token-cleanup.job.ts +166 -0
- package/src/data/migrations/.gitkeep +0 -0
- package/src/data/migrations/001_initial_schema.sql +47 -0
- package/src/data/migrations/migration.runner.ts +125 -0
- package/src/data/models/.gitkeep +0 -0
- package/src/data/models/Token.model.ts +35 -0
- package/src/data/repositories/.gitkeep +0 -0
- package/src/data/repositories/token.repository.ts +160 -0
- package/src/providers/.gitkeep +0 -0
- package/src/providers/azure/.gitkeep +0 -0
- package/src/providers/azure/auth.provider.ts +14 -0
- package/src/providers/azure/azure-errors.ts +189 -0
- package/src/providers/azure/azure-retry.ts +168 -0
- package/src/providers/azure/graph-client.ts +315 -0
- package/src/providers/azure/graph.provider.ts +294 -0
- package/src/providers/azure/queries/app.queries.ts +9 -0
- package/src/providers/azure/queries/policy.queries.ts +9 -0
- package/src/providers/azure/queries/user.queries.ts +10 -0
- package/src/providers/interfaces/.gitkeep +0 -0
- package/src/providers/interfaces/IGraphProvider.ts +117 -0
- package/src/providers/interfaces/ILDAPProvider.ts +142 -0
- package/src/providers/ldap/.gitkeep +0 -0
- package/src/providers/ldap/acl-parser.ts +231 -0
- package/src/providers/ldap/ad-mappers.ts +280 -0
- package/src/providers/ldap/ldap-client.ts +259 -0
- package/src/providers/ldap/ldap-errors.ts +188 -0
- package/src/providers/ldap/ldap-retry.ts +267 -0
- package/src/providers/ldap/ldap-sanitizer.ts +273 -0
- package/src/providers/ldap/ldap.provider.ts +293 -0
- package/src/providers/ldap/queries/computer.queries.ts +9 -0
- package/src/providers/ldap/queries/group.queries.ts +9 -0
- package/src/providers/ldap/queries/user.queries.ts +10 -0
- package/src/providers/smb/smb.provider.ts +653 -0
- package/src/server.ts +60 -0
- package/src/services/.gitkeep +0 -0
- package/src/services/audit/.gitkeep +0 -0
- package/src/services/audit/ad-audit.service.ts +1481 -0
- package/src/services/audit/attack-graph.service.ts +1104 -0
- package/src/services/audit/audit.service.ts +12 -0
- package/src/services/audit/azure-audit.service.ts +286 -0
- package/src/services/audit/detectors/ad/accounts.detector.ts +1232 -0
- package/src/services/audit/detectors/ad/adcs.detector.ts +449 -0
- package/src/services/audit/detectors/ad/advanced.detector.ts +1270 -0
- package/src/services/audit/detectors/ad/attack-paths.detector.ts +600 -0
- package/src/services/audit/detectors/ad/compliance.detector.ts +1421 -0
- package/src/services/audit/detectors/ad/computers.detector.ts +1188 -0
- package/src/services/audit/detectors/ad/gpo.detector.ts +485 -0
- package/src/services/audit/detectors/ad/groups.detector.ts +685 -0
- package/src/services/audit/detectors/ad/index.ts +84 -0
- package/src/services/audit/detectors/ad/kerberos.detector.ts +424 -0
- package/src/services/audit/detectors/ad/monitoring.detector.ts +501 -0
- package/src/services/audit/detectors/ad/network.detector.ts +538 -0
- package/src/services/audit/detectors/ad/password.detector.ts +324 -0
- package/src/services/audit/detectors/ad/permissions.detector.ts +637 -0
- package/src/services/audit/detectors/ad/trusts.detector.ts +315 -0
- package/src/services/audit/detectors/azure/app-security.detector.ts +246 -0
- package/src/services/audit/detectors/azure/conditional-access.detector.ts +186 -0
- package/src/services/audit/detectors/azure/privilege-security.detector.ts +176 -0
- package/src/services/audit/detectors/azure/user-security.detector.ts +280 -0
- package/src/services/audit/detectors/index.ts +18 -0
- package/src/services/audit/response-formatter.ts +604 -0
- package/src/services/audit/scoring.service.ts +234 -0
- package/src/services/auth/.gitkeep +0 -0
- package/src/services/auth/crypto.service.ts +230 -0
- package/src/services/auth/errors.ts +47 -0
- package/src/services/auth/token.service.ts +420 -0
- package/src/services/config/.gitkeep +0 -0
- package/src/services/config/config.service.ts +75 -0
- package/src/services/export/.gitkeep +0 -0
- package/src/services/export/export.service.ts +99 -0
- package/src/services/export/formatters/csv.formatter.ts +124 -0
- package/src/services/export/formatters/json.formatter.ts +160 -0
- package/src/services/jobs/azure-job-runner.ts +312 -0
- package/src/services/jobs/index.ts +9 -0
- package/src/services/jobs/job-runner.ts +1280 -0
- package/src/services/jobs/job-store.ts +384 -0
- package/src/services/jobs/job.types.ts +182 -0
- package/src/types/.gitkeep +0 -0
- package/src/types/ad.types.ts +91 -0
- package/src/types/adcs.types.ts +107 -0
- package/src/types/attack-graph.types.ts +260 -0
- package/src/types/audit.types.ts +42 -0
- package/src/types/azure.types.ts +68 -0
- package/src/types/config.types.ts +79 -0
- package/src/types/error.types.ts +69 -0
- package/src/types/finding.types.ts +284 -0
- package/src/types/gpo.types.ts +72 -0
- package/src/types/smb2.d.ts +73 -0
- package/src/types/token.types.ts +32 -0
- package/src/types/trust.types.ts +140 -0
- package/src/utils/.gitkeep +0 -0
- package/src/utils/entity-converter.ts +453 -0
- package/src/utils/graph.util.ts +609 -0
- package/src/utils/logger.ts +111 -0
- package/src/utils/type-name-normalizer.ts +302 -0
- package/tests/.gitkeep +0 -0
- package/tests/e2e/.gitkeep +0 -0
- package/tests/fixtures/.gitkeep +0 -0
- package/tests/integration/.gitkeep +0 -0
- package/tests/integration/README.md +156 -0
- package/tests/integration/ad-audit.integration.test.ts +216 -0
- package/tests/integration/api/.gitkeep +0 -0
- package/tests/integration/api/endpoints.integration.test.ts +431 -0
- package/tests/integration/auth/jwt-authentication.integration.test.ts +358 -0
- package/tests/integration/providers/.gitkeep +0 -0
- package/tests/integration/providers/azure-basic.integration.test.ts +167 -0
- package/tests/integration/providers/ldap-basic.integration.test.ts +152 -0
- package/tests/integration/providers/ldap-connectivity.test.ts +44 -0
- package/tests/integration/providers/ldap-provider.integration.test.ts +347 -0
- package/tests/mocks/.gitkeep +0 -0
- package/tests/setup.ts +16 -0
- package/tests/unit/.gitkeep +0 -0
- package/tests/unit/api/middlewares/authenticate.test.ts +446 -0
- package/tests/unit/providers/.gitkeep +0 -0
- package/tests/unit/providers/azure/azure-errors.test.ts +193 -0
- package/tests/unit/providers/azure/azure-retry.test.ts +254 -0
- package/tests/unit/providers/azure/graph-provider.test.ts +313 -0
- package/tests/unit/providers/ldap/ad-mappers.test.ts +392 -0
- package/tests/unit/providers/ldap/ldap-provider.test.ts +376 -0
- package/tests/unit/providers/ldap/ldap-retry.test.ts +377 -0
- package/tests/unit/providers/ldap/ldap-sanitizer.test.ts +301 -0
- package/tests/unit/sample.test.ts +19 -0
- package/tests/unit/services/.gitkeep +0 -0
- package/tests/unit/services/audit/detectors/ad/accounts.detector.test.ts +393 -0
- package/tests/unit/services/audit/detectors/ad/advanced.detector.test.ts +380 -0
- package/tests/unit/services/audit/detectors/ad/computers.detector.test.ts +440 -0
- package/tests/unit/services/audit/detectors/ad/groups.detector.test.ts +276 -0
- package/tests/unit/services/audit/detectors/ad/kerberos.detector.test.ts +215 -0
- package/tests/unit/services/audit/detectors/ad/password.detector.test.ts +226 -0
- package/tests/unit/services/audit/detectors/ad/permissions.detector.test.ts +244 -0
- package/tests/unit/services/audit/detectors/azure/app-security.detector.test.ts +349 -0
- package/tests/unit/services/audit/detectors/azure/conditional-access.detector.test.ts +374 -0
- package/tests/unit/services/audit/detectors/azure/privilege-security.detector.test.ts +374 -0
- package/tests/unit/services/audit/detectors/azure/user-security.detector.test.ts +297 -0
- package/tests/unit/services/auth/crypto.service.test.ts +296 -0
- package/tests/unit/services/auth/token.service.test.ts +579 -0
- package/tests/unit/services/export/export.service.test.ts +241 -0
- package/tests/unit/services/export/formatters/csv.formatter.test.ts +270 -0
- package/tests/unit/services/export/formatters/json.formatter.test.ts +258 -0
- package/tests/unit/utils/.gitkeep +0 -0
- 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
|
+
}
|