1import * as Lambda from "@/AWS/Lambda";
2import * as NetworkFirewall from "@/AWS/NetworkFirewall";
3import * as Duration from "effect/Duration";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import { HttpServerRequest } from "effect/http/HttpServerRequest";
7import * as HttpServerResponse from "effect/http/HttpServerResponse";
8import path from "pathe";
9
10const main = path.resolve(import.meta.dirname, "handler.ts");
11
12export class NetworkFirewallBindingsFunction extends Lambda.Function<Lambda.Function>()(
13 "NetworkFirewallBindingsFunction",
14) {}
15
16export default NetworkFirewallBindingsFunction.make(
17 {
18 main,
19 functionUrl: true,
20 timeout: Duration.seconds(120),
21 },
22 Effect.gen(function* () {
23 // A policy and a stateful rule group are cheap (seconds to provision)
24 // and exercise the policy- and rule-group-scoped grants + ARN injection.
25 const ruleGroup = yield* NetworkFirewall.RuleGroup("BindingsRuleGroup", {
26 type: "STATEFUL",
27 capacity: 10,
28 rules:
29 'pass tcp any any -> any 443 (msg:"allow https"; sid:100001; rev:1;)',
30 summaryConfiguration: { RuleOptions: ["SID", "MSG"] },
31 });
32 const policy = yield* NetworkFirewall.FirewallPolicy("BindingsPolicy", {
33 firewallPolicy: {
34 StatelessDefaultActions: ["aws:forward_to_sfe"],
35 StatelessFragmentDefaultActions: ["aws:forward_to_sfe"],
36 StatefulRuleGroupReferences: [{ ResourceArn: ruleGroup.ruleGroupArn }],
37 },
38 });
39
40 const bound = {
41 describeFirewallPolicy:
42 yield* NetworkFirewall.DescribeFirewallPolicy(policy),
43 describeRuleGroup: yield* NetworkFirewall.DescribeRuleGroup(ruleGroup),
44 describeRuleGroupSummary:
45 yield* NetworkFirewall.DescribeRuleGroupSummary(ruleGroup),
46 describeRuleGroupMetadata:
47 yield* NetworkFirewall.DescribeRuleGroupMetadata(ruleGroup),
48 };
49
50 return {
51 fetch: Effect.gen(function* () {
52 const request = yield* HttpServerRequest;
53 const url = new URL(request.originalUrl);
54 const pathname = url.pathname;
55
56 if (request.method === "GET" && pathname === "/bindings") {
57 return yield* HttpServerResponse.json({
58 bound: Object.keys(bound),
59 });
60 }
61
62 // Policy-scoped describe — the policy ARN is injected.
63 if (request.method === "GET" && pathname === "/policy") {
64 const response = yield* bound.describeFirewallPolicy();
65 return yield* HttpServerResponse.json({
66 status: response.FirewallPolicyResponse.FirewallPolicyStatus,
67 statelessDefaultActions:
68 response.FirewallPolicy?.StatelessDefaultActions ?? [],
69 });
70 }
71
72 // Rule-group-scoped describe — the rule group ARN is injected.
73 if (request.method === "GET" && pathname === "/rule-group") {
74 const response = yield* bound.describeRuleGroup();
75 return yield* HttpServerResponse.json({
76 status: response.RuleGroupResponse.RuleGroupStatus,
77 type: response.RuleGroupResponse.Type,
78 rulesString: response.RuleGroup?.RulesSource?.RulesString ?? "",
79 });
80 }
81
82 if (request.method === "GET" && pathname === "/rule-group/summary") {
83 const response = yield* bound.describeRuleGroupSummary();
84 return yield* HttpServerResponse.json({
85 ruleCount: (response.Summary?.RuleSummaries ?? []).length,
86 });
87 }
88
89 if (request.method === "GET" && pathname === "/rule-group/metadata") {
90 const response = yield* bound.describeRuleGroupMetadata();
91 return yield* HttpServerResponse.json({
92 capacity: response.Capacity ?? 0,
93 type: response.Type ?? "",
94 });
95 }
96
97 return yield* HttpServerResponse.json(
98 { error: "Not found", method: request.method, pathname },
99 { status: 404 },
100 );
101 }).pipe(Effect.orDie),
102 };
103 }).pipe(
104 Effect.provide(
105 Layer.mergeAll(
106 NetworkFirewall.DescribeFirewallPolicyHttp,
107 NetworkFirewall.DescribeRuleGroupHttp,
108 NetworkFirewall.DescribeRuleGroupSummaryHttp,
109 NetworkFirewall.DescribeRuleGroupMetadataHttp,
110 ),
111 ),
112 ),
113);