feat(iios): wire the three-proof gate into the request path (§1b)
Applies the context-attestation verifier at the request boundary via a guard, so a stolen/forged/replayed attestation is rejected before IIOS acts — while staying safe to roll out. - ContextAttestationGuard: if an X-Context-Attestation header is present, verify it (its app_id must match the actor token's app_id) → 403 on any failure; if absent, allow only when IIOS_REQUIRE_ATTESTATION != 1 (dev/zero-trust), else 403. The flag is read per-request and defaults OFF, so existing callers keep working until AppShell/ be-crm start forwarding attestations. - AttestationModule (@Global): provides the verifier + Prisma stores + guard, and dev-seeds the crm-support-widget client so locally minted attestations verify. - Guard applied to ThreadsController + SupportController. - Tests (5 pass, no DB): not-required-allows, required-rejects, valid, forged, replayed. Workload/mTLS (proof #2) stays a mesh concern. Next: SDK header passthrough + demote be-crm IiosClient to forward an attestation, then flip the flag to prove end-to-end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { Global, Module, type OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { ContextAttestationVerifier } from './context-attestation';
|
||||
import { PrismaClientRegistry, PrismaNonceStore } from './attestation-stores';
|
||||
import { ContextAttestationGuard } from './context-attestation.guard';
|
||||
|
||||
/**
|
||||
* Wires the context-attestation trust layer into DI and exposes the guard globally so any
|
||||
* controller can enforce the three-proof gate. Also dev-seeds a registered client so locally
|
||||
* minted attestations verify (prod registers clients out-of-band).
|
||||
*/
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [
|
||||
PrismaClientRegistry,
|
||||
PrismaNonceStore,
|
||||
{
|
||||
provide: ContextAttestationVerifier,
|
||||
useFactory: (reg: PrismaClientRegistry, nonces: PrismaNonceStore) =>
|
||||
new ContextAttestationVerifier(reg, nonces, { audience: process.env.IIOS_ATTESTATION_AUDIENCE ?? 'iios-core' }),
|
||||
inject: [PrismaClientRegistry, PrismaNonceStore],
|
||||
},
|
||||
ContextAttestationGuard,
|
||||
],
|
||||
exports: [ContextAttestationVerifier, ContextAttestationGuard],
|
||||
})
|
||||
export class AttestationModule implements OnModuleInit {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
/** Dev seed: register the CRM support client so an attestation signed with the shared dev
|
||||
* secret verifies locally. Gated by IIOS_DEV_TOKENS + IIOS_ATTESTATION_DEV_SECRET; best-effort. */
|
||||
async onModuleInit(): Promise<void> {
|
||||
if (process.env.IIOS_DEV_TOKENS !== '1') return;
|
||||
const secret = process.env.IIOS_ATTESTATION_DEV_SECRET;
|
||||
if (!secret) return;
|
||||
await this.prisma.iiosClientRegistry
|
||||
.upsert({
|
||||
where: { clientId: 'crm-support-widget' },
|
||||
create: { clientId: 'crm-support-widget', clientType: 'SUPPORT_BFF', allowedAppIds: ['crm-web'], attestSecret: secret, status: 'ACTIVE' },
|
||||
update: { attestSecret: secret, allowedAppIds: ['crm-web'], status: 'ACTIVE' },
|
||||
})
|
||||
.catch(() => undefined);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user