1import type * as Effect from "effect/Effect";
2import type * as Stream from "effect/Stream";
3import {
4 consumeBusEvents,
5 type EventRecord,
6 type EventRouteProps,
7} from "../EventBridge/EventSource.ts";
8
9/**
10 * The AWS Health services that carry Shield Advanced DDoS attack events.
11 * Attacks against most resources surface as `SHIELD` events; attacks against
12 * Route 53 hosted zones surface as `ROUTE53` events.
13 */
14export type AttackEventService = "SHIELD" | "ROUTE53";
15
16/**
17 * The `detail` payload of an AWS Health event for a Shield Advanced DDoS
18 * attack. Only the commonly-matched fields are typed; the AWS Health schema
19 * grows over time.
20 */
21export interface AttackEventDetail {
22 /** The AWS Health event ARN. */
23 eventArn?: string;
24 /** The service the event belongs to (`SHIELD`, or `ROUTE53` for hosted-zone attacks). */
25 service?: string;
26 /** The event type code, e.g. `AWS_SHIELD_DDOS_ATTACK_DETECTED`. */
27 eventTypeCode?: string;
28 /** The event type category — Shield attack events are `issue`s. */
29 eventTypeCategory?: string;
30 /** When the attack event started (ISO timestamp). */
31 startTime?: string;
32 /** When the attack event ended, if it has (ISO timestamp). */
33 endTime?: string;
34 /** Human-readable descriptions of the event. */
35 eventDescription?: { language?: string; latestDescription?: string }[];
36 /** The attacked resources, when AWS Health can identify them. */
37 affectedEntities?: { entityValue?: string }[];
38 /** Additional AWS Health fields (the schema grows over time). */
39 [key: string]: unknown;
40}
41
42/** A Shield Advanced attack EventBridge event delivered to the handler. */
43export type AttackEvent = EventRecord<AttackEventDetail>;
44
45export interface AttackEventSourceProps extends EventRouteProps {
46 /**
47 * Logical id for the backing EventBridge rule.
48 * @default "ShieldAttacks"
49 */
50 id?: string;
51 /**
52 * Which AWS Health services to match. Shield Advanced reports attacks on
53 * most resource types under `SHIELD`; attacks on Route 53 hosted zones are
54 * reported under `ROUTE53`.
55 * @default ["SHIELD"]
56 */
57 services?: readonly AttackEventService[];
58 /**
59 * Restrict to specific AWS Health event type codes (matched against
60 * `detail.eventTypeCode`), e.g. `["AWS_SHIELD_DDOS_ATTACK_DETECTED"]`.
61 */
62 eventTypeCodes?: readonly string[];
63}
64
65/**
66 * Event source connecting Shield Advanced DDoS attack notifications to the
67 * hosting compute. When Shield Advanced detects an attack against a protected
68 * resource it posts an event to AWS Health, which delivers it to the
69 * account's default EventBridge bus (source `aws.health`, service `SHIELD` —
70 * or `ROUTE53` for hosted-zone attacks); this subscribes the host Function to
71 * those events so it can page, annotate dashboards, or trigger mitigations.
72 *
73 * Shield publishes through AWS Health automatically — no additional resource
74 * is created besides the EventBridge rule targeting the host, but events only
75 * fire for accounts with an active Shield Advanced subscription. Provide the
76 * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the
77 * Function effect.
78 *
79 * ### Consuming Attack Events
80 * **Example:** Page on Detected DDoS Attacks
81 * ```typescript
82 * import * as AWS from "alchemy/AWS";
83 *
84 * export default AlertFunction.make(
85 * { main: import.meta.url },
86 * Effect.gen(function* () {
87 * yield* AWS.Shield.consumeAttackEvents({}, (events) =>
88 * Stream.runForEach(events, (event) =>
89 * Effect.logError(
90 * `Shield: ${event.detail.eventTypeCode} affecting ${
91 * event.detail.affectedEntities?.[0]?.entityValue
92 * }`,
93 * ),
94 * ),
95 * );
96 * return {};
97 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
98 * );
99 * ```
100 */
101export const consumeAttackEvents = <StreamReq = never, Req = never>(
102 props: AttackEventSourceProps,
103 process: (
104 events: Stream.Stream<AttackEvent, never, StreamReq>,
105 ) => Effect.Effect<void, never, Req>,
106) =>
107 consumeBusEvents(
108 props.id ?? "ShieldAttacks",
109 {
110 source: ["aws.health"],
111 "detail-type": ["AWS Health Event"],
112 detail: {
113 service: [...(props.services ?? ["SHIELD"])],
114 eventTypeCategory: ["issue"],
115 ...(props.eventTypeCodes !== undefined
116 ? { eventTypeCode: [...props.eventTypeCodes] }
117 : {}),
118 },
119 },
120 { description: props.description, state: props.state },
121 process,
122 );