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