Executive summary
Lumina Media runs entertainment at the scale of a nation state: a global direct-to-consumer service that carries SVOD, AVOD, TVOD and FAST tiers, 24×7 linear channels, live sports and news, a ~400 PB video-on-demand library, partner and affiliate distribution, and a full production/post/archive supply chain. That reaches ~120 million subscribers across web, mobile, smart-TV, console and set-top-box, and peaks at ~45 million concurrent streams during live finals — the single most demanding minute in consumer software, where 45 million players all ask for the same segment inside the same two seconds. Today the estate spans two Media Operations Centres (Los Angeles + London) for live contribution, playout and near-line archive, an on-prem AD DS forest corp.luminamedia.net, SAP back office, and a sprawl of 100+ apps. This document specifies the secure multi-cloud landing zone that becomes the network, identity, governance, delivery and data foundation for running that estate on Azure (East US 2, West Europe, Southeast Asia) and AWS (us-east-1, eu-west-1, ap-southeast-1) — active/active for Tier-1 delivery, and without ever letting a viewer see an outage.
The hard constraints are not the epilogue; they are the design. Every decision here must hold under MovieLabs Enhanced Content Protection + TPN/CDSA for premium studio content, PCI-DSS for billing and payments, GDPR + CCPA for viewer PII and consent, plus SOC 2 and DPP for the operational estate — and it must do so while hitting quality-of-experience numbers a marketing team quotes on stage: video-start-time p95 < 2s, rebuffer ratio < 0.4%, live latency < 8s, playback-failure < 0.5%, and 99.99% availability for the Tier-1 delivery path. The reference bar is three existing KloudVin designs — the zero-downtime multi-cloud landing zone for a universal bank, the secure multi-cloud landing zone for global logistics, and the secure multi-cloud landing zone for healthcare — and this one must match their depth while adding what a streaming operator actually runs: multi-DRM packaging, multi-CDN steering, server-side ad insertion, a dual-plane identity model at 120M scale, and a QoE analytics pipeline that ingests tens of millions of concurrent events.
The design rests on seven load-bearing decisions. Dual-plane identity keeps workforce identity (on-prem AD DS projected to a single Entra ID tenant, with Okta and PIM for SSO/CA) rigorously separate from consumer identity (Azure AD B2C / Amazon Cognito at 120M users) — the two never share a directory, an app registration or a token. Package once, protect once: every asset is transcoded to an ABR ladder, packaged as CMAF and encrypted CENC exactly once, with Widevine/FairPlay/PlayReady keys issued by a SPEKE-class key service, never by a CDN. Multi-CDN by default: three commercial CDNs are steered per-session on live real-user metrics and per-GB cost, fronted by origin shield, signed-URL/JWT token auth, edge WAF, bot and DDoS. Segmented media domains: Live, VOD, CIAM+Subscription, Recommendation, Ad-tech and Corporate each become their own landing zone with default-deny east-west. Tiered resilience: a four-tier RTO/RPO model puts entitlement, playback, DRM-license, origin, live-event control, SSAI-core and CIAM-auth into active/active at RTO ≤15 min / RPO ≤1 min. Guardrails as code at the Azure lm management-group root and AWS Organizations SCP/RCP perimeter make non-compliance structurally impossible. QoE is the SLO: the number the business watches is video-start-time and rebuffer ratio, and the whole platform is instrumented to defend it.
| Decision area | Target-state choice | Why it matters for Lumina Media |
|---|---|---|
| Cloud posture | Dual-cloud with workload affinity (place each domain where it runs best), active/active for Tier-1 delivery | A regional or single-cloud loss never darkens a live final; no lift-and-shift into both clouds |
| Identity | Two planes, never mixed: Entra ID + Okta (workforce) and Azure AD B2C / Cognito (120M viewers) | A CIAM breach cannot reach staff/admin; a workforce compromise cannot touch viewer PII |
| Content protection | Package once: ABR + CMAF + CENC multi-DRM; SPEKE key service; MovieLabs/TPN pipeline controls | Studio licensing and premium windows survive an audit; keys never live on a CDN |
| Delivery | Multi-CDN (CDN-A/B/C) with RUM + cost steering, origin shield, signed URL/token, edge WAF/bot/DDoS | No single CDN failure or price spike degrades QoE for 45M concurrent viewers |
| Connectivity | Dual ExpressRoute + dual Direct Connect from both MOCs; Virtual WAN + Transit Gateway hubs | Live contribution and origin egress never transit the public internet |
| Governance | Azure MG root lm + AWS Organizations / Control Tower, policy-as-code, delegated admin |
Residency, encryption and content-protection guardrails inherit down; teams cannot opt out |
| Segmentation | Per-domain landing zones, own /22 CIDR, default-deny east-west, brokered cross-domain |
A breach in Ad-tech or Corporate is not a breach in Live, VOD or CIAM |
| Resilience | Four tiers; Tier-1 active/active (RTO ≤15 m / RPO ≤1 m); viewers must not see an outage | Entitlement, playback, DRM and origin survive a regional loss with sub-minute data loss |
| Monetisation | SSAI for live + VOD; editorial/ad path separation; PCI-segmented billing | An ad-decision fault degrades ads, not the programme; card data stays in a small PCI scope |
| QoE + analytics | RUM (Conviva-class) + high-scale event ingest feeding a steering + experimentation loop | The KPI an executive watches — VST and rebuffer — is measured and defended continuously |
Target-state overview
The target state reads left to right as five planes: the two on-prem Media Operations Centres and their contribution/playout edge; a private-connectivity layer that reaches both clouds without ever touching the internet; the Azure and AWS platforms with their workload landing zones where the media supply chain is packaged and protected; a multi-CDN edge that steers and secures delivery; and the viewers themselves, measured continuously for QoE. Contribution feeds both clouds over redundant circuits; each cloud carries its own governed platform and segmented workloads; and every viewer request meets a token-and-WAF-gated edge — the origins behind it are never directly exposed.
Each cloud is built as a small set of platform capabilities that never host workloads, plus workload landing zones that do. On Azure that is the lm management-group hierarchy with platform subscriptions lm-plat-{identity,connectivity,management,security}, an Azure Virtual WAN hub per region carrying an Azure Firewall for egress and east-west inspection, and landing-zone subscriptions lm-lz-{live,vod,ciam,reco,adtech,corp,data,sandbox} per environment. On AWS the mirror is Root > Security > Infrastructure > Workloads{Live,VOD,Data,AdTech,Corp} > {Prod,NonProd} plus a Sandbox OU, a Transit Gateway hub with a Gateway Load Balancer inspection VPC, and accounts named lm-<purpose>-<env>. The two clouds are deliberately symmetric in intent — same identity planes, same guardrail classes, same tiering — but idiomatic in mechanism, because forcing Azure to behave like AWS (or vice versa) is how landing zones rot. The Azure spine follows the enterprise-scale landing zone pattern; the AWS spine follows Control Tower multi-account guardrails.
The planes below are the control surfaces every workload inherits. Read this as “what exists once, centrally, so a workload team never re-invents it”:
| Plane | Azure realization | AWS realization | Purpose |
|---|---|---|---|
| Governance / policy | Management groups under root lm; Azure Policy + initiatives; Bicep |
Organizations OUs; SCPs + Resource Control Policies; Control Tower controls | Guardrails inherit down; residency/encryption/content-protection non-negotiable |
| Identity (dual-plane) | Workforce: Entra ID + Okta + PIM/CA. Consumer: Azure AD B2C tenant | Workforce: federated to Entra (OIDC/SAML). Consumer: Amazon Cognito | Staff JML never mixes with 120M-viewer CIAM; two separate token stories |
| Connectivity | Virtual WAN hub /20 per region; Azure Firewall; Private DNS; ExpressRoute GW |
Transit Gateway; GWLB inspection VPC; Route 53 Resolver; Direct Connect GW | Private east-west + inspected egress; contribution never on the internet |
| Delivery edge | Front Door (origin shield / one CDN), steering SDK, WAF, DDoS | CloudFront (origin shield / one CDN), steering SDK, WAF, Shield | Multi-CDN steering, signed URL/token, edge security for every viewer request |
| Security operations | Defender for Cloud; Microsoft Sentinel SIEM; Key Vault (HSM); Wiz; Falcon | Security Hub; GuardDuty; CloudTrail org trail; KMS; Wiz; Falcon | Central detection, immutable audit, CMK/key custody, CSPM + EDR |
| Management / observability | Azure Monitor; central Log Analytics; Dynatrace; QoE RUM; ServiceNow | CloudWatch; central log archive account; Dynatrace; QoE RUM; ServiceNow | One pane of telemetry; QoE and ops on the same rail |
| Workload landing zones | lm-lz-* subscriptions × env; spoke VNets /22; PE subnets /26 |
Workloads OU accounts × env; spoke VPCs /22; PrivateLink subnets /26 |
Where Live, VOD, CIAM, Reco, Ad-tech and Corporate actually run |
Regions carry two distinct jobs that must never be confused: resilience pairs (two regions that back each other active/active for Tier-1 delivery) and residency boundaries (a region that exists to keep a jurisdiction’s viewer PII inside it). Conflating them is how a well-meaning failover becomes a GDPR incident. Lumina’s map pins US/global delivery to the US pair, EU viewer PII to Europe, and APAC audience latency to Southeast Asia:
| Region | Cloud | Role | Residency scope | Tier-1 pattern |
|---|---|---|---|---|
East US 2 |
Azure | Primary Americas | Global content; US viewer PII | Active/active with us-east-1 and West Europe for delivery |
West Europe |
Azure | EU primary | EU viewer PII (GDPR) | Active/active delivery; EU PII stays in-region |
Southeast Asia |
Azure | APAC primary | APAC viewer PII | Active/active delivery; regional audience latency |
us-east-1 |
AWS | Primary Americas | Global content; US viewer PII | Active/active with East US 2 (origin, VOD, data) |
eu-west-1 |
AWS | EU primary | EU viewer PII (GDPR) | Active/active delivery; EU PII stays in-region |
ap-southeast-1 |
AWS | APAC primary | APAC viewer PII | Active/active delivery; regional origin + data |
Architecture principles and operating model
Principles are the tie-breakers you appeal to when two good options collide at 02:00 during a design review — and they only earn their keep if each one is enforceable by a named control and kills a named anti-pattern. “Be secure” is useless. Lumina’s twelve principles each map to a mechanism and a failure mode it forecloses, and several are streaming-specific in a way a generic landing zone would miss (P3, P5, P7, P10):
| # | Principle | What it means in practice | How it is enforced | Anti-pattern it kills |
|---|---|---|---|---|
| P1 | Two identity planes, never mixed | Workforce and consumer identity are separate tenants, tokens and lifecycles | Entra + Okta (staff) vs Azure AD B2C / Cognito (viewers); no shared app reg | A CIAM breach reaching admin, or staff SSO leaking into the viewer app |
| P2 | Identity is the perimeter | Every access is authenticated, authorised, MFA’d, conditional | Entra Conditional Access + PIM; Cognito/B2C token policies | Flat network trust; standing admin credentials |
| P3 | Package once, protect once | One transcode → CMAF → CENC multi-DRM per asset; keys from a key service | Packager + SPEKE/Key Vault/KMS; deny un-encrypted premium egress | Re-encoding per device; DRM keys sitting on a CDN |
| P4 | Guardrails as code, not tickets | Compliance is a policy definition in git, applied at the root | Azure Policy initiatives + AWS SCP/RCP via pipeline | Drift; per-team exceptions that quietly become the norm |
| P5 | QoE is the SLO | Success is VST, rebuffer, latency and playback-failure — not CPU | RUM + synthetic SLOs; steering loop consumes the metric | Green infra dashboards while viewers buffer and churn |
| P6 | Least privilege, just-in-time | No standing production access; elevate through approval + audit | PIM eligible roles; IAM Identity Center session policies | 200 people with permanent Owner/AdministratorAccess |
| P7 | Multi-CDN, no single edge | Every asset is deliverable from ≥2 CDNs; steer on live health + cost | Steering SDK + DNS; origin shield; per-asset cache policy | One CDN outage or price spike degrading 45M viewers |
| P8 | Everything auditable + immutable | Control-plane and PII access logged to WORM storage | Immutable Blob / S3 Object Lock; Sentinel + CloudTrail org trail | Tamperable logs; unprovable “who exported this viewer’s data” |
| P9 | Resilience by tier, not by wish | Each app gets a tier with explicit RTO/RPO and a DR pattern | Tag-driven backup/replication; game-day tests | One-size DR that over-spends Tier-3, under-protects Tier-1 |
| P10 | Editorial and ad paths separate | Ad decisioning cannot break the programme; SSAI degrades gracefully | Separate SSAI service + failover to editorial manifest | An ad-server outage taking a live channel off air |
| P11 | Automate the landing zone | Subs/accounts vended by pipeline with baseline baked in | Account/subscription factory (IaC); no click-ops in prod | Snowflake environments; inconsistent security baselines |
| P12 | Residency is structural | Viewer PII placed by jurisdiction at account/subscription/key level | deny out-of-region resources; region-scoped CMKs |
A failover that moves EU viewer PII to a US region |
The operating model that runs these principles is a thin Cloud Platform / CoE owning the platform planes, a Security & GRC function owning policy, content-protection standards and audit evidence, a Media Engineering guild owning the encode/package/DRM/origin/SSAI supply chain, and workload teams who consume vended landing zones through a self-service factory. The CoE does not deploy workloads; it vends a subscription/account with network, identity, logging and policy baseline already attached, then gets out of the way. Media Engineering owns the golden path for the delivery pipeline so that Live and VOD teams inherit a compliant packager and DRM integration rather than re-implementing CENC. Security & GRC author the guardrails as code and own the TPN/MovieLabs and PCI evidence, but do not gate every deployment by hand — the policies do that continuously. This split is what lets a 120M-subscriber operator move without a central bottleneck, and it is why P3, P4 and P11 exist:
| Capability | Cloud Platform / CoE | Security & GRC | Media Engineering | Workload team | Primary tooling |
|---|---|---|---|---|---|
| Management-group / OU tree | Own | Consulted | Inherits | Inherits | Bicep / Terraform, Control Tower |
| Guardrail policies (Policy/SCP/RCP) | Implements | Own | Complies | Complies | Policy-as-code pipeline |
| Landing-zone vending | Own (factory) | Reviews baseline | Consumes | Requests + consumes | Subscription/account factory |
| Network hub + firewall | Own | Reviews rules | Requests flows | Requests spoke + rules | Virtual WAN, Transit Gateway |
| Identity — workforce (PIM/CA) | Operates | Own policy | Uses eligible roles | Uses eligible roles | Entra ID, Okta, IAM Identity Center |
| Identity — consumer (CIAM) | Enables platform | Own privacy standard | Consulted | Own (viewer flows) | Azure AD B2C, Amazon Cognito |
| Encode / package / DRM / origin | Provides landing zone | Owns content-protection std | Own | Consumes golden path | Media packager, SPEKE, Key Vault/KMS |
| Multi-CDN + edge security | Provides platform | Owns WAF/bot policy | Own steering | Requests cache policy | Front Door/CloudFront, steering SDK |
| QoE + analytics ingest | Provides pipeline | Owns privacy/consent | Instruments players | Own their KPIs | RUM SDK, Event Hubs/Kinesis |
| DR tier + game-days | Provides patterns | Validates evidence | Owns delivery RTO/RPO | Own their RTO/RPO | Backup, replication, traffic steering |
Media domains and segmentation
Segmentation is the single most important streaming-specific decision in the whole design, because the blast radius of a breach in an under-segmented media estate is either 120M viewers’ PII, the studio content that underwrites every licensing deal, or the billing system that funds the company. Lumina splits the estate into six media/functional domains, each realised as its own landing zone with a dedicated /22 from the Azure 10.20.0.0/12 super-net (AWS mirrors from 10.40.0.0/12, on-prem is 10.0.0.0/12), default-deny east-west, and its own data-sensitivity and content-protection posture. The only traffic that crosses a domain boundary is a governed contract — an entitlement check, a viewing-event stream, a personalised manifest, a billing settlement — and every one is brokered by the event mesh or integration engine and inspected at the hub firewall. There is no flat network in which a compromised ad partner can read the entitlement database, or a corporate laptop can reach a premium origin.
The domain matrix below is the authoritative placement and isolation reference. Cloud placement follows workload affinity (P-cloud posture): live encode/package/origin, CIAM and the entitlement plane lean Azure (closest to Entra, B2C and the on-prem MOCs), while the ~400 PB VOD library, the recommendation/analytics estate and much of Ad-tech lean AWS (object-storage economics, Lake Formation governance, SageMaker), with cross-cloud replication for DR. Every premium-content and PII-bearing row carries private endpoints, CMK and immutable audit as a non-negotiable baseline. The VOD deep-dive lives in video-on-demand streaming on AWS with multi-DRM; the consumer-identity deep-dive in Entra External ID / CIAM custom policies; the edge in multi-CDN edge with origin cloaking.
| Domain | Key applications | Data classes | Landing zone (Azure sub / AWS OU) | CIDR /22 |
Cloud placement | Tier | Isolation boundary |
|---|---|---|---|---|---|---|---|
| Live | Contribution ingest, live encode/package, linear playout, SSAI, live origin, event control | Premium content, ad markers (SCTE-35) | lm-lz-live / Workloads/Live |
10.20.16.0/22 |
Azure primary (EUS2+WEU a/a), AWS active | Tier-1 | Redundant origin; event-day freeze; editorial/ad path split |
| VOD | Transcode farm, packaging, VOD origin, catalog, ~400 PB library | Premium content, metadata | lm-lz-vod / Workloads/VOD |
10.20.20.0/22 |
AWS primary (S3 economics), Azure Blob cache/DR | Tier-1 playback / Tier-2 archive | Own library; lifecycle + Object Lock; no direct viewer read |
| CIAM + Subscription | Azure AD B2C / Cognito, token service, entitlement, subscription mgmt, billing | Viewer PII, PCI (billing) | lm-lz-ciam / Workloads/Corp |
10.20.24.0/22 |
Azure primary (B2C), AWS Cognito a/a | Tier-1 | PE-only; PCI scope; bot/rate-limit; never mixed with workforce IdP |
| Recommendation | Reco models, feature store, watch-history, A/B experimentation | De-identified viewing events, model features | lm-lz-reco / Workloads/Data |
10.20.28.0/22 |
AWS primary (SageMaker/Lake), Azure secondary | Tier-2 | Reads event stream only; no CIAM DB read path |
| Ad-tech | SSAI, ad-decision server (ADS), VAST/VMAP, ad-reporting | Ad impressions, some targeting PII | lm-lz-adtech / Workloads/AdTech |
10.20.32.0/22 |
AWS primary, Azure presence | Tier-1 (SSAI serve) / Tier-2 (report) | Editorial/ad path separation; consent-gated targeting |
| Corporate | SAP S/4HANA, HR, finance, content ops, collaboration | Business, some PII | lm-lz-corp / Workloads/Corp |
10.20.36.0/22 |
Azure primary (M365) | Tier-2 | Segregated from delivery + CIAM; separate IdP groups |
| Data & analytics | QoE ingest, hot/cold stores, RUM, warehouse, DSAR pipeline | De-identified events, aggregated PII | lm-lz-data / Workloads/Data |
10.20.40.0/22 |
AWS primary (Lake Formation), Azure secondary | Tier-2 | Ingest-only inbound; export audit; DSAR fulfilment |
| Sandbox / dev | Dev, test, load-generation, synthetic-content pipelines | Synthetic only, no prod PII | lm-lz-sandbox / Sandbox OU |
10.20.44.0/22 |
Both, isolated | Tier-3 | No prod data; no prod network path |
Cross-domain traffic is a small, explicit, auditable set of contracts — not an open mesh. Each is a real streaming integration pattern with a defined transport and a single broker, which is exactly what makes end-to-end traceability, consent enforcement and resilient replay possible. The event mesh follows the event-driven EventBridge/SQS/Lambda pattern, and the ingest is sized with the backpressure and flow-control discipline:
| From → To | Contract / standard | Transport | Broker | Control |
|---|---|---|---|---|
| Live → VOD | Catch-up / start-over / DVR record | Segment copy + manifest handoff | Packaging pipeline | Rights window; retention policy; watermark |
| VOD / Live → CIAM | Entitlement + concurrency + geo check | HTTPS API (JWT) over private link | Entitlement service | Token validation; concurrency counter; geo rules |
| CIAM → Ad-tech / Reco | Viewing + subscription events (de-identified) | Event stream (Event Hubs / Kinesis) | Event mesh | De-id + consent flag; no raw PII on the stream |
| Ad-tech → Live / VOD | Personalised manifest (SSAI) + ad break | Manifest manipulation + VAST/VMAP | SSAI service | SCTE-35 markers; editorial/ad split; graceful fallback |
| Reco → Playback / UI | Recommendation rows | HTTPS API (cached, rate-limited) | API gateway | Rate-limit; graceful fallback to editorial rows |
| CIAM → Corporate | Billing / revenue / PCI settlement | Tokenised API / X12 over private link | Integration + payment gateway | PCI scope; tokenised PAN; no card data at rest outside scope |
| All → Data & analytics | QoE + telemetry ingest | Event stream + batch | Event mesh + ingest | Ingest-only; export audit; backpressure/DLQ |
Requirements and non-functional targets
Non-functional targets are where “compliant” and “high quality” become numbers an engineer can build to and an auditor or a Chief Product Officer can test. Lumina’s resilience model is four tiers, each with an explicit RTO, RPO, availability SLO, multi-region pattern and backup posture — assigned by tag so the platform, not a human, enforces the right protection. The delivery core (entitlement, playback API, DRM-license, origin, live-event control, SSAI-core, CIAM-auth) is Tier-1 at RTO ≤15 min / RPO ≤1 min, delivered by active/active per the Azure active/active and AWS multi-region active/active patterns — because a viewer mid-final must not see a stall. Tier-0 (identity, DNS, network control, privileged access, core security, multi-CDN steering control) is the foundation everything depends on and carries the tightest target of all:
| Tier | Example workloads | RTO | RPO | Availability SLO | Multi-region pattern | Backup / immutability |
|---|---|---|---|---|---|---|
| Tier-0 | Entra/AD DS, DNS, network control plane, PIM, Key Vault/KMS, Sentinel, multi-CDN steering control | ≤15 min | ≈0 | 99.99% | Global/active-active; region-independent | Geo-redundant; HSM key backup; immutable audit |
| Tier-1 | Entitlement, playback API, DRM-license, origin, live-event control, SSAI-core, CIAM-auth | ≤15 min | ≤1 min | 99.99% | Active/active cross-region (EUS2+us-east-1+WEU) | Continuous replication; PITR; immutable + geo copy |
| Tier-2 | Business apps, analytics, support, ad-reporting, reco training, corporate | ≤4 h | ≤1 h | 99.9% | Active/passive warm standby; cross-region backup | Nightly + hourly log; cross-region restore tested |
| Tier-3 | Dev, sandbox, load-generation, batch | ≤24 h | ≤24 h | 99.5% | Backup/restore; single region acceptable | Daily backup; standard redundancy |
Resilience keeps the service up; quality-of-experience is what keeps viewers watching, and it is measured at the player, not the server. These are the targets the steering loop, the origin design and the ABR ladder all exist to defend — every one a real-user or synthetic number a dashboard proves against an SLO, not an aspiration:
| QoE / performance metric | Target | Measured where | Why it matters |
|---|---|---|---|
| Video-start-time (VST) p95 | < 2s | Player SDK RUM (Conviva-class) | Abandonment climbs sharply past 2s; first impression of every session |
| Rebuffer ratio | < 0.4% of watch time | Player SDK RUM | The single biggest driver of churn and complaints |
| Live latency (glass-to-glass) | < 8s (LL-CMAF) | Synthetic + player timestamp | Social spoilers beat a laggy live stream; sports demands it |
| Playback-failure rate | < 0.5% of attempts | Player SDK + gateway | A failed start is a lost session and a support ticket |
| Tier-1 availability | 99.99% | Synthetic + SLO burn | Entitlement/playback/DRM must not have a visible outage |
| CDN cache hit-ratio | > 95% edge / > 98% with shield | CDN logs + origin shield | Origin cost and rebuffer both track cache misses |
| DRM license latency p95 | < 300 ms | License service metric | A license stall delays the first frame as surely as a slow origin |
QoE and resilience are only real if the security, privacy, content-protection and durability posture holds underneath them. The NFRs below are the measurable states that make the compliance obligations concrete across both clouds — the mechanism differs, the target does not:
| NFR domain | Target / measurable state | Azure control | AWS control |
|---|---|---|---|
| Premium content protection | MovieLabs ECP + TPN controls on 100% of premium pipelines | Private origin + CMK + Bicep pipeline | PrivateLink + KMS + SPEKE |
| DRM coverage | 100% premium assets CMAF/CENC multi-DRM | Packager + Key Vault; SPEKE | MediaPackage/SPEKE + KMS |
| Viewer PII exposure | Zero public endpoints on CIAM/PII stores | Private Endpoint + Policy deny public | PrivateLink + SCP + BlockPublicAccess |
| PCI billing scope | Cardholder data tokenised; PCI zone segmented | Segregated sub + tokenisation | Segregated account + tokenisation |
| Encryption in transit | TLS 1.2+ everywhere; signed token at edge | Front Door/APIM policy | CloudFront/API GW policy |
| Consent + privacy (GDPR/CCPA) | Consent captured; DSAR ≤ statutory SLA | B2C + Purview + DSAR pipeline | Cognito + Lake Formation + DSAR |
| Identity separation | Workforce and CIAM never share a directory | Entra/Okta (staff) vs B2C (viewers) | Entra-fed IAM vs Cognito |
| Privileged access | 0 standing prod admins; JIT only | PIM eligible + approval | IAM Identity Center + session policy |
| Data residency | EU viewer PII stays in EU regions | Policy allowedLocations (EU) |
SCP aws:RequestedRegion deny |
| Bot / credential-stuffing | Login + token endpoints bot-scored + rate-limited | Front Door WAF + bot manager | WAF + Shield + rate-based rules |
| Event durability | 0 lost QoE/entitlement events; replay ≥ 72 h | Event Hubs + Capture + DLQ | Kinesis + Firehose + archive/replay |
| Audit immutability | Control-plane + PII access WORM ≥ 1 yr | Immutable Blob + Sentinel | S3 Object Lock + CloudTrail org trail |
Capacity and performance carry streaming-specific numbers too: the live plane must absorb dual contribution (SRT/RIST/Zixi) from both MOCs and fan a single live event out to 45M concurrent players without an origin-fetch storm, so origin shield and per-segment cache policy are sized for the finals, not the average night; the VOD plane must serve diagnostic-quality bitrates from a ~400 PB archive with hot-tier latency for new releases and lifecycle demotion of long-tail to archive; the CIAM token service must sustain login and entitlement bursts at launch and event start without becoming the bottleneck that fails 45M playbacks; and the QoE analytics pipeline must ingest tens of millions of concurrent events without back-pressure loss. These ceilings (Event Hubs throughput units, CDN egress commits, APIM/token capacity, storage tiers) are specified per workload in the detailed design; the platform’s job is to make them elastic and observable rather than fixed.
Requirements traceability
Traceability is the artefact an auditor — or a studio’s content-security assessor — asks for first: for every requirement, the driver that mandates it, the control that satisfies it, and the concrete cloud service that implements the control on both clouds, plus how you prove it. This matrix is the spine that ties the compliance and QoE obligations in the executive summary to the segmentation and NFRs above — and every later section of this document elaborates a row of it. Requirement IDs are stable references used throughout the design.
| Req ID | Requirement | Compliance / driver | Control | Azure service | AWS service | Evidence / verify |
|---|---|---|---|---|---|---|
| R-01 | Premium content protected end-to-end | MovieLabs ECP; TPN/CDSA | Private pipeline + multi-DRM + watermark | Media Services + Key Vault + private origin | MediaConnect/MediaPackage + SPEKE + KMS | TPN assessment; no public origin |
| R-02 | All premium assets multi-DRM encrypted | Studio licensing; MovieLabs | CMAF/CENC (cbcs+cenc); key rotation | Packager + Key Vault | MediaPackage/SPEKE + KMS | DRM coverage report; key-rotation log |
| R-03 | Viewer PII never traverses public internet | GDPR; CCPA | PE-only + deny public | Private Endpoint + Policy | PrivateLink + SCP | No public IP on PII service |
| R-04 | Billing is PCI-DSS compliant | PCI-DSS | Tokenisation + segmented PCI zone | Segregated sub + tokenise | Segregated account + tokenise | PCI ROC; scope diagram |
| R-05 | Consent captured; honour DSAR | GDPR Art. 7/15–17; CCPA | Consent store + DSAR pipeline | B2C + Purview | Cognito + Lake Formation | DSAR SLA logs; consent record |
| R-06 | Workforce and consumer identity separate | Security; least-mix | Two IdP planes | Entra/Okta (staff) + B2C (viewers) | Entra-fed IAM + Cognito | Directory inventory; no shared app reg |
| R-07 | EU viewer PII stays in EU | GDPR Art. 44–49 | Residency guardrail | Policy allowedLocations |
SCP aws:RequestedRegion |
Policy compliance; region audit |
| R-08 | Tier-1 RTO ≤15 m / RPO ≤1 m | Business continuity | Active/active + replication | Front Door + Cosmos DB geo | Route 53 + DynamoDB GT / Aurora Global | Game-day RTO/RPO evidence |
| R-09 | Playback survives a CDN failure | QoE; availability | Multi-CDN steering + origin shield | Traffic Manager + steering SDK | Route 53 + steering SDK | CDN failover drill; steer logs |
| R-10 | Meet VST/rebuffer/latency QoE targets | Customer experience | RUM + steering loop | Azure Monitor + RUM SDK | CloudWatch + RUM SDK | QoE dashboard vs SLO |
| R-11 | Enforce entitlement (plan/concurrency/geo/device) | Anti-fraud; licensing | Entitlement service + session token | APIM + entitlement svc | API Gateway + entitlement svc | Concurrency test; geo-block test |
| R-12 | Guardrails enforced, not optional | SOC 2; governance | Policy-as-code at root | Azure Policy at lm MG |
SCP/RCP + Control Tower | Deny-event logs; drift report |
| R-13 | Streaming events durable + replayable | Ad/QoE integrity; revenue | Broker + DLQ + replay | Event Hubs + Capture | Kinesis + DLQ + archive | 0 lost events; replay test |
| R-14 | Single workforce identity lifecycle (JML) | SOC 2 | Entra hub + federation | Entra Connect + Governance | Federated to Entra IdP | Joiner/leaver logs; access reviews |
| R-15 | Central detection + response | SOC 2; NIST IR | SIEM/SOAR + CSPM/EDR | Sentinel + Defender + Wiz/Falcon | Security Hub + GuardDuty + Wiz/Falcon | MTTD/MTTR; playbooks |
| R-16 | Immutable, restorable backups | Business continuity | Tiered backup + immutability | Azure Backup + immutable vault | AWS Backup + Vault Lock | Restore test; lock state |
| R-17 | De-identify viewing data before analytics/reco | GDPR minimisation | De-id gate before analytics | Data Factory + Purview | Glue + Lake Formation | De-id job logs; no PII in lake |
| R-18 | Control content-sharing / credential fraud | Anti-piracy; revenue | Concurrency + device + watermark + bot | B2C + WAF + watermark | Cognito + WAF + watermark | Fraud rate; concurrency alerts |
Azure landing zone
Azure carries Lumina Media’s largest consumer-facing footprint — the CIAM token service at 120M-subscriber scale, the entitlement and multi-DRM license plane, the live-event and origin tier, the personalisation stack and the QoE lake — so its foundation is not a generic landing zone but the Azure Landing Zone (ALZ) accelerator with a direct-to-consumer streaming overlay, deployed through Azure Verified Modules (AVM) so the hierarchy itself is reproducible Bicep, not a console artefact nobody can rebuild during a live final. A Tenant Root Group anchors a management-group tree that pushes Azure Policy and RBAC down by inheritance — the single mechanism that lets a control authored once apply to every subscription beneath it. When you peak at 45M concurrent and run active/active across three regions, that inheritance is exactly what stops an encryption, content-isolation or residency rule from silently lapsing in some ad-tech subscription nobody is watching at kickoff. Directly beneath the tenant root sits the intermediate root management group lm, and under it four deliberately different policy postures: an lm-platform group for shared services, an lm-landingzones group for workloads, an lm-sandbox group for guarded experimentation where premium content and viewer PII are structurally forbidden, and an lm-decommissioned group for subscriptions being offboarded. The full tree is code:
Tenant Root Group
└── lm # intermediate root — regs + residency + content rules
├── lm-platform
│ ├── lm-plat-identity # Entra Connect cloud sync, PHS, Okta federation, private DNS, DC extension
│ ├── lm-plat-connectivity # Virtual WAN hubs, Azure Firewall Premium, dual ExpressRoute, multi-CDN egress
│ ├── lm-plat-management # Log Analytics, Azure Monitor, Backup / Recovery Services
│ └── lm-plat-security # Sentinel, Key Vault Managed HSM (content keys + CMK), break-glass, Defender
├── lm-landingzones
│ ├── lm-lz-live → -prod / -nonprod (Tier-1 contribution, encode/package, LL-CMAF origin)
│ ├── lm-lz-vod → -prod / -nonprod (Tier-1 per-title encode, VOD origin, 400 PB archive)
│ ├── lm-lz-ciam → -prod / -nonprod (Tier-1 B2C 120M, entitlement, multi-DRM license)
│ ├── lm-lz-reco → -prod / -nonprod (Tier-2 recommendations, feature store, A/B)
│ ├── lm-lz-adtech → -prod / -nonprod (Tier-1 SSAI, ad-decision, VAST/VMAP, SCTE-35)
│ ├── lm-lz-corp → -prod / -nonprod (Tier-2 SAP, billing/payments PCI CDE, HR)
│ └── lm-lz-data → -prod / -nonprod (Tier-2 QoE ingest, lakehouse, ad-reporting)
├── lm-sandbox
│ └── lm-lz-sandbox # detached policy, egress-locked, content + PII denied by policy
└── lm-decommissioned # deny-all posture, pending deletion
The lm-platform group holds four dedicated platform subscriptions; the separation keeps shared services out of any single workload’s blast radius and lets each platform team run its own change cadence. Because Tier-1 delivery carries RTO ≤ 15 min and RPO ≤ 1 min — viewers must never see an outage — the platform subscriptions are themselves Tier-0 (RPO ≈ 0): if identity, DNS, the network control plane or the content-key custodian is down, nothing else can safely serve.
| Platform subscription | Region footprint | What it holds | Why it is isolated |
|---|---|---|---|
lm-plat-identity |
East US 2 + West Europe | Entra Connect cloud sync, Password Hash Sync, Seamless SSO, Okta federation for workforce SSO, private DNS resolver, extended AD DS domain controllers fronting corp.luminamedia.net |
Workforce identity is Tier-0; a workload compromise must never reach the directory plane, and CIAM stays entirely separate from it |
lm-plat-connectivity |
East US 2, West Europe, Southeast Asia | Virtual WAN secured hubs (/20 per region), Azure Firewall Premium, dual ExpressRoute gateways to LA + London MOCs, DDoS Network Protection, DNS Private Resolver, Bastion, controlled egress to the three commercial CDN origins | Single central inspection and egress point; workloads never own their own internet path or CDN origin exposure |
lm-plat-management |
East US 2 (US), West Europe (EU) | Log Analytics workspaces (US + EU, residency-split), Azure Monitor, Automation, Update Manager, Recovery Services vaults, Dynatrace/ServiceNow connectors | Observability and backup must survive a workload-subscription failure or a ransomware event |
lm-plat-security |
East US 2 (US), West Europe (EU) | Microsoft Sentinel, Key Vault Managed HSM (FIPS 140-3 Level 3) holding DRM content keys and CMK, the two break-glass identities, Defender for Cloud, Wiz + CrowdStrike Falcon signal ingest | Security tooling and key custody — the crown jewels for premium-content protection — must survive a compromise of everything else |
The lm-landingzones group then splits by streaming domain, not org chart, so a customer-facing live-event workload and an internal ad-reporting workload never share a guardrail set sized for the wrong risk. Each landing-zone family fans into -prod and -nonprod subscriptions, addressed out of the Azure super-net 10.20.0.0/12 with /22 spoke allocations and /26 private-endpoint subnets, and each carries a data-classification, criticality-tier and licensing-territory tag from birth.
| Landing-zone subscription | Parent MG | Tier | Sensitive data | Home regions | Env pattern | Primary systems |
|---|---|---|---|---|---|---|
lm-lz-live-* |
lm-lz-live |
Tier-1 | Premium content | East US 2 + West Europe + SE Asia (a/a) | prod / nonprod | SRT/RIST/Zixi contribution, multi-zone encode/package, LL-CMAF origin, SCTE-35, linear playout, blackout control |
lm-lz-vod-* |
lm-lz-vod |
Tier-1 | Premium content | East US 2 + West Europe + SE Asia | prod / nonprod | Per-title encode, CMAF/HLS/DASH packaging, VOD origin, 400 PB tiered archive, catalog sync, cache warming |
lm-lz-ciam-* |
lm-lz-ciam |
Tier-1 | Viewer PII | East US 2 + West Europe + SE Asia | prod / nonprod | Azure AD B2C (120M), token service, entitlement (sub + concurrency + geo + device), multi-DRM license, consent, DSAR |
lm-lz-reco-* |
lm-lz-reco |
Tier-2 | Viewer behaviour | East US 2 + West Europe | prod / nonprod | Two-tower recommenders, feature store, A/B assignment, model serving |
lm-lz-adtech-* |
lm-lz-adtech |
Tier-1 | Limited PII | East US 2 + West Europe + SE Asia | prod / nonprod | SSAI, ad-decision server, VAST/VMAP, personalised manifest, SCTE-35 conditioning |
lm-lz-corp-* |
lm-lz-corp |
Tier-2 | Cardholder (CDE) + HR PII | East US 2 | prod / nonprod | SAP back office, billing/payments PCI CDE (tokenised), partner/affiliate settlement, HR, M365 adjacency |
lm-lz-data-* |
lm-lz-data |
Tier-2 | Viewer PII + QoE | East US 2 + West Europe | prod / nonprod | QoE/RUM ingest (Conviva-class, tens of millions concurrent), hot/cold stores, lakehouse, ad-reporting |
What makes this a streaming landing zone rather than a generic one is the control plane wrapped around the hierarchy, assigned at the lm and lm-landingzones scopes so every subscription beneath inherits it. The built-in PCI DSS v4.0, ISO/IEC 27001:2013 and SOC 2 regulatory-compliance initiatives run in audit posture for continuous evidence, while a custom lm-content-baseline initiative enforces the non-negotiables for premium content and viewer PII — private endpoints, customer-managed-key encryption, immutable (WORM) content storage and approved-region placement — with Deny and DeployIfNotExists effects. The assignment is Bicep at management-group scope, with a system-assigned identity so DINE remediations can run:
targetScope = 'managementGroup'
@description('Approved Azure regions for Lumina Media workloads (US / EU / APAC).')
param allowedLocations array = [ 'eastus2', 'westeurope', 'southeastasia' ]
@description('Built-in PCI DSS v4.0 initiative — resolve display name in the pipeline to avoid a stale GUID.')
param pciDssV4SetId string = '/providers/Microsoft.Authorization/policySetDefinitions/c676748e-3af9-4e22-bc28-50feed564afb'
// Custom content + PII baseline: private endpoints + CMK + WORM + region pinning (Deny / DINE)
resource contentBaseline 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
name: 'lm-content-baseline'
location: 'eastus2'
identity: { type: 'SystemAssigned' } // required for DINE remediation tasks
properties: {
displayName: 'Lumina content + PII baseline — private endpoints, CMK, WORM, US/EU/APAC only'
policyDefinitionId: tenantResourceId(
'Microsoft.Authorization/policySetDefinitions', 'lm-content-baseline')
enforcementMode: 'Default' // Deny/DINE actively enforced (not DoNotEnforce)
parameters: { allowedLocations: { value: allowedLocations } }
}
}
// Built-in PCI DSS v4.0 — audit posture, continuous evidence for the billing CDE
resource pciDss 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
name: 'lm-pci-dss-v4'
properties: {
displayName: 'PCI DSS v4.0'
policyDefinitionId: pciDssV4SetId
enforcementMode: 'Default'
}
}
Application subscriptions are never hand-built. A subscription vending factory — the AVM avm/ptn/lz/sub-vending pattern — places each new subscription under the correct management group, inherits policy, assigns RBAC, applies the cost, data-classification, tier and licensing-territory tags, peers it to the regional Virtual WAN hub, and enrols it in logging and Defender before a single workload resource exists. A live-delivery production environment arrives already governed, well inside the vend in under one business day target — the gated self-service discipline described in ServiceNow-gated cloud provisioning of self-service landing zones:
targetScope = 'tenant'
module liveProd 'br/public:avm/ptn/lz/sub-vending:0.3.0' = {
name: 'vend-lm-lz-live-prod'
params: {
subscriptionAliasName: 'lm-lz-live-prod'
subscriptionDisplayName: 'lm-lz-live-prod'
subscriptionBillingScope: billingScope
subscriptionManagementGroupId: 'lm-lz-live' // inherits lm-content-baseline
subscriptionTags: {
dataClass: 'Content', tier: 'Tier-1', residency: 'US'
licensingTerritory: 'NA', costCenter: 'LIVE-01'
}
virtualNetworkEnabled: true
virtualNetworkAddressSpace: [ '10.20.16.0/22' ] // /22 spoke from 10.20.0.0/12
virtualNetworkPeeringEnabled: true
hubNetworkResourceId: hubVwanEastUs2Id // peer to regional hub
}
}
Several vending inputs are load-bearing rather than cosmetic — each one either drives a policy decision or wires the subscription into the platform, so the environment is compliant the moment it exists.
| Vending input | Example value | What it drives |
|---|---|---|
subscriptionManagementGroupId |
lm-lz-live |
Placement under the right MG → inherits lm-content-baseline Deny/DINE |
subscriptionTags.dataClass |
Content | PII | CDE |
Policy predicate that forces private endpoints, CMK and WORM on the matching resources |
subscriptionTags.residency |
US | EU | APAC |
Selects paired region set and residency-split Log Analytics workspace |
subscriptionTags.licensingTerritory |
NA | EMEA | APAC |
Drives geo-blackout and content-territory guardrails for rights-limited titles |
subscriptionTags.tier |
Tier-1 |
Sets backup RPO/RTO policy and Defender plan tier |
virtualNetworkAddressSpace |
10.20.16.0/22 |
/22 spoke from the 10.20.0.0/12 super-net; non-overlapping by IPAM |
hubNetworkResourceId |
regional Virtual WAN hub | Auto-peers the spoke; forces egress through Azure Firewall Premium |
Two matter most: dataClass=Content is what the lm-content-baseline policies key on to force private endpoints, CMK content keys and immutable storage, while residency and licensingTerritory select the paired region set and which titles the subscription may serve. This mirrors the enterprise-scale landing zone management-group design but tightens it for premium content and 120M-subscriber PII.
The lm tree vends and pushes policy left-to-right into platform then landing-zone subscriptions; every arrow is one-way inheritance a workload can build within but never weaken.
AWS landing zone
AWS mirrors the same intent through AWS Organizations with a multi-account model delivered by Control Tower and Account Factory for Terraform (AFT) — the same philosophy (accounts as the unit of isolation, guardrails inherited from above) in that cloud’s native primitives. A management account sits at the apex purely as the org root and billing anchor, deliberately empty of workloads. Beneath it the organisation is partitioned into organisational units whose boundaries follow blast-radius and audit lines, not the org chart: the Security OU isolates the accounts that must survive a compromise of everything else; the Infrastructure OU carries the shared network and services (including SPEKE-based DRM key origination); and workloads split by streaming domain into Prod and NonProd child OUs, with a detached Sandbox OU where premium content and viewer PII are denied outright. Accounts follow the lm-<purpose>-<env> convention, addressed from the AWS super-net 10.40.0.0/12:
Root (lm Organization)
├── Security OU
│ ├── lm-logarchive-prod # org CloudTrail sink → S3 Object Lock (compliance mode)
│ ├── lm-audit-prod # AWS Config aggregator, Security Hub delegated admin
│ └── lm-sectooling-prod # GuardDuty, Macie, Detective, Inspector, Wiz + Falcon delegated admin
├── Infrastructure OU
│ ├── lm-network-prod # Transit Gateway, Network Firewall, ingress/egress + multi-CDN origin VPCs
│ └── lm-sharedsvcs-prod # IAM Identity Center, AD Connector, private DNS, ECR, SPEKE key service
└── Workloads OU
├── Live → lm-live-prod / lm-live-nonprod (Tier-1, MediaLive encode/package, origin, SSAI)
├── VOD → lm-vod-prod / lm-vod-nonprod (Tier-1, MediaConvert, S3 origin 400 PB, CloudFront)
├── Data → lm-data-prod / lm-data-nonprod (Tier-2, Kinesis QoE ingest, lakehouse, reco ML)
├── AdTech → lm-adtech-prod / lm-adtech-nonprod (Tier-1, SSAI, ad-decision, VAST/VMAP)
├── Corp → lm-corp-prod / lm-corp-nonprod (Tier-2, billing PCI CDE, partner settlement)
└── Sandbox OU (detached) → lm-sandbox-* (SCP: deny content/media services + regions)
The Security OU is the estate’s spine. lm-logarchive-prod holds the tamper-evident record: the org-wide CloudTrail trail lands in S3 with Object Lock (compliance mode), write-once and delete-proof even for the account owner, satisfying SOC 2 and PCI DSS retention. lm-audit-prod aggregates AWS Config and runs Security Hub as delegated administrator; lm-sectooling-prod runs GuardDuty, Macie (which discovers viewer PII in S3), Detective and Inspector across every account, forwarding to the cross-cutting Wiz + CrowdStrike Falcon estate. The Infrastructure OU’s lm-network-prod owns the regional Transit Gateway and AWS Network Firewall, so live, VOD and ad-tech VPCs are segmented and every east-west and egress flow — including the controlled path to the three CDN origins — is inspected, never peered directly.
| Account | OU | Purpose | Sensitive data | Home region(s) | CIDR (primary) |
|---|---|---|---|---|---|
lm-management |
Root | Billing, org root, Control Tower home — no workloads | No | us-east-1 | n/a |
lm-logarchive-prod |
Security | Immutable CloudTrail / Config log sink (Object Lock) | Metadata | us-east-1 | 10.40.0.0/24 |
lm-audit-prod |
Security | Config aggregator, Security Hub, Audit Manager | No | us-east-1 | 10.40.1.0/24 |
lm-sectooling-prod |
Security | GuardDuty, Macie, Detective, Inspector, Wiz/Falcon | Findings | us-east-1 | 10.40.2.0/24 |
lm-network-prod |
Infrastructure | Transit Gateway, Network Firewall, ingress/egress + CDN-origin VPCs | In transit | us-east-1, eu-west-1, ap-southeast-1 | 10.40.16.0/20 |
lm-sharedsvcs-prod |
Infrastructure | IAM Identity Center, AD Connector, private DNS, ECR, SPEKE DRM key service | No | us-east-1 | 10.40.32.0/20 |
lm-live-prod |
Workloads/Live/Prod | Elemental MediaLive encode/package, LL-CMAF origin, SSAI | Premium content | us-east-1 + eu-west-1 + ap-southeast-1 (a/a) | 10.41.0.0/16 |
lm-vod-prod |
Workloads/VOD/Prod | MediaConvert per-title encode, S3 origin (400 PB), CloudFront | Premium content | us-east-1 + eu-west-1 + ap-southeast-1 | 10.42.0.0/16 |
lm-data-prod |
Workloads/Data/Prod | Kinesis QoE ingest, lakehouse, recommendation ML, Cognito CIAM surfaces | Viewer PII | us-east-1 + eu-west-1 | 10.43.0.0/16 |
lm-adtech-prod |
Workloads/AdTech/Prod | SSAI, ad-decision server, VAST/VMAP, SCTE-35 conditioning | Limited PII | us-east-1 + eu-west-1 + ap-southeast-1 | 10.44.0.0/16 |
lm-corp-prod |
Workloads/Corp/Prod | Billing/payments CDE (tokenised), partner/affiliate settlement | Cardholder (CDE) | us-east-1 | 10.45.0.0/16 |
Guardrails on the AWS side are Service Control Policies that inherit down the OU tree, expressing exactly the controls the Azure hierarchy enforces on the other cloud: approved regions only, no public exposure of content or PII stores, mandatory customer-managed KMS encryption, premium-content isolation with immutable storage, and an unbreakable path from every account to the immutable Log Archive. The OU tree, its SCPs and the accounts are all Terraform — an application account is never a manual ticket but a reviewed pull request through AFT that yields an account already wired to centralised logging, Security Tooling enrolment, a Transit Gateway attachment, IAM Identity Center federation brokered by Entra + Okta, approved-region settings and baseline KMS keys, the same discipline detailed in multi-account AWS governance with Control Tower. The region-deny SCP is the sharpest example, careful to exempt the global services that have no regional endpoint:
data "aws_iam_policy_document" "region_deny" {
statement {
sid = "DenyOutsideApprovedRegions"
effect = "Deny"
# global / control-plane services must stay reachable from any region
not_actions = [
"iam:*", "organizations:*", "sts:*", "route53:*", "cloudfront:*",
"waf:*", "wafv2:*", "shield:*", "support:*", "health:*", "kms:CreateKey",
]
resources = ["*"]
condition {
test = "StringNotEquals"
variable = "aws:RequestedRegion"
values = ["us-east-1", "eu-west-1", "ap-southeast-1"] # US / EU / APAC delivery
}
}
}
resource "aws_organizations_policy" "region_deny" {
name = "lm-scp-region-deny"
type = "SERVICE_CONTROL_POLICY"
content = data.aws_iam_policy_document.region_deny.json
}
resource "aws_organizations_policy_attachment" "region_deny_root" {
policy_id = aws_organizations_policy.region_deny.id
target_id = aws_organizations_organization.lm.roots[0].id # inherits to every OU
}
The encryption SCP rides alongside it, denying any s3:PutObject that is not aws:kms-encrypted and blocking disabling of account-level S3 Block Public Access, so a workload account physically cannot create a plaintext or internet-exposed store. A dedicated content-protection SCP on the Live/VOD OUs forces Object Lock on mezzanine buckets and blocks cross-account content sharing off an allow-list — MovieLabs/TPN isolation made structural, not procedural. Below the SCP floor, Control Tower controls and Config conformance packs add framework-specific rules, evaluated against named PCI and content-security baselines.
| SCP / control | Attached at | Effect | Streaming purpose |
|---|---|---|---|
lm-scp-region-deny |
Root | Deny actions outside us-east-1/eu-west-1/ap-southeast-1 | Residency + content-licensing territory, structurally |
lm-scp-require-kms |
Root | Deny unencrypted S3/EBS/RDS; force SSE-KMS CMK | Content-at-rest, PII and CDE encryption |
lm-scp-deny-public |
Workloads | Deny disabling S3 BPA; deny public RDS/ELB | No mezzanine, origin or PII store ever exposed to the internet |
lm-scp-content-tpn |
Workloads/Live + VOD | Force Object Lock on mezzanine; deny cross-account content share off allow-list | MovieLabs/TPN premium-content isolation |
lm-scp-cde-isolation |
Workloads/Corp | Restrict CDE account to PCI-approved services; deny lateral trust | PCI-DSS segmentation of billing/payments |
lm-scp-protect-guardrails |
Root | Deny leaving org, disabling GuardDuty/Config/CloudTrail | Detective controls cannot be silenced by a workload team |
lm-scp-sandbox-lock |
Sandbox | Deny MediaLive/MediaConvert/S3 content buckets + all regions but us-east-1 | Sandbox may never touch premium content or PII |
| Control Tower — CloudTrail enabled | Root (mandatory) | Detective | Every account streams to the immutable Log Archive |
Control Tower governs the lm Organizations root; SCPs inherit down the OU tree into the Security, Infrastructure and per-domain workload accounts, and AFT vends every account pre-governed.
Governance hierarchy and policy inheritance
The two landing zones look different on the surface — Azure management groups on one side, AWS organisational units on the other — but they are deliberately governed by one guardrail set compiled into both, the load-bearing decision of the entire foundation. Lumina cannot afford two divergent control estates audited separately, because a TPN assessment, a PCI QSA audit and a SOC 2 Type II examination each ask whether the same control holds everywhere content and viewer PII live, on either provider. So the controls are authored once, as policy-as-code in Terraform and Bicep, and rendered into each cloud’s native enforcement: Azure Policy and RBAC that inherit down the management-group tree, and Service Control Policies that inherit down the AWS OU tree. The module library is the canonical artefact; the cloud-specific assignments are compiled outputs of it. Six shared guardrails travel down both hierarchies as the non-negotiable floor every subscription and account inherits.
| Shared guardrail | Azure mechanism | AWS mechanism | Effect |
|---|---|---|---|
| Approved regions / residency only | Allowed locations policy at lm (US/EU/APAC set) |
lm-scp-region-deny at Root |
Deny (preventive) |
| No public exposure of content / PII | Custom Deny on publicNetworkAccess + built-in Private Link audit |
lm-scp-deny-public (S3 BPA, public RDS/ELB) |
Deny (preventive) |
| Private endpoints for PaaS | DeployIfNotExists private endpoint + private DNS |
VPC interface endpoints + PrivateLink baseline (AFT) | DINE / provisioned |
| CMK & content-key encryption | DINE binds Storage/SQL/KV to Managed HSM key | lm-scp-require-kms forces SSE-KMS CMK |
Deny + DINE |
| Immutable audit logging | DINE diagnostic settings → immutable Log Analytics / storage | Org CloudTrail → S3 Object Lock (compliance mode) | DINE / provisioned |
| Premium-content isolation (TPN) | Deny public content storage + WORM + no cross-tenant share | lm-scp-content-tpn + S3 Object Lock |
Deny + DINE |
Inheritance lets this operating model scale without growing a control team in proportion to the estate. Platform engineers change a guardrail in one module, and on the next pipeline run every subscription and account beneath both trees conforms — no fleet of environments to revisit by hand, no window in which half the estate runs an old rule. Crucially the relationship is one-directional: teams build freely inside the guardrails but cannot weaken a control handed down from above. A live-operations team cannot grant itself an unapproved region during an event scramble, and an ad-tech owner cannot open a personalised-manifest store to the internet, because the denial is asserted higher in the tree than they can edit. That asymmetry — full delivery autonomy inside a floor a single misconfigured workload can never sink — is precisely what a 45M-concurrent Tier-1 estate needs.
The enforcement verbs matter as much as the rules, and streaming uses all of them. Azure Policy’s effect model (covered in Azure Policy effects: Deny, Audit, Modify, DeployIfNotExists) maps cleanly onto the AWS side, and the choice of verb is a choice about when the control acts.
| Effect | What it does | Azure use | AWS analogue | Streaming example |
|---|---|---|---|---|
| Deny | Blocks a non-compliant create/update outright | Deny effect |
SCP Deny statement |
Reject a public-facing origin or mezzanine storage account at create time |
| DeployIfNotExists | Auto-provisions the missing control | DeployIfNotExists |
AFT baseline / Config remediation | Create the private endpoint + DNS record for a new CMAF packager |
| Modify | Adds/changes a property to conform | Modify |
Config auto-remediation (SSM) | Stamp dataClass=Content tag; enforce HTTPS-only on origin storage |
| Audit | Flags drift without blocking | Audit / AuditIfNotExists |
Config rule (non-blocking) | Report any content bucket without Object Lock enabled |
| DenyAction | Blocks a specific operation (e.g. delete) | DenyAction |
SCP deny on s3:DeleteObject |
Prevent deletion of an immutable master/mezzanine object under legal hold |
The custom lm-content-baseline initiative is where these verbs become concrete. Its Deny arm rejects any Content-tagged storage account that leaves public network access enabled or uses a platform-managed key; its DINE arm provisions the private endpoint the workload team forgot. A compact custom definition shows the shape — note the tags[dataClass] predicate that scopes enforcement to premium content only:
resource denyPublicContentStorage 'Microsoft.Authorization/policyDefinitions@2024-04-01' = {
name: 'lm-deny-public-content-storage'
properties: {
policyType: 'Custom'
mode: 'All'
displayName: 'Deny public network access on premium-content storage accounts'
metadata: { category: 'Lumina Content' }
policyRule: {
if: {
allOf: [
{ field: 'type', equals: 'Microsoft.Storage/storageAccounts' }
{ field: 'tags[dataClass]', equals: 'Content' }
{ anyOf: [
{ field: 'Microsoft.Storage/storageAccounts/publicNetworkAccess', notEquals: 'Disabled' }
{ field: 'Microsoft.Storage/storageAccounts/encryption.keySource', notEquals: 'Microsoft.Keyvault' }
] }
]
}
then: { effect: 'deny' } // no content store may be public OR platform-key encrypted
}
}
}
The one place inheritance bends is break-the-glass and live-event freeze. A single misapplied Deny can turn an emergency-access account into a locked-out account at exactly the wrong moment — the middle of a live final — so the two break-glass identities and the event-day emergency path are covered by a narrow, audited policy exemption at the lm-plat-security scope, time-boxed and alerting on every use, rather than a blanket carve-out. DINE remediation itself runs through a managed identity, the pattern detailed in Azure Policy remediation tasks with managed identity. The initiatives assigned at each scope build the posture up in layers, most general at the root and most specific at the workload.
| Scope | Assigned initiative(s) | Posture |
|---|---|---|
lm (intermediate root) |
Microsoft Cloud Security Benchmark; Allowed locations (US/EU/APAC) | Baseline + residency, all subs |
lm-landingzones |
PCI DSS v4.0; ISO 27001:2013; SOC 2 (audit) | Continuous framework evidence |
lm-lz-live / -vod |
lm-content-baseline (Deny + DINE) |
Private endpoints, CMK, WORM, TPN isolation |
lm-lz-ciam |
lm-ciam-baseline + consent / DSAR audit |
B2C hardening, PII private-only, consent gate |
lm-lz-corp (billing) |
lm-pci-cde-baseline |
CDE segmentation, tokenisation, no PAN at rest |
lm-sandbox |
lm-deny-content-services |
Content + PII services denied; egress-locked |
Controls are authored once and compiled into Azure Policy and AWS SCPs; each arrow is one-way inheritance from root to workload, where Deny is preventive, DINE remediates and Audit reports.
Compliance, content security and data protection
Lumina Media answers not to one regulator but to an overlapping mesh of them, and the architecture must satisfy all of them at once without forking into a dozen incompatible estates. The control plane is therefore designed so that a single set of controls maps to many drivers simultaneously — one encryption model serving PCI DSS, GDPR, CCPA and MovieLabs at once; one segmentation boundary serving both the PCI cardholder data environment and the TPN content pipeline. The regulatory reality is explicit: PCI DSS v4.0 for the billing/payment path; GDPR (Schrems II, transfer-impact) for EU viewer data in West Europe and CCPA/CPRA for California residents; the MovieLabs Enhanced Content Protection (ECP) specification and the Trusted Partner Network (TPN) / CDSA assessment for premium and pre-release content; SOC 2 Type II for service assurance; and DPP broadcast-security expectations for the contribution and playout chain. A four-band data classification underpins the mapping — the band an asset carries determines its encryption, residency, key custody and access controls.
| Data class | Examples | Residency / territory rule | Enforcement |
|---|---|---|---|
| Restricted — Premium content | Pre-release masters, mezzanine, live contribution feeds, VOD library, DRM content keys | Region + licensing-territory pinned; no plaintext on general-purpose storage | Private-only, WORM masters, CMK/content-key in Managed HSM/CloudHSM, forensic watermark, TPN-assessed pipeline |
| Restricted — Cardholder (CDE) | PAN, payment tokens, billing records | PCI-scoped segment; tokenised at the edge so PAN never lands at rest | Ring-fenced CDE, SSE-KMS CMK, no PAN storage, quarterly ASV scan |
| Confidential — Viewer PII / consent | Account, email, watch history, device, geo, consent state | Resident in data-subject jurisdiction; transfer-impact assessed | Approved-region guardrails, CMK, Purview/Macie classification, consent capture + DSAR/erasure |
| Internal / Public | Catalog metadata, artwork, public marketing | No residency constraint; integrity-protected | Standard encryption, edge WAF, signed URL for rights-limited artwork |
The control matrix below is the operational heart of the posture: it maps each control domain to Lumina’s named control, to the Azure service and AWS service that implement it, and to the policy that enforces it — so no control is orphaned and no framework unmapped. Every row is defensible to an auditor with a real tool and enforcement point, not a policy aspiration.
| Control domain | Lumina control | Azure service | AWS service | Enforcing policy / SCP |
|---|---|---|---|---|
| Content at rest | CMK + content keys, WORM masters, no plaintext | Key Vault Managed HSM + Storage CMK + immutable blob | KMS/CloudHSM + SSE-KMS + S3 Object Lock | lm-content-baseline DINE + lm-scp-require-kms |
| Content in transit | TLS 1.2+, signed URL/JWT at edge, private origin | Private Link + Front Door token auth | PrivateLink + CloudFront signed URL/OAC | Deny publicNetworkAccess + lm-scp-deny-public |
| DRM & key service | Multi-DRM (Widevine/FairPlay/PlayReady), CENC, key rotation | Media key service + Managed HSM | SPEKE + KMS content keys | Content-key policy; deny plaintext key export |
| Forensic watermarking | Session + A/B watermark on premium / pre-release | Packager watermark integration | MediaConvert / partner watermark + SSAI | TPN control set; lm-content-baseline audit |
| Cardholder data (CDE) | Tokenise at edge, no PAN at rest, minimised scope | API Management + tokenisation + isolated spoke | API Gateway + payment tokenisation + isolated VPC | lm-pci-cde-baseline + lm-scp-cde-isolation |
| Consumer identity (CIAM) | 120M B2C/Cognito, JWT, bot/credential-stuffing defence | Azure AD B2C + Front Door WAF/bot | Amazon Cognito + WAF Bot Control | CIAM baseline; WAF rate-limit policy |
| Consumer PII & consent | Consent capture, DSAR, right-to-erasure | Purview + B2C + consent store | Macie + Cognito + consent store | GDPR/CCPA initiative; residency guardrail |
| Identity & access (workforce) | Entra + Okta hub, phishing-resistant MFA, zero standing privilege | Entra ID + PIM + Conditional Access | IAM Identity Center (Entra/Okta federated) | CA policies + permission sets, Audit no-MFA |
| Network segmentation | Segmented live/vod/ciam/adtech, default-deny, central inspection | Azure Firewall Premium + NSG | Network Firewall + Security Groups | UDR-to-firewall policy + TGW route tables |
| Audit logging | Immutable, centralised, framework-aligned retention | Sentinel + immutable Log Analytics/storage | CloudTrail → S3 Object Lock | DINE diagnostic settings + lm-scp-protect-guardrails |
| Data residency / territory | US/EU/APAC pinned, licensing-territory geo | Allowed locations policy | lm-scp-region-deny |
Deny outside approved region set |
| Threat detection | 24×7 SOC, cross-cloud, credential-sharing fraud | Defender for Cloud + Sentinel + Wiz/Falcon | GuardDuty + Security Hub + Macie | Config/Defender enabled by policy; deny disable |
| Multi-CDN edge security | WAF/bot/DDoS, signed URL/token, origin shield | Front Door Premium WAF + DDoS | CloudFront + WAF + Shield Advanced | Edge WAF policy; token-auth at the edge |
The billing path is where PCI DSS bites hardest, and the decisive move is scope minimisation: card data is tokenised at the edge by the payment provider so the PAN never lands at rest in Lumina’s estate, shrinking the cardholder data environment to a ring-fenced spoke inside lm-lz-corp / lm-corp-prod — the segmentation-first discipline described in PCI-DSS cardholder data environment segmentation on AWS. Each PCI requirement lands on a named, enforced control on both clouds.
| PCI DSS v4.0 area | Lumina implementation | Azure enforcement | AWS enforcement |
|---|---|---|---|
| Req 1 — Segment the CDE | Isolated corp CDE spoke, default-deny, no flat network | NSG/UDR + Azure Firewall Premium | Security Groups + Network Firewall + lm-scp-cde-isolation |
| Req 3 — Protect stored data | No PAN at rest; tokenise; CMK on residual data | Managed HSM CMK on storage/SQL | KMS CMK + SSE-KMS |
| Req 4 — Encrypt transmission | TLS 1.2+, private paths only | Front Door / API Management TLS | API Gateway TLS + PrivateLink |
| Req 6 — Secure development | DevSecOps supply chain, SAST/DAST, IaC scan | Defender for DevOps pipeline gates | CodeBuild + Wiz Code gates |
| Req 8 — Identify & authenticate | Phishing-resistant MFA on all CDE admin access | Entra CA + PIM elevation | IAM Identity Center MFA + permission sets |
| Req 10 — Log & monitor | Immutable CDE audit trail, daily review | Sentinel + immutable Log Analytics | CloudTrail Object Lock + Config |
| Req 11 — Test security | Quarterly ASV scan, annual pen test, IDS | Defender for Cloud + ASV | GuardDuty + Inspector + ASV |
Premium content is the other high-stakes domain, governed to the MovieLabs ECP specification and the TPN assessment rather than a generic baseline — the studios that license Lumina’s content demand it. The controls run the full pipeline from contribution through encode, package, storage, edge delivery and partner exchange, mapping to concrete services just as the privacy and payment controls do and extending the multi-DRM model detailed in video-on-demand streaming with multi-DRM on AWS.
| MovieLabs ECP / TPN control | Lumina implementation | Enforcement |
|---|---|---|
| Hardware root of trust & output control | Widevine L1 / FairPlay / PlayReady SL3000, HDCP 2.2+ for UHD/4K | Multi-DRM license service enforces robustness + HDCP rules per session |
| No plaintext content on general storage | Encrypted mezzanine + content keys; WORM masters | Managed HSM/CloudHSM content keys; S3 Object Lock; deny public bucket |
| Forensic watermarking | Session-based + A/B variant watermark on 4K / pre-release | Packager/SSAI watermark; a leak traces back to a subscriber session |
| Key & title diversity, rotation | Per-title / per-session keys, CENC (cenc + cbcs), rotation | SPEKE / media key service, KMS-backed, no plaintext key export |
| Secure processing environment (TPN) | TPN-assessed encode/package env, least-privilege, segmented | Isolated live/vod accounts, private-only, MFA, fully logged |
| Partner / affiliate exchange | Signed, encrypted, time-boxed content delivery | Private share + KMS grant + expiry; no standing cross-account access |
Two crosswalks prove the “one control, many drivers” claim explicitly. A single encryption control answers PCI Req 3/4, GDPR Art. 32, CCPA reasonable-security and MovieLabs ECP storage at once; a single immutable-audit control answers PCI Req 10, GDPR breach records and TPN logging together — the design never forks into a separate estate per regulation.
| Lumina control | PCI DSS v4.0 | GDPR | CCPA / CPRA | MovieLabs / TPN | SOC 2 |
|---|---|---|---|---|---|
| CMK / content-key encryption | Req 3 / 4 | Art. 32 | §1798.150 reasonable security | ECP secure storage | CC6.1 |
| Private-endpoint-only access | Req 1 | Art. 32 | Reasonable security | ECP no-public-content | CC6.6 |
| Immutable audit trail | Req 10 | Art. 30 / 33 | Breach records | TPN logging | CC7.2 |
| Region / territory guardrail | (CDE locality) | Art. 44–49 (transfers) | — | Licensing-territory control | CC6.1 |
| Consent capture + DSAR / erasure | — | Art. 6 / 7 / 17 | §1798.100–.130 | — | Privacy (P) series |
| Forensic watermark / anti-piracy | — | — | — | ECP watermarking | CC6.1 |
Data residency closes the loop between classification and enforcement, and the decisive property is that it is enforced structurally, not by policy memo. The approved-region guardrails inherited down both trees mean a workload simply cannot place restricted content or EU viewer PII outside its geography: US data is pinned to East US 2 / us-east-1, EU data to West Europe / eu-west-1, APAC to Southeast Asia / ap-southeast-1, and the residency-split workspaces and separate CMK scopes mean even the telemetry and keys respect the boundary. On top of residency sits licensing territory — a title licensed only for EMEA cannot be packaged or served from a North American origin, because the licensingTerritory tag drives a geo-blackout guardrail the packager and the multi-CDN steering layer both honour. Content crossing to a partner passes through a signed, encrypted, time-boxed exchange with no standing access before it leaves its home segment — so Lumina can answer the two hardest questions a studio or regulator asks: where does this master live and who can decrypt it, and where does my subscriber’s data live and who can read it. The frameworks map to one control, the control maps to a named service on each cloud, and the enforcement is a policy an auditor can inspect running.
A regulatory driver maps to a control domain, then to a named Azure service and its AWS parity, then to the policy or SCP that proves it — one control answering many frameworks at once.
Global hybrid connectivity
Everything private that Lumina Media runs — live contribution from a stadium, playout out of a Media Operations Centre, an entitlement lookup for one of 120M subscribers, a DRM key request during a live final — rides through one small, deliberately boring, ruthlessly redundant core. Get this core wrong and you do not get a slow site; you get a black screen for 45M concurrent viewers during the one event they will remember. So the connectivity design has exactly two jobs: carry contribution and control traffic on private circuits that never touch the public internet, and make sure no single circuit, carrier, cloud region or even whole cloud can isolate a delivery region. Delivery itself (segments, manifests, images) rides the multi-CDN edge and is not in scope here; this is the private substrate underneath it.
The physical anchors are the two broadcast facilities: the Los Angeles MOC (Americas contribution, live sports/news ingest, near-line archive) and the London MOC (EMEA playout and post). Each MOC is dual-homed into both clouds — ExpressRoute into Azure and Direct Connect into AWS — on physically diverse fibre from different meet-me rooms. There is no third MOC in Asia; Southeast Asia and ap-southeast-1 are reached over the cloud backbones (Virtual WAN hub-to-hub and Transit Gateway inter-region peering), so a fibre cut near a MOC degrades throughput but never strands a region.
The two MOCs land on ExpressRoute and Direct Connect, which feed Azure Virtual WAN and AWS Transit Gateway, which fan out to the three regions on each cloud.
The circuit inventory is small on purpose — fewer moving parts, each one doubled:
| Path | Cloud | Service | Speed / SKU | Peering | Terminates at | Carries | | — | — | — | — | — | — | | LA MOC → East US 2 | Azure | ExpressRoute (Premium) | 2× 10 Gbps | Private peering + FastPath | vWAN ER gateway, vhub-eus2 | Americas contribution, control-plane | | LA MOC → us-east-1 | AWS | Direct Connect (dedicated) | 2× 10 Gbps, MACsec | Transit VIF → DX gateway | tgw-use1 | Americas encode/archive, control-plane | | London MOC → West Europe | Azure | ExpressRoute (Premium) | 2× 10 Gbps | Private peering + FastPath | vWAN ER gateway, vhub-weu | EMEA playout, control-plane | | London MOC → eu-west-1 | AWS | Direct Connect (dedicated) | 2× 10 Gbps, MACsec | Transit VIF → DX gateway | tgw-euw1 | EMEA post/archive, control-plane | | LA ↔ London (Azure) | Azure | ExpressRoute Global Reach | via circuits | private | both ER circuits | MOC-to-MOC failover, archive sync | | LA ↔ London (AWS) | AWS | Direct Connect SiteLink | via DX | private | both DX connections | MOC-to-MOC over AWS backbone | | SEA / ap-southeast-1 | both | Backbone only | n/a | vWAN hub-to-hub, TGW peering | via EUS2/WEU + use1/euw1 | Asia delivery, cross-region replication |
Two design choices in that table earn their keep. FastPath on ExpressRoute lets contribution traffic bypass the Virtual WAN hub’s gateway data path and go straight to the spoke, shaving the added latency for live encode ingest to under 8 ms — which matters when your live-latency budget (LL-CMAF glass-to-glass) is 8 seconds end to end and every hop counts. SiteLink on Direct Connect lets the two MOCs talk to each other over the AWS global backbone without backhauling through a cloud region, so archive replication between LA and London does not consume a region’s egress.
The routing is BGP everywhere, with a private ASN plan that keeps the two estates unambiguous. Advertise the on-prem 10.0.0.0/12 from both MOCs (with AS-path prepending so each MOC is primary for its own region and backup for the other), and accept the cloud supernets back:
| Endpoint | ASN | Advertises | Accepts | Notes |
|---|---|---|---|---|
| LA MOC edge routers | 65010 | 10.0.0.0/12 (10.0/16 no-prepend) | 10.20.0.0/12, 10.40.0.0/12 | Primary for Americas |
| London MOC edge routers | 65011 | 10.0.0.0/12 (10.1/16 no-prepend) | 10.20.0.0/12, 10.40.0.0/12 | Primary for EMEA |
| Azure MSEE (private peering) | 12076 | 10.20.0.0/12 | 10.0.0.0/12 | Microsoft-owned; FastPath on |
| AWS TGW / DX gateway | 64512 | 10.40.0.0/12 | 10.0.0.0/12 | Allowed prefixes on DX gateway |
| vWAN hub-to-hub | internal | region /16 | region /16 | Any-to-any across 3 hubs |
| TGW inter-region peering | internal | region /16 | region /16 | Static routes per peer |
A subtle but critical rule: the two clouds never learn each other’s routes through on-prem. If Azure could reach AWS via MOC → ER → BGP → DX → AWS, a routing leak would send Tier-1 traffic hairpinning through a broadcast facility. On-prem only ever originates 10.0.0.0/12; it does not re-advertise one cloud’s prefixes to the other. Cross-cloud private traffic (rare — mostly analytics replication) goes over a dedicated, inspected path, never accidental transit.
Every private hop is encrypted, because “it’s a private circuit” is not a control an auditor (or the MovieLabs/TPN content-security regime) accepts:
| Segment | Encryption | Mechanism | Why |
|---|---|---|---|
| MOC ↔ ExpressRoute | MACsec (ER Direct) or IPsec-over-ER | Layer-2 or VPN tunnel over private peering | Contribution feeds carry pre-release premium content |
| MOC ↔ Direct Connect | MACsec | 802.1AE on the dedicated port | Same premium-content requirement |
| vWAN hub-to-hub | Microsoft backbone (encrypted) | native | Inter-region control + replication |
| TGW inter-region peering | AWS backbone (encrypted) | native | Inter-region control + replication |
| Contribution transport | SRT / Zixi / RIST with AES-128/256 | app-layer over the circuit | Defence in depth; survives a mis-scoped BGP change |
az and aws to stand up the LA pair of circuits — one command each, then the peerings that make them useful:
# Azure — ExpressRoute at the LA meet-me room into East US 2, private peering + FastPath
az network express-route create \
--resource-group lm-plat-connectivity-rg --name er-lax-eus2 --location eastus2 \
--provider "Equinix" --peering-location "Los Angeles" \
--bandwidth 10000 --sku-family MeteredData --sku-tier Premium
az network express-route peering create \
--resource-group lm-plat-connectivity-rg --circuit-name er-lax-eus2 \
--peering-type AzurePrivatePeering --peer-asn 65010 --vlan-id 100 \
--primary-peer-subnet 169.254.10.0/30 --secondary-peer-subnet 169.254.10.4/30
# FastPath is enabled on the connection object that binds the circuit to the vWAN ER gateway.
# AWS — dedicated Direct Connect at the LA DX location, transit VIF into the DX gateway → TGW
aws directconnect create-connection --location EqLA3 \
--bandwidth 10Gbps --connection-name dx-lax-use1 --request-macsec-capable
aws directconnect create-transit-virtual-interface --connection-id dxcon-abc123 \
--new-transit-virtual-interface \
'{"virtualInterfaceName":"vif-lax-tgw","vlan":200,"asn":65010,
"directConnectGatewayId":"dxgw-0a1b2c","mtu":9001}'
Jumbo frames (MTU 9001 on the transit VIF, 1500 clamped where the path can’t guarantee it) matter for the encode/archive bulk transfers; get it wrong and you pay a fragmentation tax on every 400 PB you move. With the core in place, each cloud’s regional topology hangs off it.
Azure regional network
Every Azure region is the same shape — a secured Virtual WAN hub with six single-purpose spokes hanging off it — which is the whole point: build East US 2 once, then for region in weu sea and change three variables. The hub is a /20, the spokes are /22, and a /26 private-endpoint subnet lives inside each spoke. We use Virtual WAN with Routing Intent rather than hand-rolled hub-and-spoke because Routing Intent is what forces all spoke egress and all east-west traffic through Azure Firewall without you maintaining route tables by hand across three regions. (The trade-offs behind that choice are laid out in Hub-Spoke vs Virtual WAN enterprise topology.)
The ExpressRoute on-ramp lands in the hub; Routing Intent points a default and an RFC1918 route at Azure Firewall Premium; inspected traffic reaches the workload spokes; and each spoke’s private-endpoint /26 is where PaaS lives.
The hub is not a workload zone — it is fixed, reserved infrastructure with Azure’s required subnet names at fixed prefixes so the /20 never has to renumber when a hub service is added:
| Hub subnet | Prefix (EUS2) | Purpose |
|---|---|---|
| GatewaySubnet | 10.20.0.0/27 | ExpressRoute + VPN gateways (reserved name) |
| AzureFirewallSubnet | 10.20.0.64/26 | Azure Firewall Premium data plane (reserved name) |
| AzureFirewallManagementSubnet | 10.20.0.128/26 | Firewall forced-tunnel mgmt (reserved name) |
| AzureBastionSubnet | 10.20.0.192/26 | Bastion for break-glass admin (reserved name) |
| snet-dnspr-inbound | 10.20.1.0/28 | Private DNS Resolver inbound endpoint |
| snet-dnspr-outbound | 10.20.1.16/28 | Private DNS Resolver outbound + forwarding rules |
| snet-shared | 10.20.1.32/27 | Shared platform services (jump, scanners) |
| (reserved) | 10.20.2.0 – 10.20.15.255 | Hub growth — never allocated to spokes |
The six spokes map one-to-one onto the segmentation model. Each is its own VNet, peered only to the hub (no spoke-to-spoke peering exists — that is the entire security value), with NSGs written against service tags rather than raw CIDRs so rules survive renumbering:
| Segment spoke | CIDR (EUS2) | Runs | PE subnet | Egress posture |
|---|---|---|---|---|
| control-plane | 10.20.16.0/22 | GitOps, deploy runners, KMS/Key Vault access | 10.20.18.0/26 | Firewall → allow-list only |
| customer-API | 10.20.20.0/22 | Playback + entitlement APIs (behind Front Door/WAF/B2C) | 10.20.22.0/26 | No inbound public IP; PL from edge |
| media-processing | 10.20.24.0/22 | Transcode/ABR, packaging, DRM packaging, live encode | 10.20.26.0/26 | Firewall → CDN origins, DRM SaaS |
| analytics | 10.20.28.0/22 | QoE ingest, Event Hubs, Stream Analytics, ADX | 10.20.30.0/26 | Firewall → allow-list only |
| corp | 10.20.32.0/22 | SAP connectivity, back-office, internal tools | 10.20.34.0/26 | Firewall → on-prem only |
| management | 10.20.36.0/22 | Bastion targets, scanners (Wiz), agents (Falcon) | 10.20.38.0/26 | Firewall → mgmt SaaS |
The Routing Intent + firewall is the enforcement point. It is what stops the media-processing spoke from reaching corp (and therefore SAP, and therefore the crown-jewel financials) uninspected, and it is where egress FQDN allow-listing, TLS inspection and IDPS live. That the same firewall handles internet and private traffic is deliberate — one policy, one log stream, one place to prove to a TPN auditor that pre-release content cannot leave for an unapproved destination. The broader egress pattern is covered in centralized internet egress with FQDN filtering.
The hub and its Routing Intent in Terraform — this block, parameterised, is the entire per-region build:
resource "azurerm_virtual_hub" "eus2" {
name = "vhub-eus2"
resource_group_name = azurerm_resource_group.conn.name
location = "eastus2"
virtual_wan_id = azurerm_virtual_wan.lumina.id
address_prefix = "10.20.0.0/20" # the /20 hub
}
resource "azurerm_virtual_hub_routing_intent" "eus2" {
name = "ri-eus2"
virtual_hub_id = azurerm_virtual_hub.eus2.id
routing_policy { # all internet egress → firewall
name = "InternetTraffic"
destinations = ["Internet"]
next_hop = azurerm_firewall.eus2.id
}
routing_policy { # all east-west + on-prem → firewall
name = "PrivateTraffic"
destinations = ["PrivateTraffic"]
next_hop = azurerm_firewall.eus2.id
}
}
resource "azurerm_virtual_hub_connection" "media" {
name = "conn-media-processing"
virtual_hub_id = azurerm_virtual_hub.eus2.id
remote_virtual_network_id = azurerm_virtual_network.media_processing.id
# inherits Routing Intent: no manual route table needed
}
Because Routing Intent programs the effective routes, a spoke owner cannot add a 0.0.0.0/0 → Internet UDR to sneak past the firewall — the hub-injected route wins, and any attempt shows up as drift. NSG design, service tags and the effective-route mechanics that back this up are in Azure virtual networks, subnets and NSGs. The AWS side reaches the same posture by a different road.
AWS regional network
AWS gives you the pieces but not the opinion, so the regional network is an inspection-VPC hub wired up by hand: one Transit Gateway per region with segmented route tables, a dedicated inspection VPC running AWS Network Firewall, and the workload VPCs attached in appliance mode so both directions of every flow are pinned to the same firewall endpoint. This is the AWS translation of the Azure secured hub, and it lands on the same guarantees — no lateral movement, central inspected egress, symmetric flows.
The DX gateway feeds the TGW; segmented route tables send all workload traffic to the inspection VPC; Network Firewall inspects and NAT+IGW provides the single central egress; only then does traffic reach the workload VPCs and their endpoints.
The TGW route-table design is the security control. Three route tables enforce that no workload VPC has a route to any other workload VPC — the only next hop a workload gets is the inspection VPC attachment:
| TGW route table | Associated attachments | Propagations | Effect |
|---|---|---|---|
| rt-inspection | inspection VPC | none (static routes) | Sees all VPCs; returns post-inspection traffic |
| rt-workloads | all workload VPCs | none | Default 0.0.0.0/0 + RFC1918 → inspection VPC only |
| rt-onprem | DX gateway attachment | workload summaries | On-prem reaches workloads via inspection VPC |
The inspection VPC is a /20 (mirroring the Azure hub) carved for three AZs. Network Firewall needs a firewall subnet per AZ; the TGW attachment needs its own subnets; NAT gateways and the IGW provide the single central egress:
| Inspection subnet (us-east-1) | Prefix | Purpose |
|---|---|---|
| firewall-a/b/c | 10.40.0.0/28, .16/28, .32/28 | Network Firewall endpoints, one per AZ |
| tgw-a/b/c | 10.40.0.64/28, .80/28, .96/28 | TGW ENIs (appliance-mode attachment) |
| public-a/b/c | 10.40.1.0/27, .32/27, .64/27 | NAT gateways + IGW (central egress) |
| (reserved) | 10.40.2.0 – 10.40.15.255 | Growth — never allocated to workloads |
The workload VPCs repeat the same six-segment model at the same offsets as Azure, which is what makes the estate comprehensible: 10.40.24.0/22 is media-processing on AWS exactly as 10.20.24.0/22 is on Azure.
| Segment VPC | CIDR (use1) | Runs | Endpoints | Egress posture |
|---|---|---|---|---|
| control-plane | 10.40.16.0/22 | CodePipeline, deploy roles, KMS access | STS, KMS (interface) | via inspection VPC only |
| customer-API | 10.40.20.0/22 | Cognito-backed playback/entitlement (CloudFront/WAF) | Secrets Mgr, DDB (gw) | PrivateLink from edge; no public IP |
| media-processing | 10.40.24.0/22 | MediaConvert/MediaLive-class encode, packaging, SPEKE | S3 (gw), ECR, Kinesis | Firewall → CDN origins, DRM SaaS |
| analytics | 10.40.28.0/22 | Kinesis, QoE ingest, OpenSearch | S3 (gw), Kinesis | via inspection VPC only |
| corp | 10.40.32.0/22 | SAP edge, back-office | STS | Firewall → on-prem only |
| management | 10.40.36.0/22 | SSM targets, Wiz, Falcon agents | SSM, SSM-messages | Firewall → mgmt SaaS |
The one detail that separates a working inspection VPC from a mysteriously broken one is appliance mode. Without it, the return path of a flow can land on a different AZ’s firewall endpoint than the request, Suricata sees only half the conversation, and it drops the flow as invalid — an intermittent failure that looks like anything but a routing setting. Enabling it on the attachment guarantees flow affinity. The rule-engineering side of this firewall is a whole discipline of its own, covered in AWS Network Firewall Suricata egress inspection.
The core of the region in Terraform — TGW with default association/propagation disabled (so nothing is reachable until you say so), plus the appliance-mode workload attachment:
resource "aws_ec2_transit_gateway" "use1" {
description = "lm-tgw-use1"
default_route_table_association = "disable" # explicit RTs only
default_route_table_propagation = "disable" # no automatic reachability
amazon_side_asn = 64512
}
resource "aws_ec2_transit_gateway_vpc_attachment" "media" {
transit_gateway_id = aws_ec2_transit_gateway.use1.id
vpc_id = aws_vpc.media_processing.id
subnet_ids = [for s in aws_subnet.media_tgw : s.id] # one /28 per AZ
appliance_mode_support = "enable" # pin both directions to one FW endpoint
}
# workload RT: the only next hop a workload gets is the inspection VPC
resource "aws_ec2_transit_gateway_route" "wl_default" {
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.workloads.id
destination_cidr_block = "0.0.0.0/0"
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.inspection.id
}
That terraform-module-aws-transit-gateway and its Network-Firewall companion are reused verbatim across all three AWS regions (Terraform module: AWS Transit Gateway). Inter-region TGW peering, with the same three-route-table discipline, extends the segmentation to eu-west-1 and ap-southeast-1 so Asia is reachable without a MOC. None of this works without an address plan that guarantees the /22s never collide — which is the next section.
IP address management plan
The whole design above depends on one invariant: no two subnets anywhere in the estate overlap. Overlap is not a style problem; it is the failure that makes a route ambiguous, breaks a Private Endpoint’s DNS, and turns a clean multi-cloud active/active into a support ticket that never closes. So IPAM is governed, not improvised: three non-overlapping /12 supernets, each carved into per-region /16s, each of those into a /20 hub and /22 segment spokes, each spoke holding a /26 private-endpoint subnet.
The supernet → region → hub → segment → endpoint carve-up, left to right:
The top of the plan is three supernets that can never be confused for one another — a glance at the second octet tells you which estate you are in:
| Estate | Supernet | Second-octet range | Governed by |
|---|---|---|---|
| On-prem (MOCs + corp) | 10.0.0.0/12 | 10.0 – 10.15 | NetOps / on-prem IPAM |
| Azure | 10.20.0.0/12 | 10.20 – 10.31 (region /16s at .20–.22) | Azure Virtual Network Manager IPAM pools |
| AWS | 10.40.0.0/12 | 10.40 – 10.47 (region /16s at .40–.42) | AWS VPC IPAM |
| (reserved) | 10.48.0.0/12 + | future clouds / M&A | Central architecture |
On-prem consumes its /12 for the two facilities and the corporate estate, leaving generous room for the near-line archive to grow:
| On-prem block | CIDR | Contents |
|---|---|---|
| LA MOC | 10.0.0.0/16 | Contribution encoders, playout, near-line archive, SRT/Zixi receivers |
| London MOC | 10.1.0.0/16 | Playout, post, EMEA contribution |
| Corp | 10.2.0.0/16 | AD DS (corp.luminamedia.net), SAP, back-office |
| Reserved | 10.3.0.0 – 10.15.255.255 | Archive expansion, future facilities |
The Azure supernet expands per region. This is the master table an engineer opens to answer “what CIDR is the analytics spoke in West Europe?” without guessing — every region repeats the identical offset pattern, so the answer is mechanical:
| Segment | East US 2 (10.20/16) | West Europe (10.21/16) | Southeast Asia (10.22/16) | Prefix |
|---|---|---|---|---|
| Secured hub | 10.20.0.0/20 | 10.21.0.0/20 | 10.22.0.0/20 | /20 |
| control-plane | 10.20.16.0/22 | 10.21.16.0/22 | 10.22.16.0/22 | /22 |
| customer-API | 10.20.20.0/22 | 10.21.20.0/22 | 10.22.20.0/22 | /22 |
| media-processing | 10.20.24.0/22 | 10.21.24.0/22 | 10.22.24.0/22 | /22 |
| analytics | 10.20.28.0/22 | 10.21.28.0/22 | 10.22.28.0/22 | /22 |
| corp | 10.20.32.0/22 | 10.21.32.0/22 | 10.22.32.0/22 | /22 |
| management | 10.20.36.0/22 | 10.21.36.0/22 | 10.22.36.0/22 | /22 |
| private-endpoints (per spoke /26) | 10.20.18/22/26/30/34/38.0/26 | 10.21.* mirror | 10.22.* mirror | /26 |
AWS mirrors it exactly, one supernet over — which is the feature, not a coincidence. media-processing is always at offset .24.0/22; only the second octet changes between clouds and regions:
| Segment | us-east-1 (10.40/16) | eu-west-1 (10.41/16) | ap-southeast-1 (10.42/16) | Prefix |
|---|---|---|---|---|
| Inspection VPC | 10.40.0.0/20 | 10.41.0.0/20 | 10.42.0.0/20 | /20 |
| control-plane | 10.40.16.0/22 | 10.41.16.0/22 | 10.42.16.0/22 | /22 |
| customer-API | 10.40.20.0/22 | 10.41.20.0/22 | 10.42.20.0/22 | /22 |
| media-processing | 10.40.24.0/22 | 10.41.24.0/22 | 10.42.24.0/22 | /22 |
| analytics | 10.40.28.0/22 | 10.41.28.0/22 | 10.42.28.0/22 | /22 |
| corp | 10.40.32.0/22 | 10.41.32.0/22 | 10.42.32.0/22 | /22 |
| management | 10.40.36.0/22 | 10.41.36.0/22 | 10.42.36.0/22 | /22 |
A /22 (1,024 addresses) per segment is deliberate. Azure burns five addresses per subnet; AWS burns five per subnet and forces a per-AZ carve, so a /22 VPC across three AZs becomes three /24s (one per AZ) plus a /26 endpoint subnet per AZ — still comfortable for a media-processing fleet that scales to hundreds of encode tasks during a live event, without wasting a /20 on every segment. The /26 PE subnets (59 usable addresses) hold Private Endpoint NICs / interface-endpoint ENIs, which are low-count and slow-growing.
None of this is hand-maintained in a spreadsheet — both clouds have a native IPAM authority that hands out blocks and refuses overlaps:
| Concern | Azure | AWS |
|---|---|---|
| Authority | Virtual Network Manager IPAM pools | VPC IPAM (pools + scopes) |
| Enforcement | Pool allocations; VNet must draw from pool | --allocation-min/max-netmask-length; auto-import |
| Overlap detection | Pool rejects overlapping CIDR | IPAM alarms + refuses conflicting alloc |
| Utilisation alerting | Azure Monitor on pool usage | CloudWatch on IPAM utilisation (>70%) |
| Reserved-but-unallocated | Hub /20, supernet tail |
Inspection /20, supernet tail |
Standing up the pools so no team can self-allocate a colliding block:
# Azure — root pool for the Azure /12, then a region child pool the VNets must draw from
az network manager ipam-pool create \
--network-manager-name lm-avnm --resource-group lm-plat-connectivity-rg \
--name azure-root --display-name "Azure 10.20.0.0/12" \
--addressing-family IPv4 --address-prefixes "10.20.0.0/12"
az network manager ipam-pool create \
--network-manager-name lm-avnm --resource-group lm-plat-connectivity-rg \
--name eus2 --parent-pool-name azure-root \
--addressing-family IPv4 --address-prefixes "10.20.0.0/16"
# AWS — region pool with hard min/max netmask so nobody carves a /21 or a /28 spoke
aws ec2 create-ipam-pool --ipam-scope-id ipam-scope-0abc \
--address-family ipv4 --locale us-east-1 \
--allocation-min-netmask-length 22 --allocation-max-netmask-length 26 \
--allocation-default-netmask-length 22
aws ec2 provision-ipam-pool-cidr --ipam-pool-id ipam-pool-0def --cidr 10.40.0.0/16
Three guardrails keep the plan honest over years: the hub/inspection /20 and each supernet tail are reserved and never allocated (so growth never forces a renumber); overlap is a hard failure in IPAM, not a review comment; and the offset pattern is frozen — .16 control-plane, .20 customer-API, .24 media-processing, .28 analytics, .32 corp, .36 mgmt — so firewall rules, NSGs and route tables are copy-paste across six regions. That symmetry is exactly what lets the private-connectivity layer be templated too.
Private connectivity for PaaS services
Every managed data service Lumina touches — the entitlement database, the VOD-master blob origin, the CIAM session store, every Key Vault and Secrets Manager — is reached over a private IP only. The public endpoint is not firewalled; it is switched off, and a policy guardrail stops anyone switching it back on. This is the control that satisfies TPN/MovieLabs (premium content masters in Blob/S3 have no internet-reachable endpoint), PCI-DSS (the billing data store is not on the public internet), and plain blast-radius sense (a leaked SAS token or access key is useless without a route to the service). On Azure that means Private Endpoints + Private DNS; on AWS, interface/gateway VPC endpoints (PrivateLink) + Route 53 Resolver.
The workload resolves a privatelink.* name to a private IP via Private DNS, connects through the Private Endpoint / VPC endpoint NIC in the /26, and reaches the PaaS service — whose public endpoint is denied by policy.
The Azure Private Endpoint plan is one row per service per region — a NIC in the spoke’s /26, wired to the right sub-resource, with the A record auto-registered in the matching Private DNS zone:
| PaaS service | Sub-resource | PE subnet (EUS2) | Private DNS zone |
|---|---|---|---|
| Azure SQL (entitlement) | sqlServer | 10.20.22.0/26 | privatelink.database.windows.net |
| Blob (VOD masters, origin) | blob | 10.20.26.0/26 | privatelink.blob.core.windows.net |
| Cosmos DB (session/CIAM) | Sql | 10.20.22.0/26 | privatelink.documents.azure.com |
| Key Vault (DRM keys, secrets) | vault | 10.20.18.0/26 | privatelink.vaultcore.azure.net |
| Event Hubs (QoE ingest) | namespace | 10.20.30.0/26 | privatelink.servicebus.windows.net |
| Container Registry (images) | registry | 10.20.18.0/26 | privatelink.azurecr.io |
The AWS side is the same intent with AWS’s two endpoint flavours — interface endpoints (an ENI + PrivateLink, for most services) and gateway endpoints (a route-table entry, only S3 and DynamoDB, and free):
| AWS service | Endpoint type | Where | DNS |
|---|---|---|---|
| S3 (VOD masters, origin) | Gateway | route table in each VPC | native (path/virtual-host) |
| DynamoDB (session/metadata) | Gateway | route table in each VPC | native |
| Secrets Manager (DRM/secrets) | Interface | PE subnet ENI | private-DNS-enabled |
| STS / KMS (identity, keys) | Interface | PE subnet ENI | private-DNS-enabled |
| Kinesis (QoE ingest) | Interface | PE subnet ENI | private-DNS-enabled |
| ECR (api + dkr) | Interface | PE subnet ENI | private-DNS-enabled |
DNS is where private connectivity quietly succeeds or fails, so it is designed explicitly, not left to defaults. Azure Private DNS zones link to every spoke; an Azure Private DNS Resolver (inbound endpoint in the hub) answers the on-prem MOCs so LA and London resolve the same private IPs; and forwarding rules send privatelink.* and corp.luminamedia.net to the right resolver. On AWS, Route 53 Resolver inbound/outbound endpoints and a forwarding rule set do the equivalent, with private hosted zones associated to the workload VPCs. The public resolver’s answer for these names is never trusted — the entire point is that a nslookup from anywhere returns a 10.x address or nothing. The zone-linking and auto-registration mechanics are detailed in Azure Private DNS zones, auto-registration and linking, and the cross-region resolution pattern (so an EUS2 failover reaches WEU’s private IPs) in cross-region Private Link DNS for active/active apps.
A Private Endpoint plus its DNS zone group in Terraform — the public_network_access_enabled = false line is the one that actually matters:
resource "azurerm_mssql_server" "entitlement" {
name = "lm-sql-entitlement-eus2"
resource_group_name = azurerm_resource_group.data.name
location = "eastus2"
version = "12.0"
public_network_access_enabled = false # no public endpoint exists
}
resource "azurerm_private_endpoint" "sql" {
name = "pe-sql-entitlement-eus2"
location = "eastus2"
resource_group_name = azurerm_resource_group.data.name
subnet_id = azurerm_subnet.pe_customer_api.id # the /26
private_service_connection {
name = "psc-sql"
private_connection_resource_id = azurerm_mssql_server.entitlement.id
subresource_names = ["sqlServer"]
is_manual_connection = false
}
private_dns_zone_group {
name = "sql-dns"
private_dns_zone_ids = [azurerm_private_dns_zone.sql.id] # privatelink.database.windows.net
}
}
Turning the public endpoint off is necessary but not sufficient — someone with rights will eventually flip it back on “just to test,” so the deny-public guardrail is enforced at the platform, auto-remediated, and fed to Wiz as compliance evidence:
| Guardrail | Azure | AWS | Effect |
|---|---|---|---|
| Block public PaaS | Policy: deny publicNetworkAccess != Disabled on SQL/Storage/Cosmos/KV |
SCP: deny s3:PutAccountPublicAccessBlock |
Cannot create/keep a public data endpoint |
| Force central egress | Routing Intent (no spoke 0.0.0.0/0 UDR wins) |
SCP: deny ec2:CreateInternetGateway in workload OUs |
No workload-local internet path |
| Require Private Endpoint | Policy: audit resources without PE | Config rule: vpc-endpoint present |
Drift is visible + remediated |
| Private DNS only | Policy: deny linking public DNS to spokes | Resolver rule set (no public forwarder) | Names resolve to private IPs only |
The Azure Policy rule and the AWS SCP that anchor row one:
// Azure Policy — deny SQL/Storage/Cosmos with public network access enabled
{ "if": { "allOf": [
{ "field": "type", "in": ["Microsoft.Sql/servers","Microsoft.Storage/storageAccounts","Microsoft.DocumentDB/databaseAccounts"] },
{ "field": "Microsoft.Sql/servers/publicNetworkAccess", "notEquals": "Disabled" }
] },
"then": { "effect": "deny" } }
// AWS SCP — no workload account may weaken account-level public-access block or add an IGW
{ "Version": "2012-10-17", "Statement": [
{ "Sid": "DenyDisableS3AccountBPA", "Effect": "Deny",
"Action": "s3:PutAccountPublicAccessBlock", "Resource": "*" },
{ "Sid": "DenyIgwInWorkloads", "Effect": "Deny",
"Action": ["ec2:CreateInternetGateway","ec2:AttachInternetGateway"], "Resource": "*" }
] }
And the interface endpoint that keeps AWS API calls private, scoped by an endpoint policy to Lumina principals so a stolen credential cannot reach another tenant’s Secrets Manager:
aws ec2 create-vpc-endpoint --vpc-id vpc-0mediaproc \
--vpc-endpoint-type Interface \
--service-name com.amazonaws.us-east-1.secretsmanager \
--subnet-ids subnet-0pe-a subnet-0pe-b subnet-0pe-c \
--security-group-ids sg-0pe-only --private-dns-enabled \
--policy-document file://endpoint-policy-lumina-only.json
The result is a data plane with no public attack surface: the playback API has no public IP, the entitlement store has no public endpoint, DRM keys in Key Vault resolve only to a private address, and the deny-public guardrails make that state the only legal state. The trade-offs between Private Endpoints and the cheaper Service Endpoints — and why premium content mandates the former — are in Private Endpoint vs Service Endpoint, with the PaaS-scale pattern in Azure Private Link and Private DNS for PaaS. With the private substrate, regional topology and address plan settled, the platform is ready for the identity and workload tiers that ride on top of it.
Workforce identity and zero-trust control plane
For Lumina Media, identity is not a supporting service — it is the Tier-0 control plane every other tier authenticates against, and its blast radius is total. If the workforce identity plane is down, platform engineers cannot reach the clouds, the operations centres cannot drive playout, and editorial cannot open the media asset manager; if it is compromised, an attacker with one stolen admin credential can reach the encode farm, the origin, the ad-decision stack and the 400 PB archive. That is why identity carries the strictest objective in the estate — RTO ≤ 15 minutes, RPO ≈ 0 — and why the zero-trust posture (“never trust, always verify, assume breach”) is enforced here first, before network, before content. The organising principle follows the zero-trust architecture blueprint: every workforce access is an explicitly verified transaction — authenticated identity, healthy device, evaluated risk, least-privilege authorisation, immutable audit — with no trust granted by network location.
The one rule that governs this whole part: workforce identity and consumer identity are two physically separate planes that never mix. Staff live in the workforce plane (AD DS + Entra ID + Okta); the 120M subscribers live in the CIAM plane (Azure AD B2C + Amazon Cognito), covered in the next section. They share no directory, no token issuer and no trust — a compromise of a consumer account can never reach a production system, and a workforce credential can never assume a viewer identity. Everything below is workforce-only.
The system of record for the workforce is the on-prem AD DS forest corp.luminamedia.net, with domain controllers in both Media Operations Centres (Los Angeles and London) so authentication survives the loss of either facility — the forest and DC placement follow Active Directory DS forest design and DC promotion. AD DS is deliberately not the cloud identity provider. The cloud plane is deliberately dual: Entra ID is the authentication and policy authority (device trust, Conditional Access, PIM, and federation to Azure/AWS/M365), and Okta is the workforce access-management layer for the sprawling third-party SaaS and media-production tool catalogue — but Okta delegates every sign-in back to Entra, so a single Conditional Access gate covers both.
Here is the Tier-0 identity plane inventory — what each component is for, where it runs, and the recovery objective it inherits:
| Component | Role in the plane | Where it runs | Objective | Failure blast radius |
|---|---|---|---|---|
AD DS corp.luminamedia.net |
Workforce system of record; Kerberos for on-prem/broadcast apps | 4 DCs across LA + London MOCs | RTO ≤15m, RPO ≈0 | On-prem playout/editorial login, file, legacy auth |
| Entra Connect cloud sync | AD DS → Entra provisioning + PHS | HA agent pairs at LA + London | RTO ≤30m | New/changed accounts stop flowing (existing tokens fine) |
| Entra ID tenant | Auth + policy authority; cloud token issuer | Microsoft global (geo US) | RTO ≤15m, RPO ≈0 | Every cloud + M365 + Okta-fronted sign-in |
| Okta org | SaaS/media app-catalog SSO + lifecycle | Okta cloud | RTO ≤30m | Third-party SaaS SSO (auth still via Entra) |
| Conditional Access | Per-sign-in policy engine | Entra | (part of tenant) | Wrong policy = mass lockout or over-grant |
| Entra ID Protection | User/sign-in risk scoring | Entra P2 | best-effort | Risk-based blocks stop firing |
| PIM | Just-in-time privileged role activation | Entra P2 | RTO ≤15m | Admins cannot elevate (break-glass covers) |
| Break-glass accounts ×2 | Emergency Global Admin, excluded from all policy | Entra (cloud-only) | always available | Last-resort admin if PIM/CA fails |
Sync topology and the Entra + Okta split
Lumina runs Entra Connect cloud sync, not the heavyweight Connect Sync server — the trade-offs are in Entra Connect Sync vs cloud sync. Cloud sync uses lightweight provisioning agents (HA pairs at each MOC, so no single sync server is a Tier-0 SPOF) and authentication uses Password Hash Synchronisation (PHS) plus Seamless SSO. PHS is chosen over Pass-Through Auth and AD FS for one overriding reason: cloud sign-in must not depend on an on-prem service being reachable. When a MOC or circuit fails during a live final, engineers must still authenticate to the cloud control plane — PHS makes Entra self-sufficient; PTA/AD FS would couple every cloud login to an on-prem endpoint.
The Entra/Okta division of labour is the media-specific decision that generic estates skip. A streaming operator carries hundreds of niche SaaS and post-production vendor apps (MAM/DAM, non-linear editors, ad-sales order management, encoding vendor portals) that predate the Entra estate and live naturally in Okta’s catalogue; meanwhile device trust, PIM and Intune are native to Entra. Rather than fight that, Lumina assigns each plane what it is best at and wires them so Conditional Access still governs everything — the governance model mirrors multicloud identity governance with Okta and SailPoint:
| Plane | Owns | Representative apps | Why it owns this |
|---|---|---|---|
| Entra ID | Auth + policy authority: device trust, Conditional Access, PIM, ID Protection; M365 + Azure + AWS federation | M365 E5, Azure/AWS portals, security tooling | Device compliance, PIM and Intune are Entra-native; it must own the CA gate |
| Okta | Workforce app-catalog SSO + fine-grained app assignment + Workflows lifecycle for non-Microsoft SaaS | MAM/DAM, Avid/editorial, ad-sales, encoding vendor portals | Deep SaaS catalogue + Okta Workflows JML automation for the media-tool estate |
| Integration | Okta delegates sign-in to Entra via inbound federation (“Sign in with Microsoft”) | Every Okta chiclet | One CA gate — even Okta apps inherit device + phishing-resistant checks |
The load-bearing detail is that last row: Okta is configured with Entra as its Identity Provider, so a user launching an Okta-catalogued MAM is bounced to Entra, satisfies Conditional Access (compliant device, phishing-resistant MFA), and only then returns to Okta for the app SSO and assignment. There is no second, weaker authentication path around the CA gate. Confirming the sync agents and the federation are healthy:
# Cloud sync agents are HA-paired at LA + London — confirm both are active:
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/onPremisesPublishingProfiles/provisioning/agents" \
--query "value[].{id:id, status:status, machine:machineName}" -o table
# Confirm PHS is on and Seamless SSO is live (AZUREADSSOACC computer object in AD):
az rest --method GET \
--url "https://graph.microsoft.com/beta/directory/onPremisesSynchronization" \
--query "value[0].features.passwordSyncEnabled"
Federation hub, break-glass and the JML lifecycle
Every relying party trusts Entra (directly, or via Okta which itself trusts Entra). AWS consumes Entra through IAM Identity Center, which federates via SAML/OIDC and maps Entra groups to AWS permission sets — AWS has zero local IAM users except break-glass, so an HR termination revokes AWS access on the next SCIM cycle; the account-tree and permission-set model is the one in AWS Organizations and IAM foundations. M365/SaaS SSO uses SAML/OIDC with SCIM provisioning; the enterprise-app + claims pattern is Entra SAML SSO enterprise-app configuration.
Two cloud-only break-glass accounts (*.onmicrosoft.com, not synced from AD) are the emergency floor: permanently assigned Global Administrator, excluded from every Conditional Access policy and from PIM, credentialed with FIDO2 keys split across sealed safes in LA and London, and wired to a Sentinel alert that fires on any sign-in — the full model is Entra break-glass emergency access. They are the only standing privileged accounts in the tenant.
Joiner-Mover-Leaver (JML) is driven from Workday as the authoritative HR source, with a media-specific gate that generic IAM misses: studio-partner and pre-release content access requires a content-security (TPN) onboarding gate on top of the HR feed — a contractor colourist is not merely “hired”, they are cleared to touch a specific unreleased title, time-boxed to the engagement. Personas are materialised as dynamic groups (Entra dynamic groups membership rules) computed from HR attributes, so a move between roles re-buckets a user automatically:
| Lifecycle event | Authoritative source | Gate / condition | Automated action |
|---|---|---|---|
| Joiner (staff) | Workday hire → AD DS | Start date reached | Provision account, baseline apps, licence; add to persona dynamic group |
| Joiner (studio partner) | B2B invite + TPN clearance | Content-security clearance for the title — else no premium access | Scoped to the specific production, time-boxed access package |
| Mover | Workday transfer | Dept/role change | Recompute dynamic persona groups; access review of retained rights |
| Leaver (planned) | Workday termination | Last-day timestamp | Disable sign-in, revoke sessions/tokens (Entra + AWS + Okta), reclaim licence |
| Leaver (emergency) | Security/HR manual trigger | Immediate | Real-time session revoke across Entra + AWS + SaaS via SCIM de-provision |
| Contractor / freelancer | Sponsor + access package | Time-boxed package | Auto-expiry with sponsor recertification; no standing access |
AD DS as the source, dual cloud sync into an Entra + Okta hub with Conditional Access in the middle of every issuance, then federation out to AWS, SaaS and the media/ops apps:
Customer identity and access management at scale
The consumer plane is a different universe of scale and threat. It holds 120M subscriber accounts, authenticates viewers across web, mobile, smart-TV, console and set-top, and must issue playback authorization at 45M concurrent during a live final — while facing the open internet, credential-stuffing botnets, free-trial fraud and credential-sharing at population scale. This is CIAM (Customer Identity and Access Management), and it is engineered as an entirely separate estate from the workforce plane above: separate directories, separate token issuers, separate subscriptions (lm-lz-ciam), separate on-call. A viewer is never in AD DS or Entra; a staff member is never in the consumer directory. The two planes do not federate to each other in either direction.
The reason the separation is absolute is written into the tiering: CIAM authentication is Tier-1 (RTO ≤ 15 min, RPO ≤ 1 min — a viewer must never see an outage), but it must fail independently of the Tier-0 workforce plane, so a workforce identity incident cannot take down subscriber sign-in, and vice-versa. Here is how the two planes differ on every axis that matters — this is the table to internalise before anything else:
| Dimension | Workforce plane | Consumer plane (CIAM) |
|---|---|---|
| Population | ~staff + contractors + partners (thousands) | 120M subscriber accounts |
| Directory | AD DS + Entra ID + Okta | Azure AD B2C + Amazon Cognito |
| Source of truth | Workday HR → AD DS | Self-service sign-up + social IdPs |
| Auth authority | Entra (Okta delegates to it) | B2C (Cognito federates to it) |
| Primary factor | Phishing-resistant (FIDO2/passkey), CA-gated | Password / social / passwordless; step-up only on sensitive ops |
| Policy engine | Conditional Access + PIM | Edge WAF/bot + risk-based step-up + entitlement |
| Token purpose | App/API access under least privilege | Playback authorization (short-lived, per-asset) |
| Peak load | Business-hours, thousands of sign-ins | 45M concurrent, millions of token issues/min |
| Governance | JML, access reviews, PIM | Consent, DSAR (GDPR/CCPA), self-service account control |
| Compliance driver | SOC 2, least privilege, TPN | GDPR + CCPA (PII/consent), PCI-DSS (billing) |
| Blast radius of compromise | Production systems, content, cloud | One viewer’s watch history + PII; never a production system |
The CIAM platform: B2C authority, Cognito at the AWS edge
Lumina runs a dual-cloud CIAM with a single account authority. Azure AD B2C is the consumer identity provider and holds the 120M-account directory: local accounts (email/username + password), social IdPs, and custom user journeys built with the Identity Experience Framework (IEF). Amazon Cognito user pools front the AWS-hosted apps and the AWS-native playback APIs; each Cognito pool is configured with B2C as an OIDC identity provider, so B2C stays the single source of consumer truth while Cognito mints AWS-scoped access tokens (and identity-pool temporary credentials where an app must call AWS directly) at the us-east-1 / eu-west-1 / ap-southeast-1 edge. The OAuth2/OIDC flows underneath are the standard ones in OIDC and OAuth2 flows with authorization-code + PKCE — every native app uses authorization-code + PKCE, never the implicit flow.
| Component | Role | Runs in | Notes |
|---|---|---|---|
| Azure AD B2C | Consumer directory + IdP; 120M accounts; sign-up/sign-in journeys | Azure (geo US + EU) | IEF custom policies; social + local; source of consumer truth |
| Amazon Cognito user pools | AWS-edge auth + token broker for AWS-native apps | AWS ×3 regions | Federates to B2C (OIDC); issues AWS-scoped tokens |
| Token / authorization service | Stateless issuer of short-lived playback tokens | Both clouds, active/active | Signs JWT; verified at CDN edge; fed by entitlement |
| Entitlement service | Decides sub status + plan + concurrency + geo + device | Both clouds | Deny-list for instant revocation |
| Profile + consent store | Durable PII, preferences, consent, device registry | Cosmos DB / DynamoDB, region-partitioned | GDPR/CCPA residency; DSAR source |
Sign-in methods, the token model and playback authorization
Consumers sign in with whatever they already have — that is a conversion decision as much as a security one. Every method resolves to the same B2C account and the same downstream tokens:
| Sign-in method | Mechanism | User friction | Where implemented |
|---|---|---|---|
| Email/username + password | B2C local account | Low; password-manager friendly | B2C sign-up/sign-in user flow |
| Social (Apple, Google, Facebook, Amazon) | OIDC/OAuth2 federation | Lowest; one tap | B2C social IdP; Apple required for iOS/tvOS |
| Phone / OTP | SMS or authenticator OTP | Medium | B2C custom policy |
| Passwordless (passkey / magic link) | WebAuthn or one-time email link | Low; no password | B2C + device platform |
| TV/console device code | RFC 8628 device authorization grant | Enter code on phone | B2C + device flow (10-foot UX) |
The token model is where CIAM stops looking like a login box and starts looking like a media system. A consumer session issues ordinary OIDC tokens, but the playback path is authorized by a separate, deliberately short-lived token so that a leaked session cannot stream forever and a shared credential is caught by the entitlement check on every renewal:
| Token | Issued by | TTL | Audience | Verified where | Revocation |
|---|---|---|---|---|---|
| ID token (OIDC) | B2C | ~1h | The app | App | Re-auth |
| Access token | B2C / Cognito | ~1h | Account/profile APIs | API gateway | Short TTL + deny-list |
| Refresh token | B2C / Cognito | Sliding, revocable | Token endpoint | Token service | Revoke on password change / leaver |
| Playback token | Token service | ~90s | Manifest + segment + DRM | CDN edge | Expiry + entitlement deny-list (instant) |
| DRM licence | Multi-DRM (SPEKE) | Per policy | Player CDM | License service | Bound to entitlement + playback token |
The playback flow ties CIAM to the delivery and DRM stack. A viewer authenticates once (B2C/Cognito), then every play request carries a session token to the edge; the token service calls the entitlement service — which evaluates subscription status, plan tier, concurrent-stream cap, geo/blackout and registered-device binding — and only on a pass mints a ~90s signed playback token that the CDN edge verifies before serving a signed manifest/segment, with the multi-DRM licence (Widevine/FairPlay/PlayReady, CENC cbcs) issued against that same entitlement. Bot and credential-stuffing defence sits at the edge before any of this reaches the directory:
Defending 120M accounts: bots, credential-stuffing and rate limits
At this scale the login and sign-up endpoints are under continuous automated attack, and the defence is layered so no single control is load-bearing. The media-specific twist is free-trial abuse (bots minting throwaway accounts to farm trials) and credential-sharing at scale (handled by concurrency/device binding below, not by blocking):
| Threat | Control | Where | Signal / action |
|---|---|---|---|
| Credential stuffing | Breached-password check (k-anonymity), per-account velocity | Edge + B2C | Block known-breached; lock after N fails with backoff |
| Bot sign-up / trial abuse | Bot management + device fingerprint + CAPTCHA step-up | Edge WAF | Challenge/deny non-human; cap sign-ups per IP/ASN |
| Account takeover (ATO) | Risk-based step-up, impossible-travel, new-device email | B2C risk + edge | Step-up MFA; notify + optional session revoke |
| Volumetric / L7 DDoS | Per-IP/per-ASN rate limits, edge DDoS | Multi-CDN edge | Shed load before it reaches /authorize and /token |
| Token replay | Short TTL + audience/asset binding + edge verification | Token service | Expired/replayed playback token rejected at edge |
Rate limits are tiered — global, per-ASN, per-IP and per-account — so a legitimate spike (a live final sign-in surge) is absorbed while a stuffing run from a narrow ASN is throttled. The /authorize and /token endpoints are treated as the most valuable surface in the estate and are never exposed without the edge in front.
MFA, passwordless and account-change protection
Consumers are not asked to MFA on every play — that would wreck conversion and start-time. Instead, friction is reserved for sensitive account operations, and everything else stays frictionless. The rule: viewing is low-friction, changing the account is high-friction.
| Operation | Requirement | Rationale |
|---|---|---|
| Watch content | Session token + entitlement only | Zero added friction; protects QoE/VST |
| Change password / email | Step-up: OTP or passkey | Blocks ATO from a hijacked session |
| Add/remove a device | Step-up + email confirmation | Enforces the device-binding control |
| Change payment method | Step-up MFA (PCI-adjacent) | Payment fraud + chargeback defence |
| Change plan / cancel | Step-up | Prevents malicious downgrade/cancel |
| View full PII / download data | Step-up + identity verification | GDPR/CCPA DSAR safety |
Consent, DSAR and device/concurrency binding
Two population-scale governance duties fall on CIAM. First, consent and data-subject rights under GDPR and CCPA. Lumina captures granular consent at sign-up and in a self-service preference centre, stores it versioned in the consent store, and propagates it downstream (recommendations, ad personalization, analytics). Ad personalization is the sensitive one — CCPA “Do Not Sell/Share” and GDPR/ePrivacy consent must gate the ad-decision path, expressed as IAB TCF/GPP signals:
| Consent category | Default | Gates | Signal |
|---|---|---|---|
| Essential (playback, billing) | On (contractual) | Nothing to opt out of | — |
| Analytics / QoE | Opt-in (EU) / opt-out (US) | Product analytics events | Internal flag |
| Personalization (reco) | Opt-in (EU) | Recommendation engine | Internal flag |
| Advertising | Opt-in (EU) / DNS-honoured (US) | SSAI / ad-decision server | IAB TCF/GPP |
| DSAR type | Regulation | SLA | Verification | Downstream action |
|---|---|---|---|---|
| Access / portability | GDPR Art.15/20, CCPA | 30 / 45 days | Step-up + identity check | Export profile + watch history |
| Erasure (“delete me”) | GDPR Art.17, CCPA | 30 / 45 days | Step-up | Delete/anonymise across profile, analytics, backups per retention |
| Rectification | GDPR Art.16 | 30 days | Step-up | Update profile store |
| Opt-out of sale/share | CCPA/CPRA | Immediate | Session-level | Set GPP; cut ad-personalization |
Second, device and concurrency binding — the credential-sharing control and the reason a leaked password does not become free unlimited streaming. Each account has a registered-device list and a plan-based concurrency cap; the entitlement service enforces both on every playback-token mint:
| Plan | Concurrent streams | Registered devices | Geo binding | Sharing signal |
|---|---|---|---|---|
| Basic (AVOD) | 1 | 2 | Home region | 3rd stream denied |
| Standard (SVOD) | 2 | 4 | Home + travel window | Impossible-geo velocity flag |
| Premium (SVOD) | 4 | 6 | Home + travel window | Device-churn + geo spread flag |
| Live-sports add-on | Per base plan | Per base plan | Blackout-aware | Geo/blackout enforced at token |
Creating a B2C sign-up/sign-in user flow and federating a Cognito pool to B2C — the two moves that stand up the account authority and its AWS-edge bridge:
# 1) Azure AD B2C: create a sign-up/sign-in user flow (Graph beta, in the B2C tenant context)
az rest --method POST \
--url "https://graph.microsoft.com/beta/identity/b2cUserFlows" \
--headers "Content-Type=application/json" \
--body '{
"id": "signupsignin_consumer",
"userFlowType": "signUpOrSignIn",
"userFlowTypeVersion": 3,
"isLanguageCustomizationEnabled": true
}'
# 2) Amazon Cognito: register B2C as the OIDC IdP so B2C stays the single account authority
aws cognito-idp create-identity-provider \
--user-pool-id "us-east-1_LuminaViewers" \
--provider-name "AzureADB2C" --provider-type "OIDC" \
--provider-details '{
"client_id": "<cognito-app-client-id>",
"oidc_issuer": "https://luminab2c.b2clogin.com/<tenant>/v2.0/",
"authorize_scopes": "openid profile email",
"attributes_request_method": "GET"
}' \
--attribute-mapping '{"email":"email","username":"sub"}'
Employee and operations-centre access
Lumina’s workforce is not one population but ten personas, each with a different identity source, application set, device reality and acceptable authentication friction. A platform engineer with cloud-admin reach, an editor cutting an unreleased title, a Media Operations Centre engineer handing off a shift on a shared desk, an ad-sales rep in a booking tool, and a care agent taking subscriber calls from home cannot be served by one access policy — and the entire Conditional Access and Intune model downstream is keyed to these personas. Personas are materialised as dynamic groups so movement between roles re-buckets a user automatically. The productivity base is M365 E5 (Exchange, Teams, SharePoint/OneDrive), all SSO from Entra; the media estate is fronted by Okta (delegating auth to Entra).
This persona/access matrix is the backbone the rest of the part references:
| Persona | Identity source | Primary applications | Device class | Primary auth | CA persona group |
|---|---|---|---|---|---|
| Platform engineer | AD DS (separate admin acct) | Azure/AWS portals, IaC, K8s, observability | PAW | FIDO2 + PIM JIT | ca-persona-platform |
| Content-ops / MOC | AD DS | Playout, encode farm, MAM, QoE | Shared MCR WS | Passkey tap (shared mode) | ca-persona-contentops |
| Editorial / creative | AD DS | NLE (Avid/Premiere), MAM/DAM, review | Editorial WS (remote) | FIDO2 / Hello | ca-persona-editorial |
| Producer | AD DS | Scheduling, rights, MAM, dailies review | Corp laptop + mobile | Hello / authenticator | ca-persona-producer |
| Ad-sales | AD DS | Ad order mgmt, DSP/SSP, SSAI console | Corp laptop | Authenticator MFA | ca-persona-adsales |
| Finance | AD DS | SAP, billing/PCI, revenue, BI | Corp laptop | Phishing-resistant (PCI) | ca-persona-finance |
| Customer care | AD DS | CRM (Zendesk/Salesforce), account tools | Corp laptop (remote) | Authenticator MFA | ca-persona-care |
| Contractor / freelancer | Access package | Scoped project apps only | Corp or MAM BYOD | MFA, time-boxed | ca-persona-contractor |
| Studio partner | B2B guest + TPN | Content exchange, review, specific apps | Their device | Cross-tenant MFA | ca-persona-partner |
| Admin (Tier-0) | AD DS (separate admin acct) | Entra, Intune, security, cloud root | PAW | FIDO2 + PIM JIT | ca-persona-admin |
Fast, secure access on shared operations-centre workstations
The defining workplace problem in a 24×7 Media Operations Centre is the shared workstation: an MCR/NOC desk used by successive shift engineers, where during a live event seconds matter — a full username/password sign-in per handoff is operationally unacceptable — yet a generic mcr1 login shared by everyone destroys the per-engineer audit trail that TPN, SOC 2 and any post-incident review demand. Lumina resolves this the same way healthcare resolves shared clinical carts: Entra shared-device mode + passkey/FIDO2 tap, so a ~2-second tap hands the desk to the next engineer, but the identity asserted to Entra and to playout is always the individual engineer’s UPN. Walk away and the session locks; the next engineer taps and gets their session and their scoped access.
| Ops auth pattern | Speed | Audit fidelity | Entra mechanism | Best fit |
|---|---|---|---|---|
| Passkey/FIDO2 tap on shared-device mode | ~2s handoff | Per-individual UPN | Entra SharedDeviceMode + FIDO2 |
MCR/NOC shared desks |
| Windows Hello for Business | PIN/biometric | Per-individual, strong | Cert/key on device | Assigned editorial/producer WS |
| FIDO2 security key | Tap key | Per-individual, phishing-resistant | Native Entra FIDO2 | PAW, platform-eng, admin |
| Live-event fast-path (pre-approved PIM) | Seconds to elevate | Full PIM audit + Sentinel | PIM eligible + auto-approve | Playout admin during an event |
The rule that keeps this both fast and safe: even the live-event fast-path is still PIM — during a final, the playout-admin role is pre-approved and eligible so activation takes seconds, but it is never standing, always MFA-gated, always alerted to Sentinel, and time-boxed to the event window with a review afterward. Speed comes from pre-approval, not from leaving privilege switched on.
Every media application is fronted by SSO (Okta delegating to Entra, or Entra directly) with automated provisioning, so a single JML event propagates everywhere:
| Application | SSO | Provisioning | CA authentication context | Notes |
|---|---|---|---|---|
| MAM / DAM | SAML via Okta → Entra | SCIM | c1 content (phishing-resistant) |
Premium assets; no local download on unmanaged |
| Editorial (Avid/Premiere) | SAML via Okta → Entra | Group-based | c1 content |
Remote workstation; pixels not files |
| Playout / encode farm consoles | SAML/OIDC → Entra | Manual, privileged | c2 ops (PIM) |
Ops segment only; internet-dark |
| Ad order mgmt + SSAI console | SAML via Okta → Entra | SCIM | Baseline + step-up | Ad path separate from editorial |
| Care CRM (Zendesk/Salesforce) | SAML → Entra | SCIM | Baseline | Agent tooling; no CIAM directory access |
| SAP (finance/billing) | SAML → Entra | SCIM/manual | c3 PCI (phishing-resistant) |
Billing/PCI scope |
| Azure / AWS portals | Entra / IAM IC | — | c2 admin (PIM) |
PAW + FIDO2 only |
An ops-centre engineer or on-call SRE proves device compliance and a fast passkey, clears a scoped ops Conditional Access context (admins add PIM just-in-time), reaches the internet-dark content-ops, encoding and live-control systems, and every action lands in the audit + QoE plane:
Conditional Access and PIM policy model
Conditional Access is where zero trust becomes concrete: every workforce sign-in — including the ones delegated in from Okta — is scored on signals (user risk, sign-in risk, device state, application sensitivity, network location) and the policy set converts that score into grant controls (MFA, compliant device, authentication strength) and session controls (sign-in frequency, persistent browser, download limits). Lumina runs CA as a numbered, persona-targeted set using authentication context, following Conditional Access at scale with personas and authentication context, and every policy ships report-only first — the discipline from deploy baseline CA policies in report-only — so a misfire is caught in What If and the sign-in logs before it locks out a live control room.
Signals in, grant and session controls out, and a separate stricter path for administrators who hold no standing privilege and must activate just-in-time:
The Conditional Access policy set
This is the enforced set — persona-scoped, authentication-context-aware, and layered so controls are additive:
| ID | Policy | Target | Key condition | Grant | Session |
|---|---|---|---|---|---|
| CA001 | Baseline MFA all users | All users, all apps | Exclude break-glass | Require MFA | — |
| CA002 | Block legacy authentication | All users | Legacy auth clients | Block | — |
| CA003 | Require compliant/hybrid device | All users, all apps | — | Compliant OR hybrid-joined | — |
| CA004 | Content apps — phishing-resistant | c1 content (MAM, editorial) |
Editorial/content-ops/producer | Auth strength: phishing-resistant | Sign-in freq 8h; no persistent browser |
| CA005 | Content on unmanaged — no download | Content apps, unmanaged device | Device not compliant | Web-only | App-enforced: block download |
| CA006 | Admin/ops portals — phishing-resistant | c2 admin/ops (Azure/AWS/Intune/playout) |
ca-persona-platform, -admin, -contentops |
Phishing-resistant MFA | Sign-in freq 4h; no persist |
| CA007 | PIM activation — auth context | c2 role-activation context |
Privileged roles | FIDO2 auth strength | — |
| CA008 | PCI billing/finance | c3 PCI (SAP, billing) |
ca-persona-finance |
Phishing-resistant + compliant | Sign-in freq 4h |
| CA009 | Sign-in risk | All users | Risk = high | MFA + password change or block | — |
| CA010 | User risk | All users | User risk = high | Require secure password change | — |
| CA011 | Mobile — app protection | iOS/Android, mail + media apps | BYOD | Require approved app + APP policy | — |
| CA012 | Named/blocked locations | All users | Sign-in from blocked country | Block | — |
| CA013 | EU data residency | EU staff, EU-resident apps | Access to EU data | Compliant + EU region | — |
| CA014 | Studio-partner B2B | ca-persona-partner guests |
External identities | Cross-tenant MFA + TPN clearance | Sign-in freq per session |
| CA015 | Contractors time-boxed | ca-persona-contractor |
Scoped apps only | MFA + compliant/MAM | Sign-in freq 4h |
| CA016 | Live-event control | c2 ops, playout apps |
Live-event window | Phishing-resistant + PIM | Sign-in freq 4h; no persist |
Authentication strength is the mechanism behind the phishing-resistant rows — it maps a resource’s sensitivity to an allowed method list, so content, privileged and PCI access simply cannot be reached with a phishable factor; the rollout baseline is FIDO2 passwordless with Windows Hello and security keys:
| Authentication strength | Allowed methods | Applied to | Personas |
|---|---|---|---|
| Phishing-resistant MFA | FIDO2, Windows Hello, cert-based | Content (c1), admin/ops (c2), PCI (c3), PIM |
Platform-eng, admin, editorial, content-ops, finance |
| MFA (standard) | Authenticator (number match), FIDO2, Hello | Baseline all-user, corp apps | Ad-sales, care, producer, contractor |
| Passwordless | Authenticator phone sign-in, FIDO2, Hello | Corp modern | Opt-in corporate |
Assigning the built-in phishing-resistant strength to the content policy via Graph — landing in report-only first, then validated with CA sign-in log troubleshooting:
az rest --method POST \
--url "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" \
--headers "Content-Type=application/json" \
--body '{
"displayName": "CA004 - Content apps require phishing-resistant MFA",
"state": "enabledForReportingButNotEnforced",
"conditions": {
"users": { "includeGroups": ["<ca-persona-editorial>","<ca-persona-contentops>"],
"excludeGroups": ["<break-glass-group>"] },
"applications": { "includeApplications": ["<mam-app-id>","<nle-app-id>"] }
},
"grantControls": {
"operator": "OR",
"authenticationStrength": { "id": "00000000-0000-0000-0000-000000000004" }
},
"sessionControls": {
"signInFrequency": { "value": 8, "type": "hours", "isEnabled": true },
"persistentBrowser": { "mode": "never", "isEnabled": true }
}
}'
Identity Protection risk and PIM just-in-time
Rows CA009/010 are driven by Entra ID Protection, tuned per ID Protection risk-based policies, so risk becomes an automated response rather than a report:
| Risk signal | Level | Automated response | Rationale |
|---|---|---|---|
| Sign-in risk | High | Block or phishing-resistant MFA + fresh session | Stolen-token/impossible-travel on content or cloud is contained live |
| User risk | High | Secure password change at next sign-in | Compromised credential remediated before broad access |
| Leaked credentials | Any | Force reset + revoke sessions | Known-bad password cannot ride existing tokens |
| Anomalous privileged sign-in | Medium+ | Step-up + Sentinel P1 | Admin accounts get zero benefit of the doubt |
Privileged access uses no standing admin — every high-privilege role (Global Admin, Privileged Role Admin, Intune Admin, Security Admin, the AWS-infra roles federated via IAM Identity Center, and the live-playout/content-security roles) is eligible only through PIM, activated just-in-time with MFA, justification, approval for Tier-0, and a hard time cap; the model follows PIM for roles with approval workflows inside the broader PIM/PAM architecture. Activation is gated by CA007 (FIDO2):
| Role | Eligible group | Max activation | Approval | Requires | Review |
|---|---|---|---|---|---|
| Global Administrator | pim-global-admin |
4h | Yes (2 approvers) | FIDO2 + justification | Monthly |
| Privileged Role Admin | pim-priv-role-admin |
4h | Yes | FIDO2 + justification | Monthly |
| Security Administrator | pim-sec-admin |
8h | No | FIDO2 + justification | Quarterly |
| Intune Administrator | pim-intune-admin |
8h | No | FIDO2 + justification | Quarterly |
| AWS infra (via IAM IC) | pim-aws-infra |
8h | Yes for prod | FIDO2 + ticket | Quarterly |
| Live-playout admin | pim-playout-admin |
4h | Auto-approve in event window | FIDO2 + event ref | Per event |
Creating an eligible (not active) assignment and tightening its activation policy via Graph:
# Make a user ELIGIBLE for Intune Administrator (no standing access):
az rest --method POST \
--url "https://graph.microsoft.com/v1.0/roleManagement/directory/roleEligibilityScheduleRequests" \
--headers "Content-Type=application/json" \
--body '{
"action": "adminAssign",
"principalId": "<admin-account-objectId>",
"roleDefinitionId": "3a2c62db-5318-420d-8d74-23affee5d9d5",
"directoryScopeId": "/",
"scheduleInfo": { "startDateTime": "2026-07-09T00:00:00Z",
"expiration": { "type": "afterDuration", "duration": "P365D" } }
}'
# roleManagementPolicy rules then enforce: activation ≤ PT8H, require FIDO2 (auth context c2),
# require justification, and alert on activation → routed to Sentinel.
Endpoint, UEM and workstation management
A device is a first-class zero-trust signal: Conditional Access rows CA003/005/011 all consume device compliance, so endpoint management is not a productivity nicety — it is the gate that keeps unmanaged, unhealthy or personal hardware away from production content and cloud consoles. Lumina manages endpoints with Microsoft Intune, one enrolment/compliance profile per device class, each emitting a compliance fact CA turns into allow/deny — the linkage is endpoint Conditional Access with device-compliance filters. The media-specific twist mirrors healthcare’s unpatchable-device problem, but inverted: the fragile thing is not the endpoint, it is the premium pre-release content, which must never land on a workstation at all.
Each device class, how it enrols, and where its compliance verdict lands:
| Device class | OS | Enrolment | Ownership | Management profile | Compliance → CA |
|---|---|---|---|---|---|
| Corp laptop | Windows 11 | Autopilot (zero-touch) | Corporate | Full MDM, BitLocker, Defender + CrowdStrike | Compliant → all apps |
| Corp laptop | macOS | ABM/ADE auto-enrol | Corporate | Full MDM, FileVault, platform SSO | Compliant → all apps |
| Editorial / creative WS | Windows/macOS | Autopilot/ADE + remote (AVD/Teradici) | Corporate | MDM + remote-session; content stays in enclave | Compliant → content apps |
| Shared ops WS (MCR/NOC) | Windows 11 | Autopilot + shared-device mode | Corporate | Multi-user, passkey tap, auto-lock | Compliant → ops apps |
| Mobile (corp) | iOS/iPadOS | ADE (supervised) | Corporate | Full MDM, per-app VPN | Compliant → mobile apps |
| Mobile (BYOD) | iOS/Android | MAM-WE (no enrolment) | Personal | App-protection policy only | Protected app → mail/media container |
| PAW (admins) | Windows 11 | Autopilot, hardened baseline | Corporate | Locked-down, no browsing, FIDO2 only | Compliant → admin/cloud consoles |
Corp Windows uses Autopilot for zero-touch (Intune Autopilot zero-touch); macs auto-enrol through Apple Business Manager with FileVault and platform SSO (Intune macOS management); personal phones use MAM app-protection without enrolment (Intune MAM for BYOD) so a freelancer’s own phone gets a protected, encrypted, remotely-wipeable container around Teams and the media apps — Lumina never takes the personal device, only the corporate data on it. Editorial workstations are delivered as remote sessions over AVD/Teradici, the pattern in Conditional Access for AVD and Windows 365, precisely so the content never touches the endpoint.
Compliance policies as the CA signal
Compliance is a small set of hard facts per class; fail any and the device flips non-compliant and CA cuts it from content and cloud automatically — the CrowdStrike Falcon and Defender signals feed the verdict:
| Compliance setting | Corp laptop | Editorial WS | Shared ops WS | BYOD (MAM) |
|---|---|---|---|---|
| Disk encryption | BitLocker | BitLocker | BitLocker | App-level |
| Min OS version | Patch ring | Patch ring | Enforced | App-enforced |
| EDR (CrowdStrike + Defender) | Required | Required | Required | — |
| Threat level (MTD/EDR) | ≤ Medium | ≤ Low | ≤ Low | ≤ Low |
| Jailbreak/root | N/A | N/A | N/A | Blocked |
| Firewall / secure boot | Required | Required | Required | N/A |
| PIN/complexity | Required | Required | Session PIN | App PIN |
| Non-compliance action | Retire after grace | Block content | Immediate block | Block container |
Defining a Windows compliance policy that becomes the CA signal, via the Intune Graph API:
# Compliance policy: BitLocker + secure boot + Defender + min OS → emits "compliant" for CA003.
az rest --method POST \
--url "https://graph.microsoft.com/v1.0/deviceManagement/deviceCompliancePolicies" \
--headers "Content-Type=application/json" \
--body '{
"@odata.type": "#microsoft.graph.windows10CompliancePolicy",
"displayName": "Win11 Content - Compliant baseline",
"bitLockerEnabled": true,
"secureBootEnabled": true,
"defenderEnabled": true,
"osMinimumVersion": "10.0.22631.0",
"deviceThreatProtectionEnabled": true,
"deviceThreatProtectionRequiredSecurityLevel": "medium",
"scheduledActionsForRule": [{
"ruleName": "PasswordRequired",
"scheduledActionConfigurations": [ { "actionType": "block", "gracePeriodHours": 24 } ]
}]
}'
Device classes on the left, one enrolment and compliance path in the middle, the device CA gate, and the protected content and cloud targets — with premium content held in a remote enclave that never reaches the endpoint:
Premium content: the endpoint that never holds the file
The most media-specific endpoint control is the one that protects unreleased premium content, and it exists because the content, not the device, is the crown jewel. Editorial and colour work on pre-release titles is done on remote workstations — the editor sees pixels streamed from a segmented post enclave over AVD/Teradici PCoIP, and the media file, project and proxies stay in the enclave. On top of that sit the TPN / MovieLabs Enhanced Content Protection controls, and the rule is absolute: a stolen or unmanaged laptop, even one belonging to a legitimate editor, can render a review session but can never copy a frame out:
| Control | Mechanism | What it enforces |
|---|---|---|
| Remote-only editing | AVD/Teradici PCoIP; content in the post enclave | No media/project files ever on the endpoint |
| Peripheral lockdown | Session policy: no USB/clipboard/print/screen-capture | Nothing leaves the pixel stream |
| Forensic watermarking | Per-session visible + forensic marks | A leaked frame traces to a user + session |
| Enclave egress control | Deny-by-default on the media-processing segment | Content cannot route to the internet |
| Studio-partner isolation | Per-title B2B access package + TPN clearance | A partner reaches only the one production they are cleared for |
The parallel to healthcare’s unpatchable medical devices is exact: there, the network — not an endpoint agent — is the control that keeps a fragile device from reaching patient data; here, the remote enclave and its egress control — not the endpoint — are what keep a fragile-because-valuable asset from reaching an untrusted device. In both cases the sensitive thing is engineered so it can never be where it must not be.
Streaming platform architecture
Everything the previous sections built — the landing zones, the two identity planes, the network — exists to serve one transaction 45 million times at once: a viewer presses Play and video appears in under two seconds. The streaming platform is the consumer-facing plane of Lumina Media: the ~40 microservices that turn a signed-in device into an authorized, entitled, personalised playback session. It is where the paywall lives, where the catalog is browsed, where continue-watching remembers where you stopped, and where the concurrency ceiling that separates a Basic plan from a Premium one is enforced. It is almost entirely Tier-1 — a viewer must never see an outage — so it runs active/active across all three Azure regions (East US 2, West Europe, Southeast Asia) and the three AWS regions, with region-local reads and short-lived state.
The organising principle is a hard separation between the control path (which decides whether you may watch, in ≤150 ms) and the data path (the manifests and segments the CDN delivers, which the platform never touches per-request). The control path is stateless request/response over the customer-API segment; the data path is signed URLs the player redeems directly at the multi-CDN edge. That split is what lets a handful of entitlement pods authorize tens of millions of concurrent viewers — the platform authorizes the session, not every segment.
The consumer plane reads left→right as client → edge/API → entitlement/session → platform services → data, with concurrency enforced in the middle band.
The services inventory
Every consumer-plane service, its tier, how it is made highly available, the data classification it touches and the scale it must sustain. The scale column is the design point, not a wish — the playback API and entitlement service are sized for a live-final surge, everything else for steady-state plus headroom.
| Service | Tier | HA model | Data class | Design scale |
|---|---|---|---|---|
| Registration / profile | Tier-1 | Active/active, region-local writes, Cosmos multi-region | PII (GDPR/CCPA) | 120M accounts, ~300M profiles |
| Subscription / billing | Tier-1 | Active/active reads; single-writer ledger per region + async recon | PCI-DSS (cardholder) | 120M subs, ~5M txns/day |
| Entitlement | Tier-1 | Stateless pods + Redis cache, region-local | Derived (plan/geo flags) | 8–10M authz/min at peak |
| Playback API + session | Tier-1 | Stateless AKS/EKS, HPA on RPS, per-region | Session token | 45M concurrent sessions |
| Catalog / metadata | Tier-1 (read) | Read-replicated + CDN-cached; single publish origin | Public + embargoed | ~500K titles, >1M req/s reads |
| Search / discovery | Tier-2 | OpenSearch / Azure AI Search cluster per region | Public | ~200K queries/s peak |
| Recommendation | Tier-2 | Precomputed rows + online serving; degrades to editorial | Behavioural (PII) | 120M profiles, rows refreshed daily |
| Watch history / continue-watching | Tier-1 write | High-write Cosmos/DynamoDB, last-write-wins per profile | Behavioural (PII) | ~2M position writes/s |
| Device management + concurrency | Tier-1 | Redis session registry, region-local, global fraud rollup | Device fingerprint | 45M active + registered devices |
| Notifications | Tier-2 | Event Hubs / SNS fan-out, at-least-once | PII (contact) | ~50M pushes/day |
| Customer care / agent tools | Tier-2 | ServiceNow + read APIs, RTO ≤4h | PII + case data | ~2M contacts/mo |
| QoE / playback telemetry | Tier-2 | High-scale event ingest (covered in the analytics core) | Behavioural | 10s of M beacons/min |
Three inventory rules keep this honest. First, billing is the only PCI-scoped service in the consumer plane — it lives in its own subscription (lm-lz-ciam for auth, a separate PCI-scoped billing enclave for the ledger), and the playback path never reads the card ledger; it reads a cached entitlement flag that billing publishes. Second, recommendation and search are Tier-2 by deliberate choice: if either is degraded the home screen still renders from editorial rows and a cached catalog, because a viewer who cannot get a personalised row must still be able to watch. Third, watch-history is Tier-1 for writes but tolerant of eventual consistency — losing the last few seconds of a resume position is acceptable; losing the write path is not, because “continue watching” is the single most-used row on every home screen.
The playback session — the money path
This is the sequence that must complete in the sub-2-second video-start budget. Read it as a strict latency contract: every hop has a budget, and the sum of the p95s must leave room for the player to buffer its first segments.
- App start — the device already holds a CIAM JWT from Azure AD B2C / Amazon Cognito (workforce identity is never involved). The access token is short-lived (~15 min) with a rotating refresh token.
- Browse — the home screen renders from the catalog API (CDN-cached metadata) and precomputed recommendation rows. No entitlement call yet; browsing is free.
- Play pressed — the app calls
POST /playon the playback API with the content id, a device id and the JWT. - Authorize — the playback API calls entitlement synchronously: is the subscription active, is the title licensed in this geo, does the plan include it (e.g. 4K only on Premium), is the device type allowed, and — critically — is a concurrency slot free.
- Session issued — entitlement returns a decision; the session service mints a short-lived, per-session playback token and registers a concurrency slot keyed to the account.
- Manifest + license URLs — the playback API returns a signed, per-session manifest URL (SSAI-personalised for AVOD/live), the DRM license URL, and the CDN steering hint.
- Fetch manifest — the player redeems the signed URL at the multi-CDN edge (token validated at the edge, not the origin) and pulls the CMAF manifest.
- License — the player requests a DRM license, presenting the playback token; the license service validates the token, applies HDCP/output rules and returns the keys.
- Play + heartbeat — the player decrypts and renders, sends a heartbeat to the session service every ~60 s to hold the concurrency slot, and streams QoE beacons to analytics.
- Teardown — on stop, or after a missed-heartbeat timeout, the session slot is released.
Written as a latency contract, the same path with budgets and the failure that each hop must not cause:
| # | Step | Service | Call | p95 budget | Failure it must not cause |
|---|---|---|---|---|---|
| 1 | Token present | CIAM | (cached JWT) | 0 ms | Expired token → silent 401, no play |
| 2 | Browse | Catalog + reco | GET /home |
≤200 ms (edge) | Reco down blanks the screen |
| 3 | Play | Playback API | POST /play |
≤50 ms | API 5xx on the play tap |
| 4 | Authorize | Entitlement | POST /authz |
≤120 ms | Ledger read on hot path |
| 5 | Session | Session svc | mint token + slot | ≤40 ms | Double-count a slot |
| 6 | URLs | Playback API | build signed URLs | ≤30 ms | Un-signed / long-lived URL |
| 7 | Manifest | Multi-CDN edge | GET manifest |
≤150 ms | Origin hit on every start |
| 8 | License | DRM license svc | license request | ≤200 ms | License issued without token |
| 9 | First buffer | Player + CDN | fetch segments | ≤800 ms | Rebuffer at start (>0.4%) |
The POST /play request and response make the contract concrete. The response carries everything the player needs and nothing it must re-derive — the manifest URL is already CDN-steered and signed, the license URL is bound to the session, and the heartbeat interval is server-controlled so concurrency policy can change without a client release:
// POST /v3/play (Authorization: Bearer <B2C/Cognito JWT>)
{
"contentId": "ttl_90f3a2",
"deviceId": "dev_7c1e-appletv-4k",
"capabilities": { "codecs": ["hvc1", "avc1"], "hdcp": "2.2", "hdr": ["hdr10", "dv"] }
}
// 200 OK — session authorized
{
"sessionId": "sess_b81c…",
"playbackToken": "eyJ… (300 s TTL, per-session)",
"manifest": "https://cdn-steer.luminamedia.net/v/ttl_90f3a2/master.m3u8?sig=…&exp=…",
"drm": {
"licenseUrl": "https://lic.luminamedia.net/cenc",
"schemes": ["com.apple.fps", "com.widevine.alpha", "com.microsoft.playready"]
},
"heartbeatSec": 60,
"cdnHint": "cdn-a", // client SDK may re-steer on QoE
"ssai": { "enabled": true, "manifestType": "personalised" }
}
// 403 — denied, with a machine-readable reason the app can act on
{ "error": "concurrency_limit", "plan": "standard", "activeStreams": 2, "maxStreams": 2 }
Device management and concurrency control
Concurrency is where the plan tiers become real money and where credential-sharing fraud is fought. The rule is simple to state — a plan permits N simultaneous streams — and unforgiving to implement at 45M concurrent, because the count must be globally correct, cheap to check on the hot path, and self-healing when a device dies without saying goodbye. Lumina keeps a region-local Redis session registry for the fast check and an async global rollup for cross-region correctness and fraud analytics.
| Plan | Max streams | 4K / HDR | Enforcement point | Signal that frees a slot |
|---|---|---|---|---|
| Basic (AVOD) | 1 | No | Entitlement at /play |
Heartbeat timeout (180 s) or explicit stop |
| Standard | 2 | 1080p | Entitlement + session registry | Heartbeat timeout or LRU evict on 3rd play |
| Premium | 4 | Yes (4K/HDR) | Entitlement + session registry | Heartbeat timeout or LRU evict on 5th play |
| Household add-on | +2 | Per base plan | Registry + household fingerprint | As above, within household device set |
When the ceiling is hit the platform can either deny the new play (403 concurrency_limit, shown above) or LRU-evict the oldest active stream and admit the new one — Lumina evicts for the account owner’s own devices and denies across a suspected-shared boundary. The fraud controls layer on top of the raw count: device fingerprinting, IP/geo clustering, and impossible-travel detection (the same account streaming from two continents inside an hour) feed a risk score that tightens enforcement (step-up re-auth, household verification) without blocking legitimate travel. None of this touches billing — it reads the cached plan flag entitlement already holds.
Data-store selection
The consumer plane is polyglot on purpose: each workload gets the store whose consistency and scale model fits, and the multi-region model is chosen per store so no single global write bottleneck exists on the hot path.
| Workload | Store | Multi-region model | Consistency | Why |
|---|---|---|---|---|
| Profile / preferences | Cosmos DB / DynamoDB | Multi-region writes | Session | Region-local writes, low read latency |
| Entitlement cache | Redis (region-local) | Independent per region | Best-effort + TTL | Sub-ms authz reads, rebuildable |
| Session / concurrency | Redis + async rollup | Region-local + global merge | Eventual (rollup) | Fast slot check, global fraud view |
| Watch history | Cosmos / DynamoDB | Multi-region, LWW | Eventual | High write, tolerant of small loss |
| Catalog / metadata | Cosmos + search index + CDN | Single publish → fan-out | Read-your-publish | One source of truth, cached everywhere |
| Billing ledger | Azure SQL / RDS (PCI enclave) | Single-writer region + async recon | Strong (per region) | Financial integrity, PCI scope |
The load-bearing decision is that entitlement reads never cross a region and never hit the billing database. Billing publishes a compact entitlement document (plan, status, geo rights, renewal) into the region-local Redis cache on every change; the hot path reads that cache. A regional Redis loss is a rebuild-from-source event, not an outage — entitlement fails closed on paid content and open on free-tier content while the cache warms, so the worst case is a brief period where paid titles ask for a retry, not a global paywall failure.
Content ingest, processing and origin
If the streaming platform is how a viewer starts watching, this is how the bits they watch are made. The supply side of Lumina Media takes two very different inputs — a file (a VOD mezzanine master arriving from a studio, a post house or the on-prem archive) and a stream (a live contribution feed from a stadium or the LA/London MOC) — and converts both into the same output: a set of adaptive-bitrate, multi-DRM, CMAF-packaged renditions sitting on a durable origin behind a shield, ready for the multi-CDN to deliver. The governing philosophy is encode-once, package-efficiently: spend CPU once to produce an optimal bitrate ladder, package that single set of media segments once as CMAF, encrypt it once with cbcs, and let HLS and DASH both reference the same bytes. Doing this wrong — re-encoding per format, re-encrypting per DRM, or transcoding on demand — multiplies both cost and the surface where premium content can leak.
The pipeline is a strict left→right sequence with a gate at every stage: ingest → validate/QC → transcode → package + DRM → origin + shield. Premium content (pre-release films, live sports) runs the same shape on an isolated pipeline with its own keys, its own quarantine and TPN/MovieLabs controls.
The ingest interfaces
Two front doors, four real protocols, each with its own security posture and SLA. VOD ingest is a durable object drop plus an API; live contribution is a low-latency transport with forward error correction and 1+1 path redundancy for anything premium.
| Path | Protocol | Typical source | Security | SLA / redundancy |
|---|---|---|---|---|
| VOD file ingest | S3 Multipart / Blob (accelerated) + REST | Studios, post houses, archive | Signed upload URL, per-partner key, VPC/PE | Durable 11-nines; retry idempotent |
| Aspera / signed transfer | FASP over UDP | Large mezzanine masters | Token auth, encrypted at rest on land | High-throughput WAN, resumable |
| Live contribution (standard) | SRT (caller/listener) | Remote encoders, MOC | AES-128/256, stream id auth | Single leg for non-premium |
| Live contribution (open std) | RIST (TR-06) | Interop / third-party feeds | DTLS, SRP auth | ARQ FEC, bonded paths |
| Live contribution (premium) | Zixi | Sports, tentpole live | Encrypted, SST error correction | 1+1 hitless, dual-region ingest |
For premium live the contribution is dual-region and 1+1 hitless: the same feed arrives on SRT into one region and Zixi into another, and the encoder switches legs on packet loss without a visible glitch. That is the single most important reliability decision on the supply side — a fibre cut or a stadium uplink failure during a final cannot be allowed to black out 45 million viewers.
QC, validation and malware — the deny-by-default gate
Nothing transcodes until it passes. Every asset — file or stream sample — runs a battery of automated checks, and premium files additionally sit in a no-public quarantine bucket until a malware verdict clears them. This is both a quality gate (a broadcaster cannot ship clipped audio or the wrong aspect ratio) and a content-security gate (a compromised upload must never reach the origin).
| Gate | Check | Tool / method | Fail action |
|---|---|---|---|
| Container / codec | Valid MP4/MXF, expected codec & profile | MediaInfo / probe | Reject, notify partner |
| A/V integrity | Sync, dropped frames, black/freeze | Automated QC (Baton/Vidchecker-class) | Quarantine, human review |
| Loudness | EBU R128 / ATSC A/85 compliance | Loudness meter in QC | Auto-correct or reject |
| Photosensitivity | Harding / PSE flash test | PSE analyser | Block until cleared (regulatory) |
| Subtitle / caption | Presence, timing, language coverage | Caption validator | Warn; block if contractually required |
| Malware | Signature + heuristic scan | EDR/AV on quarantine bucket | Hard block, isolate, alert SecOps |
| Content-security | Watermark/fingerprint, key class | MovieLabs/TPN premium path | Route to isolated pipeline |
The per-title ABR ladder
The transcode stage is where cost and quality are decided. Lumina does not use one fixed bitrate ladder for every title — it uses per-title encoding: a complexity analysis pass builds a convex hull of quality-vs-bitrate across candidate resolutions and QP settings, and the ladder is trimmed to the points that actually improve VMAF. A simple animated title might top out at 1080p/3.5 Mbps at target quality; a grainy action film needs the full 4K/16 Mbps rung. The result is 20–30% less egress at equal perceptual quality — at Lumina’s scale, that is the difference between viable and ruinous CDN bills.
| Rendition | Resolution | Video codec | Bitrate (target) | Notes |
|---|---|---|---|---|
| 2160p (4K) | 3840×2160 | HEVC (hvc1) | 14–16 Mbps (HDR ~18) | Premium plan only; HDR10/DV |
| 1080p high | 1920×1080 | HEVC / AVC | 5.0–6.0 Mbps | AVC fallback for older devices |
| 720p | 1280×720 | AVC (avc1) | 3.0 Mbps | Broad device baseline |
| 540p | 960×540 | AVC | 1.6 Mbps | Mobile / constrained Wi-Fi |
| 480p | 854×480 | AVC | 1.1 Mbps | Cellular |
| 360p | 640×360 | AVC | 0.65 Mbps | Low-bandwidth floor |
| 240p | 426×240 | AVC | 0.30 Mbps | Emergency rung, keeps play alive |
A representative per-title encode job — the ladder is derived, not hand-set; the complexity pass sets the top rung and the packaging is CMAF fMP4 so the same segments serve HLS and DASH:
{
"job": "encode_ttl_90f3a2",
"input": "s3://lm-ingest-premium/masters/ttl_90f3a2.mxf",
"perTitle": { "enabled": true, "metric": "vmaf", "targetVmaf": 93, "maxRes": "2160p" },
"renditions": [
{ "name": "2160p", "codec": "hevc", "bitrate": 15000, "gop": 2, "bframes": 3, "hdr": "hdr10" },
{ "name": "1080p", "codec": "hevc", "bitrate": 5500, "gop": 2 },
{ "name": "720p", "codec": "h264", "bitrate": 3000, "gop": 2 },
{ "name": "540p", "codec": "h264", "bitrate": 1600, "gop": 2 },
{ "name": "360p", "codec": "h264", "bitrate": 650, "gop": 2 }
],
"segmenting": { "container": "cmaf", "segmentSec": 4, "gopAligned": true },
"audio": [ { "codec": "aac", "bitrate": 128 }, { "codec": "eac3", "bitrate": 384, "layout": "5.1" } ]
}
Packaging and multi-DRM — encode-once made real
The packaging stage produces one set of CMAF fMP4 segments and encrypts them once with CENC cbcs. The reason cbcs matters is history collapsing into simplicity: Apple FairPlay requires AES-CBC (cbcs), while Widevine and PlayReady historically used AES-CTR (cenc) — but modern Widevine and PlayReady now accept cbcs, so a single cbcs ciphertext, delivered as CMAF, can be decrypted by all three DRMs. That is the whole payoff of the encode-once philosophy: one media set, one encryption, three license paths. Keys are fetched from the DRM key service over SPEKE (Secure Packager and Encoder Key Exchange), the standard packager↔key-provider API that a multi-DRM SaaS or AWS SPEKE implements.
| Delivery format | Container | DRM(s) | CENC scheme | Target devices |
|---|---|---|---|---|
| HLS (fMP4) | CMAF | FairPlay (+ WV/PR) | cbcs | Apple, Safari, tvOS, AirPlay |
| DASH | CMAF | Widevine + PlayReady | cbcs (cenc legacy) | Android, Chrome, Edge, smart-TV |
| HLS (legacy TS) | MPEG-TS | FairPlay | cbcs | Old iOS / STB fallback |
| Low-latency live | CMAF (chunked) | All three | cbcs | LL-HLS + LL-DASH players |
Alongside the media, packaging emits the manifests (an HLS master .m3u8 and a DASH .mpd) plus thumbnails/trickplay (a sprite grid or I-frame playlist for scrubbing), subtitles/captions (WebVTT/IMSC1, multi-language), and audio renditions (stereo + 5.1/Atmos where licensed). The manifests are thin pointers into the shared CMAF segment set — the DASH SegmentTemplate and the HLS EXT-X-MAP/#EXT-X-STREAM-INF both address the same init and media segments, which is exactly why encode-once works:
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-STREAM-INF:BANDWIDTH=5500000,CODECS="hvc1.2.4.L153.90,mp4a.40.2",RESOLUTION=1920x1080
1080p/stream.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3000000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1280x720
720p/stream.m3u8
#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES,KEYFORMAT="com.apple.streamingkeydelivery",URI="skd://ttl_90f3a2"
Origin, shield and orchestration
The packaged output lands on a durable, versioned origin (Blob / S3, object-locked for premium) and is fronted by an origin shield — a single caching tier per region that collapses concurrent cache-miss requests into one origin fetch. When a new release unlocks and ten million players ask for the same first segment inside a second, the shield answers all but one from a single origin read; without it, a launch is a self-inflicted DDoS on your own origin. The whole pipeline is driven by a queue-based orchestrator (Step Functions / a durable workflow over SQS/Service Bus) where every stage is idempotent and keyed so a retry re-runs a stage safely rather than duplicating output — essential when a transcode of a two-hour master can fail at 90% and must resume without paying for the first 90% again. This is the classic event-driven, at-least-once pipeline pattern applied to media: every hop assumes redelivery and defends with an idempotency key.
| Stage | Queue / trigger | Idempotency key | Retry policy | On repeated failure |
|---|---|---|---|---|
| Ingest register | Object-created event | assetId + etag |
3× exp backoff | DLQ + partner notify |
| QC / scan | qc-jobs queue |
assetId + qcVersion |
2× | Quarantine, human review |
| Transcode | encode-jobs queue |
assetId + ladderHash |
3× (checkpointed) | DLQ + alert, keep master |
| Package + DRM | package-jobs queue |
assetId + drmKeyId |
3× | DLQ, block publish |
| Publish to origin | publish queue |
assetId + renditionSet |
5× | Alarm; origin never partial |
| Cache warm | scheduled / launch event | titleId + region |
best-effort | Log; delivery still works |
Live streaming and channel operations
Live is where every weakness in the supply chain becomes visible in real time to millions of people at once, with no second take. A VOD encode that fails can be retried; a dropped live final is a headline. Lumina runs two distinct live shapes on shared infrastructure — linear channels (24×7 FAST and scheduled channels with a playout schedule) and live events (sports and news, spun up per event, torn down after) — and both are engineered around one uncompromising rule: on event day, nothing scales from cold. Contribution is redundant, encoders are hot in two availability zones, origins are active/active, and the multi-CDN is pre-warmed. The latency target is glass-to-glass under 8 seconds using LL-CMAF, which is aggressive enough that every stage’s delay contribution is budgeted.
The live path reads: dual contribution → multi-zone encode/package → playout + SSAI → origin redundancy → multi-CDN.
Contribution redundancy
The first leg of a live event is the one you least control — it rides public or leased networks from a venue. Lumina engineers it as 1+1 hitless: two independent transports, diverse carriers, diverse ingest regions, with the downstream encoder reconstructing a clean signal by switching between them packet by packet.
| Leg | Protocol | Path | Ingest region | Role |
|---|---|---|---|---|
| Primary | SRT (listener) | Venue → LA MOC → cloud | us-east-1 / East US 2 |
Active |
| Backup | Zixi | Venue → London MOC → cloud | eu-west-1 / West Europe |
Hitless standby |
| Tertiary (marquee) | RIST bonded | Cellular bond from venue | Nearest region | Break-glass |
The rule enforced by alarm: the moment the channel runs on a single leg, page. Redundancy that silently degrades to a single point of failure is worse than none, because it hides the risk until the second leg also fails.
The low-latency budget
Sub-8-second live is not one trick; it is a chain of small delays kept individually small. Chunked CMAF (a 4 s segment delivered as eight 500 ms chunks over chunked-transfer encoding) lets the player start rendering a segment before the encoder has finished writing it. LL-HLS adds partial segments (parts) with preload hints and blocking playlist reloads; LL-DASH uses availabilityTimeOffset and chunked SegmentTemplate.
| Stage | Delay contribution | Technique to hold it |
|---|---|---|
| Contribution + de-jitter | ~1.0–1.5 s | SRT/Zixi buffer sized to path jitter |
| Encode | ~1.0 s | Low-latency GOP, look-ahead capped |
| Package (chunked CMAF) | ~0.5–1.0 s | 500 ms chunks, chunked transfer |
| Origin + shield | ~0.3–0.5 s | Shield co-located, request collapse |
| CDN edge | ~0.5–1.0 s | LL-HLS parts / LL-DASH ATO passthrough |
| Player buffer | ~2.0–2.5 s | Tuned live-edge target, small buffer |
| Glass-to-glass | < 8 s | End-to-end probe alarms on breach |
Playout, SCTE-35 and blackout
Linear channels are assembled by a playout service that follows a schedule (the electronic program guide), splices content and ads, and inserts SCTE-35 markers at ad and program boundaries. Those markers are the nervous system of monetization and rights enforcement: SSAI reads them to stitch personalised ads into each viewer’s manifest (VAST/VMAP creative from the ad-decision server), and blackout/geo rules read them to replace content with a slate in territories where a rights window forbids it. The markers travel from the transport stream into the manifests as HLS EXT-X-DATERANGE and DASH emsg/EventStream so the packager and SSAI stay frame-accurate.
| Event | SCTE-35 signal | Manifest carriage | Action |
|---|---|---|---|
| Ad break start | splice_insert (out) |
HLS EXT-X-DATERANGE / DASH emsg |
SSAI splices personalised pod |
| Ad break end | splice_insert (in) |
matching range close | Return to content |
| Program boundary | time_signal + segmentation |
DATERANGE with segmentation type | EPG update, chapter mark |
| Blackout start | time_signal (blackout) |
edge geo-rule + slate | Replace with slate in territory |
| Blackout end | time_signal (restore) |
edge rule lifts | Resume live content |
A representative ad-avail marker as it appears in an HLS manifest — SSAI keys off exactly this to decide where the pod goes and how long it runs:
#EXT-X-DATERANGE:ID="ad-8842",CLASS="com.lumina.ad",START-DATE="2026-07-09T20:15:00Z",\
DURATION=90.0,SCTE35-OUT=0xFC30200000000000... # 90 s avail, SSAI fills per viewer
#EXT-X-CUE-OUT:90.000
...ad segments spliced per-viewer by SSAI...
#EXT-X-CUE-IN
Event-day design
A live final is run to a script. The design freezes change, pre-provisions capacity, and rehearses failover before a single viewer arrives — because active/active multi-region only protects you if the standby is genuinely warm and the runbook has been walked.
| Phase | Action | Owner |
|---|---|---|
| T-7 days | Change freeze on all Tier-1 live services; capacity + quota review | Live eng + platform |
| T-3 days | Game-day: force contribution failover, origin failover, CDN drain | Live ops + SRE |
| T-1 day | Pre-warm CDN caches, pre-scale encoders/packagers hot in 2 AZ | Live ops |
| T-2 h | War room open; multi-CDN steering to lowest-VST config; monitors up | Live ops + NOC |
| Live | Watch per-leg health, VST, rebuffer, per-CDN QoE; single-leg = page | NOC |
| Post | Unfreeze; retro on any switch/failover; archive the event to VOD | Live eng |
VOD architecture
The VOD library is Lumina’s largest and oldest asset: ~400 PB of masters and renditions spanning half a million titles, most of which are watched rarely and a few of which are watched by tens of millions the week they launch. The architecture is shaped by that brutal distribution — a small hot head and an enormous cold tail — and by one economic fact: you cannot afford to keep 400 PB on hot storage, and you cannot afford to re-encode on demand. So VOD is built on two disciplines: transcode-once (every title is encoded to its full ABR ladder exactly once, at ingest, and the packaged CMAF is stored and reused forever) and tiered storage with lifecycle (renditions and masters migrate across hot/cool/cold/archive tiers by age and popularity, and are promoted back on demand).
The VOD path reads: upload → transcode-once → tiered storage → cache-warm → delivery. It reuses the ingest pipeline from earlier and adds the storage-economics and catalog-sync layers that make a library this size affordable. Lumina’s design here mirrors the reference VOD multi-DRM streaming architecture, extended to two clouds and six regions.
Tiered storage lifecycle
The single most important cost lever in the whole media estate. Masters go straight to the coldest tier — they exist only to re-encode from, so hours-long retrieval is fine. Renditions live where their audience is: new and popular titles Hot, mid-catalog Cool/Cold, long-tail Archive, promoted back the moment demand returns. This is the S3 storage-class lifecycle discipline applied at 400 PB, with the Azure Blob equivalents in lockstep.
| Tier | Access pattern | Azure Blob | AWS S3 class | Retrieval | What lives here |
|---|---|---|---|---|---|
| Hot | New + popular renditions | Hot | S3 Standard | Instant | This week’s launches, top catalog |
| Cool | Mid catalog, occasional | Cool | S3 Standard-IA | Instant (fee) | Titles cooling after launch window |
| Cold | Long-tail renditions | Cold | Glacier Instant Retrieval | ms–instant | Rarely-watched but must play now |
| Archive | Mezzanine masters | Archive | Glacier Deep Archive | Hours (rehydrate) | 400 PB of masters, re-encode source |
The lifecycle is policy-driven, not manual. A representative object-lifecycle policy demotes renditions by age and archives masters immediately, while a promotion job (triggered by a demand signal or a scheduled re-release) rehydrates on the way back up:
// S3 lifecycle — renditions bucket (masters bucket sends everything to Deep Archive on day 0)
{ "Rules": [
{ "ID": "renditions-cooldown", "Filter": { "Prefix": "renditions/" }, "Status": "Enabled",
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER_IR" },
{ "Days": 365, "StorageClass": "DEEP_ARCHIVE" } ] },
{ "ID": "masters-archive", "Filter": { "Prefix": "masters/" }, "Status": "Enabled",
"Transitions": [ { "Days": 0, "StorageClass": "DEEP_ARCHIVE" } ] }
] }
// Azure Blob lifecycle management — equivalent tiers
{ "rules": [ { "enabled": true, "name": "renditions-cooldown", "type": "Lifecycle",
"definition": { "filters": { "blobTypes": ["blockBlob"], "prefixMatch": ["renditions/"] },
"actions": { "baseBlob": {
"tierToCool": { "daysAfterModificationGreaterThan": 30 },
"tierToCold": { "daysAfterModificationGreaterThan": 90 },
"tierToArchive": { "daysAfterModificationGreaterThan": 365 } } } } } ] }
Renditions, trailers and optimization
Not every asset gets the same treatment. A flagship series carries the full ladder plus HDR and Atmos; a catalog filler carries a trimmed ladder; trailers and previews are tiny, always-hot, and aggressively cached because they front the discovery experience and must start instantly.
| Asset class | Rendition set | Storage posture | Notes |
|---|---|---|---|
| Tentpole / new release | Full per-title ladder + 4K HDR + Atmos | Hot, cache-warmed | Highest quality, pre-warmed at launch |
| Standard catalog | Per-title ladder to 1080p | Hot → Cool by age | Bulk of the library |
| Long-tail | Trimmed ladder (≤720p common) | Cold, promote on demand | Rarely watched; keep it playable |
| Trailers / previews | 1080p + mobile rungs | Always Hot, long TTL | Discovery surface, instant start |
| Masters (mezzanine) | N/A (source) | Archive / Deep Archive | Re-encode source only |
Cache warming and the long tail
The multi-CDN handles delivery, but for VOD the platform actively warms caches ahead of predictable demand rather than waiting for the first viewer to pay the origin round-trip. A tentpole premiere is a scheduled event: top renditions are pushed to the origin shield and key edge PoPs before the regional unlock, so the T0 surge is served from cache, not from a cold origin. The long tail is the opposite problem — millions of titles too rare to keep hot in every PoP — and there the origin shield does the heavy lifting, collapsing the occasional request and accepting a lower edge hit-ratio on obscure content while protecting the origin.
| Trigger | Warm strategy | Scope |
|---|---|---|
| Scheduled premiere / launch | Pre-push top renditions to shield + edge before unlock | Per-region, staged by unlock time |
| Trending detection | Reactive warm as plays ramp | Popular PoPs |
| Editorial feature (home row) | Warm trailer + first segments | Global |
| Long-tail request | Shield request-collapse, no proactive warm | On-demand fill |
| New device/app launch | Warm onboarding + demo content | Targeted |
Catalog and metadata sync
Metadata is the connective tissue: entitlement reads it to know rights windows, search indexes it, recommendation features off it, and the client renders it. Because it is read everywhere and written rarely, Lumina treats it as a single-publish, fan-out system — one authoritative publish per change, versioned, replicated asynchronously to region-local read replicas, so every one of the six regions serves a consistent view without a global write bottleneck.
| Entity | Source of truth | Sync mechanism | Region model |
|---|---|---|---|
| Title metadata | Catalog master (single publish) | Versioned doc → async fan-out | Region-local read replicas |
| Rights / availability windows | Rights management system | Event on change → entitlement cache | Region-local, embargo-aware |
| Artwork / images | Asset service + CDN | Immutable URLs, long TTL | Global CDN cache |
| Search index | Derived from catalog | Reindex on publish | Per-region cluster |
| Recommendation features | Derived from catalog + behaviour | Batch + streaming refresh | Per-region serving store |
The rule that ties the VOD library back to the streaming platform: catalog is the source of truth, and it is versioned. A title’s availability, its rendition set, its rights window and its artwork all carry a version; entitlement, search and the client all resolve against that version, so a change (a new territory unlock, a pulled title, a re-encoded rendition) propagates cleanly instead of leaving regions disagreeing about what exists. At half a million titles across six regions, version drift is the failure mode you design against from day one.
DRM, entitlement and content protection
This is where the money lives, so this is where the attackers, the password-sharers and the studios all point at once. Lumina’s studio partners contractually require MovieLabs Enhanced Content Protection and a TPN/CDSA-audited chain for premium and pre-window titles; the finance team wants password-sharing crushed; the viewer wants a play button that never argues. Those three pressures collapse into one subsystem: a DRM + entitlement plane that decides — for this play, on this device, in this region, right now — whether a decryption key is allowed to reach the player, and under what output rules. It is a Tier-1 service (RTO ≤15m, RPO ≤1m): if entitlement or the license service is down, 45M concurrent viewers see a spinner, so it runs active/active across Azure East US 2 / West Europe / Southeast Asia and AWS us-east-1 / eu-west-1 / ap-southeast-1.
The single most important architectural decision is to encrypt the content once and license it three ways. Lumina must reach Widevine (Android, Chrome, most smart-TVs), FairPlay (Apple/Safari) and PlayReady (Windows, Xbox, many STBs/TVs). Naïvely that is three encodes and three storage copies of a 400 PB library — unaffordable. The escape hatch is CENC (Common Encryption): one CMAF asset, encrypted once, that all three DRMs can license. The following table is the reason the whole pipeline is economically viable.
Below is the multi-DRM landscape Lumina actually ships against — the client reach, the security levels that gate resolution, and the output rules each system enforces.
| DRM system | Platforms it owns | Security levels | Container / scheme | HDCP / output control | Offline license |
|---|---|---|---|---|---|
| Google Widevine | Android, Chrome, Chromecast, most smart-TVs, Fire TV | L1 (HW TEE), L2, L3 (SW) | CMAF/fMP4, cenc + cbcs |
HDCP version + type via policy | Yes (persistent license) |
| Apple FairPlay (FPS) | Safari, iOS, iPadOS, tvOS, macOS | Hardware SVE (single tier) | CMAF/fMP4, cbcs only |
HDCP enforced by platform | Yes (persistent key) |
| Microsoft PlayReady | Windows, Xbox, many STB/TV, Edge | SL150 (dev), SL2000 (SW), SL3000 (HW) | CMAF/fMP4, cenc + cbcs |
HDCP + output protection levels | Yes (persistent license) |
| Single-asset target | all of the above | floor set per content tier | one CMAF, cbcs |
policy set at license time | policy-gated |
The bottom row is the whole game: cbcs (AES-128 CBC with a pattern, 1:9 for video) is the one scheme all three systems support in CMAF, so a single cbcs-encrypted CMAF asset licenses to Widevine, FairPlay and PlayReady. cenc (AES-128 CTR) is retained only for legacy DASH clients that predate cbcs. Package cbcs for the modern estate and you carry one encrypted copy, not three.
| Encryption scheme | Cipher | Pattern | DRMs that accept it | Use it for | Avoid when |
|---|---|---|---|---|---|
cbcs |
AES-128 CBC | 1:9 (crypt:skip) |
Widevine, FairPlay, PlayReady | Default — LL-CMAF, modern apps/TVs | Very old DASH-only clients |
cenc |
AES-128 CTR | full-sample | Widevine, PlayReady (not FPS) | Legacy DASH fallback catalogue | FairPlay in scope (it can’t) |
| Mixed asset | both | n/a | — | don’t — doubles storage + keys | Always avoid on new content |
The license-acquisition flow
The player never holds a key it can inspect. The Content Decryption Module (CDM) inside the device — Widevine L1, FairPlay SVE, PlayReady SL3000 — generates a license challenge that only the license server (and the DRM root of trust) can answer. The playback control plane sits in front of the license server: the player first exchanges its session/playback token for a green light, then the license request is decorated with that entitlement so the license server issues a key only for the KIDs the viewer is allowed. The path is short and every hop is a checkpoint.
The steps below map exactly to the diagram; keep them side by side.
| # | Step | Actor | What is checked / carried | Failure = |
|---|---|---|---|---|
| 1 | Get playback token | Player → Playback API | valid CIAM JWT, title id, device id | 401/403, no play |
| 2 | Entitlement decision | Playback API → Entitlement | sub status, plan, rights window, geo, device, concurrency | 403, blocked |
| 3 | Issue playback token | Entitlement → Player | short-lived JWT: KIDs, policy, exp |
— |
| 4 | License challenge | Player CDM → License svc | DRM challenge + playback token | invalid → denied |
| 5 | Key + usage rules | License svc → Player | content key(s) under output/HDCP policy | non-compliant sink → SD/black |
| 6 | Decrypt + play | Player CDM | key held in secure world only | — |
Here is the DRM and entitlement plane end to end — the player asks, entitlement decides, the multi-DRM service issues a policy-bound key, and every issuance is logged for anti-fraud.
Keys, SPEKE and rotation
The packager and the license server must agree on content keys without a human ever touching plaintext key material. Lumina uses the SPEKE (Secure Packager and Encoder Key Exchange) contract — the packager POSTs a CPIX (Content Protection Information eXchange) document naming the key ids and DRM systems it wants; the key provider returns keys and the per-DRM PSSH/pssh and license-URL data. For VOD the keys are stable for the asset; for live channels the key rotates per period so a leaked key is worthless within minutes. Content keys are wrapped by a KEK in AWS KMS / Azure Key Vault (HSM) and never persisted in plaintext — the license service fetches and unwraps per request. See Azure Key Vault: secrets, keys and certificates for the HSM-backed key custody model this reuses.
A SPEKE/CPIX request from the packager to the key service looks like this (abridged CPIX — the packager asks for one cbcs key covering all three DRMs):
<!-- Packager → SPEKE key server (CPIX 2.3, cbcs, KID = content key id) -->
<cpix:CPIX id="asset-9f21-uhd" xmlns:cpix="urn:dashif:org:cpix"
xmlns:speke="urn:aws:amazon:com:speke">
<cpix:ContentKeyList>
<cpix:ContentKey kid="2b6f0cc9-04e9-4c73-8f2a-9a1d4c8b1e77"
commonEncryptionScheme="cbcs"/>
</cpix:ContentKeyList>
<cpix:DRMSystemList>
<!-- one KID, three systemIds → Widevine, FairPlay, PlayReady -->
<cpix:DRMSystem kid="2b6f0cc9-04e9-4c73-8f2a-9a1d4c8b1e77"
systemId="edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/> <!-- Widevine -->
<cpix:DRMSystem kid="2b6f0cc9-04e9-4c73-8f2a-9a1d4c8b1e77"
systemId="94ce86fb-07ff-4f43-adb8-93d2fa968ca2"/> <!-- FairPlay -->
<cpix:DRMSystem kid="2b6f0cc9-04e9-4c73-8f2a-9a1d4c8b1e77"
systemId="9a04f079-9840-4286-ab92-e65be0885f95"/> <!-- PlayReady -->
</cpix:DRMSystemList>
</cpix:CPIX>
Entitlement rules — the decision that matters
Entitlement is where “do you have Netflix” becomes “can this frame decrypt.” It is a server-side decision — the client is never trusted — evaluated on every license acquisition and re-validated on long plays via the token exp. The rule set is a conjunction: all must pass. Each rule below states the signal it reads, where the source of truth lives, and the deny action a viewer actually sees.
| Rule | Signal / source of truth | Evaluated where | Deny action |
|---|---|---|---|
| Subscription status | active/grace/cancelled — billing + CIAM claim | Entitlement API | 403, “renew to watch” |
| Plan / tier | SD vs HD vs UHD, add-on packs | Entitlement API | downgrade ladder or block |
| Concurrency | active streams vs plan cap (2/4/…) | Concurrency ledger | block newest / evict oldest |
| Geo / territory | client IP geo + account country + license rights | Edge + Entitlement | 403, geo-block screen |
| Rights window | title availability (start/end), blackout | Rights service | “not available in your region/now” |
| Device class | approved device, min security level | Entitlement + license | SD-only or block |
| Content tier floor | premium/4K → HW DRM required | License policy | refuse HW-incapable CDM |
| Blackout (live) | sports/event territorial blackout | Live event control | alt feed or block |
The playback token that carries this decision to the license server is a tight JWT — short-lived, audience-scoped, and stripped of anything a shared link could reuse. These are the claims Lumina signs.
| Claim | Example | Purpose | Notes |
|---|---|---|---|
sub |
acct_8c1a… (pairwise) |
account id (pseudonymous) | never the email/PII |
did |
dev_5f2b… |
device binding | token bound to one device |
kids |
["2b6f0cc9…"] |
keys this play may license | scopes the license request |
plan |
uhd |
resolution/output ceiling | drives HDCP floor |
geo |
GB |
territory at issue time | re-checked at edge |
cc |
2 |
concurrency lease id | ties to the ledger slot |
exp |
+120s (license) |
short expiry | license token ≠ session token |
pol |
hdcp22-type1 |
output policy ref | resolves to usage rules |
A denied decision is explicit and cheap — the license server is never reached:
// Entitlement API → player (concurrency exceeded)
{ "decision": "deny", "reason": "concurrency_limit",
"plan_cap": 2, "active_streams": 2,
"message": "You're watching on the maximum number of devices for your plan.",
"retry_after_s": 30 }
HDCP, robustness and premium/4K
Studios do not accept “we encrypted it.” They require that a 4K/UHD frame can only ever reach a hardware-protected output. That is enforced by two knobs baked into the license: the DRM security-level floor (Widevine L1, PlayReady SL3000, FairPlay hardware) and the HDCP requirement on the display link. A device with a software CDM, or an HDMI capture path without HDCP 2.2, is stepped down to SD or refused — not blocked with an error, just quietly denied the UHD key. This is the MovieLabs ECP contract in practice.
| Content tier | DRM level floor | HDCP required | Max resolution if not met | Extra controls |
|---|---|---|---|---|
| Pre-theatrical / early window | WV L1 · PR SL3000 · FPS HW | HDCP 2.2 Type-1 | refuse (no play) | forensic watermark, TPN chain |
| UHD / 4K / HDR | WV L1 · PR SL3000 · FPS HW | HDCP 2.2 Type-1 | step down to HD/SD | disallow analogue out |
| HD | WV L1/L3 · PR SL2000 | HDCP 1.4+ | step down to SD | — |
| SD / catalogue | WV L3 · PR SL2000 | none | play | — |
| Live premium sport | WV L1 · PR SL3000 | HDCP 2.2 | step down + blackout | key rotation, low-latency |
Anti-fraud: credential sharing and token abuse
Password-sharing is not a DRM break — the keys are fine — it is an entitlement problem, and Lumina fights it in the concurrency ledger and the anti-share scorer, not the CDM. The controls below turn “one login, ten households” into an economically pointless exercise without punishing a legitimate family on holiday.
| Threat | Signal | Control | Response |
|---|---|---|---|
| Password sharing | plays from many distinct home networks/geos | device + household clustering, concurrency cap | cap streams, prompt “add a member” |
| Concurrency abuse | streams > plan cap | strongly-consistent stream ledger + lease TTL | block/evict oldest heartbeat |
| Token replay | same playback token from many IPs/ASNs | bind token to did, tight exp, one-time nonce |
reject, force re-auth |
| License farming | high license-request velocity per account | rate-limit + anomaly score | throttle, step-up challenge |
| Impossible travel | GB→SG in 60s on one account | geo-velocity model | challenge / soft-block |
| Manifest/link scraping | signed-URL reuse off-device | edge token bound to session + IP | edge 403 at expiry |
Two rules keep this from becoming a support nightmare. First, the license token is short (≈120s) and device-bound; the session token is longer but only unlocks requesting a license, never a key. Second, denials are graded — a first anomaly gets a step-up challenge, not a lockout, so churn stays low. Every issuance is logged (account, KID, device, geo, resolution, decision) into the audit store, which does triple duty: fraud model training, TPN forensic evidence, and windowing/royalty reporting.
Offline playback (download-to-go) gets its own policy because a persistent license is a key sitting on a device. Lumina scopes it hard.
| Offline control | Policy | Why |
|---|---|---|
| Persistent license TTL | 30-day validity, 48h play window once started | limits key exposure |
| Renewal | requires online entitlement re-check | catches cancellations |
| Max downloads | per-plan device/title cap | limits sharing |
| Security level | HW CDM only for HD+ offline | studio requirement |
| Territory | licensed at download, re-checked on renew | rights compliance |
CDN, multi-CDN and edge delivery
Bytes-to-eyeballs is 95%+ of Lumina’s egress and the single biggest driver of both QoE and cloud/CDN spend. One CDN is a single point of failure at 45M concurrent, a single pricing negotiation, and a single set of regional weak spots. So Lumina runs three commercial CDNs (CDN-A / CDN-B / CDN-C) in front of a shielded origin, and steers traffic between them on live quality and cost signals. The targets are non-negotiable: VST p95 < 2s, rebuffer ratio < 0.4%, playback-failure < 0.5%, live latency < 8s (LL-CMAF), 99.99% availability for Tier-1 delivery. Hitting those at scale, across three CDNs, is an engineering discipline, not a purchase.
Here is the delivery path — viewer and client steering pick a CDN from live scores, DNS steering covers clients that can’t, the edge authenticates and filters, and an origin shield protects the packager.
Steering: how a session picks a CDN
Steering happens in two layers. The client-side SDK in the player holds a ranked CDN list and can switch mid-stream, on the next segment, when it sees rebuffering or 5xx — this is the fast loop that saves a live final. DNS steering is the slow, coarse loop for clients that can’t run the SDK (some TVs/STBs), routing by geo/latency at resolve time. Both are fed by a decision service that scores each CDN per region/ASN from RUM beacons and synthetic probes. The signals below are what actually move traffic.
| Signal | Source | Cadence | Steering effect |
|---|---|---|---|
| Rebuffer ratio | RUM (player beacon) | seconds | down-weight CDN in region/ASN |
| VST (start time) | RUM | seconds | prefer faster CDN for cold start |
| HTTP error rate (4xx/5xx) | RUM + edge logs | seconds | fast fail-away on browning CDN |
| Throughput / goodput | RUM | seconds | pick highest sustainable ladder |
| Synthetic availability | probes per POP/region | ~1 min | pull CDN from rotation on outage |
| Cost / commit tier | billing model | daily | spill to cheaper CDN once QoE-safe |
| Region / ASN affinity | geo-IP | per session | keep last-mile short |
| Live vs VOD class | request context | per session | route live to lowest-latency CDN |
The three CDNs are not interchangeable clones — Lumina assigns them roles so steering has a sane default and a clear failover order.
| CDN | Primary role | Steered by | Notes |
|---|---|---|---|
| CDN-A | primary for Tier-1 live + top VOD | QoE-first | best low-latency footprint, origin-shield tuned |
| CDN-B | co-primary / regional strength | QoE + geo | strongest in EU/APAC pockets, failover for A |
| CDN-C | cost spill + burst | cost-weighted | absorbs long-tail VOD + overflow once QoE safe |
Per-asset cache policy — the highest-leverage config
The most common self-inflicted outage in streaming is a wrong TTL on a live manifest: cache a low-latency playlist for 10s and every player replays a stale segment list and stalls. Each asset type has a different correctness contract, and the cache policy must match it exactly. This table is the reference the CDN configs are generated from.
| Asset type | Path pattern | Edge TTL | Cache key | Notes |
|---|---|---|---|---|
| Live manifest (LL-CMAF) | *.m3u8 / *.mpd (live) |
1-2 s | path + session-agnostic | must refresh each segment period |
| VOD manifest | *.m3u8 / *.mpd (vod) |
30-60 s | path | rarely changes post-publish |
| Media segment | *.cmf* / *.m4s / *.ts |
long / immutable (7-30 d) | path (+ no query) | content-addressed, never mutates |
| Init / key manifest | init.mp4, *.pssh |
long | path | stable per rendition |
| Image / artwork | *.jpg *.webp (/img/*) |
7-30 d | path + resize params | poster/thumb, heavy long-tail |
| Playback API | /api/*, /entitlement/* |
no-store | — | dynamic, per-user, never cache |
| DRM license | /license/* |
no-store | — | key material, never cache |
| SSAI manifest | /ssai/*/manifest |
no-store (per-session) | session token | unique per viewer |
Two rules make this safe: media segments are immutable and versioned in the URL so a long TTL is always correct (a new encode = a new path), and anything per-user or key-bearing is no-store so a shared cache can never leak one viewer’s entitlement or key to another.
Edge security: signed URLs, tokens, WAF
Every media URL is signed and every edge validates it before touching origin. Lumina uses short-lived signed URLs / edge JWTs bound to path, expiry and (where possible) client IP/ASN, so a leaked manifest link dies at exp and can’t be farmed. On CDNs that support edge compute, a small function verifies the token; the CloudFront-class pattern is a signed URL with a canned/custom policy, e.g.:
# Signed segment URL — expires in 300s, path-scoped, one session
https://cdn-a.lumina.tv/vod/asset-9f21/uhd/seg_0421.cmfv
?Expires=1751990400
&Signature=Jx8f...~redacted~...q2
&Key-Pair-Id=K2LUMINA9F21
&tok=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9... # session JWT: exp, path, ip, did
And the edge check (edge-function pseudocode, run before origin fetch):
// Edge token validation (runs at every CDN POP, pre-origin)
function onViewerRequest(req) {
const t = verifyJwt(req.qs.tok, EDGE_PUBKEY); // ES256, rotated
if (!t || t.exp < now()) return deny(403, "expired");
if (!req.uri.startsWith(t.path)) return deny(403, "path");
if (t.ip && t.ip !== req.clientIp && !sameAsn(t.ip, req.clientIp))
return deny(403, "ip");
if (isBlockedGeo(req.geo, t.rights)) return deny(451, "geo");
return allow(req); // WAF/bot/DDoS rules run in front of this
}
The full edge security stack layers on top of token validation, and it all runs at the edge so attacks never reach origin. The WAF/bot layer reuses the same pattern as Azure Application Gateway with WAF and mTLS at L7.
| Edge control | What it stops | Where | Config note |
|---|---|---|---|
| Signed URL / JWT | link sharing, hotlinking | every POP, pre-origin | ES256, exp ≤ 300s, path-scoped |
| Token→session binding | token replay/farming | edge function | bind to did + IP/ASN |
| WAF (L7) | injection, bad requests | edge | managed + custom rules on /api |
| Bot management | credential stuffing, scraping | edge | challenge on /login, /manifest |
| DDoS (L3/4 + L7) | volumetric + app floods | edge/scrubbing | always-on, per-CDN |
| Rate limiting | license/API abuse | edge | per-token + per-IP buckets |
| Geo / rights block | territorial violations | edge | 451 on blacked-out territory |
Origin shield, warmup and monitoring
Behind the three CDNs is one logical shielded origin. Each CDN points at a single origin-shield POP that collapses concurrent misses — so a live scale-up or a Friday launch that spikes 10M cold requests hits the packager once per segment, not 10M times. Tiered caching plus request collapsing is what keeps origin CPU flat during a drop. For launches and big live events, Lumina pre-warms: it pushes the bitrate ladder + init/key manifests into every CDN before the doors open, so the first wave hits warm edges. This is the CDN sibling of the origin design in Azure Front Door: routing and caching.
Finally, you cannot steer what you cannot see. Lumina measures per-CDN, per-region, per-ASN, continuously, and both the QoE and the cost sides drive steering.
| Metric | Target / watch | Drives | Source |
|---|---|---|---|
| Cache hit ratio (offload) | > 95% segment, > 90% edge | shield/cost tuning | CDN logs |
| VST p95 | < 2 s | steering weight | RUM |
| Rebuffer ratio | < 0.4% | steering, fail-away | RUM |
| Playback failure | < 0.5% | incident + steering | RUM |
| Edge 5xx rate | < 0.1% | pull CDN from rotation | edge logs |
| Origin egress / offload | maximise offload | shield health, cost | origin + CDN |
| $ per GB / commit burn | vs contract tier | cost-spill steering | billing |
| Live latency (glass-to-glass) | < 8 s | live encode/CDN tuning | RUM + synthetic |
Ad-tech and monetization
Lumina makes money five ways at once — SVOD subscriptions, AVOD ad-supported tiers, TVOD rentals/buys, FAST free ad channels, and live sports with premium ads — and the streaming architecture has to serve all of them from the same content without letting the ad plane ever endanger editorial delivery or premium content security. The centrepiece is SSAI (server-side ad insertion): instead of the player calling an ad SDK (which ad-blockers strip and which breaks on TVs), the ad is stitched into the video stream server-side so the player sees one seamless, un-blockable CMAF playlist.
Here is the ad path — the player asks for one manifest, SSAI reads the SCTE-35 markers and calls the ad-decision server, editorial and ad content stay on separate origins, and the stitched manifest ships through the same multi-CDN with server-side impression tracking.
How SSAI stitches an ad break
For live, the encoder emits SCTE-35 markers (splice_insert / time_signal) at the exact PTS where a break may open; the SSAI service turns those into ad opportunities and, per session, fills them from the ad decision. For VOD, break positions come from a VMAP document (pre/mid/post-roll cue points). The critical constraint: the ad decision call must never stall playback — it is capped, and on timeout the stitcher inserts a slate or house ad. Ads and content share one CMAF encoding profile so the splice is seamless (mismatched codecs = a stall at the boundary). The flow, step by step:
| # | Step | Component | Detail / guardrail |
|---|---|---|---|
| 1 | Manifest request | Player → SSAI | per-session personalised manifest URL |
| 2 | Read cue points | SSAI | SCTE-35 (live) / VMAP (VOD) → break plan |
| 3 | Ad decision | SSAI → ADS (VAST/VMAP) | user + context macros, timeout ≤ ~1s |
| 4 | Fill or fallback | SSAI | creatives returned, else slate/house ad |
| 5 | Transcode-match | Ad origin | ads pre-conditioned to content ABR profile |
| 6 | Stitch manifest | SSAI | insert ad segments at PTS boundary |
| 7 | Deliver | Multi-CDN | stitched segments, per-asset cache |
| 8 | Track impression | SSAI (server-side) | quartile beacons, immune to blockers |
The ad-decision request carries targeting without leaking PII — the VAST tag URL is built with macros the ADS/SSP resolves:
# SSAI → Ad Decision Server (VAST 4.x tag, macros resolved per session)
https://ads.lumina.tv/vast?
pod=preroll&dur=90& # break duration to fill (s)
content=asset-9f21&genre=sport&livestream=1&
ip=[CLIENT_IP]&ua=[USER_AGENT]&
gdpr=1&gdpr_consent=[CONSENT]&us_privacy=[USP]& # consent passed through
ppid=[PSEUDO_ID]& # pseudonymous, never account/email
cb=[CACHEBUSTER]&maxpods=3
SCTE-35 conditioning deserves its own note: a marker that arrives late clips the programme, and one that isn’t frame-accurate causes a visible glitch at the join. Lumina honours the pre-roll delay in the splice and conditions live content to frame-accurate IDR boundaries so the stitch is invisible.
Editorial and ad path separation
Premium content carries a TPN/MovieLabs chain of custody; ad creatives come from dozens of third parties and change constantly. Mixing them in one pipeline would let an ad-ops mistake corrupt editorial delivery or contaminate the content-security chain. So Lumina keeps them on separate origins and pipelines, joined only at the stitched manifest.
| Concern | Editorial path | Ad path | Why separate |
|---|---|---|---|
| Origin | packager/editorial origin | dedicated ad origin | blast-radius isolation |
| Security | DRM/CENC, TPN chain | standard, no premium keys | don’t expose keys to ad flow |
| Change cadence | slow, governed release | fast, per-campaign | ad-ops can’t break content |
| Transcode | full ABR ladder, per-title | conditioned to match profile | seamless splice |
| Failure mode | Tier-1, must not drop | fallback to slate/house ad | ads degrade gracefully |
| Caching | per-asset (segments long) | creatives cacheable across sessions | dedupe at edge |
Ad delivery has its own QoE that Lumina measures separately from content QoE — an ad that buffers is a lost impression and a viewer annoyance, but it must never be confused with a content stall in the dashboards.
| Ad QoE / quality metric | Target | Meaning |
|---|---|---|
| Ad VST (start time) | < 1 s | ad begins without a gap at the splice |
| Ad fill rate | high, per pod | % of break filled vs slate fallback |
| Ad error / stall rate | < content threshold | broken/timed-out creatives |
| Impression discrepancy | low ADS-vs-server delta | billing integrity |
| Break completion | high | viewer watched the pod through |
Monetization models and settlement
The same catalogue is sold five ways, and each model has a different billing event, a different rights posture, and a different partner-settlement rule. Subscription billing runs under PCI-DSS (a separate, tokenized payments plane), while ad revenue flows through impression counts and partner splits.
| Model | Revenue event | Content posture | Ads | Settlement / billing |
|---|---|---|---|---|
| SVOD | recurring subscription (PCI) | full library per plan | none | payment processor, dunning, proration |
| AVOD | ad impressions | ad-supported catalogue | SSAI | SSP/DSP revenue, CPM × verified impressions |
| TVOD | one-off rent/buy (PCI) | title unlock, rights window | none | per-transaction, studio revenue share |
| FAST | ad impressions | linear free channels | SSAI, high load | ad-network settlement, per-channel |
| Live sports/events | sub + premium ads (± PPV) | blackout + rights window | SSAI, dynamic | rights-holder settlement, PPV recon |
Two monetization mechanics ride on top of the models. Promotions and entitlements — free trials, bundle/partner grants (telco, device OEM), win-back offers — resolve into the same entitlement decision the DRM plane already enforces, so a “3 months free” promo is just a rights window on the account, not a separate code path. Partner settlement — studios (TVOD/subscription share), sports rights-holders (event revenue), ad networks (impression revenue), and distribution partners (bundle splits) — is reconciled from the license-issuance audit log and the server-side impression log, the two authoritative records of what was actually watched and which ads actually played. Because SSAI fires impressions server-side, those counts are blocker-proof and audit-clean, which is exactly what an ad buyer and a rights-holder need before they pay.
Analytics, QoE and personalization
From the data team’s chair, Lumina is a firehose that never closes. With 45M concurrent sessions during a live final and a player that emits a QoE heartbeat roughly every 10 seconds, the analytics plane must absorb on the order of 4–5 million events per second — and the events that matter most (a rebuffer, a fatal playback error, an ad that failed to stitch) arrive in bursts exactly when load is highest. The design goal is never “store everything.” It is to make three very different consumers — the Media Operations Centre’s real-time dashboard, the growth team’s subscription funnel, and the ML feature pipeline — read from one honest event stream without any of them taking the others down, and without a single un-consented byte of PII leaking into a place it was never allowed to be.
Lumina’s players emit a beacon into a high-scale bus, a stream processor enriches and windows it, and the result forks into a hot store (seconds-fresh) and a cold lake (years-deep), with the same curated events feeding ML. Read the path left to right:
The single most important measurement discipline is agreeing what each QoE metric means before arguing about its value. A “rebuffer” counted at the player, the CDN, and the origin are three different numbers; the player’s is the only one the viewer felt. This is the metric catalog the whole company aligns on, with the Tier-1 targets from the QoE budget:
| Metric | Definition (player-side) | Tier-1 target | Reporting grain | Primary source |
|---|---|---|---|---|
| VST p95 (video-start-time) | Play intent → first frame rendered | < 2.0 s | session · title · device · CDN | player SDK beacon |
| Rebuffer ratio | Σ rebuffer time ÷ Σ playing time | < 0.4 % | session · CDN · geo | player SDK beacon |
| EBVS (exit-before-video-start) | Sessions abandoned before first frame | < 1.5 % | title · device · CDN | player SDK beacon |
| VPF (video-playback-failure) | Fatal errors ÷ play attempts | < 0.5 % | error code · CDN · DRM | player SDK beacon |
| Live latency (glass-to-glass) | Encoder input → player render | < 8 s (LL-CMAF) | event · CDN | player + encoder correlation |
| Completion rate | Sessions ended ≥ 90 % ÷ starts | title-dependent | title · profile | player SDK beacon |
| Average bitrate | Delivered kbps (ABR sustained) | ladder-dependent | title · device · network | player SDK beacon |
| License-fail rate | DRM license denials ÷ requests | < 0.2 % | DRM · geo · plan | license service + player |
Ingestion is a capacity-planning problem, not a code problem. The trap is sizing for the daily average and getting shredded at kickoff. You size for the peak — 4–5M events/s — and you make ordering a per-session property, never a global one, so the bus can shard horizontally. The two clouds run the same shape with different primitives, and Lumina runs both (Azure East US 2/West Europe/Southeast Asia, AWS us-east-1/eu-west-1/ap-southeast-1) for regional locality and failover:
| Concern | Azure path | AWS path | Sizing rule at Lumina scale |
|---|---|---|---|
| Scale unit | Event Hubs Dedicated (Processing Units) | Kinesis Data Streams on-demand (shards) | provision to peak/s, not mean; headroom ≥ 40 % |
| Ordering | partition (32–100+) keyed on sessionId |
shard keyed on sessionId |
one session → one partition, ordered per session |
| Ingress guard | throttle + 429 at collector |
ProvisionedThroughputExceeded backoff |
client SDK batches + retries with jitter |
| Retention | 1–7 days on the bus | 24 h–7 days | bus is a buffer, not the store of record |
| Fan-out | consumer groups | enhanced fan-out consumers | separate group per consumer (hot / cold / ML) |
| Capture to lake | Event Hubs Capture → Parquet | Kinesis Firehose → Parquet | land query-ready, buffer to 128 MB / 300 s |
The events themselves are a versioned contract. If the SDK, the stream job, and the lake disagree on the shape of a beacon, every downstream number quietly forks. This is the QoE beacon Lumina’s players emit — pseudonymous by construction, consent-carrying, and partition-keyed on sessionId:
{
"$schema": "lumina/qoe-beacon/v3",
"eventType": "rebuffer_end", // first_frame|rebuffer_start|rebuffer_end|bitrate_switch|error|ad|heartbeat|ended
"ts": "2026-08-14T20:11:07.412Z",
"sessionId": "9f3c-...-e21", // PARTITION KEY — ordering is per-session
"subscriberHash": "sha256:7b12...", // pseudonymous; reversible ONLY in the DSAR vault
"profileId": "p2",
"device": { "class": "ctv", "model": "Samsung Q80", "os": "Tizen 7.0", "app": "8.4.1" },
"content": { "titleId": "EIDR:10.5240/1A2B", "type": "vod", "drm": "widevine", "cdn": "CDN-B", "packaging": "cmaf" },
"playback":{ "vstMs": 1840, "bitrateKbps": 7200, "bufferedMs": 12000, "rebufferMs": 640, "droppedFrames": 3, "positionS": 512 },
"ad": { "breakId": "mid-2", "creativeId": "cr-8841", "ssai": true, "stitchOk": true },
"error": null, // { "code": "3.2.1", "fatal": false, "message": "license renew" }
"geo": { "country": "DE", "asn": 3320 },
"consent": { "tcf": "CPx...v2", "analytics": true, "ccpaOptOut": false },
"experiments": { "abr_v4": "treatment" }
}
The stream processor is where correctness is won or lost. A tumbling window turns the firehose into rollups the NOC can read, but late beacons — a phone that buffered offline in a tunnel and flushed five minutes later — must be handled deliberately, not dropped. Lumina uses a bounded watermark and routes stragglers to a reconciliation path; the mechanics of watermarks and late-arriving events are their own discipline, covered in Stream Analytics watermarks and late events. This is the 1-minute QoE rollup that feeds the hot store, Azure Stream Analytics / Flink-SQL style:
-- 1-minute tumbling QoE rollup per CDN × title → hot store; consent-gated at the source
SELECT
System.Timestamp() AS window_end,
content.cdn,
content.titleId,
COUNT(DISTINCT sessionId) AS sessions,
SUM(playback.rebufferMs) * 1.0
/ NULLIF(SUM(playback.bufferedMs), 0) AS rebuffer_ratio, -- target < 0.004
PERCENTILE_CONT(0.95)
WITHIN GROUP (ORDER BY playback.vstMs) AS vst_p95_ms, -- target < 2000
SUM(CASE WHEN error.fatal = 1 THEN 1 ELSE 0 END) * 1.0
/ COUNT(*) AS vpf_ratio -- target < 0.005
INTO [adx-qoe-hot]
FROM [eh-qoe-beacons] TIMESTAMP BY ts
WHERE consent.analytics = 1
GROUP BY content.cdn, content.titleId, TumblingWindow(minute, 1);
Not every question wants the same store. The NOC needs a five-second answer for the last hour; the growth team needs a 90-day cohort; the ML team needs three years of raw history to reprocess a feature. Forcing all three into one engine is either ruinously expensive or uselessly slow. Lumina splits by latency-of-question:
| Tier | Store | Freshness | Retention | Query pattern | Cost posture | Canonical use |
|---|---|---|---|---|---|---|
| Hot | Azure Data Explorer / Druid | 5–30 s | 7–14 days | dashboards, alerting | $$$ /TB | live rebuffer by CDN during a final |
| Warm | Synapse / Redshift / Databricks SQL | minutes–hours | 90–400 days | interactive, funnels | $$ /TB | 30-day churn cohort |
| Cold | ADLS Gen2 / S3 (Delta/Parquet) | hours | years | batch, ML, replay | $ /TB | 3-year QoE history reprocess |
Beyond raw playback, the same pipeline powers four other analytics domains, each with a different grain and owner. Keeping them on one event backbone (not five bespoke pipelines) is what lets the reco model and the ad-yield model see the same definition of “completion”:
| Domain | Grain | Hot use | Cold use | Key metrics | Owner |
|---|---|---|---|---|---|
| QoE / playback | session · 1-min | NOC live board | encode QA, trends | VST, rebuffer, VPF | Video eng |
| CDN & origin | edge PoP · 1-min | multi-CDN steering | offload, $/GB | hit ratio, edge RTT, origin 5xx | Delivery |
| Subscription funnel | user · event | signup live board | cohort, LTV, churn | signup → trial → paid, MRR | Growth |
| Engagement | title · day | trending row | reco training | plays, completion, dwell | Product |
| Ad delivery (SSAI) | impression | fill-rate board | yield, revenue share | fill %, VAST error, stitch latency | Ad ops |
Finally, two cross-cutting disciplines sit over all of it. A/B testing must be deterministic and sticky — a viewer who flip-flops between arms mid-session poisons the result — and consent is not a footnote but a partition of the data itself. Under GDPR and CCPA, a beacon that arrived without analytics consent may be counted for aggregate operational health but must never be joined to identity or used to train a personalization model. Lumina enforces this at write time, at the consent gate, not with a hopeful WHERE clause at query time:
| Concern | Mechanism | Where enforced | Note |
|---|---|---|---|
| Experiment assignment | deterministic hash(subscriberHash + expId) |
edge SDK + config service | sticky; no mid-session flip |
| Metric analysis | guardrail (VST, rebuffer) + goal (D30 retention) | warehouse | CUPED for variance reduction |
| Consent capture | IAB TCF v2 string on every beacon | ingest consent gate | no analytics consent → pseudonymised, restricted prefix, never joined to PII |
| CCPA opt-out | ccpaOptOut flag |
consent gate | excluded from personalization features |
| DSAR erasure | subscriberHash re-identifiable only in the vault |
governance | erase / export within 30 days |
The hot-path dashboards themselves are just queries over the hot store — KQL over Azure Monitor / Data Explorer drives the NOC’s “rebuffer ratio by CDN, last 15 minutes” board — but the discipline that makes them trustworthy lives upstream, in the schema, the windowing, and the consent gate. Get those right and the number on the wall is the number the viewer felt.
Data platform and AI
QoE analytics answer “is playback healthy right now.” The data platform answers everything slower and deeper — “why did this cohort churn,” “what should we recommend,” “which titles are worth relicensing” — and it does so by turning the same event streams, plus billing, catalog, and rights data, into governed tables that BI and ML can trust. Lumina runs a medallion lakehouse on both clouds: data lands once in an immutable raw zone, is conformed into curated Delta, and only governed, tagged, masked data is ever exposed broadly. The architecture, sources through models:
The zone model is the backbone, and the discipline that separates a lake from a swamp is that each zone has one job and one access posture. Lumina implements the identical pattern on Azure and AWS so a workload can run in whichever region is closest to the data; the medallion pattern itself is detailed in ADLS Gen2 medallion bronze/silver/gold and medallion streaming/batch unification:
| Zone | Purpose | Azure | AWS | Format | Who reads it |
|---|---|---|---|---|---|
| Raw (bronze) | Immutable landing, replay source of record | ADLS Gen2 (lm-lz-data) |
S3 raw bucket (Data OU) | Avro/JSON → Parquet | ingest identity only |
| Curated (silver) | Conformed, deduped, typed, dimensions resolved | ADLS + Delta / Databricks | S3 + Delta / Glue | Delta / Iceberg | data engineering |
| Governed (gold) | Tagged, masked, serving-ready | Unity Catalog / Synapse | Lake Formation / Redshift | Delta / Parquet | broad, by tag policy |
| Sandbox | Exploratory, time-boxed | per-team schema | per-team prefix | any | data science, no PII |
Data reaches those zones through two pipeline styles that must converge on one landing contract. If streaming lands JSON with one key layout and batch lands CSV with another, the curated layer forks and every metric downstream inherits the split. Lumina pins a single Avro/Parquet schema at ingest and lets the engine differ:
| Pipeline | Engine (Azure / AWS) | Latency | Typical use | Correctness mechanism | Trigger |
|---|---|---|---|---|---|
| Streaming | Stream Analytics · Flink / Kinesis + Spark Structured Streaming | seconds | QoE rollups, live ad, fraud | checkpoint + exactly-once sink | continuous |
| Micro-batch | Databricks Auto Loader / DLT | 1–5 min | catalog CDC, near-real-time marts | Delta transaction log | file arrival |
| Batch ELT | ADF · Synapse / Glue · dbt | hourly–daily | funnels, finance, reco training | idempotent MERGE |
schedule |
Governance is the feature, not the paperwork. At 120M subscribers under GDPR + CCPA, the platform’s value is bounded by how confidently you can expose it. A single catalog gives every engine one schema; lineage lets you trace a churn dashboard field back to the raw beacon that produced it; quality gates reject bad partitions before they poison a model; and tag-based access control means a new PII column is masked by policy the moment it’s classified, not by a grant somebody remembers to add. The tooling maps cleanly across clouds, and the deeper pattern is in enterprise data catalog, lineage and governance and data mesh domain products & federated governance:
| Capability | Azure | AWS | What it buys you |
|---|---|---|---|
| Catalog | Microsoft Purview / Unity Catalog | Glue Data Catalog / Lake Formation | one schema, discovery, ownership |
| Lineage | Purview / Unity | Glue + OpenLineage | trace a dashboard field to source, impact analysis |
| Quality | Great Expectations / DLT expectations | Deequ / Glue Data Quality | reject bad partitions at the gate |
| Classification | Purview sensitivity labels | Macie + LF-tags | auto-find PII (email, payment) |
| Access | Unity grants / Purview policies | Lake Formation tag-based | column masking, row filters, no per-table grant rot |
On top of governed gold sit the ML workspaces — Azure Machine Learning and SageMaker — where recommendation, churn, and content-intelligence models are built, registered, and served. The workspace anatomy (compute, datastores, jobs, registry) is covered in Azure ML workspace anatomy; what matters at the platform level is that models are versioned, approval-gated for Tier-1, and fed from the same curated events as the warehouse. Here is the nightly churn pipeline — a point-in-time feature join into a registry-pinned model, so training and scoring never disagree:
# aml-churn-pipeline.yml — nightly churn scoring; point-in-time features, approval-gated model
$schema: https://azuremlschemas.azureedge.net/latest/pipelineJob.schema.json
type: pipeline
display_name: churn-nightly
settings: { default_compute: azureml:cpu-cluster-d8 }
inputs: { as_of_date: "2026-08-14" }
jobs:
build_features:
type: command
component: azureml:feature_join@1.4.0 # AS-OF join — no label leakage
inputs:
events: azureml:qoe_curated@latest
billing: azureml:billing_curated@latest
as_of: ${{parent.inputs.as_of_date}}
outputs: { feature_set: { type: uri_folder } }
score:
type: command
component: azureml:churn_score@2.1.0
inputs:
model: azureml:churn_gbm:7 # from registry; promotion is approval-gated
features: ${{parent.jobs.build_features.outputs.feature_set}}
outputs:
scores:
type: uri_folder
path: azureml://datastores/gold/paths/churn/${{parent.inputs.as_of_date}}/
The model portfolio is deliberately small and each model earns its place. Recommendation is the flagship — a two-tower retrieval stage feeding a ranker, the pattern in two-tower recommendation at scale — but content intelligence (multimodal tagging of scenes, thumbnails, and chapters) and QoE anomaly detection are equally load-bearing:
| Use case | Model family | Key features | Output | Refresh | Consumer |
|---|---|---|---|---|---|
| Recommendation | two-tower retrieval + ranker | watch history, embeddings, context | ranked rows | candidates hourly / rank real-time | app home |
| Churn / retention | gradient-boosted + survival | tenure, QoE, engagement, billing | churn score | daily | growth / CRM |
| Content intelligence | multimodal (video/audio/text) | scenes, subtitles, thumbnails | tags, chapters, thumbs | on-ingest | catalog / search |
| QoE anomaly | streaming detection (robust z / ESD) | rebuffer, error by CDN/geo | incident alert | real-time | NOC |
| Ad yield | contextual bandit | context, fill, floor price | ad decision | near-real-time | SSAI / ADS |
| Sharing / fraud | graph + rules | device, geo, concurrency | risk score | real-time | entitlement |
The connective tissue under all of these is the feature store, and its non-negotiables are freshness and point-in-time correctness. A reco model that trains on features computed after the label leaked will look brilliant offline and fail in production. The feature store serves an online copy (Redis / DynamoDB) for inference and an offline copy (Delta) for training from one definition, so “watch_completion” means exactly one thing in both — the discipline detailed in MLOps platform with a feature store:
| Control | Mechanism | Applies to |
|---|---|---|
| Feature freshness | online store (Redis / DynamoDB) + offline (Delta) | reco, fraud |
| Point-in-time correctness | as-of joins on event_time |
all training |
| Consent propagation | consent flag rides the feature row | all |
| PII minimization | hashed subscriber key; no raw email in features | all |
| Model governance | MLflow / registry, approval gate, model cards | Tier-1 models |
| Bias & eval | offline eval + online guardrail metrics | reco, ranking |
Partner distribution and B2B integration
Not everyone who consumes Lumina’s platform is a viewer with a login. Partners — cable/MVPD operators, smart-TV and console OEMs, FAST channel aggregators, and white-label operators who resell a Lumina-powered service under their own brand — integrate machine-to-machine, at scale, over APIs. The security model is different in kind from consumer CIAM: there is no interactive login, no password, and the thing you are protecting is not a viewer’s PII but a studio’s licensing rights. One partner serving one title one day outside its licensed window is a contract breach with a rights holder, not a bug ticket. So every partner enters through a single hardened gateway that authenticates them, enforces the rights window centrally, and serves only their isolated tenant slice:
The partner API surface is deliberately narrow and each API has a distinct security posture. Metadata is cacheable and low-risk; entitlement mints playback tokens and is the highest-traffic, highest-sensitivity call; content delivery never proxies bytes, only signed URLs:
| API | Purpose | Shape | AuthZ scope | Rate posture | Returns |
|---|---|---|---|---|---|
| Metadata / catalog | title feed, artwork, avails | REST/GraphQL + bulk feed | partner scope | high, cacheable | in-window catalog subset |
| Entitlement / playback | verify session, mint playback token | REST | per-session | very high | DRM token + manifest URL |
| Content delivery | signed URLs / manifests | REST | per-title | high | short-TTL signed URL |
| Reporting | consumption, QoE, revenue share | REST + bulk export | partner scope | low | aggregates only, no PII |
| Provisioning | account link / SSO federation | SCIM / OIDC | admin | low | link status |
The heart of the gateway is the policy that runs before any partner request touches a backend. It validates a pinned mutual-TLS certificate and a client-credentials JWT, pulls the licensing window for the requested title from the rights service, and refuses with 451 Unavailable For Legal Reasons if the title is out of window for that partner — then stamps a tenant header that drives row-level security downstream. This is Lumina’s Azure API Management inbound policy (the policy primitives — validate-jwt, send-request, rate-limit-by-key — are covered in API Management policy fundamentals):
<!-- inbound: authenticate partner, enforce rights window, tag tenant for downstream RLS -->
<inbound>
<validate-client-certificate /> <!-- pinned mTLS, per partner -->
<validate-jwt header-name="Authorization" require-scheme="Bearer">
<openid-config url="https://login.lumina.tv/partners/.well-known/openid-configuration" />
<required-claims>
<claim name="azp" match="any"><value>{{partner_id}}</value></claim>
</required-claims>
</validate-jwt>
<!-- pull the licensing window for the requested title from the rights service -->
<send-request mode="new" response-variable-name="rights" timeout="2">
<set-url>@($"https://rights.internal.lumina.tv/v1/avails/{context.Request.MatchedParameters["titleId"]}?partner={context.Variables["partnerId"]}")</set-url>
<set-method>GET</set-method>
<authentication-managed-identity resource="api://rights" />
</send-request>
<choose>
<when condition="@(!(bool)((IResponse)context.Variables["rights"]).Body.As<JObject>()["inWindow"])">
<return-response><set-status code="451" reason="Unavailable For Legal Reasons" /></return-response>
</when>
</choose>
<rate-limit-by-key calls="20000" renewal-period="1"
counter-key="@(context.Request.Headers.GetValueOrDefault("X-Partner-Id"))" />
<set-header name="X-Tenant" exists-action="override">
<value>@(context.Variables.GetValueOrDefault<string>("partnerId"))</value> <!-- drives RLS -->
</set-header>
</inbound>
Rights-window enforcement is the reason this whole tier exists. A title is only distributable to a given partner inside its licensed window, territory, device class, and quality ceiling — and those constraints come from the rights/avails system, never from a partner’s promise to honor them. Critically, rights (may Lumina offer this to this partner) and entitlement (may this end-user’s session play it) are separate checks that must both pass; conflating them is precisely how out-of-window content leaks to an otherwise-valid subscriber:
| Attribute | Example | Source | Enforcement point |
|---|---|---|---|
| Window start/end | 2026-08-01 → 2027-01-31 | rights / avails service | APIM policy + catalog feed filter |
| Territory | licensed countries only | rights service | geo check at gateway + CDN edge |
| Device / platform class | CTV only, no mobile | rights service | token claims + license policy |
| Exclusivity / holdback | no FAST syndication until +90d | rights service | catalog feed filter |
| Max quality / HDCP | UHD requires HDCP 2.2 | rights + DRM policy | license server |
| Blackout | regional live-sports blackout | live control | manifest + SSAI |
Onboarding a partner is a governed lifecycle, not a config change — it starts at the contract and ends with revocable, audited access. Federated partner identity (B2B) is set up per Entra External ID B2B collaboration:
| Stage | Action | Tooling | Control gate |
|---|---|---|---|
| Contract & avails | rights + revenue terms loaded | rights service | legal sign-off |
| Identity | client-credentials app + mTLS cert issued | Entra / Cognito app reg | short-lived secrets in Key Vault |
| Scoping | partner_id, allowed APIs, quotas |
APIM products / subscriptions | least privilege |
| Sandbox | test tenant + synthetic catalog | non-prod OU | no real PII |
| Certification | conformance + security review | TPN / CDSA checklist | gate to prod |
| Go-live | prod subscription enabled | APIM | audit baseline captured |
| Offboarding | revoke keys, expire windows | automation | evidence retained |
White-label is where isolation gets strict: multiple brands share the platform’s compute but must never share each other’s data, keys, or configuration. Lumina isolates per tenant on the axes that matter and shares only the stateless gateway:
| Layer | Shared? | Isolation mechanism |
|---|---|---|
| Gateway / compute | shared | partner_id claim + policy enforcement |
| Data (catalog, entitlement) | shared store | row-level security keyed on partner_id |
| Secrets / DRM keys | isolated | per-tenant Key Vault / KMS key |
| Branding / config | isolated | per-brand config + theme + domain |
| Reporting storage | isolated | per-tenant container/prefix + scoped SAS |
| Observability | isolated view | log partition + RBAC-scoped dashboards |
Finally, every partner interaction is evidence. Rights holders and CDSA/TPN audits ask “who accessed what, when” — and Lumina answers from immutable, per-partner logs, not memory:
| Audit question | Source of truth | Retention |
|---|---|---|
| Who called which API, when | APIM + gateway access logs | 400 d hot, years cold |
| Which titles served (and any out-of-window denials) | rights decision log | immutable, years |
| Token mint / redemption | entitlement service log | 90 d+ |
| What data was exported | reporting export log | per contract |
| Key / cert rotation | Key Vault / IAM audit | years |
Production, archive and media supply chain
Everything upstream of playback is the media supply chain: how a master arrives from a studio or Lumina’s own production, gets checked, described, localized, preserved for decades, and finally published. This is where MovieLabs Enhanced Content Protection and TPN/CDSA obligations bite hardest, because pre-release masters are the single most valuable, most leak-sensitive asset the company touches — and where ~400 PB of archive lives. The pipeline runs from secure contribution through QC, metadata, and localization into deep archive, then out to the VOD origin:
Each stage has a clear input, action, and output, and the sequencing matters: you QC before you spend money transcoding a 4K master, and you normalize metadata before it reaches five systems that would each guess differently:
| Stage | Input | Action | Output | Tooling |
|---|---|---|---|---|
| Contribution | mezzanine / IMF from studio | secure transfer, watermark, virus scan | ingest master | Aspera / Signiant, MediaConnect |
| Ingest & QC | master | automated + eyeball QC | pass/fail + report | Baton / Vidchecker |
| Normalize | master + sidecars | conform, proxy, checksum | canonical asset | IMF, xxHash / MD5 |
| Metadata | asset | enrich, EIDR / OMC mapping | catalog record | Purview + MovieLabs OMC, EIDR |
| Localize | asset | subtitles, dub, audio, art | localized renditions | vendors + ASR |
| Archive | master + renditions | tier + preserve | archived object | Glacier DA / Azure Archive |
| Package & publish | approved asset | transcode, DRM, package | CMAF + CENC on origin | MediaConvert / packager |
Archive is preservation, not backup — and the tier is a cost decision measured in retrieval hours. A mezzanine master that may not be touched for a decade belongs in the deepest, cheapest tier that still guarantees WORM immutability, checksums, and geo-redundancy; an active rendition being packaged this week belongs in hot. Lumina drives this with lifecycle policy across all three clouds (values are order-of-magnitude and region-dependent), building on Azure Blob access tiers and S3 storage classes & lifecycle:
| Tier | Azure | AWS | GCP | Retrieval | ~$/GB-mo | Use |
|---|---|---|---|---|---|---|
| Hot | Blob Hot | S3 Standard | Standard | instant | ~$0.020 | active packaging |
| Cool | Blob Cool | S3 Standard-IA | Nearline | instant | ~$0.010 | recent renditions |
| Cold | Blob Cold | S3 Glacier Instant | Coldline | ms–instant | ~$0.004 | long-tail masters |
| Deep archive | Blob Archive | S3 Glacier Deep Archive | Archive | hours (rehydrate) | ~$0.001 | preservation masters |
The lifecycle rules that move masters down the tiers are declarative and identical in intent on both clouds — tier the immutable IMF masters to the deepest tier after they age out of active use, while proxies stay warm:
// Azure Blob lifecycle policy — masters to Archive, proxies stay Cool
{ "rules": [ {
"name": "masters-to-deep-archive",
"type": "Lifecycle",
"definition": {
"filters": { "blobTypes": ["blockBlob"], "prefixMatch": ["masters/imf/"] },
"actions": { "baseBlob": {
"tierToCool": { "daysAfterModificationGreaterThan": 30 },
"tierToArchive": { "daysAfterLastAccessTimeGreaterThan": 90 }
} }
}
} ] }
// AWS S3 lifecycle — same intent, Glacier Deep Archive for preservation
{ "Rules": [ {
"ID": "masters-preservation",
"Filter": { "Prefix": "masters/imf/" },
"Status": "Enabled",
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER_IR" },
{ "Days": 180, "StorageClass": "DEEP_ARCHIVE" }
],
"NoncurrentVersionTransitions": [ { "NoncurrentDays": 30, "StorageClass": "GLACIER" } ]
} ] }
Localization is a supply chain of its own, and for a global service it is the difference between a title that travels and one that dies at a border. Each rendition is produced, standards-conformed, and QC’d before it can be attached to a publish. Lumina blends specialist vendors with AI-assisted first passes (ASR for captions, AI dubbing) under mandatory human review for premium tiers:
| Rendition | Produced by | Standard / format | QC focus |
|---|---|---|---|
| Subtitles / captions | ASR (Whisper-class) + human review | IMSC 1.1 / WebVTT / TTML | timing, reading speed, compliance |
| Dubbed audio | studio + AI-assisted | separate track, −24 LKFS loudness | lip-sync spot check |
| Audio description | vendor | AD track | coverage, placement |
| Forced narratives | editorial | soft-subs / burned | correctness, placement |
| Localized art & metadata | localization team | per-territory assets | rights + territory match |
Underpinning contribution and any partner exchange of masters is the content-security regime that TPN Gold and CDSA assessments actually test. Pre-release material moves only over encrypted, resumable, audited transfer into a segregated, watermarked enclave — never an open FTP, never an emailed link — and every hand that touches a master is logged. The publish itself follows encode-once/package-efficiently: one CMAF/CENC package with cenc + cbcs serves Widevine, FairPlay, and PlayReady, which is what keeps 400 PB from ballooning into an exabyte of format duplicates (the multi-DRM packaging path is detailed in VOD streaming with multi-DRM on AWS):
| Control | TPN / MovieLabs requirement | Lumina implementation |
|---|---|---|
| Secure transfer | encrypted, resumable, no open FTP | Aspera / Signiant, signed-URL pull, PrivateLink |
| Forensic watermarking | trace a leak to its source | session + distribution watermark (Nexguard-class) |
| Access control | least privilege, MFA, no shared creds | Entra PIM, per-vendor scoped roles |
| Segregation | pre-release enclave isolated from GA | dedicated subnet / OU, no lateral path |
| Logging | full audit of who touched masters | immutable logs, retained as TPN evidence |
| Assessment | annual TPN Gold / CDSA | control mapping + tracked remediation |
The through-line across the whole supply chain is the same one that runs through partner distribution: the rights service is the single source of truth for what may go live, where, and when — and Lumina’s own VOD publish is gated by it just as strictly as any affiliate’s feed. Package once, protect always, preserve deliberately, and let one rights decision govern every path a title can take to a screen.
Multi-layer security model
A global streaming operator cannot draw a perimeter around its estate, because the estate has no edge left to draw a line at: a viewer authenticates from a smart-TV in a living room, a colourist grades a pre-release feature from a home suite, a partner affiliate pulls a linear feed across a distribution link, and a decade-old set-top box in a hotel requests a DRM licence three hops from the key service. The model Lumina Media builds therefore rests on one uncomfortable assumption — every layer must hold after the layer outside it has already failed — sharpened by a fact that finance and healthcare do not share: the primary asset is not a record but the content itself, and the studios that license it do not merely prefer that you guard it, their contracts prescribe the controls and the notification clock when you do not. MovieLabs Enhanced Content Protection, a TPN (Trusted Partner Network) assessment and CDSA oblige Lumina to evidence a zero-trust content workflow — hardware-rooted keys, encryption in flight and at rest, forensic watermarking, least-privilege on every pre-release asset — while PCI-DSS guards the billing plane and GDPR/CCPA guard 120M viewers’ PII and consent. So the seven layers below are engineered so a gap is a missing metric an assessor can see, not a matter of opinion.
The identity layer is the new perimeter, and streaming is unusual in carrying two identity planes that must never touch. Workforce identity — editors, live-control operators, DRM-key admins — authenticates at Microsoft Entra ID federated with Okta, with Conditional Access weighing user, device, location and sign-in risk on every call, phishing-resistant FIDO2 for all privileged and content-touching roles, and zero standing privilege through PIM. Customer identity (CIAM) is a wholly separate plane — Azure AD B2C / Amazon Cognito at 120M-user scale — and the two are never merged, because a compromise of the consumer token service must never reach a mezzanine asset. The consumer plane’s dominant threats are not phishing but credential stuffing, account-takeover and account-sharing fraud, defended by bot management, device binding and concurrency limits rather than MFA prompts a viewer would abandon. The device layer refuses to trust a credential alone: Intune + CrowdStrike Falcon posture gate workforce access and a Privileged Access Workstation is mandatory for the live-control and key-management paths, while the vast consumer device fleet (smart-TV, console, STB, mobile) is attested by a wholly different mechanism — the DRM device certificate and HDCP capability, checked at licence issuance, not workforce compliance.
The network layer assumes a foothold exists and denies lateral movement: hub-and-spoke with default-deny, Azure Firewall Premium and AWS Network Firewall for IDPS and TLS inspection, and the standing rule that control-plane, customer-API, media-processing, analytics, corporate and management are separate segments — the origin and the DRM key service are never on a public route. The workload layer constrains a compromised runtime on the encode, package, origin and playback fleets: Defender for Containers / GuardDuty gate images and behaviour, Wiz admission control blocks non-compliant pods, and pre-release content pipelines run only signed, attested images as MovieLabs requires. The application/API layer protects each service at its own front door — WAF in OWASP-prevention mode, bot management and per-endpoint rate limits on the playback, entitlement, CIAM and SSAI APIs, plus signed-URL / JWT token authentication at the CDN edge so a scraped manifest URL is useless without a valid, short-lived token. The DRM/data layer is the streaming crown jewel and the industry’s real “last line”: multi-DRM (Widevine + FairPlay + PlayReady, CENC cenc+cbcs) encrypts every segment, content keys live hardware-rooted in Key Vault Managed HSM / AWS CloudHSM / KMS, keys rotate on a cadence, HDCP and output protection are enforced per title, and forensic (session) watermarking makes a leaked stream traceable to the account that ripped it. Across all six sits the monitoring layer: Microsoft Sentinel correlates signal from both clouds, Defender XDR and GuardDuty feed it, Wiz holds a CSPM posture score ≥ 85, and an anti-piracy analytics stream watches concurrency, geo and token anomalies alongside Conviva-class QoE.
The control model below is the operational heart of the posture. Every row names what the layer assumes, the concrete Azure and AWS control that answers it, the media-specific requirement that makes streaming stricter than a generic enterprise, and the telemetry the SOC watches — so no layer is aspirational and none is unmonitored.
| Layer | Assume-breach premise | Azure control | AWS control | Media-specific requirement | Telemetry signal |
|---|---|---|---|---|---|
| 1 · Identity | The network is hostile | Entra ID + Okta, CA, PIM JIT, FIDO2 | IAM Identity Center federated via Entra, permission sets | Workforce and CIAM planes never merged; break-glass for live-control room | Risky sign-in, PIM activation, break-glass event |
| 2 · Device | The credential is stolen | Intune + Defender/Falcon, PAW for key admins | Verified-access posture, SSM-managed | Consumer devices attested by DRM cert + HDCP, not workforce trust | Device-compliance %, non-compliant blocks, HDCP downgrade |
| 3 · Network | A foothold exists inside | Firewall Premium (IDPS/TLS), NSG/ASG deny, Private Link | Network Firewall (Suricata), SG/NACL deny, VPC endpoints | Origin + DRM key service never public; segment isolation provable | Firewall IDPS hits, flow logs, quarantine events |
| 4 · Workload | Malicious code is running | Defender for Containers, Wiz admission, patch 7d/30d | GuardDuty EKS + Inspector, ECR scan-on-push | Content pipeline images signed + attested (MovieLabs) | Admission denials, runtime findings, unpatched-critical count |
| 5 · App / API | Network controls were bypassed | App Gateway WAF v2, APIM, bot + rate limit | AWS WAF on ALB/API GW, Bot Control | Edge signed-URL/JWT token auth; manifest/segment schema checks | WAF blocks, token-reject rate, rate-limit trips |
| 6 · DRM / Data | Everything above has fallen | Multi-DRM CENC, CMK in Key Vault MHSM, Private Endpoint | Multi-DRM/SPEKE, CMK in KMS/CloudHSM, PrivateLink | Hardware-rooted content keys, key rotation, HDCP, forensic watermark | Key-access logs, licence-issue anomalies, watermark hits |
| 7 · Monitoring | Prevention is eventually defeated | Sentinel SIEM/SOAR, Defender XDR, Wiz CSPM ≥ 85 | GuardDuty + Security Hub + Detective, Macie | Anti-piracy analytics (concurrency/geo/token) + QoE fused | MTTD/MTTR, CSPM score, piracy-signal count |
The tool estate that realises the monitoring and workload layers spans both clouds and is deliberately not symmetrical service-for-service — each provider’s native stack is used where it is strongest, and Sentinel is the single pane both feed. The mapping below is what a new analyst learns on day one, and what the Defender for Cloud CSPM rollout plugs into.
| Capability | Azure | AWS | What it catches for Lumina |
|---|---|---|---|
| CSPM / posture | Defender for Cloud + Wiz + Secure Score | Security Hub (FSBP) + Wiz + Config | Public origin/mezzanine bucket, unencrypted disk, disabled Private Endpoint |
| CWPP / server + container | Defender for Servers/Containers | GuardDuty Runtime + Inspector | Crypto-miner in an encode pod, vuln in a packager image |
| Threat detection (cloud) | Defender XDR + Defender for Cloud alerts | GuardDuty (VPC/DNS/S3/EKS/Malware) | Exfil from a content store, C2 beaconing, anomalous S3 read of mezzanine |
| SIEM / SOAR | Microsoft Sentinel (both clouds’ logs) | (feeds Sentinel via connector) | Cross-cloud correlation, DRM-key-svc abuse, impossible-travel on admin |
| EDR / XDR endpoint | Defender for Endpoint / CrowdStrike Falcon | CrowdStrike / SSM | Ransomware precursor on an editor / live-control workstation |
| Data discovery / DLP | Purview + Defender for Storage | Macie + Access Analyzer | Unclassified PII in an analytics lake, over-shared pre-release asset |
| Vuln management | Defender vuln assessment (MDVM) | Inspector (EC2/ECR/Lambda) | Log4Shell-class CVE in an SSAI or entitlement adapter |
| Investigation graph | Sentinel entity behaviour + hunting | Detective | Lateral-movement path from playback API to the key service |
Posture is a set of standards mapped to the frameworks Lumina answers to, so a failing control has a named consequence rather than a red tile. PCI-DSS, MovieLabs ECP / TPN, SOC 2 and GDPR/CCPA resolve down to concrete cloud standards that Defender, Wiz and Security Hub evaluate continuously.
| Posture control | Azure Defender / Wiz | AWS standard | Framework driver | Auto-remediation |
|---|---|---|---|---|
| Content store not publicly reachable | “Storage public access disabled” | S3 FSBP public-access controls | MovieLabs ECP; TPN | Deny policy + Config auto-fix |
| Encryption with CMK enforced | “CMK required” recommendation | KMS-CMK required controls | MovieLabs ECP; PCI 3.5 | Azure Policy DeployIfNotExists |
| Cardholder data scope isolated | “SQL/network isolation” recs | PCI DSS standard (Security Hub) | PCI-DSS 1.x / 3.x | SCP + NSG guardrail |
| Audit logging immutable + retained | “Diagnostic settings to immutable” | CloudTrail + Object Lock controls | SOC 2 CC7; PCI 10.5 | Enable + lock at vend time |
| DRM key material HSM-backed | “Key Vault MHSM required for keys” | CloudHSM/KMS-origin controls | MovieLabs ECP (hardware root) | Policy audit + pipeline gate |
| Vulnerable images blocked pre-deploy | Defender for Containers gate | Inspector + ECR scan-on-push | SOC 2 CC8; MovieLabs | Admission deny / pipeline fail |
Secrets and keys are the sharpest expression of the DRM/data layer, because in streaming the answer to “who can decrypt this title” must be only a validly entitled player, achieved by holding the content-key root of trust yourself. Every content key and every PCI datastore key uses a customer-managed key, and the most sensitive classes — pre-release / early-window titles under studio contract — escalate to a dedicated Managed HSM key with a narrower access policy and its own rotation cadence, following the Key Vault secrets, keys and certificates model. Application secrets never live in config; they resolve at runtime from Key Vault or Secrets Manager against a managed identity, so a leaked deployment artefact contains a reference, not a credential.
| Secret / key type | Azure | AWS | Rotation | CMK / key ownership |
|---|---|---|---|---|
| DRM content keys (CENC) | Key Vault Managed HSM (RSA/AES-HSM) | KMS CMK / CloudHSM via SPEKE | Per-title + periodic | Customer-managed, HSM-held |
| Pre-release / early-window keys | Dedicated MHSM key, scoped RBAC | CloudHSM cluster key | Accelerated (per window) | Customer-managed, isolated |
| Billing / PCI database keys | Key Vault MHSM (TDE CMK) | KMS CMK (PCI scope) | Annual + on-demand | Customer-managed |
| App / service credentials | Key Vault secret + managed identity | Secrets Manager + IAM role | 30–90 day auto | Platform store, CMK-encrypted |
| CDN edge token signing keys | Key Vault key + Managed Identity | KMS asymmetric CMK | 24–72 h rotation | Customer-managed |
| TLS / mTLS partner certs | Key Vault certificate + ACME | ACM / Secrets Manager | Auto (ACM) / 1-yr | Customer-managed |
| Break-glass account credentials | Key Vault + PIM-gated, sealed | Secrets Manager + isolated account | On use + re-seal | Customer-managed, offline copy |
The edge is the seventh place content and identity are protected before a request reaches an application, and it does four jobs that overlap but are not the same: absorb volumetric attacks (which spike hardest at live-event kickoff), enforce OWASP and bot rules, stop the content-theft and credential-abuse patterns unique to streaming, and rate-limit the abusable endpoints a fraud campaign targets. The DDoS protection tiering and Akamai App & API Protector / Bot Manager patterns underpin this layer.
| Threat class | Azure edge control | AWS edge control | Streaming endpoint / asset it protects |
|---|---|---|---|
| Volumetric / L3-4 DDoS (event spike) | DDoS Protection (Network) + Front Door | Shield Advanced + CloudFront | Live ingress, playback API, CIAM login |
| OWASP L7 (injection/XSS) | App Gateway WAF v2 (CRS 3.2) | AWS WAF managed rules | Playback/entitlement API, SSAI, portal |
| Content theft / stream ripping | Signed-URL/JWT token at edge + geo/concurrency | CloudFront signed URLs + WAF | Manifest, segment, DRM-licence request |
| Credential stuffing / ATO | WAF bot protection + rate limit + device binding | WAF Bot Control + rate rules | CIAM login, token endpoint, sign-up |
| Account-sharing / concurrency abuse | Entitlement concurrency + geo rules | Cognito + entitlement service | Entitlement check, licence issuance |
| Payment / TVOD fraud | WAF + APIM policy + risk scoring | WAF + API Gateway + fraud rules | Checkout, TVOD purchase, promo redemption |
Multi-layer network security
Network security inherits the same assumption-of-breach discipline and expresses it as six independent controls, no one of which is trusted to be sufficient. First, the multi-CDN global edge — three commercial CDNs with performance/cost steering, plus authoritative DNS and edge WAF/bot/DDoS — absorbs volumetric and scraping traffic and cloaks the regional origins behind origin-shield and signed tokens. Second, the regional WAF at the cloud edge (Application Gateway WAF v2 / AWS WAF on the ALB) re-checks every request closer to the workload. Third, central inspection (Azure Firewall Premium / AWS Network Firewall) forces all north-south and inter-spoke traffic through IDPS and TLS inspection. Fourth, micro-segmentation with NSG/ASG and security-group/NACL default-deny contains a compromise to one tier. Fifth, private connectivity (Private Endpoint / PrivateLink) removes the entitlement store, DRM key service and billing data from the public internet entirely. Sixth, host and workload controls hold if every layer above is crossed. Two rules bind the design: there is no unrestricted east-west traffic anywhere, and all egress is policy-controlled and logged through the central firewalls.
The segmentation map is where streaming diverges from a generic enterprise, because the segments are content-sensitivity and regulatory boundaries, not just tiers. The IPAM plan is Azure 10.20.0.0/12 (East US 2 shown), AWS 10.40.0.0/12 (us-east-1 shown) and on-prem 10.0.0.0/12, with a hub /20 per region, spokes /22 and PE subnets /26 — no overlaps, so crossing a boundary is always an explicit, inspected, logged event.
| Segment | Azure CIDR (East US 2) | AWS CIDR (us-east-1) | Contains | Crosses boundary via | Data class |
|---|---|---|---|---|---|
| Control-plane | 10.20.16.0/22 | 10.40.16.0/22 | Live-event control, playout orchestration, playback-session broker | Firewall + Private Endpoint | Restricted (control) |
| Customer-API | 10.20.20.0/22 | 10.40.20.0/22 | Entitlement, playback API, CIAM token svc, DRM-licence proxy | Firewall + WAF | Confidential (PII + entitlement) |
| Media-processing | 10.20.24.0/22 | 10.40.24.0/22 | Encode/transcode, packager, origin, SSAI stitcher | Firewall + private origin | Restricted (premium content) |
| Analytics | 10.20.28.0/22 | 10.40.28.0/22 | QoE ingest, reco, ad-reporting, lakehouse | Firewall + de-ID gateway | Confidential (behavioural) |
| Corporate | 10.20.32.0/22 | 10.40.32.0/22 | SAP back office, production/post, archive management | Firewall | Confidential |
| Management | 10.20.0.0/24 | 10.40.0.0/24 | Bastion, jump, agents, PE subnets | Bastion-only inbound | Internal |
Azure network security (deep dive)
On Azure the controls compose around the Virtual WAN secured hub. Inbound viewer traffic terminates at Front Door (WAF, bot, DDoS, token check) and, for regional APIs, Application Gateway WAF v2 in the dedicated AppGatewaySubnet, running OWASP CRS 3.2 in Prevention mode with per-URI rate limits — an application is never the first thing to see a raw request. Everything beyond it, north-south and east-west, is routed by User-Defined Routes through Azure Firewall Premium, whose IDPS and TLS inspection decrypt, inspect and re-encrypt lateral traffic rather than waving it through; egress is governed by FQDN application rules so a packager reaches only its named CDN origin-pull and multi-DRM licence endpoints. Within and between spokes, NSGs and Application Security Groups enforce default-deny: tiers are expressed as ASGs so rules reference intent (asg-play-api) rather than fragile CIDRs, and the DRM licence tier is provably isolated from the public playback tier — exactly the segmentation a TPN assessor asks you to evidence.
The customer-API spoke’s inbound NSG reads top-to-bottom as an explicit allow-list with a deny backstop; the priorities and ASG references below are the real shape.
| Priority | Name | Dir | Source | Destination | Port | Action |
|---|---|---|---|---|---|---|
| 100 | allow-fd-web | In | AzureFrontDoor.Backend | asg-play-web | 443 | Allow |
| 110 | allow-web-api | In | asg-play-web | asg-play-api | 8443 | Allow |
| 120 | allow-api-drm | In | asg-play-api | asg-drm-license | 8443 | Allow |
| 130 | allow-api-ciam | In | asg-play-api | asg-ciam | 8443 | Allow |
| 140 | allow-api-data | In | asg-play-api | asg-play-data (Cosmos PE) | 443 | Allow |
| 150 | allow-bastion-mgmt | In | AzureBastionSubnet | asg-play-* | 22, 3389 | Allow |
| 4000 | deny-vnet-inbound | In | VirtualNetwork | VirtualNetwork | * | Deny |
| 4096 | deny-all-inbound | In | * | * | * | Deny |
The ASGs make the rules readable and portable across every customer-API spoke — membership is what a NIC joins, and the NSG never mentions an IP.
| ASG | Membership | Purpose |
|---|---|---|
asg-play-web |
Playback/portal front-ends, AKS ingress NICs | Accepts only from Front Door / App Gateway |
asg-play-api |
Entitlement, playback, concurrency services | East-west from web tier only |
asg-drm-license |
DRM licence proxy / SPEKE front, key-svc callers | Reachable from playback API only |
asg-ciam |
B2C token endpoints, custom-policy hosts | API tier + Front Door only |
asg-mgmt |
Jumpboxes, monitoring/backup agents | Bastion-only inbound |
Defining an ASG-referencing rule in Terraform keeps the intent legible and is the pattern every spoke module reuses:
resource "azurerm_network_security_rule" "api_to_drm" {
name = "allow-api-drm"
priority = 120
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_application_security_group_ids = [azurerm_application_security_group.play_api.id]
destination_application_security_group_ids = [azurerm_application_security_group.drm_license.id]
source_port_range = "*"
destination_port_range = "8443"
resource_group_name = azurerm_resource_group.customer_api.name
network_security_group_name = azurerm_network_security_group.customer_api.name
}
Central inspection is a Firewall Policy whose rule-collection groups separate threat control from egress control, so adding a new CDN origin-pull FQDN never touches the IDPS baseline. Threat-intel and IDPS run in Deny mode for high/critical signatures; egress is FQDN-scoped per segment.
| Rule collection group | Type | Priority | Action | Example rule |
|---|---|---|---|---|
rcg-threat |
(IDPS / TI) | 100 | Deny | IDPS high/critical + threat-intel Deny, TLS inspect on |
rcg-media-egress |
Application | 200 | Allow | asg-media → CDN origin-pull + multi-DRM SaaS licence + SPEKE FQDNs :443 |
rcg-ciam-egress |
Application | 250 | Allow | asg-ciam → social IdP + payment-gateway (PCI) FQDNs :443 |
rcg-default |
— | 65000 | Deny | implicit deny-all egress (logged) |
The entitlement store, DRM key service and billing PaaS are reached only through Private Endpoints with public network access disabled, so a stolen connection string resolves to a private address inside Lumina’s network and no public route exists. The private DNS zones must be linked to the hub or resolution silently falls back to the public name — the classic failure the Key Vault firewall/RBAC recovery playbook walks through.
| PaaS service | PE subnet | Private DNS zone | Data class |
|---|---|---|---|
| Cosmos DB (entitlement) | snet-pe-customer /26 |
privatelink.documents.azure.com |
Confidential |
| Azure SQL (billing/PCI) | snet-pe-customer /26 |
privatelink.database.windows.net |
PCI |
| Storage (VOD origin/blob) | snet-pe-media /26 |
privatelink.blob.core.windows.net |
Restricted content |
| Event Hubs (QoE telemetry) | snet-pe-analytics /26 |
privatelink.servicebus.windows.net |
Confidential |
| Key Vault Managed HSM (DRM keys) | snet-pe-shared /26 |
privatelink.managedhsm.azure.net |
Restricted (keys) |
Administrative access uses Azure Bastion in its own subnet, so no RDP or SSH port is ever exposed to a network the workload teams can route to, and every session is brokered and logged. The whole design is validated the way an auditor validates it: NSG flow logs, Firewall logs and VNet flow logs stream to Sentinel, and Traffic Analytics is used to prove there is no media-processing-to-corporate or analytics-to-DRM flow that policy did not intend.
AWS network security (deep dive)
AWS realises the identical intent in its own primitives. Viewer traffic enters through CloudFront fronted by AWS WAF (and, behind it, an Application Load Balancer with AWS WAF) running the same OWASP-prevention rule set and bot rules as the Azure edge so both clouds present an equal bar. All traffic between VPCs and to the internet is steered by the regional Transit Gateway into a dedicated inspection VPC, where AWS Network Firewall runs in appliance mode — the mode that guarantees symmetric, stateful inspection so a long segment transfer’s return traffic is examined by the same engine that saw the request instead of dying asymmetrically mid-stream. Inside each VPC, security groups and NACLs enforce default-deny: security groups are stateful tier allow-lists that reference each other rather than CIDRs, and NACLs add a stateless subnet backstop. Playback, DRM and origin are separate VPCs, not subnets, so a blast radius stops at an account boundary.
| SG | Dir | Source / Dest | Port | Purpose |
|---|---|---|---|---|
sg-play-web |
In | sg-alb |
8443 | Playback/portal ingress from ALB only |
sg-play-api |
In | sg-play-web |
8080 | Entitlement/playback API from web only |
sg-drm-license |
In | sg-play-api |
8443 | SPEKE / licence proxy from API tier only |
sg-ssai |
In | sg-play-api |
8080 | SSAI stitcher from playback API |
| all workload SGs | Out | (no 0.0.0.0/0) | — | Egress only via TGW → Network Firewall |
Referencing a source security group instead of a CIDR is the control that keeps the allow-list honest as instances churn:
resource "aws_security_group_rule" "drm_from_api" {
type = "ingress"
security_group_id = aws_security_group.drm_license.id
source_security_group_id = aws_security_group.play_api.id
from_port = 8443
to_port = 8443
protocol = "tcp"
description = "DRM licence proxy accepts only the playback API tier"
}
The NACL is the stateless subnet backstop — deliberately coarse, denying by default, allowing only intra-estate and post-inspection egress:
| Rule | Dir | CIDR | Port | Action |
|---|---|---|---|---|
| 100 | In | 10.40.0.0/12 | 8443 | Allow (intra-AWS estate) |
| 110 | In | 10.0.0.0/12 | 8443 | Allow (on-prem MOC control) |
| 32767 | In | 0.0.0.0/0 | * | Deny |
| 100 | Out | 10.0.0.0/8 | * | Allow (to hub / inspection) |
| 110 | Out | 0.0.0.0/0 | 443 | Allow (post-inspection egress) |
Network Firewall runs a STRICT_ORDER stateful policy — the ordering that stops a Suricata pass from beating a drop and letting a denied domain leak — with HOME_NET pinned to the workload supernet and SNI/TLS allow-rules only for the DRM, payment and CDN partners.
| Type | Rule | Action |
|---|---|---|
| Stateless | Fragmented / malformed packets | drop |
| Stateful (managed) | AWS threat-intel + abused-domains group | drop |
| Stateful (custom) | TLS SNI ∈ {multi-DRM SaaS, SPEKE, payment gw, CDN origin} | pass |
| Stateful (default) | HOME_NET → any :443 not allow-listed |
drop_established |
Detection is continuous and org-wide: GuardDuty (VPC flow, DNS, S3, EKS, Malware) and Inspector feed Security Hub with the FSBP and PCI-DSS standards, and findings route to the delegated-admin Security account, then on to Sentinel. GuardDuty findings are wired to graded automated responses so a high-severity content or key event does not wait for a human to notice — and an anomalous read of a mezzanine bucket is treated as a potential TPN/MovieLabs content-security incident, not just an alert.
| GuardDuty finding | Severity | Automated response |
|---|---|---|
Exfiltration:S3/ObjectRead.Unusual (origin/mezzanine bucket) |
High | S3 public-block + page Content Security (TPN incident assessment) |
CredentialAccess:IAMUser/AnomalousBehavior (DRM key-svc role) |
High | Revoke session, rotate SPEKE key, review CloudTrail |
UnauthorizedAccess:EC2/SSHBruteForce |
High | SSM swaps to isolation SG, snapshots for forensics |
Backdoor:EC2/C&CActivity!DNS |
High | Quarantine ENI, page SOC, capture memory |
Policy:S3/BucketPublicAccessGranted (VOD/origin) |
High | Config rule auto-remediates public-access-block |
Administrative access uses AWS Systems Manager Session Manager rather than bastion hosts or open SSH — no inbound management ports, every session brokered, recorded and shipped to the immutable Log Archive. Enabling GuardDuty for the whole organisation from the delegated admin is a one-time control that auto-enrolls every new workload account at vend time:
aws guardduty update-organization-configuration \
--detector-id "$DETECTOR" --auto-enable-organization-members ALL \
--features '[{"Name":"S3_DATA_EVENTS","Status":"ENABLED"},
{"Name":"EKS_RUNTIME_MONITORING","Status":"ENABLED"},
{"Name":"EBS_MALWARE_PROTECTION","Status":"ENABLED"}]'
As on Azure, the two binding rules hold: no unrestricted east-west traffic between accounts or tiers, and all egress is policy-controlled and logged through the central Network Firewall.
Zero-downtime release patterns
The mandate is unambiguous: viewers never see a maintenance window. There is no 2 a.m. Sunday when a live channel goes dark for a deploy, no hour when the playback API may return “try again,” no window when a subscriber cannot resume a film. Zero-downtime is therefore a property the estate is engineered to preserve, not a deployment convenience, and it rests on two load-bearing ideas. The first is that deployment is not release: new code reaches production long before any viewer traffic exercises it, decoupled by feature flags in Azure App Configuration / AWS AppConfig, so a new player capability or ad format ships dark and is activated by a runtime flag flip for one cohort before it widens. The second is that every release is reversible: no change reaches a Tier-1 service unless it can be withdrawn in seconds — a flag off, traffic shifted back, a canary aborted — so reversibility is an entry condition, not an improvised contingency.
These principles are realised through a constrained set of patterns, each matched to a workload class and a QoE risk profile. The decisive streaming nuance is that the health gate is viewer-experience telemetry, not just HTTP status: a canary that keeps returning 200 but pushes video-start-time past 2s or rebuffer past 0.4% is failing and must roll back, because a degraded stream is an outage the viewer feels even when the API says fine.
| Pattern | Mechanism | Azure implementation | AWS implementation | Best-fit streaming service | Rollback trigger |
|---|---|---|---|---|---|
| Blue-green | Parallel env, cutover | App Service slots / AKS + AGIC | CodeDeploy blue-green, ALB target groups | Playback API, entitlement, portal | Swap back / shift target group |
| Canary | Weighted ramp + QoE gates | Front Door weights, Argo Rollouts + Flagger | CodeDeploy canary, ALB weighted | Reco, SSAI, CIAM token svc | QoE breach → auto-abort |
| Rolling | Incremental replace | AKS/VMSS rolling, maxUnavailable |
EKS/ECS rolling update | Encode/package workers, QoE ingest | Probe fail → halt + revert |
| Feature flags | Runtime toggle | Azure App Configuration | AppConfig / LaunchDarkly | New player feature, ad format, ABR ladder | Flag off |
| Expand-contract | Parallel schema | pgroll / EF Core additive | Liquibase + Aurora | Entitlement store, billing SoR | Stop dual-write, drop new |
| Health traffic shift | Probe-gated weight | Front Door / AGW health probe | ALB health + Route 53 | Every Tier-1 service | Unhealthy probe → drain |
| Event-freeze | Change moratorium | CAB freeze + pipeline lock | CAB freeze + pipeline lock | Live finals / premium events | No change permitted; break-glass only |
Blue-green governs the viewer-facing control services: a fully provisioned green slot runs the new version alongside live blue, smoke-tested in isolation against a synthetic playback of a reference title, an entitlement round-trip and a DRM licence issuance before any viewer traffic, then cut over by a swap that carries no cold start because the slot is warmed first. The App Service swap-with-preview is the concrete mechanism, and blue is kept warm as an instant rollback target:
az webapp deployment slot swap -g lm-lz-vod-prod \
-n lm-eus2-playback-api-prod --slot green --target-slot production --action preview
az webapp deployment slot swap -g lm-lz-vod-prod \
-n lm-eus2-playback-api-prod --slot green --target-slot production --action swap
Canary and progressive delivery apply where traffic can be split by weight — reco, SSAI and CIAM ramp 5→25→50→100 with a bake per step, each gated on the QoE and business KPIs that actually matter. The gate is automatic and refuses to promote unless Dynatrace and the Conviva-class QoE plane hold VST p95 < 2s, rebuffer ratio < 0.4%, playback-failure < 0.5% and (for SSAI) ad-fill rate within budget for the whole bake window; a drop in ad-fill means the stitcher is failing calls to the ad-decision server and the release holds. A guardrail breach shifts weight back with no human in the critical path. The per-service strategy below is the operational contract each streaming team signs.
| Service | Tier | Pattern | Traffic shift | Gate KPI | Rollback | Window |
|---|---|---|---|---|---|---|
| Playback API / entitlement | T1 | Blue-green + dark + long bake | 5→25→50→100 over 12h | Playback-failure, VST p95, entitlement 2xx | Swap to blue | None (dark) |
| DRM licence service | T1 | Blue-green | 10→100 | Licence-issue success, licence latency | Swap | None |
| CIAM token service | T1 | Canary | 5→50→100 over 2h | Login success, token latency, ATO rate | Shift to blue | None |
| SSAI stitcher | T1 | Canary + session drain | 10→50→100 | Ad-fill %, manifest errors, rebuffer | Drain + shift | None |
| Live origin / packager | T1 | Rolling + redundant origin | Node-by-node | Segment-publish lag, LL-CMAF latency | Halt node | None (never mid-event) |
| Encode / transcode | T2 | Rolling | n/a | Job success, ABR-ladder parity | Redeploy prev | Off-peak OK |
| Reco / ad-reporting | T2 | Canary / rolling | n/a | Job success, model KPIs | Redeploy | Off-hours OK |
Underpinning every stateful service is the discipline that most often defeats zero-downtime ambition: schema change. Lumina never takes a lock-acquiring migration against a live entitlement or billing store. Instead it applies expand/contract (parallel-change): expand the schema additively, dual-write old and new shapes while backfilling history, run both in parallel until the new path is proven, then contract by removing the old structure once nothing reads it. Each step is an additive, backwards-compatible, sub-50ms metadata operation, so application code and schema deploy independently and each tolerates the other’s previous version.
| Phase | Action | Application behaviour | Reversible? |
|---|---|---|---|
| Expand | Add nullable column / table / index additively | Reads and writes old shape | Yes — drop the new object |
| Dual-write | Write both old and new shapes | Both populated, old authoritative | Yes — stop writing new |
| Backfill | Batch historical rows into new shape (idempotent) | Old still authoritative | Yes — rerun or discard |
| Migrate reads | Flip read path to new shape behind a flag | New authoritative | Yes — flag back to old |
| Contract | Drop old column after a bake period | New shape only | No — final, one-way |
The pattern menu above is the default regime. For a premium live event — a sports final peaking at 45M concurrent — the regime inverts: the safest change is no change at all. Lumina runs a formal event-freeze (change-freeze) around every marquee live window, because a routine deploy that would be a shrug on a Tuesday becomes an unrecoverable on-air failure when 45M viewers are watching a single stream that cannot be re-run. The freeze is not a vague “be careful” — it is scoped, enforced by the pipeline, and has exactly one exception path.
| Freeze scope | Enforced by | During the freeze | Exception path |
|---|---|---|---|
| Tier-1 live path (origin, packager, playback, DRM, SSAI) | Pipeline lock + CAB freeze flag on the release gate | No deploy, config, schema or IaC apply | Break-glass CAB, two-person, live-incident only |
| Multi-CDN steering + edge config | Steering weights pinned to the rehearsed profile | Locked to pre-event profile | Emergency re-steer runbook (pre-approved) |
| Autoscale / capacity | Pre-scaled to N× the finals peak before freeze | Scale-out allowed; scale-in disabled | Automatic only, never manual |
| CIAM / entitlement | Rate-limits + bot rules pre-tuned | No policy or rule change | Break-glass CAB |
| Non-Tier-1 (VOD encode, analytics, corp) | Not frozen | Normal CI/CD continues | n/a |
The freeze has a timeline as well as a scope, so every team knows when the door closes and when it reopens. The window widens with the stakes: a world final freezes earlier and thaws later than a mid-season fixture.
| Phase | Window (relative to event) | What happens | Gate to advance |
|---|---|---|---|
| Readiness & prep | T-72h → T-24h | Final canaries land; caches pre-warmed; capacity pre-scaled; DR rehearsal run | Readiness review sign-off |
| Change freeze | T-24h → event start | Zero change to the Tier-1 live path; pipeline locked | CAB declares freeze active |
| Event window | Live | Monitoring + autoscale-out only; war-room staffed | Break-glass CAB for any manual act |
| Post-event thaw | T+2h after end | Staged resume of normal CI/CD; retrospective | Incident review clean |
The common thread across the whole regime is traffic shifting plus health-based rollback for every major release — and, for the events that cannot tolerate even that, a disciplined freeze. Whether the unit of change is a blue-green slot, a canary weight or an active-active region, the release advances by moving a controlled proportion of traffic against a QoE gate and retreats automatically the moment a viewer-experience signal degrades; and because deployment is separated from release by flags, even an already-deployed change is neutralised in seconds without redeploying anything.
Active-active multi-region data topology
Zero-downtime release depends on a data topology that contains failure as readily as it contains change, and streaming forces a harder problem than most because the datastores have irreconcilable consistency needs. The billing and subscription system of record (PCI scope) demands that a committed charge is durable and correct — a silently dropped or duplicated write is a chargeback or a lost renewal; entitlement, playback-session and watchlist state can tolerate eventual convergence for the sake of low-latency multi-region writes so a viewer near any region gets an instant answer; and the 400 PB VOD library is write-once and simply needs to arrive. So the topology splits by consistency need rather than treating storage as one homogeneous thing, and the organising rule for anything money-like is region-pin the writer: each account’s billing writes go to its home region, so two regions never post to the same subscription at once. Lumina runs this across the Azure East US 2 / West Europe / Southeast Asia set and the AWS us-east-1 / eu-west-1 / ap-southeast-1 set, mirroring the patterns in Azure multi-region active-active design and AWS multi-region active-active.
Each datastore is matched to its need, and the table below is the load-bearing reference: what replicates, how, in which topology, at what RPO, and — the question people forget until an incident — how a conflict is resolved.
| Datastore | Data domain | Replication mechanism | Topology | RPO | Conflict handling | Consistency |
|---|---|---|---|---|---|---|
| Cosmos DB (multi-write) | Entitlement, watchlist, resume-point, concurrency | Multi-region write | Active-active | ~0 | LWW on _ts (prefs) / stored-proc merge (entitlement) |
Session |
| DynamoDB global tables | CIAM session, device registration, notif counters | Stream-based replication | Active-active | Sub-second | LWW per item | Eventual |
| Azure SQL / Aurora Global | Billing / subscription SoR (PCI), TVOD purchases | Auto-failover group / Aurora Global DB | Active-passive (RW region-pinned) | ≤5m (0 planned switchover) | Single-writer, region-pin | Strong in-region |
| Event Hubs / Kinesis + SB/SQS geo-DR | QoE telemetry, ad/beacon events, SCTE signals | Geo-DR / cross-region | Active-passive (paired) | Near-0 | Idempotent replay by event id | At-least-once |
| Blob GRS / S3 CRR | VOD library (400 PB), origin, mezzanine | Async object replication | Active-passive | Minutes (RA-GRS) | Immutable objects — no conflict | Eventual + reconciliation |
| Key Vault MHSM / KMS MRK | DRM content keys, edge signing keys | HSM replication / multi-region keys | Active-active | 0 | n/a (same key material) | Strong |
| Azure AD B2C / Cognito | CIAM identity (120M) | Native multi-region | Active-active | 0 | Multi-master convergence | Eventual → strong |
The billing path deserves its own detail because it is the one that touches money and PCI scope. Azure SQL auto-failover groups and Aurora Global Database replicate the billing/subscription store to the secondary region; a planned switchover is RPO 0, an unplanned failover loses at most the replication lag, which is why lag is alarmed at RPO/2. The read-write listener is what the application connects to so a failover is transparent to connection strings:
az sql failover-group create -n lm-billing-fog -g lm-lz-data-prod \
--server lm-eus2-billing-sql --partner-server lm-weu-billing-sql \
--failover-policy Manual --grace-period 1 \
--add-db lm-billing lm-tvod-purchases
# app connects to lm-billing-fog.database.windows.net (RW listener) — never the raw server
The engagement stores are genuinely active-active, and the discipline there is knowing exactly where last-writer-wins is safe and where it is lethal. For Cosmos multi-region write, LWW on _ts is fine for a watchlist add or a resume-point, but entitlement uses a stored-procedure custom-merge policy and the conflicts feed is monitored for silent drops — because a lost entitlement write shows a paying subscriber a “not entitled” error, and a wrongly-merged concurrency counter lets one shared password stream on ten devices at once. DynamoDB global tables are LWW per item — perfect for a CIAM session token or a device registration, and categorically never the billing ledger. The per-domain routing contract makes the boundary explicit.
| Domain | Store | Region model | Write routing | Conflict rule | RPO |
|---|---|---|---|---|---|
| Entitlement / concurrency | Cosmos multi-write | Active-active | Nearest region | Custom merge (entitlement) | ~0 |
| Playback session / resume | DynamoDB global | Active-active | Nearest region | LWW per item | Sub-second |
| Billing / TVOD (PCI) | Azure SQL failover group | A-active / B-standby | Region-pin by account | None (single writer) | ≤5m |
| CIAM identity | B2C / Cognito | Active-active | Nearest region | Multi-master converge | ~0 |
| VOD / origin objects | Blob GRS / S3 CRR | Active-passive | Primary ingest region | Immutable, reconciled | Minutes |
| QoE / ad telemetry | Event Hubs / Kinesis geo-DR | Active-passive | Primary namespace | Idempotent replay | ~0 |
| DRM content keys | Key Vault MHSM / KMS MRK | Active-active | Any region | Same key material | 0 |
The VOD library is the outlier and by far the largest: it replicates by Blob GRS / S3 CRR, which copies new objects only, asynchronously, and never replicates a lifecycle transition or a delete marker by default. So a reconciliation job compares per-title object counts and manifest integrity between regions on a schedule, surfacing a missed segment before a viewer in the secondary region hits a gap in a title. Across every store, the governing rule is the one covered in depth in multi-region data replication strategies: replication lag is your live RPO, and if lag breaches the tier objective, failing over means losing committed data — a trade to be decided before the incident, not during it.
Active-passive and warm-standby topology
The engagement tier of this platform is deliberately active-active — Cosmos and DynamoDB multi-write, health-based edge steering and CRDT-style counters let entitlement and session state take writes in more than one region at once, as the topology section established. The billing and subscription system of record cannot join it, and understanding why is the whole of this section. A subscription balance has exactly one correct value at any instant; the moment two regions accept a charge or a plan change to the same account concurrently you are running not one ledger but two divergent ones, with no mathematically safe merge after the fact — last-write-wins silently double-charges or drops a renewal. So for the strongly-consistent billing core, the PCI-scoped stores that hang off it, and the SAP back office an auditor expects to see fail over on command, the correct topology is active-passive with a warm standby: one region is the sole writer, a paired region in the same legal geography stands warm and continuously fed, and failover is a deliberate, fenced, one-way act.
Active-active is the default only for stateless, eventually-consistent, or per-account-partitioned work. For everything with a single authoritative writer, active-passive is not a weaker choice — it is the correct one, and treating it as a fallback is how operators end up with split books. The properties that decide it, with the streaming example for each:
| Property of the workload | Active-active is right when… | Active-passive / warm standby is right when… | Lumina example |
|---|---|---|---|
| Consistency model | Eventually consistent; conflicts converge | Strongly consistent; one correct value per key | Billing/TVOD ledger → passive |
| Write ownership | Any region may own any write | One writer authoritative at a time | Subscription state machine |
| Merge correctness | Concurrent writes merge without loss | No safe merge of concurrent writes exists | Double-charge is unrecoverable |
| Latency budget | Local-region write latency required | A quorum/replication delay is tolerable | SAP HANA, payment state |
| Regulatory expectation | Continuous availability suffices | Auditor expects a demonstrable failover | PCI-DSS, SOC 2 exit test |
| Blast radius of a bug | A bad write is contained per region | A bad write corrupts the single source | Schema/config error on billing SoR |
Recovery is not one posture but a ladder of four, each trading cost for recovery speed. Lumina places every workload on a rung deliberately; a flat posture either over-insures a sandbox or, far worse, under-insures the playback path.
| Strategy | What runs in the DR region | Typical RTO | Typical RPO | Rel. monthly cost | Recovery action |
|---|---|---|---|---|---|
| Backup & restore | Nothing but immutable backups | 4–24 h+ | Hours (last backup) | ~1.05× baseline | Provision from IaC, restore, cut over |
| Pilot light | Core data replicating; compute scaled-to-zero | 30 min – 4 h | Minutes | ~1.15–1.25× | Scale up the dormant core, cut over |
| Warm standby | Scaled-down but functional copy, data live | 2–30 min | Seconds – minutes | ~1.4–1.6× | Scale out, promote data, cut over |
| Active-active | Full-capacity peer taking live traffic | Seconds (traffic shift) | ≈0 | ~2.0×+ | Shift traffic weight; no promote/restore |
The four recovery tiers already defined for this estate map cleanly onto the ladder. The mapping is the operational contract: it names which primitive delivers each tier’s RTO/RPO.
| Tier | Representative workloads | DR strategy | Primary primitive | RTO / RPO target |
|---|---|---|---|---|
| T0 | Identity, DNS, network + security control plane | Active-active (identity itself multi-region) | Front Door + Traffic Manager / Route 53, global tables | ≤15 min / ≈0 |
| T1 | Entitlement, playback API, DRM-licence, origin, live-control, SSAI-core, CIAM-auth | Warm standby (edge active-active) | SQL failover group · Aurora Global · Cosmos/DDB multi-write · geo origin | ≤15 min / ≤1 min |
| T2 | Business apps, analytics, support, ad-reporting | Pilot light | Data replicated, compute scaled-to-zero | ≤4 h / ≤1 h |
| T3 | Dev, sandbox, non-critical | Backup & restore | Cross-region immutable backup | ≤24 h / ≤24 h |
A warm standby is only as good as the mechanism that promotes it, and the unifying discipline is that promotion of a system of record is manual and one-way: no automatic failover on billing, because a false positive — a transient partition mistaken for a region loss — promotes a second writer and splits the ledger. Automation prepares, humans decide, and the decision is gated on a change record. Each data platform exposes its own primitive:
| Primitive | Platform | Replication | Promotion type | Split-brain guard | Failover gotcha |
|---|---|---|---|---|---|
| SQL failover group | Azure SQL DB / MI | Async continuous | Manual planned / forced | Manual RW policy |
--allow-data-loss = the RPO you accept |
| Aurora Global Database | AWS Aurora | Storage-level, ~1 s lag | Managed (0 RPO) / detach-promote | Managed failover only | Detach-promote costs the in-flight lag |
| Cosmos multi-write | Azure | Multi-master | Automatic per region | Conflict feed monitored | LWW silently drops — merge policy for entitlement |
| Traffic Manager | Azure DNS | n/a (control plane) | Priority endpoint flip | Health-probe threshold | DNS TTL ≥30 s slows cutover |
| Route 53 failover | AWS DNS | n/a (control plane) | Health-check record flip | Health-check threshold | Client IP-pinning caches past TTL |
The unplanned billing failover forces the RPO decision into the open — the flag is the data-loss you accept:
# PLANNED (0 RPO): reverses roles, waits for the secondary to catch up
az sql failover-group set-primary --name lm-billing-fog \
--resource-group lm-lz-data-prod --server lm-weu-billing-sql
# UNPLANNED (primary region gone): forced, accepts the in-flight replication lag as RPO
az sql failover-group set-primary --name lm-billing-fog \
--resource-group lm-lz-data-prod --server lm-weu-billing-sql --allow-data-loss
Split-brain — two nodes both believing they are primary and both accepting writes — is the failure mode that turns a routine failover into an unreconcilable ledger, and it is defended in depth rather than by a single control. Failback is not a mirror-image auto-return: once the standby is promoted it is the primary, and the recovered region is rebuilt as the new warm standby and re-synchronised from the current writer before any future failover considers it.
| Control | What it prevents | Implementation |
|---|---|---|
| Manual-only RW failover | Auto-promotion on a transient partition | Manual policy; managed-failover-only on Aurora |
| Quorum witness / fence | Both nodes claiming primary | Odd-node witness; revoke write identity |
| Write-identity revocation | Old primary accepting a late write | Rotate/revoke the primary’s DB credential on fence |
| One-way promotion | Ambiguous “who is primary” after failback | Old primary rebuilt as the new standby, never re-attached live |
| Idempotency keys | Double-charging an in-flight TVOD purchase | Every purchase carries a key; replay is a no-op |
Disaster recovery and resiliency
Resiliency is engineered, not assumed, and the engineering begins by classifying every service into a recovery tier and designing each tier to a stated objective drawn straight from the operating model. A flat DR posture either over-invests in a sandbox that can tolerate a day’s recovery or, far worse, under-protects the playback path. Each strategic region is paired with a DR region in the same geography and cloud — East US 2 ↔ West Europe for the Americas/EU pairing, Southeast Asia held for APAC-in-APAC residency, us-east-1 ↔ eu-west-1, ap-southeast-1 local — so a regional loss has a pre-built, in-jurisdiction destination that respects GDPR/CCPA data-residency boundaries. The tier definitions are the contract, and streaming’s numbers are tight at the top because a 15-minute playback outage during a live final is a brand event measured in millions of abandoned sessions.
| Tier | Scope / examples | RTO | RPO | DR strategy | Cost posture |
|---|---|---|---|---|---|
| T0 | Identity, DNS, network + security control plane, privileged access | ≤15 min | ≈0 | Active-active across regions | High (always-on) |
| T1 | Entitlement, playback API, DRM-licence, origin, live-event control, SSAI-core, CIAM-auth | ≤15 min | ≤1 min | Warm standby + active-active edge | High |
| T2 | Business apps, analytics, support, ad-reporting | ≤4 h | ≤1 h | Pilot-light | Medium |
| T3 | Dev, sandbox, non-critical reporting | ≤24 h | ≤24 h | Backup-restore | Low |
The recovery strategy is differentiated by tier rather than applied uniformly, because the cheapest posture that meets the objective is the right one — the RTO/RPO fundamentals behind these choices are laid out in BC/DR RTO/RPO fundamentals. Tier-0 and the Tier-1 edge run active-active and recover by traffic shift; the Tier-1 control core runs warm standby with continuous replication so promotion is a fast, controlled switchover; Tier-2 runs pilot-light; Tier-3 restores from immutable backup.
| Strategy | How it recovers | Azure | AWS | Tiers |
|---|---|---|---|---|
| Active-active | Traffic shift, no restore | Front Door + Traffic Manager, multi-write stores | Route 53 + global tables | T0, T1 edge |
| Warm standby | Promote a running replica | SQL failover group + ASR-replicated compute | Aurora Global + pre-scaled ECS/EKS | T1 core |
| Pilot-light | Scale minimal footprint on failover | ASR minimal + replicated data | AMI + replicated RDS, scale-out | T2 |
| Backup-restore | Restore from vault | Azure Backup + LTR | AWS Backup | T3 |
A recovery objective is only as credible as the last test that proved it, so DR testing is a standing obligation, not an annual ceremony. Lumina runs a layered cadence — automated fault injection continuously, per-tier failover quarterly, a full-region game-day annually — and adds a streaming-specific drill the other industries lack: a live-event failover rehearsal run before every marquee event, where origin, DRM and CDN-steering failover are exercised under synthetic concurrency so a real failover during the final does not produce a black screen.
| Test | Scope | Frequency | Tool | Pass criteria |
|---|---|---|---|---|
| Chaos / fault injection | Dependency, AZ, instance loss | Monthly | Azure Chaos Studio / AWS FIS | Graceful degrade, no cascade |
| Per-tier DR failover | One tier to DR region | Quarterly | ASR / AWS Backup + runbook | Meets tier RTO/RPO |
| Live-event failover rehearsal | Origin / DRM / steering failover under synthetic load | Before each premium event | FIS + load generator | QoE holds; LL latency < 8s; no black screen |
| Backup restore test | Random workloads | Monthly | Backup restore | Integrity + boot pass |
| Full-region game-day | Lose an entire region | Annual | FIS + ASR orchestration | Tier-0/1 within target |
| Ransomware recovery | Immutable restore, clean rebuild | Semi-annual | Vault-lock restore + IaC | Clean-state attestation |
Disaster recovery runbooks
A recovery target nobody has rehearsed is a guess dressed as a commitment. RTO/RPO numbers are meaningful only when a named owner has executed the procedure that meets them against a realistic scenario, recently enough that the runbook reflects the current estate. Lumina’s orchestration builds on the pattern in DR orchestration with Site Recovery and ServiceNow: each runbook names its trigger, its steps, its single accountable owner, the tier target it must meet, and — the step most often omitted — how recovery is validated before the incident is declared closed. Recovery always proceeds in tier order, because nothing else can come back until identity, DNS and the network control plane are up.
The Tier-0 runbooks run first and fastest; their failure blocks every runbook below them, which is why identity and connectivity are active-active with RPO≈0. The AD forest recovery runbook covers the deepest workforce-identity-loss case behind this.
| Scenario | Trigger | Procedure steps | Owner | Target | Validation |
|---|---|---|---|---|---|
| Identity / access-plane failover | Entra / Okta or CA plane degraded in primary | Confirm break-glass path; fail CA + PIM plane to secondary region; promote private DNS resolver; verify Entra-to-AWS federation intact; confirm CIAM plane (separate) healthy | Identity & Security | T0 · 15m / ≈0 | Workforce + admin auth succeed in DR; CA/PIM enforced; break-glass re-sealed |
| Network / security control failover | Loss of hub, firewall, or an ExpressRoute / Direct Connect on-ramp | Verify BGP withdrew the failed path; confirm second circuit carrying load; shift vWAN / TGW hub; confirm firewall policy active in secondary | Network & Connectivity | T0 · 15m / ≈0 | End-to-end reachability on surviving circuits; IDPS active; no SPOF remaining |
The Tier-1 runbooks are the ones a viewer feels, and each streaming domain fails over differently — entitlement/playback shifts edge weight and reconciles concurrency, the DRM service promotes a key-service replica, live origin re-points to a redundant packager, and CIAM (already active-active) simply drains a region. The unifying validation is no viewer sees an outage and no protected title becomes unplayable.
| Scenario | Trigger | Procedure steps | Owner | Target | Validation |
|---|---|---|---|---|---|
| Entitlement + playback-API failover | Loss of the region hosting playback control | Confirm Cosmos multi-write + billing SQL failover-group synced; shift Front Door / Route 53 weight to survivor; scale hot playback + entitlement; reconcile concurrency counters by idempotent merge | Playback Platform | T1 · 15m / ≤1m | Playback resumes; VST p95 < 2s; no false “not entitled”; concurrency correct |
| DRM-licence failover | Loss of the DRM / key-service region | Promote multi-DRM SaaS / SPEKE secondary; re-point licence proxy; confirm MHSM / KMS multi-region key material present; verify Widevine + FairPlay + PlayReady issuance and HDCP | Content Security | T1 · 15m / ≈0 | Licences issue in DR; protected content plays; HDCP enforced; no key gap |
| Live origin + event-control failover | Loss of the live encode / package / origin region | Fail to the redundant origin/packager (second zone/region); re-point CDN origin-pull; confirm dual contribution (SRT / Zixi) still landing; resume SCTE-35 + SSAI stitching | Live Operations | T1 · 15m / ≤1m | Segments publishing; LL-CMAF latency < 8s; no black screen; ad markers intact |
| CIAM-auth failover | Loss of a CIAM region (B2C / Cognito) | Drain the failed region (both active-active); confirm social/email login on survivor; verify rate-limit + bot rules active; reconcile session/device state | CIAM Platform | T1 · 15m / ~0 | Login + token issuance succeed; ATO defences active; no lockout storm |
Failing over is only half the discipline; failing back is where estates that never rehearse it corrupt data by reversing replication carelessly. Failback is a deliberate, off-peak operation gated on the primary being healthy and attested clean — never re-entering a compromised environment.
| Step | Action | Guard / gate |
|---|---|---|
| 1 | Confirm primary region healthy, patched, and attested clean | No reintroduced compromise (ransomware case) |
| 2 | Reverse-replicate the delta accumulated in DR back to primary | Lag < tier RPO before proceeding |
| 3 | Switch writers back during an off-peak, non-event window | No live premium event in flight |
| 4 | Validate reconciliation across billing, entitlement, catalogue | Charge / entitlement / object parity clean |
| 5 | Re-seal break-glass, restore normal steering, close incident | Full audit trail complete; lessons logged |
Finally, a runbook is executed by people under pressure, so the roles and communications are pre-assigned — a streaming DR incident carries content-security and viewer-privacy dimensions that make the content-security and privacy roles as load-bearing as the platform ones.
| Role | Responsibility | Escalates to |
|---|---|---|
| Incident Commander | Declares DR, owns the go/no-go, runs the bridge | VP Platform / CTO |
| Content Security Officer | Owns TPN/MovieLabs incident assessment, piracy/leak response | CISO |
| Live Event Director | Protects on-air continuity; owns break-glass during events | Incident Commander |
| Privacy Officer | Runs GDPR/CCPA breach assessment (72-hour clock) | Legal / Compliance |
| Platform / Network / Identity leads | Execute the per-domain runbooks in tier order | Incident Commander |
| Communications lead | Viewer status page, partner/affiliate liaison, regulator | Executive sponsor |
Backup, immutability and ransomware recovery
Disaster recovery and backup solve different problems and it is dangerous to conflate them. The active-passive topology above protects against a region going away with its data intact; backup protects against the data itself being wrong — a bad migration, a fat-fingered DELETE against the catalogue, and above all ransomware, where an adversary who has reached production admin does not knock a region over but encrypts the live estate and, first, tries to destroy the very backups you would recover from. For a streaming operator ransomware has a second, industry-specific edge: the attacker may exfiltrate the 400 PB content library or the mezzanine masters (a MovieLabs/TPN content-security incident with studio notification obligations), and if they encrypt the DRM content keys, every protected title in the catalogue becomes unplayable at once even though the media objects are intact. A warm standby is no defence, because the corruption replicates to it in seconds. The backup architecture is therefore engineered around one unflattering assumption: the attacker will, at some point, hold production administrator rights, and recovery must survive that.
That assumption is what the industry codifies as 3-2-1-1-0, and every element of the design below exists to satisfy one of its digits, following the pattern in multicloud backup and ransomware recovery.
| Digit | Rule | How Lumina satisfies it |
|---|---|---|
| 3 | Three copies of the data | Production + on-cloud backup vault + cross-region/cross-cloud copy |
| 2 | On two different media/platforms | Azure Backup vaults and AWS Backup / S3 — cross-cloud, not one vendor |
| 1 | One copy off-site | Cross-region copy in the paired geography |
| 1 | One copy offline / air-gapped / immutable | Immutable, isolated tenant/account with no standing access |
| 0 | Zero errors — verified by restore testing | Automated monthly restore tests with integrity validation |
The estate standardises on Azure Backup with Recovery Services / Backup vaults and AWS Backup as the two native planes, orchestrated centrally so protection is assigned by tag and policy, not configured per resource, following AWS Backup DR strategies. Frequency, retention and copy topology are differentiated by data class — the billing ledger and the DRM keys are not protected like a dev VM, and the 400 PB library is protected by immutable object versioning rather than nightly copies.
| Data class | Tool | Frequency / RPO | Hot retention | Long-term (WORM) | Copies |
|---|---|---|---|---|---|
| Billing / subscription SoR (PCI) | Native DB PITR + vault | Continuous PITR (≤5 min) | 35 days | 7–10 yr immutable | Local + cross-region + air-gap |
| Entitlement / CIAM data | Cosmos continuous backup / DynamoDB PITR | Continuous | 30 days | Per retention | Local + cross-region |
| VOD library / mezzanine (400 PB) | Blob immutability + S3 Object Lock + versioning | On ingest (versioned) | n/a | Per content schedule (yrs) | Local + cross-region, WORM |
| DRM content keys / secrets | Key Vault / KMS backup + soft-delete + purge-protect | On change | Soft-delete window | HSM escrow | Vault + HSM |
| VMs / AKS / EKS (encode/origin) | Azure Backup · AWS Backup | Daily (critical: 4–6 h) | 30 days | 1 yr | Local + cross-region |
| Config / IaC state | Git + versioned state backend | On every apply | Full history | Repo-retained | Git remote + state replica |
The last two rows are the ones teams under-protect, and in streaming they are lethal to skip. DRM keys are the subtlest single point of failure in the whole estate: a restored catalogue is worthless if the keys that decrypt it sat in a vault the attacker purged, so Key Vault and KMS run soft-delete with purge protection, key material is escrowed to a managed HSM, and the Key Vault backup and rotation model is part of the recovery plan, not an afterthought. Configuration and Terraform state are as load-bearing as data — a landing zone you cannot rebuild is one you cannot recover a live event into — so IaC repos, pipeline definitions and the encrypted, versioned remote state backends are themselves backed up and replicated. What each source needs and where it bites:
| Source | Backup method | Native gotcha |
|---|---|---|
| Azure SQL / Aurora (billing) | PITR + long-term retention / snapshots | PITR alone is same-region; add cross-region copy |
| Cosmos / DynamoDB (entitlement) | Continuous backup / PITR | PITR window bounded; export for long retention |
| Blob / S3 (VOD, origin, mezzanine) | Versioning + Object Lock + backup | Versioning without Object Lock is still deletable |
| DRM keys (Key Vault / KMS) | Soft-delete + purge-protect + HSM escrow | Purge protection OFF = an admin permanently deletes → catalogue bricked |
| VMs (encode/origin) | Application-consistent snapshot | Crash-consistent only unless VSS/pre-scripts run |
| Terraform state | Versioned, replicated backend | A lost state file orphans the entire estate |
The difference between a backup and a recoverable backup, in a ransomware scenario, is whether the recovery points can be deleted or encrypted by the same identity that owns production. Four controls make them survivable, layered so no single compromised credential can defeat the set. Immutability / WORM makes a recovery point write-once for its retention: on Azure, Recovery Services and Backup vaults support an immutability setting that, once Locked, is irreversible; on AWS, Backup Vault Lock in compliance mode and S3 Object Lock in compliance mode make points undeletable until expiry by anyone, including the root account.
# AWS Backup Vault Lock in COMPLIANCE mode — after the cooling-off, no one can shorten or delete
resource "aws_backup_vault_lock_configuration" "airgap" {
backup_vault_name = aws_backup_vault.airgap.name
changeable_for_days = 3 # cooling-off; after this the lock is immutable (compliance)
min_retention_days = 2555 # 7 years
max_retention_days = 3650
}
# S3 Object Lock (COMPLIANCE) for the mezzanine/VOD air-gap copy — GOVERNANCE is bypassable, never for content
resource "aws_s3_bucket_object_lock_configuration" "mezzanine_airgap" {
bucket = aws_s3_bucket.mezzanine_airgap.id
rule { default_retention { mode = "COMPLIANCE" days = 2555 } }
}
Soft-delete keeps deleted points in a recoverable recycle state for a window so a deletion is reversible; multi-user authorisation / MFA-delete (Azure Resource Guard; S3 MFA-delete) ensures no lone identity can both disable protection and purge points; and air-gap is the last and most important — the immutable copy sits in a separate cloud, tenant and account, with no standing credentials and no network path from production, so the identity that owns the primary estate simply cannot reach it.
| Control | Cloud primitive | What it stops | Failure if misconfigured |
|---|---|---|---|
| Immutable / WORM (compliance) | Locked vault immutability · Vault Lock · S3 Object Lock | Encryption or deletion of recovery points | Governance mode / unlocked = a privileged role deletes them |
| Soft-delete | Enhanced soft-delete · versioning | An immediate hard-delete of points | Disabled or short window = purge succeeds |
| Multi-user auth / MFA-delete | Azure Resource Guard · S3 MFA-delete | A lone admin disabling protection | Single identity owns both protect and delete |
| Air-gap / isolation | Separate tenant/account, no standing access | The prod identity reaching the copy | Same account/tenant = one breach reaches all copies |
| Cross-region / cross-cloud copy | AWS Backup copy · GRS · cross-cloud pipe | Regional or single-vendor loss | Single region/vendor = correlated failure |
A backup is a cost until it is restored, so restore is specified as an SLA per data class and — the step most often skipped — tested on a cadence into an isolated, throwaway landing zone that runs row-count, checksum and application-level integrity checks and records the measured RTO. That automated proof is the 0 of 3-2-1-1-0.
| Data class | Restore method | Restore RTO target | Restore test cadence |
|---|---|---|---|
| Billing / subscription SoR | DB PITR to timestamp | ≤2 h | Monthly, into isolated LZ |
| Entitlement / CIAM | Continuous-backup restore | ≤2 h | Monthly |
| VOD / mezzanine objects | Version / Object-Lock restore | ≤4 h (per scope) | Quarterly |
| DRM keys / secrets | Key Vault / KMS restore + HSM escrow | ≤1 h | Quarterly |
| VMs / IaaS (encode/origin) | Vault restore / rebuild from IaC | ≤4 h | Quarterly |
| Config / IaC state | IaC re-apply + state restore | ≤1 h | Quarterly |
Ransomware recovery is not a bigger restore; it is a forensic operation with a restore inside it, and it fails if run as a hurried “restore latest.” The governing insight is dwell time: an adversary typically lives inside the estate for weeks before triggering encryption, so the newest recovery points are the most likely to already contain the implant. The task is not to find the last backup but the last clean backup — a point that predates the intrusion — and to bring it into an environment the attacker does not already own.
| # | Stage | Action | Owner | Exit criterion |
|---|---|---|---|---|
| 1 | Detect & declare | Confirm ransomware (not outage); declare incident; open crisis bridge | SOC | Incident classified; privacy + content clocks started |
| 2 | Isolate & contain | Sever affected networks; rotate suspect identities; freeze blast radius | SOC + Cloud Security | Attacker path cut; no lateral movement |
| 3 | Preserve evidence | Snapshot compromised state for forensics before any change | SOC | Immutable forensic copies captured |
| 4 | Find the clean point | Walk backups backwards from encryption; scan candidates for IOCs | SOC + Cloud Ops | Point validated as pre-intrusion, not just pre-encryption |
| 5 | Stand up clean room | Rebuild isolated LZ from Terraform — patched, no path to prod | Cloud Platform Eng | Clean LZ up; known TTPs closed |
| 6 | Restore & scan | Restore clean point (incl. DRM keys from HSM escrow) from the air-gapped copy; re-scan | Cloud Ops | Data + keys restored; EDR scan clean |
| 7 | Validate integrity | Row-count, checksum, catalogue + entitlement reconciliation, licence-issue test | Application Enablement | Data reconciles; protected titles play; no reintroduced compromise |
| 8 | Reconnect per tier | Restore services T0→T3 behind fresh credentials; resume gradually | Cloud Ops + Network | Clean-state attestation before viewer traffic |
| 9 | Forensics & harden | Root-cause, close the entry vector, TPN/studio notification if content exfiltrated | SOC + Content Security | Vector closed; detections + guardrails updated |
Three points distinguish this from a routine restore. The restore point must be scanned, not trusted (step 4): pulling the most recent backup usually restores the malware with the data. The target must be clean-built, not the original estate (step 5): restoring into the compromised landing zone re-infects on contact, which is precisely why Terraform state and pipeline definitions are first-class recovery assets. And the DRM keys are restored from the HSM escrow, not the live vault (step 6): because the on-cloud vaults may have been within the attacker’s reach, the escrowed, air-gapped key material is the one trusted to be uncorrupted — without it, a perfectly restored 400 PB library is still a wall of undecryptable ciphertext. Recovery resumes tier by tier behind wholly rotated credentials, and no viewer traffic returns until a clean-state attestation confirms the environment being reopened is not the one the attacker still holds a key to.
Application onboarding for 100+ services
Lumina Media runs 100+ apps and services — a live-playback API, VOD catalog service, entitlement engine, DRM-license proxy, SSAI stitcher, CIAM token service, recommendation ranker, ad-decision connector, dozens of back-office and partner services. If every team hand-builds its own subnet, policy, identity and pipeline, you do not get 100 landing zones — you get 100 snowflakes, each a bespoke audit finding and a bespoke outage. The only way this scales is a paved road: a self-service, manifest-driven onboarding where a team declares intent and the platform vends the matching landing zone with the guardrails already welded on. The team’s freedom is deliberately bounded — they choose an archetype, not a network.
The road has five stages, and each one is a gate with an owner, an artifact and an SLA. Nothing advances by email; every stage transition is a merged pull request or a pipeline run.
| Stage | Input | Machine gate | Human/owner | Artifact produced | SLA |
|---|---|---|---|---|---|
| 1 · Intake | service.yaml in the onboarding repo |
Schema + owner/cost-centre validation | App team lead | Registered service record | same day |
| 2 · Classify | Tier + data-class + archetype fields | Tier↔RTO/RPO consistency, data-class allow-list | Architecture + Security | Approved classification | 1 day |
| 3 · Land | Classification + archetype | IPAM allocation, no CIDR overlap | Platform (automated) | Vended spoke + repo scaffold | < 1 hr (pipeline) |
| 4 · Guardrail | Vended spoke | Policy attach, WIF, OTel, tags | Platform (automated) | Compliant baseline | in the same run |
| 5 · Go-live | Deployed workload | DR test, QoE smoke, on-call wired | Service owner + SRE | Production sign-off | 1–5 days |
The intake manifest is the entire contract. There is no portal form that emails a human; the team opens a PR against platform/onboarding/services/ with a file the pipeline can parse, validate and act on:
# services/live-playback-api.yaml — the ONLY onboarding input
apiVersion: lumina.tv/v1
kind: ServiceOnboarding
metadata:
name: live-playback-api
owner: team-live-delivery # must resolve to a real Entra group
costCentre: CC-4821
slack: "#live-delivery-oncall"
spec:
tier: tier-1 # tier-0 | tier-1 | tier-2 | tier-3
dataClass: [pii-viewer, premium] # pci | pii-viewer | premium | internal | public
archetype: streaming-api # see the archetype→pattern table
clouds: [azure, aws] # active/active Tier-1
regions:
azure: [eastus2, westeurope, southeastasia]
aws: [us-east-1, eu-west-1, ap-southeast-1]
dependencies: [drm-license-proxy, entitlement-engine, ciam-token-svc]
dataResidency: gdpr # drives EU-only routing of PII
estPeakRps: 900000 # informs scaling + load-test target
Classification turns those declared fields into controls. The tier sets the RTO/RPO band and therefore the redundancy the pattern must buy; the data-class pulls the policy set and the network segment. Getting this wrong is the most expensive mistake in onboarding — a PCI service landed in the analytics segment, or a premium MovieLabs pipeline without hardware-root-of-trust controls, is a finding you pay for at audit.
| Tier | Workloads (Lumina) | RTO / RPO | Redundancy the pattern buys | Change control |
|---|---|---|---|---|
| Tier-0 | Identity, DNS, network-control, privileged access, core security | ≤15 min / ≈0 | Active/active multi-region, no single control plane | CAB + freeze-aware |
| Tier-1 | Playback-API, entitlement, DRM-license, origin, live-event-control, SSAI-core, CIAM-auth | ≤15 min / ≤1 min | Active/active both clouds, auto-failover, ≥2 AZ | Change-gated, freeze-blocked |
| Tier-2 | Business, analytics, support, ad-reporting | ≤4 hr / ≤1 hr | Warm standby, single-region primary | Standard change |
| Tier-3 | Dev, sandbox, experiments | ≤24 hr / best-effort | Single AZ, redeploy-from-code | Self-service |
| Data-class | Regime | Policy set attached | Segment | Encryption / key control |
|---|---|---|---|---|
| pci | PCI-DSS | Cardholder-scope, no-internet-egress, flow logs | customer-API (isolated) | HSM-backed keys, tokenised PAN |
| pii-viewer | GDPR + CCPA | EU-routing, consent tags, DSAR-ready storage | customer-API | CMK, field-level for identifiers |
| premium | MovieLabs ECP / TPN / CDSA | Hardware-root, no-copy watermark, restricted egress | media-processing | CMK + KMS grants, HDCP enforced |
| internal | SOC 2 | Baseline deny + tag policy | corporate / analytics | CMK, platform-managed |
| public | — | Baseline + WAF | static-web / edge | TLS only |
The heart of the paved road is the archetype → landing-pattern map. Five archetypes cover essentially every service in the estate; each one binds to exactly one Azure pattern and one AWS pattern so that “how do we host this?” is answered before the team asks. This is the menu — there is no à la carte.
| Archetype | Lumina example | Azure pattern | AWS pattern | Segment | Scaling | Default tier |
|---|---|---|---|---|---|---|
| streaming-api | Playback / entitlement / token API | APIM → AKS (HPA/KEDA) behind Front Door, PE to data | API GW/ALB → EKS (HPA/Karpenter), CloudFront | customer-API | Request-driven, per-region autoscale | Tier-1 |
| stateful-service | Recommendation store, session/concurrency | AKS + Cosmos DB (multi-region write), Redis | EKS + DynamoDB global tables, ElastiCache | media-processing | Partitioned, provisioned + burst | Tier-1/2 |
| batch-encode | VOD transcode, package, DRM-encrypt | AKS batch / Batch pool + Blob, event-triggered | MediaConvert / Batch on ECS + S3, EventBridge | media-processing | Queue-depth driven, spot-heavy | Tier-2 |
| event-consumer | QoE ingest, catalog sync, ad-reporting | Functions/AKS + Event Hubs, Service Bus | Lambda/ECS + Kinesis, SQS/SNS | analytics | Concurrency = shard/partition count | Tier-2 |
| static-web | Marketing site, help centre, config JSON | Static Web Apps / Blob + Front Door + WAF | S3 + CloudFront + WAF | static-web / edge | CDN-cached, near-zero origin | Tier-2/3 |
Landing is where the pattern becomes real infrastructure. The vending pipeline allocates a /22 spoke from the region’s supernet (Azure 10.20.0.0/12, AWS 10.40.0.0/12) with a fixed segment offset, scaffolds the app repo from the archetype template, and writes back the addresses — the team never picks a CIDR, so overlaps with on-prem 10.0.0.0/12 are structurally impossible. A trimmed Terraform vend for the Azure streaming-api pattern:
module "spoke" {
source = "app.terraform.io/lumina/landing-zone-spoke/azurerm"
version = "3.6.0" # pinned, from the private registry
name = "live-playback-api"
tier = "tier-1"
archetype = "streaming-api"
segment = "customer-api" # → subnet offsets + NSG intent
region = "eastus2"
address_space = module.ipam.next_free_slash22 # 10.20.x.0/22, no overlap by construction
hub_vwan_id = data.azurerm_virtual_wan.hub.id
private_dns_zones = ["privatelink.vaultcore.azure.net", "privatelink.documents.azure.com"]
policy_set = "tier1-pii-premium" # deny-by-default, attached NOT optional
workload_identity = true # federated creds, zero static secrets
}
The AWS side is symmetrical. The same manifest drives a Control Tower Account Factory vend for the OU the archetype maps to (Workloads/Live/Prod, Workloads/VOD/NonProd, etc.), with a Service Catalog product provisioning the VPC, endpoints, IAM roles and SCP-enforced guardrails — so a streaming-api service gets an EKS/ALB spoke behind CloudFront on AWS exactly as it gets an AKS/APIM spoke on Azure, from one declaration. Active/active Tier-1 services vend in both clouds and both are wired into the go-live test.
Guardrails then attach from the pattern, not from a ticket. They are applied by the vending pipeline in the same run and are not removable by the owning team — a spoke that can reach unfiltered internet egress, or that runs on static keys, is proof the pattern was bypassed and fails the go-live gate.
| Guardrail | Applied by | Azure | AWS | Failure mode it prevents |
|---|---|---|---|---|
| Deny-by-default policy | Policy set / SCP | Azure Policy tier1-pii-premium |
SCP + Config rules | Public blob, wrong region, untagged |
| Identity (no static keys) | Workload-identity federation | Entra WIF → Key Vault | IAM Roles Anywhere / IRSA | Leaked long-lived credentials |
| Network isolation | Segment + private DNS | Private Endpoint + NSG | VPC endpoint + SG | Data-plane over the internet |
| Observability | Auto-instrumentation | OTel + Azure Monitor | OTel + CloudWatch | Blind spot at go-live |
| Cost + ownership | Tag policy | Required tags enforced | Tag policy + budget | Orphaned, unattributable spend |
The go-live gate is the one stage a human cannot rubber-stamp — a Tier-1 service does not go live until it has passed a real regional failover test proving RTO ≤15 min / RPO ≤1 min, a QoE smoke test that plays a stream end-to-end through the multi-CDN, and a named on-call rota wired into the NOC.
The whole path, and the six places it silently fails, in one view:
ERP, HR, ITSM and business platforms
Behind the consumer streaming plane sits the business that funds it: SAP S/4HANA back office, Workday/SuccessFactors for HR, ServiceNow for ITSM, and the billing/payments stack under PCI scope. These are not streaming workloads and must never share the customer-API segment — they live in the corporate and dedicated SAP segments, reached over ExpressRoute/Direct Connect from corp.luminamedia.net, with their own identity plane (workforce Entra ID + Okta, never CIAM). But they are deeply integrated with streaming: content royalties, per-title content cost, rights windows and residuals all flow between SAP and the delivery platform.
| Platform | Function | Hosting | Tier | Identity | Integration surface |
|---|---|---|---|---|---|
| SAP S/4HANA | Finance, royalties, content cost | Azure IaaS (RISE optional), M-series + HANA | Tier-2 | Entra + SAML, SSO | OData / IDoc / Event Mesh |
| SAP BW/4HANA | Analytics, revenue reporting | Azure IaaS, HANA | Tier-2 | Entra | Batch extract → data lake |
| SAP Ariba | Procurement (production spend) | SaaS | Tier-3 | Okta SAML | API + cXML |
| Workday / SuccessFactors | HR, workforce, org | SaaS | Tier-2 | Okta/Entra SCIM + SAML | SCIM provisioning, SAML SSO |
| ServiceNow | ITSM, CMDB, change, incident | SaaS (ITOM) | Tier-1 (ops) | Okta SAML | REST + Event Mgmt + CMDB |
| Billing / payments | Subscriptions, PSP | Azure, PCI segment | Tier-1 | Workforce + service | Tokenised, PE only |
SAP carries the most demanding IaaS footprint in the estate, and it is the one place where Ansible rather than Kubernetes owns the day-2 configuration. The HANA database tier runs on memory-optimised, HANA-certified VMs across availability zones, with HANA System Replication for HA and cross-region async replication East US 2 → West Europe for DR; the application tier scales horizontally behind an ASCS/ERS cluster.
| SAP layer | Azure shape | HA | DR | Backup |
|---|---|---|---|---|
| HANA DB | M-series (M128s/M208s_v2), Write Accelerator | HSR sync, zone-redundant | HSR async → West Europe | Azure Backup for HANA (Backint), 30-day |
| ASCS/ERS | E-series, Pacemaker cluster | Zone-redundant, fencing agent | Rebuild from IaC + Ansible | Config in Ansible, state replicated |
| App servers (PAS/AAS) | E-series scale set | ≥2 per zone | Redeploy + attach | Stateless, snapshot base image |
| Shared files (sapmnt) | Azure NetApp Files / AFS | Zone-redundant volume | Cross-region snapshot | ANF snapshot policy |
Integration is where SAP touches the streaming plane. The cadence and PII sensitivity dictate the mechanism — real-time entitlement-adjacent flows go over SAP Event Mesh or Event Hubs; heavy financial extracts go batch to the data lake; nothing exposes a public endpoint, all of it rides Private Link/PrivateLink.
| Flow | Source → Target | Mechanism | Cadence | PII / sensitivity |
|---|---|---|---|---|
| Content royalties | Streaming usage → SAP FI | Event Hubs → BTP Integration Suite | Hourly micro-batch | Aggregated, low |
| Per-title content cost | SAP → recommendation/finance | OData pull via APIM, PE | Daily | Internal |
| Rights & windows | Rights system → catalog/playout | Service Bus topic, idempotent | Near-real-time | Internal, high-impact |
| Subscriber revenue | Billing → SAP FI/CO | IDoc over PrivateLink | Daily close | PCI-adjacent (tokenised) |
| Residuals / talent | SAP HR/FI ↔ Workday | SCIM + secure file (SFTP/PE) | Weekly | PII, high |
ServiceNow is the connective tissue for operations, and it is wired in two directions. Inbound, the observability stack (next section) auto-opens incidents and change records; outbound, ServiceNow’s CMDB is populated by discovery so that every landed spoke and every vended service appears as a configuration item mapped to its owner and tier. This is what makes the event-day war room possible — a rebuffer alert resolves to a CI, an owner and a runbook, not a mystery.
| ServiceNow surface | Direction | Feed | Purpose |
|---|---|---|---|
| Event Management | Inbound | Dynatrace / Azure Monitor / SIEM webhooks | Alert → dedup → incident |
| CMDB | Inbound | Service Graph connectors + onboarding manifest | CI per service, owner, tier, dependencies |
| Change Management | Bi-directional | Pipeline change-gate ↔ CAB | Prod deploys + freeze windows |
| Incident / Major Incident | Bi-directional | NOC bridge, on-call paging | Event-day command record |
A representative outbound wiring — the CI/CD change gate calling ServiceNow before a Tier-1 prod apply, so that a change record (and a freeze check) precedes any production mutation:
# pipeline step: create + gate on a ServiceNow normal change before prod apply
- task: ServiceNow-CreateChange@2
inputs:
connection: 'snow-prod'
shortDescription: 'Deploy $(Build.Repository.Name) $(Build.BuildNumber) to prod'
category: 'Streaming Platform'
cmdbCi: '$(service.ciSysId)' # resolved from CMDB by service name
assignmentGroup: 'Live Delivery Ops'
changeType: 'normal'
# gate: pipeline BLOCKS if change not approved OR a live-event freeze is active
Terraform and Ansible multi-stage CI/CD
Everything above — spokes, guardrails, SAP IaaS — is delivered by one delivery system, not by hand. The design is deliberately conventional so it is boring and reliable: repo-per-module for versioned building blocks, repo-per-app for the compositions, a single YAML template project so there is one way to build, Artifacts feeds as the only trusted dependency source, and a strict dev → uat → staging → prod promotion. The patterns here are the ones documented in Reusable Terraform Modules at Scale: Repo-per-Module, Versioning & Composition and Centralized Azure Pipeline YAML Templates + Azure Artifacts Feeds; the module interface discipline is in Designing Composable Terraform Modules.
| Repo type | Contents | Versioning | Consumed by | Access |
|---|---|---|---|---|
| Module repos (repo-per-module) | One resource pattern (aks, apim, cosmos, cdn, spoke) | Semver git tag → private registry | App repos, pin exact version | Platform writes, all read |
| App repos (repo-per-app) | main.tf composing modules + app code |
Branch + build number | Its own pipeline | Owning team |
| Pipeline templates | Shared build/plan/gate/apply stages | Semver, extends reference |
Every app repo | Platform writes |
| Policy repo | OPA/Conftest + Sentinel policies | Semver | Gate stage | Security writes |
| Ansible repo | Roles + playbooks for IaaS day-2 | Semver, tagged | Apply stage (post-Terraform) | Platform + SAP Basis |
The YAML template project means an app repo carries almost no pipeline logic — it extends the central template and passes parameters. This is what keeps 100+ pipelines consistent; a change to the plan/gate/apply flow ships once. A repo-per-app pipeline in full:
# azure-pipelines.yml in the app repo — extends the ONE central template
resources:
repositories:
- repository: templates
type: git
name: platform/pipeline-templates
ref: refs/tags/v4.2.0 # pin the template version
extends:
template: terraform/multistage.yml@templates
parameters:
service: live-playback-api
tfWorkingDir: infra/
environments: # dev/uat auto, staging/prod gated
- { name: dev, subscription: lm-lz-live-dev, autoPromote: true }
- { name: uat, subscription: lm-lz-live-uat, autoPromote: true }
- { name: staging, subscription: lm-lz-live-stg, approval: sre }
- { name: prod, subscription: lm-lz-live-prod, approval: cab, freezeAware: true }
ansiblePlaybook: playbooks/encoder-node.yml # runs only where IaaS is provisioned
Inside the template, each environment runs the same shape: init (remote state per env), fmt/validate/tflint, a speculative plan posted to the PR, the policy gate on the plan JSON, a human approval for staging/prod, then apply. The promotion rules make each environment mean something specific.
| Env | Subscription / account | Promotion | Data | Purpose | Approver |
|---|---|---|---|---|---|
| dev | lm-lz-*-dev |
Auto on green | Synthetic | Fast feedback, module wiring | None |
| uat | lm-lz-*-uat |
Auto on green | Masked sample | Integration, contract tests | None |
| staging | lm-lz-*-stg |
Manual | Prod-mirror scale | Load/soak at event-scale slice | SRE |
| prod | lm-lz-*-prod |
Manual + change | Live | Serve viewers | CAB, freeze-aware |
Dependencies never resolve from the public internet. Azure Artifacts (and CodeArtifact on the AWS side) front every feed — the Terraform private module registry, npm, NuGet, Maven and the base-image feed in ACR/ECR. An upstream-only dependency is both a supply-chain risk and an availability risk you cannot afford mid-event.
| Feed | Azure | AWS | Holds | Upstream policy |
|---|---|---|---|---|
| Terraform modules | Azure Artifacts / TFC registry | S3 module registry / TFC | Versioned modules | Publish-only, no proxy |
| npm / NuGet / Maven | Azure Artifacts | CodeArtifact | App dependencies | Proxy + retention + scan |
| Container base images | ACR | ECR | Distroless bases | Curated, signed, scanned |
| Runner images | Shared image gallery | AMI / CodeBuild image | Agent images | Platform-built |
Runners are self-hosted in the management subscription — VMSS agents for Azure DevOps (or Actions runners on AKS), CodeBuild-in-VPC on AWS — so that apply reaches private endpoints and egresses through the firewall, never over a public agent. Ansible picks up where Terraform stops: Terraform builds the VM, Ansible configures the OS. This seam matters most for the IaaS-heavy media tier — encoder/transcode nodes, playout automation, the on-prem MOC contribution agents in Los Angeles and London, and SAP.
| Ansible play | Target | Trigger | What it does |
|---|---|---|---|
encoder-node.yml |
Transcode/encode VMs | Post-Terraform apply | SRT/Zixi tuning, GPU driver, agent, CIS hardening |
playout-node.yml |
Linear playout servers | Provision + drift check | Playout config, SCTE-35 signalling, NTP/PTP |
moc-contribution.yml |
On-prem MOC (LA/London) | Scheduled + on-change | Contribution encoders, monitoring agents |
sap-app-tier.yml |
SAP PAS/AAS | Provision + patch window | Kernel params, SAP host agent, patch |
baseline-harden.yml |
All IaaS | Nightly | CIS benchmark, EDR (CrowdStrike), patch state |
A representative play — the encoder node, hardened and tuned from the same pipeline that provisioned it:
# playbooks/encoder-node.yml — Ansible owns the OS; Terraform built the VM
- hosts: encoders
become: true
roles:
- role: cis-hardening # CIS Level 1, no interactive root
- role: crowdstrike-falcon # EDR sensor + sensor tags = tier/segment
- role: srt-contribution # SRT latency, overhead-bw, passphrase from Key Vault
vars:
srt_latency_ms: 120
srt_overhead_bw: 25
- role: otel-agent # node metrics + logs → Collector
tasks:
- name: Assert encoder reachable on contribution port
ansible.builtin.wait_for: { port: 9000, timeout: 30 }
Repos, gates and promotion, in one path — the multi-stage flow and its six controls:
The branching and multi-environment mechanics generalise directly to GitHub, Bitbucket and AWS-native tooling — see The Same Enterprise CI/CD Platform on GitHub, Bitbucket & AWS and the Enterprise Branching Strategy + Multi-Stage CI/CD for the blue-green slot detail.
DevSecOps software supply chain
A pipeline that only builds is negligent at this scale; the pipeline is where security and quality are enforced, closed. Every build clears a fixed gate set before it can ever serve a viewer, and the non-negotiable rule is that a gate either fails the build or it is not a gate — an informational annotation nobody actions is a vulnerability with extra steps. The shift-left security and testing patterns here are covered in depth in Shift-Left Security, Testing, Observability — and Mobile + Database Delivery and Shift-Left Testing and Quality Gates in CI/CD.
| Security gate | Tool (example) | Stage | Blocking threshold |
|---|---|---|---|
| Secret scan | gitleaks / TruffleHog | Pre-merge (diff + history) | Any verified secret → fail |
| SAST | SonarQube + Semgrep | On PR | New High/Critical or quality gate fail |
| SCA | Snyk / OWASP Dependency-Check | On PR + nightly | Reachable High/Critical CVE, copyleft licence |
| IaC scan | checkov / tfsec / KICS | On plan | Public exposure, missing encryption/tags |
| Container scan | Trivy / Wiz | Post-build | Fixable High/Critical in image |
| DAST | OWASP ZAP | Deployed to uat | New High finding on auth/API surface |
| Pipeline integrity | SBOM + cosign + SLSA | Build + admission | Unsigned or attestation missing → reject |
Security gates prove the code is safe; functional gates prove it works — and for a streaming platform, “works” means “holds at 45M concurrent.” Load and soak are not optional smoke tests; a Tier-1 playback API that passes every unit test and folds at peak has failed viewers, not CI.
| Functional gate | Tool | Stage | Threshold |
|---|---|---|---|
| Unit | Jest / xUnit / JUnit | On PR | Coverage floor, all pass |
| API / contract | Pact / Schemathesis | uat | No breaking contract change |
| UI / E2E | Playwright | uat/staging | Critical journeys green (login → play) |
| Load / soak | k6 / Locust | staging | p95 latency + error budget at event-scale slice |
| Chaos | Chaos Studio / FIS | staging (scheduled) | Survives AZ + dependency failure |
The three practices that turn “we think this image is ours” into “the cluster proves it” are SBOM, signing and admission. Every image gets a CycloneDX SBOM and an SLSA provenance attestation; cosign signs it keyless using the pipeline’s OIDC identity (no long-lived key to leak); and the cluster admits only signed, attested images.
| Integrity control | Tool | Produces / enforces | Payoff |
|---|---|---|---|
| SBOM | Syft → CycloneDX | Component inventory per image | Answer “are we exposed to CVE-X?” in minutes |
| Sign | cosign (keyless OIDC) | Image + attestation signature | Provenance anchor, no key to steal |
| Provenance | SLSA generator | Build metadata attestation | Prove how + where it was built |
| Admission | Kyverno / Gatekeeper / Binary Auth | Reject unsigned/unscanned | The gate that makes all others real |
A compact build-and-sign job showing the chain — build, SBOM, sign, verify, all failing closed:
# .github/workflows/build.yml — supply-chain integrity, keyless
permissions: { id-token: write, contents: read, packages: write }
steps:
- uses: docker/build-push-action@v6
with: { push: true, tags: ${{ env.IMG }}, provenance: true } # SLSA provenance
- name: SBOM
run: syft ${{ env.IMG }} -o cyclonedx-json > sbom.json
- name: Sign (keyless, OIDC — no stored key)
run: cosign sign --yes ${{ env.IMG }}
- name: Attest SBOM
run: cosign attest --yes --predicate sbom.json --type cyclonedx ${{ env.IMG }}
- name: Verify BEFORE it can deploy (fails closed)
run: |
cosign verify \
--certificate-identity-regexp '^https://github.com/lumina/.+$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
${{ env.IMG }}
Two surfaces need bespoke handling. Database schema-as-code removes the last place humans run ad-hoc SQL against prod: migrations are versioned, gated, and use expand/contract so a rollback never strands the app. Mobile and TV app distribution is a release channel CI must own end-to-end, because a bad player build reaches millions of devices and cannot be hot-fixed like a server.
| DB schema-as-code | Detail |
|---|---|
| Tooling | Flyway / Liquibase, migrations in the app repo |
| Gate | Migration dry-run + lint on PR; forbidden DROP without approval |
| Strategy | Expand → migrate → contract (backward-compatible) |
| Rollback | Down-migration tested in uat; never destructive in one step |
| Client target | Store / channel | Automation | Test track |
|---|---|---|---|
| iOS / tvOS | App Store Connect | Fastlane + TestFlight | TestFlight beta ring |
| Android / Android TV | Play Console | Fastlane + Firebase App Distribution | Internal → closed → open |
| Roku | Roku channel store | packaged build + API | Beta channel |
| Tizen / webOS | Samsung / LG portals | vendor CLI in pipeline | Vendor test devices |
The full path from commit to admission, and the six gates that must fail closed:
Secret handling across every one of these stages follows the pipeline-secrets discipline in CI/CD Secrets and Credential Management — federated identity, short-lived tokens, nothing stored in a variable group.
Observability, QoE and event-day operations
Two truths make streaming observability different from any other platform. First, infra health and viewer experience are different signals — every pod can be green while video-start-time climbs past 2 s and rebuffer past 0.4%, and only a client-side QoE beacon measures the thing viewers actually feel. Second, the volume is extreme: a live final emits tens of millions of QoE events per second. The design ingests both planes, correlates them on one trace context, and feeds one pane plus the SIEM and ServiceNow. The collector patterns are those in Building Production OpenTelemetry Collector Pipelines; the SLO mechanics in SLOs and Error Budgets in Practice.
| Signal source | Type | Pipeline | Store | Consumer |
|---|---|---|---|---|
| App SDK | Traces, metrics, logs | OTel SDK → Collector (tail-sample) | Tempo / Mimir / Loki, Monitor | Dashboards, APM |
| Player QoE | RUM beacons (VST, rebuffer) | Beacon → Event Hubs / Kinesis | ADX (hot) → S3/Blob (cold) | QoE dashboards, A/B |
| CDN + edge | Access logs, synthetics | Log pull + synthetic probes | ADX / Athena | Multi-CDN steering, hit-ratio |
| Infra | Metrics, k8s events | OTel / Prometheus | Mimir / Monitor | SRE dashboards, autoscale |
| APM | Deep transaction traces | Dynatrace OneAgent | Dynatrace | Root-cause, service flow |
The QoE SLIs are the contract with the audience. They are watched directly, with burn-rate alerting, and they lead every incident — an alarm on infra is a lagging indicator next to a concurrency cliff.
| SLI | Target | Source | Alert trigger |
|---|---|---|---|
| Video-start-time (VST) | p95 < 2 s | Player beacon | p95 > 2 s sustained 5 min |
| Rebuffer ratio | < 0.4% | Player beacon | > 0.4% by region/CDN |
| Live latency (LL-CMAF) | < 8 s | Player + origin | > 8 s glass-to-glass |
| Playback-failure | < 0.5% | Player beacon | Multi-window burn-rate |
| Availability (Tier-1) | 99.99% | Synthetic + API | Fast + slow burn |
A trimmed OTel Collector config for the media pipeline — tail sampling keeps the slow and errored traces and drops the boring majority, and one trace context propagates edge → API → DRM → origin so a rebuffer complaint resolves to a hop:
# otel-collector: keep what matters, survive the spike
receivers:
otlp: { protocols: { grpc: {}, http: {} } }
processors:
tail_sampling:
decision_wait: 10s
policies:
- { name: errors, type: status_code, status_code: { status_codes: [ERROR] } }
- { name: slow, type: latency, latency: { threshold_ms: 800 } }
- { name: baseline, type: probabilistic, probabilistic: { sampling_percentage: 1 } }
batch: { send_batch_size: 8192 }
exporters:
otlphttp/tempo: { endpoint: https://tempo.lumina.internal }
prometheusremotewrite/mimir: { endpoint: https://mimir.lumina.internal/api/v1/push }
service:
pipelines:
traces: { receivers: [otlp], processors: [tail_sampling, batch], exporters: [otlphttp/tempo] }
metrics: { receivers: [otlp], processors: [batch], exporters: [prometheusremotewrite/mimir] }
Dashboards are role-specific; the concurrency dashboard is the heartbeat the war room watches — a cliff in viewers-now with flat infra is a delivery or DRM failure the audience already feels.
| Dashboard | Audience | Key panels | Refresh |
|---|---|---|---|
| Concurrency | War room / exec | Viewers now by title/region/CDN | 10 s |
| QoE | Delivery / SRE | VST, rebuffer, failure by CDN/device | 30 s |
| Multi-CDN steering | Delivery | Hit-ratio, RTT, cost, steer split | 1 min |
| Origin / encode | Media ops | Origin health, package errors, SCTE-35 | 30 s |
| DRM / entitlement | Security / delivery | License req/s, denials, concurrency fraud | 30 s |
CDN and edge monitoring close the loop between observation and action. The multi-CDN steering decision — which of CDN-A/B/C a player is sent to — is not static; it is driven by the same telemetry, blending real-user QoE (measured rebuffer and RTT per CDN per region) with synthetic probes and per-GB cost. When RUM shows one CDN’s rebuffer climbing in eu-west, the steering service shifts that region’s split before the war room has to intervene, and the change is visible on the steering dashboard within a minute. This is the difference between a monitored outage and a mitigated one: the observability plane does not just alert, it feeds the control plane that reroutes viewers.
The alert that leads the incident is a multi-window burn-rate on the playback-failure SLO — fast burn pages immediately, slow burn opens a ticket:
# playback-failure SLO = 99.5%; page on fast burn (1h & 5m both hot)
(
sum(rate(playback_requests_total{result="fail"}[5m]))
/ sum(rate(playback_requests_total[5m])) > (14.4 * 0.005)
)
and
(
sum(rate(playback_requests_total{result="fail"}[1h]))
/ sum(rate(playback_requests_total[1h])) > (14.4 * 0.005)
)
For a premium live final, none of this is watched ad-hoc — Lumina runs a pre-staffed NOC / war room with a defined operating model. The mistake teams make is assembling the bridge at kickoff; by then it is too late. Roles are named, the timeline is rehearsed, and a change freeze is in force.
| War-room role | Responsibility | Escalation |
|---|---|---|
| Incident Commander | Owns the call, single decision-maker | VP Engineering |
| Comms lead | Status page, exec + partner updates | Comms / PR |
| Delivery / CDN ops | Multi-CDN steer, origin shield, cache | CDN TAMs on standby |
| DRM / entitlement ops | License service, concurrency, fraud | Security on-call |
| Platform / API ops | Playback API, autoscale, CIAM | Cloud on-call |
| Scribe | Timeline, decisions, actions → ServiceNow | — |
| Phase | Window | Actions | Owner |
|---|---|---|---|
| Prepare | T-72h → T-24h | Capacity pre-scale, cache warm, CDN commit, freeze on | Platform + Delivery |
| Rehearse | T-24h → T-2h | Failover drill, runbook walk-through, dashboards up | IC + all ops |
| Standby | T-2h → T-0 | War room live, ingest pre-warmed, contribution dual-path verified | All |
| Live | T-0 → end | Watch concurrency + QoE, execute runbooks, hold freeze | IC drives |
| Post | +0 → +72h | Scale down, blameless review, action items | SRE + IC |
The rehearsed live runbooks turn a viewer-visible symptom into a first action in seconds, not a debate:
| Symptom | Leading signal | First action | Escalate if |
|---|---|---|---|
| Rebuffer spike | QoE rebuffer > 0.4% one CDN | Steer traffic off the CDN (client SDK + DNS) | Multi-CDN all degraded |
| CDN brownout | Hit-ratio drop, RTT climb | Shift to shield origin + alternate CDN | Origin CPU/egress saturating |
| DRM license surge/fail | License denials climb | Scale license service, check key/cert expiry | Entitlement store latency |
| Origin failover | Origin health red | Promote standby origin region, verify manifests | Both regions impacted |
| Concurrency ceiling | Autoscale at max, latency up | Pre-approved burst capacity, shed non-Tier-1 | CIAM token service throttling |
The whole loop — signals in, correlated in the pipeline, out to dashboards, SIEM/SOC and ServiceNow, and up into the event-day NOC:
The on-call and paging discipline behind the NOC — routing, escalation and actionable runbooks — is the practice described in Building an On-Call Practice: PagerDuty Escalation, Alert Routing, and Actionable Runbooks, extended here with a pre-staffed command model for premium live.
Cost model and TCO
A streaming platform is not a cost structure you can reason about by counting servers. Lumina Media’s bill is dominated by bytes leaving the edge, not by compute running in a region. At 120M subscribers, a peak of 45M concurrent streams during a live final, and a blended average bitrate of roughly 3.2 Mbps, the platform delivers on the order of 12,000 PB (12 EB) per month — and every one of those bytes has a per-GB price at the CDN. That single fact reorders the entire TCO: egress/CDN delivery, encoding, and storage together are ~79% of run-rate, while the app platform that everyone spends design time on is a distant fourth. The cost-engineering job is therefore to make each delivered gigabyte cheaper — steering, per-title encoding, cache-hit-ratio — and to keep the 400 PB archive off expensive tiers, not to right-size a node pool.
The cost model has ten domains. The table names each, the unit that drives it, and the lever that moves it — read the “lever” column as the FinOps backlog:
| Cost domain | Primary driver | Unit economics | Dominant lever |
|---|---|---|---|
| Multi-CDN delivery | Delivered GB × per-GB rate | ~12,000 PB/mo × blended $/GB | Steering to cheapest-that-meets-QoE; committed volume |
| Cloud origin egress | Origin-fill GB (1 − cache-hit-ratio) | 2% of delivery via origin shield | Cache-hit-ratio; origin shield; private interconnect |
| Per-tier compute | Concurrency × service fan-out | AKS/EKS + API + DB, 6 regions A/A | Savings Plans / reservations; autoscale to concurrency |
| Encoding / transcode | New content-hours + live channels | Live encode fleet + VOD per-title | Per-title encoding; spot for VOD; encode-once |
| Storage (400 PB) | Library size × tier price | Hot/Cool/Archive lifecycle | Aggressive tiering; single-copy archive |
| CIAM | Monthly-active identities | B2C/Cognito MAU + token service | Refresh-token TTL; cache token validation |
| Analytics / QoE ingest | Heartbeat events/sec | Event Hubs/Kinesis + hot/cold stores | Sampling cold path; TTL on hot store |
| Networking | ExpressRoute/DX + inter-region GB | Circuits + cross-region replication | Region affinity; compress replication |
| Security tooling | Log-ingest GB + endpoints | Sentinel/GuardDuty ingest + HSM ops | Ingestion tiering; archive cold logs |
| Licensing / SaaS | Seats + volume tiers | Wiz/Falcon/Dynatrace/QoE/DRM/ADS | Consolidate tools; negotiate at renewal |
The delivery model, worked. Delivery is where the money is, so it gets a real per-GB calculation rather than a round number. Lumina runs three commercial CDNs with a client-side selection SDK plus DNS steering; the steering policy routes each request to the cheapest CDN that still meets the QoE floor (VST, rebuffer, throughput), spilling to the pricier performance CDN only when a POP is degraded or during a launch spike. The blended effective rate lands near $0.00228/GB — far below retail because volume is committed. Origin fill is held to ~2% of delivered bytes by origin shield and per-asset cache keys, so cloud egress is a rounding error next to CDN spend:
| Delivery path | Share of 12,000 PB | Rate ($/GB) | Monthly GB | Monthly USD |
|---|---|---|---|---|
| CDN-A (volume commit, cacheable segments) | 60% | $0.0018 | 7.20 B | $12.96M |
| CDN-B (performance / second source) | 30% | $0.0025 | 3.60 B | $9.00M |
| CDN-C (premium geos / overflow) | 10% | $0.0045 | 1.20 B | $5.40M |
| Blended CDN delivery | 100% | $0.00228 | 12.00 B | $27.36M |
| Cloud origin egress (2% fill @ private rate) | — | $0.015 | 0.24 B | $3.60M |
| Total delivery | — | — | — | $30.96M |
The full run-rate. Rolling delivery together with the other nine domains gives the steady-state monthly bill. USD is the negotiating currency; INR is shown at ₹86/USD for the finance plan (Lumina reports in both). The right-hand column is the share of total — the point being that content (deliver + encode + store) is four-fifths of the bill:
| Domain | USD / month | INR / month | Share |
|---|---|---|---|
| Multi-CDN delivery | $27.36M | ₹235.3 Cr | 61.6% |
| Cloud origin egress | $3.60M | ₹31.0 Cr | 8.1% |
| Per-tier compute (platform, 6 regions A/A) | $4.50M | ₹38.7 Cr | 10.1% |
| Encoding / transcode (live + VOD per-title) | $2.20M | ₹18.9 Cr | 5.0% |
| Storage (400 PB tiered + working + replication) | $1.80M | ₹15.5 Cr | 4.1% |
| Analytics / QoE event ingest + stores | $1.20M | ₹10.3 Cr | 2.7% |
| Licensing / SaaS (Wiz·Falcon·Dynatrace·QoE·DRM·ADS) | $1.00M | ₹8.6 Cr | 2.3% |
| Networking (ER/DX·VWAN/TGW·PE·inter-region) | $0.90M | ₹7.7 Cr | 2.0% |
| Platform / management (backup·DR reserve·non-prod) | $0.70M | ₹6.0 Cr | 1.6% |
| CIAM (B2C/Cognito MAU + token service) | $0.60M | ₹5.2 Cr | 1.4% |
| Security tooling (Sentinel/GuardDuty ingest·HSM) | $0.50M | ₹4.3 Cr | 1.1% |
| Total run-rate | $44.36M | ₹381.5 Cr | 100% |
| Annualised | $532.3M | ₹4,578 Cr | — |
| Per subscriber (infra only) | $0.37/sub/mo | ₹31.8/sub/mo | — |
At $0.37/subscriber/month of pure infrastructure, delivery is a single-digit percentage of ARPU — which is the number that makes the whole D2C model work, and the number a single-CDN retail contract would quietly destroy.
Storage tiering — why 400 PB does not cost a fortune. If the entire archive sat on hot blob/S3-Standard it would be $7.2M/month. Lifecycle rules push the long tail down two tiers, and the effective storage bill is a quarter of that. The lifecycle policy is a few lines of JSON applied at the container level and left to run:
| Tier | Share of 400 PB | PB | Rate ($/GB/mo) | Monthly USD |
|---|---|---|---|---|
| Hot (origin-active catalog, live windows) | 5% | 20 | $0.018 | $0.360M |
| Cool (mid-tail, recent seasons) | 15% | 60 | $0.010 | $0.600M |
| Archive (deep library, masters, compliance) | 80% | 320 | $0.00099 | $0.317M |
| Working / mezzanine + multi-region hot copy | — | — | — | $0.523M |
| Total storage | 100% | 400 | — | $1.80M |
// Azure Storage lifecycle — same shape as an S3 lifecycle rule on the AWS origin
{ "rules": [{
"name": "vod-library-tiering", "enabled": true,
"type": "Lifecycle",
"definition": {
"filters": { "blobTypes": ["blockBlob"], "prefixMatch": ["vod/masters/", "vod/mezzanine/"] },
"actions": { "baseBlob": {
"tierToCool": { "daysAfterLastAccessTimeGreaterThan": 30 },
"tierToArchive": { "daysAfterLastAccessTimeGreaterThan": 120 },
"enableAutoTierToHotFromCool": true
} } } }] }
Cost-control levers, quantified. The un-optimised version of this platform — retail CDN pricing, all-hot storage, on-demand compute, naive caching — runs near $96.8M/month. The optimised run-rate is $44.4M. The gap is the FinOps programme, and every lever is measurable:
| Lever | Mechanism | Baseline | Optimised | Monthly saving |
|---|---|---|---|---|
| Committed / volume CDN pricing | 12-EB commit vs retail $0.006/GB | $72.0M | $27.4M | $44.6M |
| Cache-hit-ratio + origin shield | Origin fill 10% → 2% (per-asset keys) | $18.0M | $3.6M | $14.4M |
| Storage lifecycle tiering | 400 PB hot → hot/cool/archive | $7.2M | $1.8M | $5.4M |
| Per-title / per-scene encoding | −20% delivered bitrate → −20% volume | +$5.5M | — | $5.5M |
| Savings Plans / Reserved compute | 1–3 yr commit on Tier-0/1 baseline | $6.9M | $4.5M | $2.4M |
| Multi-CDN steering (cheapest-meets-QoE) | Shift ~700 PB from CDN-C/B → CDN-A | +$1.6M | — | $1.6M |
| Programme total | — | $96.8M | $44.4M | ≈ $52.4M (54%) |
Three-year TCO. The commitment posture changes across the programme: Year 1 is build-heavy and mostly on-demand; Years 2–3 lock in Savings Plans and the full CDN commit. Delivery grows with subscriber and viewing-hour growth (~12% YoY assumed), partly offset by deeper per-title encoding and steering maturity:
| Fiscal year | Run-rate profile | Compute posture | Annual USD | Annual INR |
|---|---|---|---|---|
| Year 1 (build + ramp) | 8 mo partial + on-demand | On-demand + short SP | $486M | ₹4,180 Cr |
| Year 2 (steady + commit) | Full run-rate, committed CDN | 1-yr Savings Plans | $532M | ₹4,578 Cr |
| Year 3 (scale + optimise) | +12% volume, deeper encoding | 3-yr SP + reserved DB | $561M | ₹4,825 Cr |
| 3-year TCO | — | — | $1,579M | ₹13,583 Cr |
# Lock baseline Tier-0/1 compute (steady, always-on) onto commitments
az billing benefits savings-plan-order purchase \
--sku "Compute_Savings_Plan" --term P3Y --billing-plan Monthly \
--commitment-amount 55000 --commitment-currency USD
aws savingsplans create-savings-plan \
--savings-plan-offering-id <compute-3yr> --commitment "55000" --upfront-payment-amount "0"
Bill of materials
The bill of materials is the design made purchasable — every plane mapped to the exact service, SKU and quantity a platform team can order and stand up. It spans four supply lines: Azure, AWS, the two on-prem broadcast facilities, and the third-party CDN/DRM/QoE/security tooling no cloud provides at the quality premium content demands. Azure first — the workforce-identity and one CIAM/edge-API home:
| Azure service | SKU / tier | Qty / scale | Purpose | Tier |
|---|---|---|---|---|
| Management Groups + Subscriptions | EA | lm root + ~24 subs |
Governance scaffold (lm-plat-*, lm-lz-*) |
T0 |
| Entra ID | P2 | ~9,000 workforce | Workforce SSO, CA, PIM | T0 |
| Azure AD B2C | Go-tiered | ~60M MAU (region split) | CIAM plane (social + email, JWT) | T1 |
| ExpressRoute | 10 Gbps × region | 3 circuits | On-prem ↔ Azure private path | T0 |
| Virtual WAN + Azure Firewall | Premium | 3 hubs | Hub-spoke, IDPS, egress control | T0 |
| Front Door + WAF | Premium | 1 global | Edge WAF/bot for API + DRM license | T1 |
| API Management | Premium, multi-region | 3 regions | Playback / entitlement / token APIs | T1 |
| AKS | Standard, GPU + CPU pools | 3 regions A/A | Playback, entitlement, SSAI, encode | T1 |
| Azure Functions | Elastic Premium | per region | Event glue, manifest personalisation | T2 |
| Cosmos DB | Multi-region write | 3 regions | Entitlement / session / concurrency | T1 |
| Blob + ADLS Gen2 | Hot/Cool/Archive | ~200 PB (Azure share) | VOD masters, mezzanine, analytics lake | T1–T2 |
| Event Hubs | Dedicated | 3 regions | QoE telemetry ingest | T2 |
| Key Vault + Managed HSM | Managed HSM | FIPS 140-2 L3 | DRM keys, token signing | T0 |
| Microsoft Sentinel + Defender | — | estate-wide | SIEM + CSPM/CWP | T0 |
| Recovery Services Vaults | GRS | per region | Backup / DR | T1 |
The AWS supply line carries the media-processing heavy lifting (AWS Elemental), the second CIAM plane, and CloudFront as one of the three CDNs:
| AWS service | SKU / tier | Qty / scale | Purpose | Tier |
|---|---|---|---|---|
| Organizations + Control Tower | — | Root→OU tree | Multi-account guardrails | T0 |
| IAM Identity Center | — | workforce fed. | AWS access via Entra/Okta | T0 |
| Amazon Cognito | — | ~60M MAU | CIAM plane (second cloud) | T1 |
| Route 53 + CloudFront + Shield Adv | — | global | DNS steering, CDN-C, DDoS | T1 |
| Transit Gateway + Direct Connect | 10 Gbps | 3 regions | Backbone + on-prem private path | T0 |
| EKS + Fargate | GPU + Graviton | 3 regions A/A | Streaming platform, reco inference | T1 |
| Elemental MediaLive | HD/UHD channels | ~200 live | Live encode (ABR ladder) | T1 |
| Elemental MediaConvert | on-demand | VOD farm | Transcode-once, per-title | T2 |
| Elemental MediaPackage | LL-CMAF | 3 regions | JIT packaging, origin, DVR | T1 |
| Elemental MediaConnect | SRT/Zixi/RIST | dual feeds | Contribution transport | T1 |
| Elemental MediaTailor | — | live + VOD | SSAI ad insertion | T1 |
| SPEKE | — | key provider | Multi-DRM key exchange | T1 |
| S3 (Std/IA/Glacier/Deep Archive) | tiered | ~200 PB (AWS share) | Origin + archive | T1–T2 |
| DynamoDB (Global Tables) | on-demand | 3 regions | Entitlement / session (2nd plane) | T1 |
| Kinesis Data Streams + Firehose | — | 3 regions | QoE telemetry (2nd plane) | T2 |
| KMS + CloudHSM | — | FIPS L3 | Key management | T0 |
On-prem and third-party close the BoM. The two Media Operations Centres carry contribution, playout and near-line archive; the SaaS/tooling line carries everything the clouds do not — multi-CDN, multi-DRM license servers, QoE analytics, and the content-security stack that TPN/MovieLabs demands:
| Supply line | Item | Scale / spec | Purpose |
|---|---|---|---|
| On-prem (LA + London MOC) | Contribution encoders | SRT/RIST/Zixi, dual-path | Live signal → cloud |
| On-prem | Playout automation + master control | SMPTE 2110 / SDI | Linear channels, blackout |
| On-prem | Near-line archive | LTO + object, PB-scale | Masters, DR copy |
| On-prem | AD DS corp.luminamedia.net + SAP |
HA pair + ECC/S4 | Workforce root, back office |
| Third-party | CDN-A / CDN-B / CDN-C | 3 commercial CDNs | Multi-CDN delivery |
| Third-party | CDN steering SDK | client + DNS | Cheapest-meets-QoE routing |
| Third-party | Multi-DRM license service | Widevine + FairPlay + PlayReady | CENC license issuance |
| Third-party | Forensic watermarking | A/B server-side + session | MovieLabs / TPN leak trace |
| Third-party | QoE analytics (Conviva-class) | RUM at 45M concurrent | VST/rebuffer/steering data |
| Third-party | Wiz + CrowdStrike Falcon | CSPM + EDR/CWP | Posture + workload protection |
| Third-party | Dynatrace + ServiceNow | APM + ITSM | Observability + change/incident |
| Third-party | Ad-decision server / SSP | VAST/VMAP | Ad selection for SSAI |
Operating model and RACI
The platform is run by a thin set of platform-owning teams that vend paved-road landing zones, and workload-owning streaming/live/data/ad teams that consume them — the same split that lets a large estate move without a central bottleneck. The Architecture Review Board (ARB) owns standards and exceptions but does not gate day-to-day deploys; policy-as-code does that continuously. Nine functions own the estate:
| Function | Owns | Vends / consumes | Primary planes |
|---|---|---|---|
| Platform Engineering | Landing zones, IaC, golden paths | Vends | Governance, connectivity, CI/CD |
| Security & Compliance | Guardrails, SIEM, IR, TPN/PCI/GDPR | Vends | Identity floor, CSPM/CWP, audit |
| Network & CDN Engineering | Backbone, edge, multi-CDN steering | Vends | Connectivity, delivery |
| Streaming Platform | Encode/package/origin/DRM/playback | Consumes | Media-processing, customer-API |
| Live Operations / Broadcast | MOC, contribution, playout, events | Consumes | Live path, event control |
| Data & Personalization | Analytics, QoE, reco, A/B, DSAR | Consumes | Analytics, consent |
| Ad-Tech | SSAI, ADS, VAST, ad reporting | Consumes | Ad path (separated) |
| Cloud Operations / SRE | Run, on-call, capacity, FinOps | Both | All (run-state) |
| Architecture Review Board | ADRs, standards, exceptions | Governs | Cross-cutting |
The RACI makes the ownership real for the activities that actually cause 2 a.m. pages and audit findings. R = does the work, A = single accountable owner, C = consulted, I = informed:
| Activity | Plat | Sec | Net/CDN | Stream | Live | Data | Ad | SRE | ARB |
|---|---|---|---|---|---|---|---|---|---|
| Landing-zone / IaC guardrail change | A/R | C | C | I | I | I | I | C | C |
| Workforce identity (Entra/Okta/PIM) | C | A/R | I | I | I | I | I | C | I |
| CIAM (B2C/Cognito) config + scale | C | A | C | R | I | C | I | C | I |
| Multi-CDN steering policy | I | C | A/R | C | C | C | I | C | I |
| Encode / packaging pipeline change | I | I | C | A/R | C | I | I | C | I |
| DRM / key management + rotation | I | C | I | A/R | I | I | I | C | C |
| Live event runbook + execution | I | C | C | C | A/R | I | C | C | I |
| QoE / analytics pipeline | I | C | C | C | I | A/R | C | C | I |
| SSAI / ad configuration | I | C | I | C | C | C | A/R | C | I |
| Sev1 incident response | C | C | C | C | C | C | C | A/R | I |
| Tier-1 DR failover test | C | C | C | C | C | I | I | A/R | C |
| FinOps / cost optimisation | C | I | C | C | C | C | C | A/R | I |
| Compliance audit (PCI/TPN/GDPR) | C | A/R | C | C | C | C | C | C | C |
| ADR approval / exception grant | C | C | C | C | C | C | C | C | A/R |
Two operating regimes bind the model together. The severity/escalation ladder decides who wakes up and how fast; the change-freeze regime protects live events — the single largest reputational risk, because a failed final is on every screen at once:
| Regime | Trigger | Response | Owner |
|---|---|---|---|
| Sev1 (Tier-1 viewer-facing outage) | Playback-failure > 0.5% or region down | Bridge < 5 min, active-active shift, exec comms | SRE |
| Sev2 (degraded QoE, single POP/CDN) | Rebuffer > 0.4% or VST p95 > 2s in a geo | Steer away from POP/CDN, page owning team | Net/CDN |
| Sev3 (non-viewer, business) | Ad-reporting / back-office fault | Next-business-day, ticket | Owning team |
| Event freeze | 48h before → 6h after a Tier-1 live event | No prod change except approved break-glass | Live Ops + ARB |
| Peak freeze | Launch tent-pole / seasonal peak | Change-advisory board approval only | SRE + ARB |
Migration and onboarding waves
Delivery is sequenced into six waves over 24 months, each with a hard entry gate (what must be true to start) and a hard exit gate (the acceptance event that unlocks the next wave). Nothing lands on an un-baselined landing zone, nothing goes live without DRM, and no live event runs until it has been proven at concurrency. The waves and their gates:
| Wave | Window | Scope | Entry gate | Exit gate |
|---|---|---|---|---|
| W1 Foundation | M0–M4 | MG/OU tree, IPAM, VWAN/TGW/ER/DX, core security, CI/CD, policy | Funding + region selection | LZ conformance ≥ 98%, connectivity reachable, security baseline green |
| W2 Identity + CIAM | M4–M9 | Entra+Okta workforce, AD DS, B2C/Cognito, PE segmentation | W1 exit | Workforce SSO cutover; CIAM load-proven to 120M / 45M-concurrent auth |
| W3 Streaming core (VOD) | M9–M14 | Encode-once, JIT package, origin, playback/entitlement API, tiered storage | W2 exit | First title live; VST p95 < 2s + rebuffer < 0.4% on pilot; catalog sync |
| W4 DRM + CDN + Live | M14–M19 | CENC multi-DRM, SPEKE, signed edge auth, multi-CDN, contribution, LL-CMAF, SCTE-35 | W3 exit | DRM cutover; multi-CDN steering live (≥98% offload); live event proven |
| W5 Data + Ad-Tech | M19–M22 | QoE at scale, reco/personalisation, A/B, SSAI/VAST/VMAP, consent/DSAR | W4 exit | SSAI in prod (live+VOD); QoE dashboards; personalisation live; DSAR working |
| W6 Run + Optimise | M22–M24 | Active-active DR proof, FinOps, audits, legacy decommission | W5 exit | Tier-1 failover < 15m proven; PCI/TPN/GDPR passed; cost targets met; legacy off |
Each wave carries specific workloads at a defined resilience tier, which is what makes the RTO/RPO commitments testable rather than aspirational:
| Wave | Key workloads onboarded | Tier | RTO / RPO |
|---|---|---|---|
| W1 | Identity floor, network control, security tooling | T0 | ≤ 15m / ≈ 0 |
| W2 | Entitlement, CIAM auth, token service | T0–T1 | ≤ 15m / ≤ 1m |
| W3 | Playback API, origin, VOD packaging | T1 | ≤ 15m / ≤ 1m |
| W4 | DRM license, live-event control, SSAI-core, origin shield | T1 | ≤ 15m / ≤ 1m |
| W5 | QoE analytics, reco, ad-reporting | T2 | ≤ 4h / ≤ 1h |
| W6 | Optimisation, sandbox, decommission | T3 | ≤ 24h / best-effort |
Architecture decision records
Every pivotal choice in this design is captured as an ADR so a future engineer can see not just what was decided but why, and what was rejected. The index lists the accepted decisions; the pivotal three carry a scored options table underneath. All are Accepted unless noted:
| ADR | Decision | Status | Drives |
|---|---|---|---|
| ADR-001 | Workforce identity = Entra ID + Okta federation, on-prem AD DS as source | Accepted | SSO, CA, PIM; kept separate from CIAM |
| ADR-002 | CIAM = Azure AD B2C and Amazon Cognito (per-cloud), not a single IdP | Accepted | 120M-scale token service, no single point of failure |
| ADR-003 | Multi-CDN (three CDNs + steering), not single CDN | Accepted | Delivery resilience + cost steering + geo coverage |
| ADR-004 | CENC multi-DRM (Widevine + FairPlay + PlayReady, cenc + cbcs) | Accepted | Device coverage under one packaging + key flow |
| ADR-005 | Active-active Tier-1 across regions, not active-passive | Accepted | Viewers see no region outage; RPO ≤ 1m |
| ADR-006 | SSAI (server-side ad insertion), not client-side (CSAI) | Accepted | Ad-block resilience, seamless on TV/STB |
| ADR-007 | Event-day change-freeze regime around live finals | Accepted | Protect the highest-blast-radius moments |
| ADR-008 | Archive tiering (hot/cool/archive lifecycle) for 400 PB | Accepted | ~$5.4M/mo saving vs all-hot |
| ADR-009 | Encode-once + JIT packaging, not pre-packaged per-format | Accepted | Storage/agility over per-request compute |
| ADR-010 | LL-CMAF for live low-latency, not LL-HLS-only or WebRTC | Accepted | < 8s glass-to-glass at broadcast scale |
ADR-002 — CIAM platform (dual, per-cloud). Context: 120M identities, 45M-concurrent auth at a live final, and a hard requirement never to co-mingle consumer and workforce identity. Decision: run B2C and Cognito each as the CIAM authority in its own cloud, with a shared token contract, rather than forcing one IdP to front both clouds. Consequence: no single-vendor blast radius, per-region token issuance, at the cost of dual operational surface and a token-format reconciliation layer.
| Option | 120M scale | Multi-cloud resilience | Lock-in | Verdict |
|---|---|---|---|---|
| Single B2C (or single Cognito) | OK | Single-cloud dependency | High | Rejected |
| Third-party CIAM SaaS only | OK | Vendor-dependent | High | Rejected |
| B2C + Cognito, per-cloud | OK | No single point | Low | Accepted |
ADR-003 — Multi-CDN vs single. Context: one CDN is one outage and one price. Decision: three CDNs with RUM/synthetic-driven steering to the cheapest that meets the QoE floor, origin shield in front of cloud origin. Consequence: +$44.6M/mo saved vs retail single-CDN, delivery survives a whole-CDN failure, at the cost of steering-SDK complexity and per-CDN cache-policy management.
| Option | Resilience | Cost control | Complexity | Verdict |
|---|---|---|---|---|
| Single premium CDN | One-outage risk | Retail rates | Low | Rejected |
| Two CDNs, failover only | Better | Some | Medium | Considered |
| Three CDNs + active steering | Best | Best ($/GB) | High | Accepted |
ADR-005 — Active-active Tier-1. Context: a viewer must not see an outage when a region fails during a final. Decision: Tier-1 (entitlement, playback API, DRM license, origin, live-event control, CIAM auth) runs active-active across regions with global data replication (Cosmos multi-write / DynamoDB Global Tables), RPO ≤ 1m. Consequence: double the steady-state footprint and cross-region write reconciliation, bought in exchange for RTO ≤ 15m with no cold-start and no viewer-visible failover.
| Option | RTO | RPO | Run cost | Verdict |
|---|---|---|---|---|
| Active-passive (warm standby) | Minutes, visible | ≤ 5m | Lower | Rejected for T1 |
| Pilot-light | Tens of min | Higher | Lowest | Rejected for T1 |
| Active-active | ≤ 15m, invisible | ≤ 1m | Higher | Accepted (T1) |
Risks, assumptions, issues and dependencies
The RAID register is the honest ledger of what could derail delivery and what is being done about it. Risks are pre-mitigated with a named owner; assumptions are the load-bearing beliefs the plan rests on; issues are live problems; dependencies are the external gates. The top risks first, scored L(ikelihood)×I(mpact):
| # | Risk | L | I | Mitigation | Owner |
|---|---|---|---|---|---|
| R1 | Live-final concurrency exceeds tested ceiling (>45M) | M | H | Pre-scale + game-day load test to 1.5× peak; multi-CDN overflow to CDN-C | SRE |
| R2 | A whole CDN degrades during a Tier-1 event | M | H | Three-CDN steering, real-time RUM, instant weight shift | Net/CDN |
| R3 | DRM key/license service outage blocks playback | L | H | Multi-region SPEKE + HSM, license caching, break-glass | Streaming |
| R4 | CIAM credential-stuffing / token abuse at scale | H | M | Bot defence, rate-limit, anomaly detection, concurrency caps | Security |
| R5 | Egress spend overruns as viewing hours grow | H | M | Per-title encoding, CHR targets, committed pricing, budget alerts | SRE/FinOps |
| R6 | Content leak fails TPN/MovieLabs audit | L | H | Forensic watermarking, hardware DRM (HDCP), TPN controls | Security |
| R7 | Region failover misses RTO under real load | M | H | Quarterly active-active DR game-days with viewer-traffic replay | SRE |
The assumptions, issues and dependencies that condition the plan — each is tracked because a wrong assumption is a schedule slip waiting to happen:
| Type | Item | Detail | Status / owner |
|---|---|---|---|
| Assumption | Blended bitrate ~3.2 Mbps, ~12,000 PB/mo delivered | Drives the entire CDN cost model | Validate in W3 pilot / SRE |
| Assumption | CDN volume commit yields ~$0.00228/GB blended | Contract-dependent | Procurement |
| Assumption | 98% origin offload holds via shield + cache keys | Governs origin egress line | Net/CDN |
| Issue | Azure Media Services retired — no PaaS encoder on Azure | Encoding standardised on AWS Elemental + on-prem | Open / Streaming |
| Issue | Token-format reconciliation between B2C and Cognito | Needs shared claims contract | Open / Security |
| Dependency | ExpressRoute / Direct Connect circuit delivery | Gates W1 exit | Carrier / Net |
| Dependency | Widevine + PlayReady + FairPlay license agreements | Gates W4 DRM cutover | Legal / Streaming |
| Dependency | TPN site + cloud assessment | Gates premium-content go-live | Security / TPN |
| Dependency | Multi-CDN + steering-SDK contracts | Gates W4 delivery | Procurement |
Delivery roadmap and acceptance
The roadmap reads left to right as six waves over 24 months, each unlocked only by the prior wave’s exit gate. The diagram below is the one-page view every stakeholder shares: foundation and connectivity, then the two identity planes, the streaming core, the go-live wave that adds DRM, multi-CDN and live, and finally the data/ad and run/optimise waves that turn the platform into a steady, audited, cost-tuned operation.
The wave path with its milestone gates, left to right:
| Milestone | Month | Wave | Acceptance event |
|---|---|---|---|
| M1 LZ signed off | M4 | W1 | Conformance ≥ 98%, connectivity + security baseline green |
| M2 CIAM proven | M9 | W2 | Token service load-passed 120M / 45M-concurrent; SSO cutover |
| M3 First title | M14 | W3 | VOD pilot meets VST/rebuffer; catalog sync live |
| M4 DRM + CDN | M19 | W4 | CENC cutover; three-CDN steering; ≥98% offload |
| M5 Live proven | M19 | W4 | LL-CMAF < 8s; SCTE-35 + SSAI; freeze rehearsed |
| M6 Run-state | M24 | W6 | DR < 15m; PCI/TPN/GDPR passed; FinOps targets met |
Acceptance is defined against the proposal’s objectives, so “done” is measurable, not a matter of opinion. Each objective maps to a concrete test, the wave that proves it, and the evidence produced:
| # | Objective | Acceptance criterion (measurable) | Wave | Evidence |
|---|---|---|---|---|
| O1 | Viewers see no region outage | Tier-1 active-active failover RTO ≤ 15m, RPO ≤ 1m, no viewer-visible drop | W6 | DR game-day report |
| O2 | Meet QoE targets | VST p95 < 2s · rebuffer < 0.4% · live latency < 8s · playback-fail < 0.5% | W3–W4 | QoE dashboard export |
| O3 | Scale to 120M / 45M-concurrent | Load test sustains 1.5× peak; CIAM issues tokens at peak | W2–W4 | Load-test evidence |
| O4 | Protect premium content | CENC multi-DRM + HDCP + forensic watermark; TPN/MovieLabs passed | W4 | TPN assessment |
| O5 | Separate identity + comply | Workforce ≠ consumer plane; PCI-DSS + GDPR + CCPA controls evidenced | W2, W6 | Audit reports |
| O6 | Multi-cloud, no single dependency | Survive loss of one cloud region and one whole CDN with no outage | W4, W6 | Chaos/DR test |
| O7 | Monetise SVOD/AVOD/TVOD/FAST | SSAI live for linear + VOD; VAST/VMAP + ad-reporting reconciled | W5 | Ad-ops report |
| O8 | Cost efficiency | Blended ≤ $0.00228/GB; infra ≤ $0.37/sub/mo; Savings Plans applied | W6 | FinOps statement |
| O9 | Low-latency live with redundancy | Dual SRT/Zixi contribution; LL-CMAF < 8s at concurrency | W4 | Live event report |
| O10 | Consent-compliant analytics | QoE at 45M-concurrent; DSAR + consent honoured end-to-end | W5 | DSAR test log |
When all ten acceptance rows are green — and only then — the programme moves from delivery to steady-state run, and the operating model, RACI and FinOps levers above become the discipline that keeps 120M subscribers watching without ever seeing the machinery behind the glass.