193 lines
15 KiB
Markdown
193 lines
15 KiB
Markdown
---
|
|
phase: 09-cert-manager-module
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- apps/api/package.json
|
|
- apps/api/vitest.config.ts
|
|
- apps/api/src/cert-manager/cert-manager.module.ts
|
|
- apps/api/src/cert-manager/cert-manager.seed.ts
|
|
- apps/api/src/cert-manager/cert-manager.service.ts
|
|
- apps/api/src/cert-manager/cert-manager.controller.ts
|
|
- apps/api/src/cert-manager/dto/parse-cert.dto.ts
|
|
- apps/api/src/cert-manager/dto/merge-certs.dto.ts
|
|
- apps/api/src/cert-manager/dto/convert-cert.dto.ts
|
|
- apps/api/src/cert-manager/cert-manager.service.spec.ts
|
|
- apps/api/src/app.module.ts
|
|
autonomous: true
|
|
requirements: [CERT-06]
|
|
user_setup:
|
|
- service: marketplace-activation
|
|
why: "isSystem:true seeds the module in the registry but does NOT auto-activate it per tenant. ModuleGuard returns 403 until an admin activates cert-manager via the Marketplace UI."
|
|
dashboard_config:
|
|
- task: "Activate the cert-manager module for the tenant"
|
|
location: "Tessera Portal -> Marketplace -> Cert Manager -> Aktivieren (after this plan runs and the API is restarted)"
|
|
|
|
must_haves:
|
|
truths:
|
|
- "The API boots and seeds a Module registry row with slug 'cert-manager' on startup"
|
|
- "The cert-manager Vitest suite runs via `pnpm --filter @tessera/api test`"
|
|
- "Shared node-forge helpers (format detection, fingerprint, PEM-chain split, buffer conversion) exist and are unit-tested"
|
|
artifacts:
|
|
- "apps/api/vitest.config.ts (node environment)"
|
|
- "apps/api/src/cert-manager/cert-manager.module.ts (OnModuleInit seed)"
|
|
- "apps/api/src/cert-manager/cert-manager.seed.ts (seedCertManagerModule)"
|
|
- "apps/api/src/cert-manager/cert-manager.service.ts (shared helpers + operation method stubs)"
|
|
- "apps/api/src/cert-manager/cert-manager.controller.ts (4 POST endpoints, @UseModule guard)"
|
|
- "apps/api/src/cert-manager/cert-manager.service.spec.ts (RED/GREEN helper + seed tests)"
|
|
key_links:
|
|
- "CertManagerModule registered in app.module.ts imports array"
|
|
- "seedCertManagerModule -> moduleRegistryService.seedModule({ slug: 'cert-manager' })"
|
|
- "@Controller('modules/cert-manager') + @UseModule('cert-manager') -> ModuleGuard"
|
|
---
|
|
|
|
<objective>
|
|
Establish the API foundation for the cert-manager module: install node-forge and a Vitest runner for `@tessera/api`, scaffold the NestJS module following the domaincheck analog exactly, seed the module into the registry (CERT-06), and implement + unit-test the shared node-forge helpers every later slice depends on.
|
|
|
|
This is the first vertical slice's enabling half: after this plan the module registers itself at startup so it can be activated in the Marketplace and its endpoints become reachable.
|
|
|
|
Purpose: All later feature slices (Inspect, Split, Convert, Merge/PFX) build on this module skeleton, the shared crypto helpers, and the API test harness.
|
|
Output: Registered cert-manager module + running API test suite + tested shared helpers.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
|
@$HOME/.claude/gsd-core/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/09-cert-manager-module/09-CONTEXT.md
|
|
@.planning/phases/09-cert-manager-module/09-RESEARCH.md
|
|
@.planning/phases/09-cert-manager-module/09-PATTERNS.md
|
|
@apps/api/src/domaincheck/domaincheck.module.ts
|
|
@apps/api/src/domaincheck/domaincheck.seed.ts
|
|
@apps/api/src/domaincheck/domaincheck.controller.ts
|
|
@apps/api/src/app.module.ts
|
|
</context>
|
|
|
|
<artifacts>
|
|
## Artifacts this plan produces
|
|
|
|
- New file: `apps/api/vitest.config.ts` — Vitest config, `test.environment: 'node'`, `test.include: ['src/**/*.spec.ts']`
|
|
- New npm scripts in `apps/api/package.json`: `test` (`vitest run`), `test:watch` (`vitest`)
|
|
- New deps in `apps/api`: `node-forge@^1.4.0`, `@types/node-forge@^1.3.14` (dev), `vitest@^3` (dev), `@vitest/*` as needed
|
|
- New symbol: `CertManagerModule` (class, implements OnModuleInit)
|
|
- New symbol: `seedCertManagerModule(moduleRegistryService)` (async function)
|
|
- New symbol: `CertManagerService` (@Injectable) with methods: `parseCert`, `splitCerts`, `mergeCerts`, `convertCert` (operation stubs) and helpers `detectFormat`, `toForgeBuffer`, `getFingerprint`, `parsePemChain`
|
|
- New symbol: `CertManagerController` (@Controller('modules/cert-manager'), @UseModule('cert-manager')) with routes `POST parse`, `POST split`, `POST merge`, `POST convert`
|
|
- New DTO classes: `ParseCertDto`, `MergeCertsDto`, `ConvertCertDto`
|
|
- New test file: `apps/api/src/cert-manager/cert-manager.service.spec.ts`
|
|
</artifacts>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Install node-forge + Vitest runner for @tessera/api</name>
|
|
<files>apps/api/package.json, apps/api/vitest.config.ts</files>
|
|
<read_first>
|
|
- apps/api/package.json (current scripts + deps — no test runner exists yet)
|
|
- apps/web/vitest.config.ts (reference Vitest config shape; API uses environment 'node' instead of 'jsdom', no react plugin)
|
|
- .planning/phases/09-cert-manager-module/09-RESEARCH.md (Installation section + Package Legitimacy Audit — node-forge is Approved)
|
|
</read_first>
|
|
<action>
|
|
Install runtime + dev deps in the API workspace: run `pnpm --filter @tessera/api add node-forge@^1.4.0`, then `pnpm --filter @tessera/api add -D @types/node-forge@^1.3.14 vitest@^3`. node-forge is Approved in the Package Legitimacy Audit (npm, 35.3M/wk, github.com/digitalbazaar/forge) — no legitimacy checkpoint required.
|
|
Create apps/api/vitest.config.ts using `defineConfig` from `vitest/config` with: `test.environment` set to `'node'`, `test.globals` set to `true`, `test.include` set to `['src/**/*.spec.ts']`. Do NOT add jsdom or the react plugin (API is server-only).
|
|
Add two scripts to apps/api/package.json: `"test": "vitest run"` and `"test:watch": "vitest"`. Do not add watch flags to the `test` script (must exit).
|
|
</action>
|
|
<verify>
|
|
<automated>pnpm --filter @tessera/api test --run 2>&1 | grep -Eiq 'no test files|passed|Test Files' && echo VITEST_OK</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `node -e "const p=require('./apps/api/package.json'); process.exit(p.dependencies['node-forge']?0:1)"` exits 0
|
|
- `node -e "const p=require('./apps/api/package.json'); process.exit(p.devDependencies['@types/node-forge']&&p.devDependencies['vitest']?0:1)"` exits 0
|
|
- `apps/api/package.json` `scripts.test` equals `vitest run`
|
|
- `apps/api/vitest.config.ts` exists and contains `environment: 'node'`
|
|
- `pnpm --filter @tessera/api test --run` exits 0 (0 tests or passing tests, never a runner error)
|
|
</acceptance_criteria>
|
|
<done>node-forge + @types/node-forge + vitest installed in @tessera/api; `pnpm --filter @tessera/api test` runs Vitest in a node environment and exits cleanly.</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 2: Scaffold cert-manager module + shared node-forge helpers with failing spec</name>
|
|
<files>apps/api/src/cert-manager/cert-manager.module.ts, apps/api/src/cert-manager/cert-manager.seed.ts, apps/api/src/cert-manager/cert-manager.service.ts, apps/api/src/cert-manager/cert-manager.controller.ts, apps/api/src/cert-manager/dto/parse-cert.dto.ts, apps/api/src/cert-manager/dto/merge-certs.dto.ts, apps/api/src/cert-manager/dto/convert-cert.dto.ts, apps/api/src/cert-manager/cert-manager.service.spec.ts, apps/api/src/app.module.ts</files>
|
|
<read_first>
|
|
- apps/api/src/domaincheck/domaincheck.module.ts (OnModuleInit + seed pattern to copy exactly)
|
|
- apps/api/src/domaincheck/domaincheck.seed.ts (seedModule call shape)
|
|
- apps/api/src/domaincheck/domaincheck.service.ts (Injectable + Logger structure)
|
|
- apps/api/src/domaincheck/dto/check-domain.dto.ts (DTO style)
|
|
- .planning/phases/09-cert-manager-module/09-PATTERNS.md (Pattern Assignments: full module.ts, seed.ts, service structure, DTO shapes)
|
|
- .planning/phases/09-cert-manager-module/09-RESEARCH.md (Pattern 5 node-forge helpers + Pattern 6 format detection + Pitfall 1 binary encoding)
|
|
</read_first>
|
|
<behavior>
|
|
- Test: seedCertManagerModule calls moduleRegistryService.seedModule once with an object whose slug is 'cert-manager', category is 'security-tools', isSystem is true (CERT-06). Use a mock ModuleRegistryService.
|
|
- Test: detectFormat('cert.pfx', anyBuffer) returns 'pfx'; detectFormat('cert.p7b', anyBuffer) returns 'p7b'; detectFormat('cert.der', anyBuffer) returns 'der'; detectFormat('cert.pem', pemBuffer) returns 'pem'.
|
|
- Test: detectFormat('cert.cer', buffer starting with '-----BEGIN') returns 'pem'; detectFormat('cert.cer', binaryBuffer) returns 'der' (ambiguous .cer resolved by content sniff).
|
|
- Test: getFingerprint(cert, 'sha256') returns an uppercase colon-separated hex string (matches /^[0-9A-F]{2}(:[0-9A-F]{2})+$/) computed over DER bytes, for a self-signed cert generated in beforeAll via forge.pki.rsa.generateKeyPair + forge.pki.createCertificate.
|
|
- Test: parsePemChain(concatenation of two cert PEMs) returns an array of length 2.
|
|
</behavior>
|
|
<action>
|
|
Create the module directory apps/api/src/cert-manager/ mirroring domaincheck.
|
|
cert-manager.module.ts: copy domaincheck.module.ts structure — @Module imports [ModuleRegistryModule], controllers [CertManagerController], providers [CertManagerService], implements OnModuleInit, constructor injects ModuleRegistryService, onModuleInit calls seedCertManagerModule and logs success/failure via a Logger named CertManagerModule.
|
|
cert-manager.seed.ts: export async seedCertManagerModule(moduleRegistryService) calling moduleRegistryService.seedModule with slug 'cert-manager', name 'Cert Manager', version '1.0.0', category 'security-tools', description { de: 'Zertifikate analysieren, konvertieren und verwalten', en: 'Inspect, convert and manage certificates' }, isSystem true (per PATTERNS seed pattern — implements CERT-06).
|
|
cert-manager.service.ts: @Injectable with a Logger named CertManagerService. Implement the shared helpers concretely: detectFormat(filename, buffer) per RESEARCH Pattern 6; toForgeBuffer(buffer) returning forge.util.createBuffer(buffer.toString('binary')) (NEVER 'utf-8' — Pitfall 1); getFingerprint(cert, algorithm) per RESEARCH Pattern 5 (hash the DER bytes, uppercase colon-joined hex); parsePemChain(pem) using the BEGIN/END CERTIFICATE regex. Add operation method stubs parseCert, splitCerts, mergeCerts, convertCert that each throw a NestJS NotImplementedException for now (filled by later slices). Never log the password parameter.
|
|
cert-manager.controller.ts: @Controller('modules/cert-manager') decorated with @UseModule('cert-manager') from ../module-registry/module.guard; constructor injects CertManagerService. Declare the four POST routes (parse, split, merge, convert) delegating to the service; parse/split/convert use FileInterceptor('file', { limits: { fileSize: 5*1024*1024 } }), merge uses FilesInterceptor('files', 20, { limits: { fileSize: 5*1024*1024 } }) per PATTERNS controller pattern. Reject missing input with BadRequestException. Route bodies may delegate to the (still-stubbed) service methods.
|
|
dto/parse-cert.dto.ts, dto/merge-certs.dto.ts, dto/convert-cert.dto.ts: per PATTERNS DTO shapes (ParseCertDto { pemText, password? }, MergeCertsDto { outputFormat: 'pem'|'pfx', password? }, ConvertCertDto { targetFormat: 'pem'|'der'|'pfx'|'p7b', password? }).
|
|
Register the module: add `import { CertManagerModule } from './cert-manager/cert-manager.module';` to apps/api/src/app.module.ts and add `CertManagerModule` to the @Module imports array (after DomaincheckModule).
|
|
Create cert-manager.service.spec.ts implementing the Behavior tests above. Write the tests FIRST and confirm they fail (RED) against empty helpers, then implement the helpers until they pass (GREEN). Generate the test cert(s) in a beforeAll using node-forge (self-signed), so no key material is committed.
|
|
</action>
|
|
<verify>
|
|
<automated>pnpm --filter @tessera/api test cert-manager --run</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `pnpm --filter @tessera/api test cert-manager --run` exits 0 with the seed, detectFormat, getFingerprint, and parsePemChain tests passing
|
|
- `grep -q "slug: 'cert-manager'" apps/api/src/cert-manager/cert-manager.seed.ts`
|
|
- `grep -q "@UseModule('cert-manager')" apps/api/src/cert-manager/cert-manager.controller.ts`
|
|
- `grep -q "CertManagerModule" apps/api/src/app.module.ts`
|
|
- `grep -q "toString('binary')" apps/api/src/cert-manager/cert-manager.service.ts` and no occurrence of `toString('utf-8')` in a forge.util.createBuffer call
|
|
- `pnpm --filter @tessera/api type-check` exits 0
|
|
</acceptance_criteria>
|
|
<done>The cert-manager module is scaffolded per the domaincheck analog, registered in app.module.ts, seeds slug 'cert-manager' (CERT-06), and the shared node-forge helpers are implemented and green under Vitest.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## Trust Boundaries
|
|
|
|
| Boundary | Description |
|
|
|----------|-------------|
|
|
| client -> API upload | Untrusted certificate bytes cross into node-forge parsing |
|
|
| npm registry -> build | Third-party crypto dependency (node-forge) enters the build |
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
|
| T-09-SC | Tampering | node-forge / @types/node-forge install | high | mitigate | node-forge Approved in Package Legitimacy Audit (npm, 35.3M/wk, DigitalBazaar); pinned `^1.4.0`; no [ASSUMED]/[SUS] packages so no legitimacy checkpoint |
|
|
| T-09-04 | Elevation of Privilege | CertManagerController routes | high | mitigate | Global JwtAuthGuard + TenantGuard (app.module) plus `@UseModule('cert-manager')` ModuleGuard on the controller — unauthenticated/unactivated requests get 401/403 |
|
|
| T-09-03 | Denial of Service | FileInterceptor / FilesInterceptor upload | high | mitigate | `limits: { fileSize: 5 * 1024 * 1024 }` on every upload interceptor caps memory per request |
|
|
| T-09-02 | Information Disclosure | service/controller password param | high | mitigate | `password` is never passed to a logger; service Logger only logs failure category text |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
- `pnpm --filter @tessera/api test cert-manager --run` — seed + helper suite green
|
|
- `pnpm --filter @tessera/api type-check` — API compiles with new module
|
|
- Manual (deferred to phase gate): restart API, activate cert-manager in Marketplace, confirm no startup errors and the registry row exists
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- node-forge + Vitest installed in @tessera/api; `pnpm --filter @tessera/api test` runs
|
|
- cert-manager module registered and seeding slug 'cert-manager' (CERT-06)
|
|
- Shared helpers implemented and unit-tested; binary encoding uses 'binary' not 'utf-8'
|
|
- API type-checks clean
|
|
</success_criteria>
|
|
|
|
<output>
|
|
Create `.planning/phases/09-cert-manager-module/09-01-SUMMARY.md` when done
|
|
</output>
|