Skip to main content

regulator-portal-service — Deployment Topology

Version: 1.0 Status: Draft Owner: Regulator-facing + Legal + SRE Last Updated: 2026-04-21 References: SERVICE_OVERVIEW.md, docs/architecture/ADR-0004-national-backbone-resilience.md

Runtime and Kubernetes topology. The service runs as three Deployments (Web BFF, REST API, SIEM forwarder) plus CronJobs for scheduled reports and evidence collection.


1. Runtime​

DimensionChoice
LanguageTypeScript 5.x strict
FrameworkNestJS + Fastify
Web BFFNext.js 14 (App Router)
Node.js20 LTS
ORMPrisma 5.x
PDF generationHandlebars + Puppeteer (for signed PDFs)
HSMPKCS#11 via @ghasi/hsm-client
SFTPssh2-sftp-client with strict host-key
ContainerDistroless gcr.io/distroless/nodejs20

2. Kubernetes Resources​

2.1 Web BFF Deployment​

apiVersion: apps/v1
kind: Deployment
metadata: { name: regulator-portal-web, namespace: ghasi-prod }
spec:
replicas: 3
selector: { matchLabels: { app: regulator-portal, component: web } }
template:
metadata:
labels: { app: regulator-portal, component: web, tier: regulator-facing }
spec:
serviceAccountName: regulator-portal
nodeSelector: { node-pool: np-ctrl }
containers:
- name: web
image: ghcr.io/ghasi/regulator-portal-web:<digest>
ports: [{ name: http, containerPort: 3081 }, { name: metrics, containerPort: 9464 }]
envFrom:
- { configMapRef: { name: regulator-portal-config } }
- { secretRef: { name: regulator-portal-secrets } }
resources:
requests: { cpu: "250m", memory: "256Mi" }
limits: { cpu: "1000m", memory: "1Gi" }
readinessProbe: { httpGet: { path: /health/ready, port: http }, periodSeconds: 5 }
livenessProbe: { httpGet: { path: /health/live, port: http }, periodSeconds: 10 }
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
capabilities: { drop: [ALL] }

2.2 REST API Deployment​

Handles LI workflow, complaint ingest, report generation, attestation catalog.

apiVersion: apps/v1
kind: Deployment
metadata: { name: regulator-portal-api, namespace: ghasi-prod }
spec:
replicas: 3
selector: { matchLabels: { app: regulator-portal, component: api } }
template:
metadata:
labels: { app: regulator-portal, component: api, tier: regulator-facing }
spec:
serviceAccountName: regulator-portal
nodeSelector:
node-pool: np-ctrl
hsm-accessible: "true"
containers:
- name: api
image: ghcr.io/ghasi/regulator-portal-service:<digest>
args: ["node", "dist/apps/api/main.js"]
ports: [{ name: http, containerPort: 3082 }, { name: metrics, containerPort: 9465 }]
envFrom:
- { configMapRef: { name: regulator-portal-config } }
- { secretRef: { name: regulator-portal-secrets } }
resources:
requests: { cpu: "500m", memory: "512Mi" }
limits: { cpu: "2000m", memory: "2Gi" }
volumeMounts:
- { name: tmp, mountPath: /tmp }
- { name: hsm-socket, mountPath: /var/run/hsm }
volumes:
- name: tmp
emptyDir: {}
- name: hsm-socket
hostPath: { path: /var/run/hsm, type: Socket }

2.3 SIEM Forwarder Deployment​

apiVersion: apps/v1
kind: Deployment
metadata: { name: regulator-portal-siem-forwarder, namespace: ghasi-prod }
spec:
replicas: 2 # HA; distributed-lock ensures single runner per destination
selector: { matchLabels: { app: regulator-portal, component: siem-forwarder } }
template:
metadata:
labels: { app: regulator-portal, component: siem-forwarder, tier: regulator-facing }
spec:
serviceAccountName: regulator-portal
nodeSelector: { node-pool: np-ctrl }
containers:
- name: siem-forwarder
image: ghcr.io/ghasi/regulator-portal-service:<digest>
args: ["node", "dist/apps/siem-forwarder/main.js"]
ports: [{ name: metrics, containerPort: 9466 }]
envFrom:
- { configMapRef: { name: regulator-portal-config } }
- { secretRef: { name: regulator-portal-secrets } }
resources:
requests: { cpu: "250m", memory: "256Mi" }
limits: { cpu: "1000m", memory: "1Gi" }
volumeMounts:
- { name: siem-wal, mountPath: /var/lib/siem-wal } # disk-WAL for backpressure
volumes:
- name: siem-wal
persistentVolumeClaim:
claimName: regulator-portal-siem-wal
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: regulator-portal-siem-wal, namespace: ghasi-prod }
spec:
accessModes: [ReadWriteOnce]
resources: { requests: { storage: "50Gi" } }
storageClassName: fast-ssd

2.4 HPA (Web + API)​

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: { name: regulator-portal-api, namespace: ghasi-prod }
spec:
scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: regulator-portal-api }
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource: { name: cpu, target: { type: Utilization, averageUtilization: 65 } }

2.5 PodDisruptionBudgets​

Each Deployment has minAvailable: 2 (or 1 for small forwarder).

2.6 Services​

apiVersion: v1
kind: Service
metadata: { name: regulator-portal-web, namespace: ghasi-prod }
spec: { selector: { app: regulator-portal, component: web }, ports: [{ port: 3081, targetPort: http }], type: ClusterIP }
---
apiVersion: v1
kind: Service
metadata: { name: regulator-portal-api, namespace: ghasi-prod }
spec: { selector: { app: regulator-portal, component: api }, ports: [{ port: 3082, targetPort: http }], type: ClusterIP }

2.7 Ingress (mTLS)​

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: regulator-portal-ingress
namespace: ghasi-prod-edge
annotations:
kong.ingress.kubernetes.io/plugins: mtls-regulator,request-id
spec:
ingressClassName: kong
tls:
- hosts: [regulator.ghasi.io]
secretName: regulator-portal-tls
rules:
- host: regulator.ghasi.io
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: regulator-portal-web, port: { number: 3081 } } }
- path: /v1/
pathType: Prefix
backend: { service: { name: regulator-portal-api, port: { number: 3082 } } }

Kong mtls-regulator plugin configured to require national-PKI client certs with revocation-check (CRL + OCSP staple).

2.8 NetworkPolicy​

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: regulator-portal-ingress, namespace: ghasi-prod }
spec:
podSelector: { matchLabels: { app: regulator-portal } }
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector: { matchLabels: { name: ghasi-prod-edge } }
podSelector: { matchLabels: { app: kong } }
ports: [{ port: 3081, protocol: TCP }, { port: 3082, protocol: TCP }]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: regulator-portal-egress, namespace: ghasi-prod }
spec:
podSelector: { matchLabels: { app: regulator-portal } }
policyTypes: [Egress]
egress:
- to: [{ podSelector: { matchLabels: { app: postgres-primary } } }]
ports: [{ port: 5432, protocol: TCP }]
- to: [{ podSelector: { matchLabels: { app: redis-cluster } } }]
ports: [{ port: 6379, protocol: TCP }]
- to: [{ podSelector: { matchLabels: { app: nats } } }]
ports: [{ port: 4222, protocol: TCP }]
- to: [{ podSelector: { matchLabels: { app: minio } } }]
ports: [{ port: 9000, protocol: TCP }]
- to: [{ podSelector: { matchLabels: { app: hsm-proxy } } }]
ports: [{ port: 9211, protocol: TCP }]
- to: # gRPC mesh to upstream read-through services
- podSelector: { matchLabels: { app: compliance-engine } }
- podSelector: { matchLabels: { app: consent-ledger-service } }
- podSelector: { matchLabels: { app: sender-id-registry-service } }
- podSelector: { matchLabels: { app: cdr-mediation-service } }
- podSelector: { matchLabels: { app: analytics-service } }
ports: [{ port: 50061, protocol: TCP }, { port: 50071, protocol: TCP }, { port: 50091, protocol: TCP }]
- to: # SIEM destinations (external CIDRs per configured destination)
- ipBlock: { cidr: 198.18.1.0/24 } # Splunk example
- ipBlock: { cidr: 198.18.2.0/24 } # QRadar example
- ipBlock: { cidr: 198.18.3.0/24 } # Logstash example
ports: [{ port: 443, protocol: TCP }, { port: 8089, protocol: TCP }] # Splunk HEC
- to: # ATRA SFTP + HTTPS
- ipBlock: { cidr: 198.18.0.0/24 }
ports: [{ port: 22, protocol: TCP }, { port: 443, protocol: TCP }]

3. CronJobs​

NameSchedulePurpose
regulator-daily-report30 6 * * * (daily 06:30 UTC)Generates daily CDR-status report
regulator-monthly-summary0 6 1 * * (1st day, 06:00 UTC)Monthly compliance summary
regulator-evidence-collector0 3 * * * (daily 03:00 UTC)Collects auto-evidence
regulator-auditor-token-sweep*/30 * * * * (every 30 min)Revokes expired auditor tokens
regulator-annual-bundle0 2 1 1 * (Jan 1, 02:00 UTC)Annual attestation bundle

4. Region Affinity​

Regulator portal is sovereign-region only (kbl). Mazar region is read-only standby; no regulator-write surface.


5. Infrastructure Dependencies​

DependencyPurpose
PostgreSQL 16regulator schema
Redis 7Hot cache + distributed locks
NATS JetStreamSIEM consumer source + event publishing
HSM (PKCS#11)PDF + bundle signing
VaultSFTP creds, SIEM creds, signing key handles
S3 (MinIO-compat)Reports, bundles, evidence
ClickHouse (optional, via analytics-service)Long-range audit queries
SPIRE / SPIFFEWorkload identity
upstream gRPC servicescompliance-engine, consent-ledger, sender-id-registry, cdr-mediation, analytics-service

6. Secrets (Vault)​

SecretPath
Postgres dynamic credsecret/data/regulator-portal/db
NATS NKeysecret/data/regulator-portal/nats-nkey
HSM PINsecret/data/regulator-portal/hsm-pin
ATRA SFTP keysecret/data/regulator-portal/atra-sftp
SIEM destination credentialssecret/data/regulator-portal/siem/{destination}
Auditor CA trust anchorssecret/data/regulator-portal/auditor-ca

7. Config (ConfigMap)​

apiVersion: v1
kind: ConfigMap
metadata: { name: regulator-portal-config, namespace: ghasi-prod }
data:
LOG_LEVEL: "info"
REGION: "kbl"
TZ: "UTC"
REGULATOR_DOMAIN: "regulator.ghasi.io"
AUDITOR_DOMAIN: "auditor.ghasi.io"
CRL_URL_PRIMARY: "https://atra-pki.example.af/crl"
OCSP_STAPLE_REQUIRED: "true"
CRL_CACHE_TTL_SECONDS: "14400"
LI_SLA_ACK_HOURS: "1"
LI_SLA_INPROGRESS_HOURS: "4"
LI_SLA_DELIVERED_HOURS: "24"
COMPLAINT_SLA_DAYS: "5"
REPORT_RETENTION_DAYS_HOT: "90"
REPORT_RETENTION_YEARS_COLD: "7"
SIEM_WAL_MAX_BYTES: "5368709120" # 5 GB
AUDITOR_ACCESS_DEFAULT_DAYS: "30"

8. Scaling​

  • Web + API scale on CPU; expected peak ~100 RPS (regulator users are few; auditors sporadic).
  • SIEM forwarder scales via manual replica adjustment; throughput driven by NATS subscriber count, not Web RPS.
  • Report builders are IO-bound on upstream reads; scale-up affects PG + gRPC load on upstream services.

9. Deployment Gate​

  • All 16 spec docs Complete.
  • mTLS test with real ATRA staging cert.
  • SIEM mock test with all 3 destinations.
  • 7-day WAL-drain drill.
  • HSM sign test in staging.
  • Rollback validated.
  • On-call approves.

10. Cost Envelope (monthly, sovereign region only)​

ComponentCost
Web + API pods~$150
SIEM forwarder + WAL disk~$40
CronJobs~$20
Postgres (shared)~$30
Redis (shared)~$15
NATS (shared)~$15
HSM (amortised)~$50
S3 reports + bundles~$10
Egress (ATRA + SIEM)nominal