@stacksjs/ts-cloud-core 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +321 -0
- package/package.json +31 -0
- package/src/advanced-features.test.ts +465 -0
- package/src/aws/cloudformation.ts +421 -0
- package/src/aws/cloudfront.ts +158 -0
- package/src/aws/credentials.test.ts +132 -0
- package/src/aws/credentials.ts +545 -0
- package/src/aws/index.ts +87 -0
- package/src/aws/s3.test.ts +188 -0
- package/src/aws/s3.ts +1088 -0
- package/src/aws/signature.test.ts +670 -0
- package/src/aws/signature.ts +1155 -0
- package/src/backup/disaster-recovery.test.ts +726 -0
- package/src/backup/disaster-recovery.ts +500 -0
- package/src/backup/index.ts +34 -0
- package/src/backup/manager.test.ts +498 -0
- package/src/backup/manager.ts +432 -0
- package/src/cicd/circleci.ts +430 -0
- package/src/cicd/github-actions.ts +424 -0
- package/src/cicd/gitlab-ci.ts +255 -0
- package/src/cicd/index.ts +8 -0
- package/src/cli/history.ts +396 -0
- package/src/cli/index.ts +10 -0
- package/src/cli/progress.ts +458 -0
- package/src/cli/repl.ts +454 -0
- package/src/cli/suggestions.ts +327 -0
- package/src/cli/table.test.ts +319 -0
- package/src/cli/table.ts +332 -0
- package/src/cloudformation/builder.test.ts +327 -0
- package/src/cloudformation/builder.ts +378 -0
- package/src/cloudformation/builders/api-gateway.ts +449 -0
- package/src/cloudformation/builders/cache.ts +334 -0
- package/src/cloudformation/builders/cdn.ts +278 -0
- package/src/cloudformation/builders/compute.ts +485 -0
- package/src/cloudformation/builders/database.ts +392 -0
- package/src/cloudformation/builders/functions.ts +343 -0
- package/src/cloudformation/builders/messaging.ts +140 -0
- package/src/cloudformation/builders/monitoring.ts +300 -0
- package/src/cloudformation/builders/network.ts +264 -0
- package/src/cloudformation/builders/queue.ts +147 -0
- package/src/cloudformation/builders/security.ts +399 -0
- package/src/cloudformation/builders/storage.ts +285 -0
- package/src/cloudformation/index.ts +30 -0
- package/src/cloudformation/types.ts +173 -0
- package/src/compliance/aws-config.ts +543 -0
- package/src/compliance/cloudtrail.ts +376 -0
- package/src/compliance/compliance.test.ts +423 -0
- package/src/compliance/guardduty.ts +446 -0
- package/src/compliance/index.ts +66 -0
- package/src/compliance/security-hub.ts +456 -0
- package/src/containers/build-optimization.ts +416 -0
- package/src/containers/containers.test.ts +508 -0
- package/src/containers/image-scanning.ts +360 -0
- package/src/containers/index.ts +9 -0
- package/src/containers/registry.ts +293 -0
- package/src/containers/service-mesh.ts +520 -0
- package/src/database/database.test.ts +762 -0
- package/src/database/index.ts +9 -0
- package/src/database/migrations.ts +444 -0
- package/src/database/performance.ts +528 -0
- package/src/database/replicas.ts +534 -0
- package/src/database/users.ts +494 -0
- package/src/dependency-graph.ts +143 -0
- package/src/deployment/ab-testing.ts +582 -0
- package/src/deployment/blue-green.ts +452 -0
- package/src/deployment/canary.ts +500 -0
- package/src/deployment/deployment.test.ts +526 -0
- package/src/deployment/index.ts +61 -0
- package/src/deployment/progressive.ts +62 -0
- package/src/dns/dns.test.ts +641 -0
- package/src/dns/dnssec.ts +315 -0
- package/src/dns/index.ts +8 -0
- package/src/dns/resolver.ts +496 -0
- package/src/dns/routing.ts +593 -0
- package/src/email/advanced/analytics.ts +445 -0
- package/src/email/advanced/index.ts +11 -0
- package/src/email/advanced/rules.ts +465 -0
- package/src/email/advanced/scheduling.ts +352 -0
- package/src/email/advanced/search.ts +412 -0
- package/src/email/advanced/shared-mailboxes.ts +404 -0
- package/src/email/advanced/templates.ts +455 -0
- package/src/email/advanced/threading.ts +281 -0
- package/src/email/analytics.ts +467 -0
- package/src/email/bounce-handling.ts +425 -0
- package/src/email/email.test.ts +431 -0
- package/src/email/handlers/__tests__/inbound.test.ts +38 -0
- package/src/email/handlers/__tests__/outbound.test.ts +37 -0
- package/src/email/handlers/converter.ts +227 -0
- package/src/email/handlers/feedback.ts +228 -0
- package/src/email/handlers/inbound.ts +169 -0
- package/src/email/handlers/outbound.ts +178 -0
- package/src/email/index.ts +15 -0
- package/src/email/reputation.ts +303 -0
- package/src/email/templates.ts +352 -0
- package/src/errors/index.test.ts +434 -0
- package/src/errors/index.ts +416 -0
- package/src/health-checks/index.ts +40 -0
- package/src/index.ts +360 -0
- package/src/intrinsic-functions.ts +118 -0
- package/src/lambda/concurrency.ts +330 -0
- package/src/lambda/destinations.ts +345 -0
- package/src/lambda/dlq.ts +425 -0
- package/src/lambda/index.ts +11 -0
- package/src/lambda/lambda.test.ts +840 -0
- package/src/lambda/layers.ts +263 -0
- package/src/lambda/versions.ts +376 -0
- package/src/lambda/vpc.ts +399 -0
- package/src/local/config.ts +114 -0
- package/src/local/index.ts +6 -0
- package/src/local/mock-aws.ts +351 -0
- package/src/modules/ai.ts +340 -0
- package/src/modules/api.ts +478 -0
- package/src/modules/auth.ts +805 -0
- package/src/modules/cache.ts +417 -0
- package/src/modules/cdn.ts +1062 -0
- package/src/modules/communication.ts +1094 -0
- package/src/modules/compute.ts +3348 -0
- package/src/modules/database.ts +554 -0
- package/src/modules/deployment.ts +1079 -0
- package/src/modules/dns.ts +337 -0
- package/src/modules/email.ts +1538 -0
- package/src/modules/filesystem.ts +515 -0
- package/src/modules/index.ts +32 -0
- package/src/modules/messaging.ts +486 -0
- package/src/modules/monitoring.ts +2086 -0
- package/src/modules/network.ts +664 -0
- package/src/modules/parameter-store.ts +325 -0
- package/src/modules/permissions.ts +1081 -0
- package/src/modules/phone.ts +494 -0
- package/src/modules/queue.ts +1260 -0
- package/src/modules/redirects.ts +464 -0
- package/src/modules/registry.ts +699 -0
- package/src/modules/search.ts +401 -0
- package/src/modules/secrets.ts +416 -0
- package/src/modules/security.ts +731 -0
- package/src/modules/sms.ts +389 -0
- package/src/modules/storage.ts +1120 -0
- package/src/modules/workflow.ts +680 -0
- package/src/multi-account/config.ts +521 -0
- package/src/multi-account/index.ts +7 -0
- package/src/multi-account/manager.ts +427 -0
- package/src/multi-region/cross-region.ts +410 -0
- package/src/multi-region/index.ts +8 -0
- package/src/multi-region/manager.ts +483 -0
- package/src/multi-region/regions.ts +435 -0
- package/src/network-security/index.ts +48 -0
- package/src/observability/index.ts +9 -0
- package/src/observability/logs.ts +522 -0
- package/src/observability/metrics.ts +460 -0
- package/src/observability/observability.test.ts +782 -0
- package/src/observability/synthetics.ts +568 -0
- package/src/observability/xray.ts +358 -0
- package/src/phone/advanced/analytics.ts +349 -0
- package/src/phone/advanced/callbacks.ts +428 -0
- package/src/phone/advanced/index.ts +8 -0
- package/src/phone/advanced/ivr-builder.ts +504 -0
- package/src/phone/advanced/recording.ts +310 -0
- package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
- package/src/phone/handlers/incoming-call.ts +117 -0
- package/src/phone/handlers/missed-call.ts +116 -0
- package/src/phone/handlers/voicemail.ts +179 -0
- package/src/phone/index.ts +9 -0
- package/src/presets/api-backend.ts +134 -0
- package/src/presets/data-pipeline.ts +204 -0
- package/src/presets/extend.test.ts +295 -0
- package/src/presets/extend.ts +297 -0
- package/src/presets/fullstack-app.ts +144 -0
- package/src/presets/index.ts +27 -0
- package/src/presets/jamstack.ts +135 -0
- package/src/presets/microservices.ts +167 -0
- package/src/presets/ml-api.ts +208 -0
- package/src/presets/nodejs-server.ts +104 -0
- package/src/presets/nodejs-serverless.ts +114 -0
- package/src/presets/realtime-app.ts +184 -0
- package/src/presets/static-site.ts +64 -0
- package/src/presets/traditional-web-app.ts +339 -0
- package/src/presets/wordpress.ts +138 -0
- package/src/preview/github.test.ts +249 -0
- package/src/preview/github.ts +297 -0
- package/src/preview/index.ts +37 -0
- package/src/preview/manager.test.ts +440 -0
- package/src/preview/manager.ts +326 -0
- package/src/preview/notifications.test.ts +582 -0
- package/src/preview/notifications.ts +341 -0
- package/src/queue/batch-processing.ts +402 -0
- package/src/queue/dlq-monitoring.ts +402 -0
- package/src/queue/fifo.ts +342 -0
- package/src/queue/index.ts +9 -0
- package/src/queue/management.ts +428 -0
- package/src/queue/queue.test.ts +429 -0
- package/src/resource-mgmt/index.ts +39 -0
- package/src/resource-naming.ts +62 -0
- package/src/s3/index.ts +523 -0
- package/src/schema/cloud-config.schema.json +554 -0
- package/src/schema/index.ts +68 -0
- package/src/security/certificate-manager.ts +492 -0
- package/src/security/index.ts +9 -0
- package/src/security/scanning.ts +545 -0
- package/src/security/secrets-manager.ts +476 -0
- package/src/security/secrets-rotation.ts +456 -0
- package/src/security/security.test.ts +738 -0
- package/src/sms/advanced/ab-testing.ts +389 -0
- package/src/sms/advanced/analytics.ts +336 -0
- package/src/sms/advanced/campaigns.ts +523 -0
- package/src/sms/advanced/chatbot.ts +224 -0
- package/src/sms/advanced/index.ts +10 -0
- package/src/sms/advanced/link-tracking.ts +248 -0
- package/src/sms/advanced/mms.ts +308 -0
- package/src/sms/handlers/__tests__/send.test.ts +40 -0
- package/src/sms/handlers/delivery-status.ts +133 -0
- package/src/sms/handlers/receive.ts +162 -0
- package/src/sms/handlers/send.ts +174 -0
- package/src/sms/index.ts +9 -0
- package/src/stack-diff.ts +389 -0
- package/src/static-site/index.ts +85 -0
- package/src/template-builder.ts +110 -0
- package/src/template-validator.ts +574 -0
- package/src/utils/cache.ts +291 -0
- package/src/utils/diff.ts +269 -0
- package/src/utils/hash.ts +227 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/parallel.ts +294 -0
- package/src/validators/credentials.test.ts +274 -0
- package/src/validators/credentials.ts +233 -0
- package/src/validators/quotas.test.ts +434 -0
- package/src/validators/quotas.ts +217 -0
- package/test/ai.test.ts +327 -0
- package/test/api.test.ts +511 -0
- package/test/auth.test.ts +632 -0
- package/test/cache.test.ts +406 -0
- package/test/cdn.test.ts +247 -0
- package/test/compute.test.ts +861 -0
- package/test/database.test.ts +523 -0
- package/test/deployment.test.ts +499 -0
- package/test/dns.test.ts +270 -0
- package/test/email.test.ts +439 -0
- package/test/filesystem.test.ts +382 -0
- package/test/integration.test.ts +350 -0
- package/test/messaging.test.ts +514 -0
- package/test/monitoring.test.ts +634 -0
- package/test/network.test.ts +425 -0
- package/test/permissions.test.ts +488 -0
- package/test/queue.test.ts +484 -0
- package/test/registry.test.ts +306 -0
- package/test/security.test.ts +462 -0
- package/test/storage.test.ts +463 -0
- package/test/template-validator.test.ts +559 -0
- package/test/workflow.test.ts +592 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
import type { ACMCertificate, KMSAlias, KMSKey, WAFv2IPSet, WAFv2WebACL } from '@stacksjs/ts-cloud-aws-types'
|
|
2
|
+
import type { EnvironmentType } from '@stacksjs/ts-cloud-types'
|
|
3
|
+
import { Fn } from '../intrinsic-functions'
|
|
4
|
+
import { generateLogicalId, generateResourceName } from '../resource-naming'
|
|
5
|
+
|
|
6
|
+
export interface CertificateOptions {
|
|
7
|
+
domain: string
|
|
8
|
+
subdomains?: string[]
|
|
9
|
+
slug: string
|
|
10
|
+
environment: EnvironmentType
|
|
11
|
+
validationMethod?: 'DNS' | 'EMAIL'
|
|
12
|
+
hostedZoneId?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface KmsKeyOptions {
|
|
16
|
+
description: string
|
|
17
|
+
slug: string
|
|
18
|
+
environment: EnvironmentType
|
|
19
|
+
enableRotation?: boolean
|
|
20
|
+
multiRegion?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FirewallOptions {
|
|
24
|
+
slug: string
|
|
25
|
+
environment: EnvironmentType
|
|
26
|
+
scope?: 'CLOUDFRONT' | 'REGIONAL'
|
|
27
|
+
defaultAction?: 'allow' | 'block'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RateLimitRule {
|
|
31
|
+
name: string
|
|
32
|
+
priority: number
|
|
33
|
+
requestsPerWindow: number
|
|
34
|
+
aggregateKeyType?: 'IP' | 'FORWARDED_IP'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface GeoBlockRule {
|
|
38
|
+
name: string
|
|
39
|
+
priority: number
|
|
40
|
+
countryCodes: string[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface IpBlockRule {
|
|
44
|
+
name: string
|
|
45
|
+
priority: number
|
|
46
|
+
ipAddresses: string[]
|
|
47
|
+
ipVersion?: 'IPV4' | 'IPV6'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ManagedRuleGroup {
|
|
51
|
+
name: string
|
|
52
|
+
priority: number
|
|
53
|
+
vendorName: string
|
|
54
|
+
ruleName: string
|
|
55
|
+
excludedRules?: string[]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Security Module - ACM, KMS, WAF Management
|
|
60
|
+
* Provides clean API for creating and configuring security resources
|
|
61
|
+
*/
|
|
62
|
+
export class Security {
|
|
63
|
+
/**
|
|
64
|
+
* Create an SSL/TLS certificate with ACM
|
|
65
|
+
*/
|
|
66
|
+
static createCertificate(options: CertificateOptions): {
|
|
67
|
+
certificate: ACMCertificate
|
|
68
|
+
logicalId: string
|
|
69
|
+
} {
|
|
70
|
+
const {
|
|
71
|
+
domain,
|
|
72
|
+
subdomains = [],
|
|
73
|
+
slug,
|
|
74
|
+
environment,
|
|
75
|
+
validationMethod = 'DNS',
|
|
76
|
+
hostedZoneId,
|
|
77
|
+
} = options
|
|
78
|
+
|
|
79
|
+
const resourceName = generateResourceName({
|
|
80
|
+
slug,
|
|
81
|
+
environment,
|
|
82
|
+
resourceType: 'acm',
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const logicalId = generateLogicalId(`${resourceName}-${domain.replace(/\./g, '')}`)
|
|
86
|
+
|
|
87
|
+
// Build SubjectAlternativeNames (SANs) - includes the main domain plus subdomains
|
|
88
|
+
const sans: string[] = [domain]
|
|
89
|
+
for (const subdomain of subdomains) {
|
|
90
|
+
// Support wildcard notation
|
|
91
|
+
if (subdomain === '*') {
|
|
92
|
+
sans.push(`*.${domain}`)
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
sans.push(`${subdomain}.${domain}`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const certificate: ACMCertificate = {
|
|
100
|
+
Type: 'AWS::CertificateManager::Certificate',
|
|
101
|
+
Properties: {
|
|
102
|
+
DomainName: domain,
|
|
103
|
+
SubjectAlternativeNames: sans,
|
|
104
|
+
ValidationMethod: validationMethod,
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Add DNS validation with Route53 if hostedZoneId is provided
|
|
109
|
+
if (validationMethod === 'DNS' && hostedZoneId) {
|
|
110
|
+
certificate.Properties.DomainValidationOptions = sans.map(domainName => ({
|
|
111
|
+
DomainName: domainName,
|
|
112
|
+
HostedZoneId: hostedZoneId,
|
|
113
|
+
}))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return { certificate, logicalId }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Create a KMS encryption key
|
|
121
|
+
*/
|
|
122
|
+
static createKmsKey(options: KmsKeyOptions): {
|
|
123
|
+
key: KMSKey
|
|
124
|
+
alias?: KMSAlias
|
|
125
|
+
logicalId: string
|
|
126
|
+
aliasId?: string
|
|
127
|
+
} {
|
|
128
|
+
const {
|
|
129
|
+
description,
|
|
130
|
+
slug,
|
|
131
|
+
environment,
|
|
132
|
+
enableRotation = true,
|
|
133
|
+
multiRegion = false,
|
|
134
|
+
} = options
|
|
135
|
+
|
|
136
|
+
const resourceName = generateResourceName({
|
|
137
|
+
slug,
|
|
138
|
+
environment,
|
|
139
|
+
resourceType: 'kms',
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const logicalId = generateLogicalId(resourceName)
|
|
143
|
+
|
|
144
|
+
// Default key policy - allows root account full access
|
|
145
|
+
const keyPolicy = {
|
|
146
|
+
Version: '2012-10-17' as const,
|
|
147
|
+
Statement: [
|
|
148
|
+
{
|
|
149
|
+
Sid: 'Enable IAM User Permissions',
|
|
150
|
+
Effect: 'Allow' as const,
|
|
151
|
+
Principal: {
|
|
152
|
+
AWS: Fn.Sub('arn:aws:iam::${AWS::AccountId}:root'),
|
|
153
|
+
},
|
|
154
|
+
Action: 'kms:*',
|
|
155
|
+
Resource: '*',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
Sid: 'Allow services to use the key',
|
|
159
|
+
Effect: 'Allow' as const,
|
|
160
|
+
Principal: {
|
|
161
|
+
Service: [
|
|
162
|
+
's3.amazonaws.com',
|
|
163
|
+
'cloudfront.amazonaws.com',
|
|
164
|
+
'logs.amazonaws.com',
|
|
165
|
+
'secretsmanager.amazonaws.com',
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
Action: [
|
|
169
|
+
'kms:Decrypt',
|
|
170
|
+
'kms:GenerateDataKey',
|
|
171
|
+
],
|
|
172
|
+
Resource: '*',
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const key: KMSKey = {
|
|
178
|
+
Type: 'AWS::KMS::Key',
|
|
179
|
+
Properties: {
|
|
180
|
+
Description: description,
|
|
181
|
+
Enabled: true,
|
|
182
|
+
EnableKeyRotation: enableRotation,
|
|
183
|
+
KeyPolicy: keyPolicy,
|
|
184
|
+
KeySpec: 'SYMMETRIC_DEFAULT',
|
|
185
|
+
KeyUsage: 'ENCRYPT_DECRYPT',
|
|
186
|
+
MultiRegion: multiRegion,
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Create alias for easier reference
|
|
191
|
+
const aliasId = generateLogicalId(`${resourceName}-alias`)
|
|
192
|
+
const alias: KMSAlias = {
|
|
193
|
+
Type: 'AWS::KMS::Alias',
|
|
194
|
+
Properties: {
|
|
195
|
+
AliasName: `alias/${slug}-${environment}`,
|
|
196
|
+
TargetKeyId: Fn.Ref(logicalId),
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return { key, alias, logicalId, aliasId }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Create a WAF Web ACL
|
|
205
|
+
*/
|
|
206
|
+
static createFirewall(options: FirewallOptions): {
|
|
207
|
+
webAcl: WAFv2WebACL
|
|
208
|
+
logicalId: string
|
|
209
|
+
} {
|
|
210
|
+
const {
|
|
211
|
+
slug,
|
|
212
|
+
environment,
|
|
213
|
+
scope = 'CLOUDFRONT',
|
|
214
|
+
defaultAction = 'allow',
|
|
215
|
+
} = options
|
|
216
|
+
|
|
217
|
+
const resourceName = generateResourceName({
|
|
218
|
+
slug,
|
|
219
|
+
environment,
|
|
220
|
+
resourceType: 'waf',
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
const logicalId = generateLogicalId(resourceName)
|
|
224
|
+
|
|
225
|
+
const webAcl: WAFv2WebACL = {
|
|
226
|
+
Type: 'AWS::WAFv2::WebACL',
|
|
227
|
+
Properties: {
|
|
228
|
+
Name: resourceName,
|
|
229
|
+
Scope: scope,
|
|
230
|
+
DefaultAction: defaultAction === 'allow' ? { Allow: {} } : { Block: {} },
|
|
231
|
+
Description: `WAF for ${slug} ${environment}`,
|
|
232
|
+
Rules: [],
|
|
233
|
+
VisibilityConfig: {
|
|
234
|
+
SampledRequestsEnabled: true,
|
|
235
|
+
CloudWatchMetricsEnabled: true,
|
|
236
|
+
MetricName: `${resourceName}-metric`,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { webAcl, logicalId }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Add rate limiting to a Web ACL
|
|
246
|
+
*/
|
|
247
|
+
static setRateLimit(
|
|
248
|
+
webAcl: WAFv2WebACL,
|
|
249
|
+
rule: RateLimitRule,
|
|
250
|
+
): WAFv2WebACL {
|
|
251
|
+
if (!webAcl.Properties.Rules) {
|
|
252
|
+
webAcl.Properties.Rules = []
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
webAcl.Properties.Rules.push({
|
|
256
|
+
Name: rule.name,
|
|
257
|
+
Priority: rule.priority,
|
|
258
|
+
Statement: {
|
|
259
|
+
RateBasedStatement: {
|
|
260
|
+
Limit: rule.requestsPerWindow,
|
|
261
|
+
AggregateKeyType: rule.aggregateKeyType || 'IP',
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
Action: {
|
|
265
|
+
Block: {},
|
|
266
|
+
},
|
|
267
|
+
VisibilityConfig: {
|
|
268
|
+
SampledRequestsEnabled: true,
|
|
269
|
+
CloudWatchMetricsEnabled: true,
|
|
270
|
+
MetricName: `${rule.name}-metric`,
|
|
271
|
+
},
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
return webAcl
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Block specific countries
|
|
279
|
+
*/
|
|
280
|
+
static blockCountries(
|
|
281
|
+
webAcl: WAFv2WebACL,
|
|
282
|
+
rule: GeoBlockRule,
|
|
283
|
+
): WAFv2WebACL {
|
|
284
|
+
if (!webAcl.Properties.Rules) {
|
|
285
|
+
webAcl.Properties.Rules = []
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
webAcl.Properties.Rules.push({
|
|
289
|
+
Name: rule.name,
|
|
290
|
+
Priority: rule.priority,
|
|
291
|
+
Statement: {
|
|
292
|
+
GeoMatchStatement: {
|
|
293
|
+
CountryCodes: rule.countryCodes,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
Action: {
|
|
297
|
+
Block: {},
|
|
298
|
+
},
|
|
299
|
+
VisibilityConfig: {
|
|
300
|
+
SampledRequestsEnabled: true,
|
|
301
|
+
CloudWatchMetricsEnabled: true,
|
|
302
|
+
MetricName: `${rule.name}-metric`,
|
|
303
|
+
},
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
return webAcl
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Block specific IP addresses
|
|
311
|
+
*/
|
|
312
|
+
static blockIpAddresses(
|
|
313
|
+
webAcl: WAFv2WebACL,
|
|
314
|
+
rule: IpBlockRule,
|
|
315
|
+
slug: string,
|
|
316
|
+
environment: EnvironmentType,
|
|
317
|
+
): {
|
|
318
|
+
webAcl: WAFv2WebACL
|
|
319
|
+
ipSet: WAFv2IPSet
|
|
320
|
+
ipSetLogicalId: string
|
|
321
|
+
} {
|
|
322
|
+
const resourceName = generateResourceName({
|
|
323
|
+
slug,
|
|
324
|
+
environment,
|
|
325
|
+
resourceType: 'ipset',
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
const ipSetLogicalId = generateLogicalId(`${resourceName}-${rule.name}`)
|
|
329
|
+
|
|
330
|
+
// Create IP Set
|
|
331
|
+
const ipSet: WAFv2IPSet = {
|
|
332
|
+
Type: 'AWS::WAFv2::IPSet',
|
|
333
|
+
Properties: {
|
|
334
|
+
Name: `${resourceName}-${rule.name}`,
|
|
335
|
+
Scope: webAcl.Properties.Scope,
|
|
336
|
+
IPAddressVersion: rule.ipVersion || 'IPV4',
|
|
337
|
+
Addresses: rule.ipAddresses,
|
|
338
|
+
Description: `Blocked IPs for ${rule.name}`,
|
|
339
|
+
},
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Add rule to Web ACL
|
|
343
|
+
if (!webAcl.Properties.Rules) {
|
|
344
|
+
webAcl.Properties.Rules = []
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
webAcl.Properties.Rules.push({
|
|
348
|
+
Name: rule.name,
|
|
349
|
+
Priority: rule.priority,
|
|
350
|
+
Statement: {
|
|
351
|
+
IPSetReferenceStatement: {
|
|
352
|
+
Arn: Fn.GetAtt(ipSetLogicalId, 'Arn') as any,
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
Action: {
|
|
356
|
+
Block: {},
|
|
357
|
+
},
|
|
358
|
+
VisibilityConfig: {
|
|
359
|
+
SampledRequestsEnabled: true,
|
|
360
|
+
CloudWatchMetricsEnabled: true,
|
|
361
|
+
MetricName: `${rule.name}-metric`,
|
|
362
|
+
},
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
return { webAcl, ipSet, ipSetLogicalId }
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Add AWS Managed Rules
|
|
370
|
+
*/
|
|
371
|
+
static addManagedRules(
|
|
372
|
+
webAcl: WAFv2WebACL,
|
|
373
|
+
rule: ManagedRuleGroup,
|
|
374
|
+
): WAFv2WebACL {
|
|
375
|
+
if (!webAcl.Properties.Rules) {
|
|
376
|
+
webAcl.Properties.Rules = []
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const managedRuleStatement: NonNullable<WAFv2WebACL['Properties']['Rules']>[0]['Statement'] = {
|
|
380
|
+
ManagedRuleGroupStatement: {
|
|
381
|
+
VendorName: rule.vendorName,
|
|
382
|
+
Name: rule.ruleName,
|
|
383
|
+
},
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (rule.excludedRules && rule.excludedRules.length > 0) {
|
|
387
|
+
managedRuleStatement.ManagedRuleGroupStatement!.ExcludedRules = rule.excludedRules.map(name => ({ Name: name }))
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
webAcl.Properties.Rules.push({
|
|
391
|
+
Name: rule.name,
|
|
392
|
+
Priority: rule.priority,
|
|
393
|
+
Statement: managedRuleStatement,
|
|
394
|
+
VisibilityConfig: {
|
|
395
|
+
SampledRequestsEnabled: true,
|
|
396
|
+
CloudWatchMetricsEnabled: true,
|
|
397
|
+
MetricName: `${rule.name}-metric`,
|
|
398
|
+
},
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
return webAcl
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Common managed rule groups from AWS
|
|
406
|
+
*/
|
|
407
|
+
static readonly ManagedRuleGroups = {
|
|
408
|
+
/**
|
|
409
|
+
* AWS Core Rule Set - protects against common threats
|
|
410
|
+
*/
|
|
411
|
+
CoreRuleSet: {
|
|
412
|
+
vendorName: 'AWS',
|
|
413
|
+
ruleName: 'AWSManagedRulesCommonRuleSet',
|
|
414
|
+
},
|
|
415
|
+
/**
|
|
416
|
+
* Known Bad Inputs - blocks patterns known to be invalid
|
|
417
|
+
*/
|
|
418
|
+
KnownBadInputs: {
|
|
419
|
+
vendorName: 'AWS',
|
|
420
|
+
ruleName: 'AWSManagedRulesKnownBadInputsRuleSet',
|
|
421
|
+
},
|
|
422
|
+
/**
|
|
423
|
+
* SQL Database - protects against SQL injection
|
|
424
|
+
*/
|
|
425
|
+
SqlDatabase: {
|
|
426
|
+
vendorName: 'AWS',
|
|
427
|
+
ruleName: 'AWSManagedRulesSQLiRuleSet',
|
|
428
|
+
},
|
|
429
|
+
/**
|
|
430
|
+
* Linux Operating System - protects against Linux-specific exploits
|
|
431
|
+
*/
|
|
432
|
+
LinuxOS: {
|
|
433
|
+
vendorName: 'AWS',
|
|
434
|
+
ruleName: 'AWSManagedRulesLinuxRuleSet',
|
|
435
|
+
},
|
|
436
|
+
/**
|
|
437
|
+
* POSIX Operating System - protects against POSIX-specific exploits
|
|
438
|
+
*/
|
|
439
|
+
PosixOS: {
|
|
440
|
+
vendorName: 'AWS',
|
|
441
|
+
ruleName: 'AWSManagedRulesUnixRuleSet',
|
|
442
|
+
},
|
|
443
|
+
/**
|
|
444
|
+
* Amazon IP Reputation List - blocks IPs with poor reputation
|
|
445
|
+
*/
|
|
446
|
+
AmazonIpReputation: {
|
|
447
|
+
vendorName: 'AWS',
|
|
448
|
+
ruleName: 'AWSManagedRulesAmazonIpReputationList',
|
|
449
|
+
},
|
|
450
|
+
/**
|
|
451
|
+
* Anonymous IP List - blocks requests from anonymizing services
|
|
452
|
+
*/
|
|
453
|
+
AnonymousIpList: {
|
|
454
|
+
vendorName: 'AWS',
|
|
455
|
+
ruleName: 'AWSManagedRulesAnonymousIpList',
|
|
456
|
+
},
|
|
457
|
+
/**
|
|
458
|
+
* Bot Control - protects against bots and scrapers
|
|
459
|
+
*/
|
|
460
|
+
BotControl: {
|
|
461
|
+
vendorName: 'AWS',
|
|
462
|
+
ruleName: 'AWSManagedRulesBotControlRuleSet',
|
|
463
|
+
},
|
|
464
|
+
} as const
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Add path-based rate limiting
|
|
468
|
+
* Rate limit specific URL paths (e.g., login, API endpoints)
|
|
469
|
+
*/
|
|
470
|
+
static setPathRateLimit(
|
|
471
|
+
webAcl: WAFv2WebACL,
|
|
472
|
+
rule: RateLimitRule & { paths: string[] },
|
|
473
|
+
): WAFv2WebACL {
|
|
474
|
+
if (!webAcl.Properties.Rules) {
|
|
475
|
+
webAcl.Properties.Rules = []
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Build path patterns for the rule
|
|
479
|
+
const pathConditions = rule.paths.map(path => ({
|
|
480
|
+
SearchString: path,
|
|
481
|
+
FieldToMatch: {
|
|
482
|
+
UriPath: {},
|
|
483
|
+
},
|
|
484
|
+
TextTransformation: [{
|
|
485
|
+
Priority: 0,
|
|
486
|
+
Type: 'LOWERCASE',
|
|
487
|
+
}],
|
|
488
|
+
PositionalConstraint: 'STARTS_WITH',
|
|
489
|
+
}))
|
|
490
|
+
|
|
491
|
+
webAcl.Properties.Rules.push({
|
|
492
|
+
Name: rule.name,
|
|
493
|
+
Priority: rule.priority,
|
|
494
|
+
Statement: {
|
|
495
|
+
RateBasedStatement: {
|
|
496
|
+
Limit: rule.requestsPerWindow,
|
|
497
|
+
AggregateKeyType: rule.aggregateKeyType || 'IP',
|
|
498
|
+
ScopeDownStatement: {
|
|
499
|
+
OrStatement: {
|
|
500
|
+
Statements: pathConditions.map(condition => ({
|
|
501
|
+
ByteMatchStatement: condition,
|
|
502
|
+
})),
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
Action: {
|
|
508
|
+
Block: {},
|
|
509
|
+
},
|
|
510
|
+
VisibilityConfig: {
|
|
511
|
+
SampledRequestsEnabled: true,
|
|
512
|
+
CloudWatchMetricsEnabled: true,
|
|
513
|
+
MetricName: `${rule.name}-metric`,
|
|
514
|
+
},
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
return webAcl
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Add header-based rate limiting
|
|
522
|
+
* Useful for API key or user-based rate limiting
|
|
523
|
+
*/
|
|
524
|
+
static setHeaderRateLimit(
|
|
525
|
+
webAcl: WAFv2WebACL,
|
|
526
|
+
rule: RateLimitRule & { headerName: string, headerValue?: string },
|
|
527
|
+
): WAFv2WebACL {
|
|
528
|
+
if (!webAcl.Properties.Rules) {
|
|
529
|
+
webAcl.Properties.Rules = []
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const statement: any = {
|
|
533
|
+
RateBasedStatement: {
|
|
534
|
+
Limit: rule.requestsPerWindow,
|
|
535
|
+
AggregateKeyType: 'CUSTOM_KEYS',
|
|
536
|
+
CustomKeys: [
|
|
537
|
+
{
|
|
538
|
+
Header: {
|
|
539
|
+
Name: rule.headerName,
|
|
540
|
+
TextTransformations: [{ Priority: 0, Type: 'NONE' }],
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
},
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Optionally scope down to specific header value
|
|
548
|
+
if (rule.headerValue) {
|
|
549
|
+
statement.RateBasedStatement.ScopeDownStatement = {
|
|
550
|
+
ByteMatchStatement: {
|
|
551
|
+
SearchString: rule.headerValue,
|
|
552
|
+
FieldToMatch: {
|
|
553
|
+
SingleHeader: { Name: rule.headerName },
|
|
554
|
+
},
|
|
555
|
+
TextTransformation: [{ Priority: 0, Type: 'NONE' }],
|
|
556
|
+
PositionalConstraint: 'EXACTLY',
|
|
557
|
+
},
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
webAcl.Properties.Rules.push({
|
|
562
|
+
Name: rule.name,
|
|
563
|
+
Priority: rule.priority,
|
|
564
|
+
Statement: statement,
|
|
565
|
+
Action: {
|
|
566
|
+
Block: {},
|
|
567
|
+
},
|
|
568
|
+
VisibilityConfig: {
|
|
569
|
+
SampledRequestsEnabled: true,
|
|
570
|
+
CloudWatchMetricsEnabled: true,
|
|
571
|
+
MetricName: `${rule.name}-metric`,
|
|
572
|
+
},
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
return webAcl
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Add login endpoint protection
|
|
580
|
+
* Combines rate limiting with common attack patterns
|
|
581
|
+
*/
|
|
582
|
+
static protectLoginEndpoint(
|
|
583
|
+
webAcl: WAFv2WebACL,
|
|
584
|
+
options: {
|
|
585
|
+
loginPaths: string[]
|
|
586
|
+
priority: number
|
|
587
|
+
requestsPerMinute?: number
|
|
588
|
+
},
|
|
589
|
+
): WAFv2WebACL {
|
|
590
|
+
const { loginPaths, priority, requestsPerMinute = 10 } = options
|
|
591
|
+
|
|
592
|
+
// Add rate limiting for login paths
|
|
593
|
+
Security.setPathRateLimit(webAcl, {
|
|
594
|
+
name: 'LoginRateLimit',
|
|
595
|
+
priority,
|
|
596
|
+
requestsPerWindow: requestsPerMinute * 5, // AWS rate is per 5 minutes
|
|
597
|
+
paths: loginPaths,
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
return webAcl
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Add API rate limiting
|
|
605
|
+
* Apply stricter limits on API endpoints
|
|
606
|
+
*/
|
|
607
|
+
static protectApiEndpoints(
|
|
608
|
+
webAcl: WAFv2WebACL,
|
|
609
|
+
options: {
|
|
610
|
+
apiPaths: string[]
|
|
611
|
+
priority: number
|
|
612
|
+
requestsPerMinute?: number
|
|
613
|
+
},
|
|
614
|
+
): WAFv2WebACL {
|
|
615
|
+
const { apiPaths, priority, requestsPerMinute = 100 } = options
|
|
616
|
+
|
|
617
|
+
Security.setPathRateLimit(webAcl, {
|
|
618
|
+
name: 'ApiRateLimit',
|
|
619
|
+
priority,
|
|
620
|
+
requestsPerWindow: requestsPerMinute * 5, // AWS rate is per 5 minutes
|
|
621
|
+
paths: apiPaths,
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
return webAcl
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Create a comprehensive WAF with common protections
|
|
629
|
+
*/
|
|
630
|
+
static createProtectedFirewall(options: {
|
|
631
|
+
slug: string
|
|
632
|
+
environment: EnvironmentType
|
|
633
|
+
scope?: 'CLOUDFRONT' | 'REGIONAL'
|
|
634
|
+
enableBotControl?: boolean
|
|
635
|
+
enableRateLimiting?: boolean
|
|
636
|
+
rateLimitPerMinute?: number
|
|
637
|
+
}): {
|
|
638
|
+
webAcl: WAFv2WebACL
|
|
639
|
+
logicalId: string
|
|
640
|
+
} {
|
|
641
|
+
const {
|
|
642
|
+
slug,
|
|
643
|
+
environment,
|
|
644
|
+
scope = 'CLOUDFRONT',
|
|
645
|
+
enableBotControl = false,
|
|
646
|
+
enableRateLimiting = true,
|
|
647
|
+
rateLimitPerMinute = 2000,
|
|
648
|
+
} = options
|
|
649
|
+
|
|
650
|
+
// Create base firewall
|
|
651
|
+
let { webAcl, logicalId } = Security.createFirewall({
|
|
652
|
+
slug,
|
|
653
|
+
environment,
|
|
654
|
+
scope,
|
|
655
|
+
defaultAction: 'allow',
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
let priority = 0
|
|
659
|
+
|
|
660
|
+
// Add AWS IP Reputation list
|
|
661
|
+
webAcl = Security.addManagedRules(webAcl, {
|
|
662
|
+
name: 'AWSIPReputationList',
|
|
663
|
+
priority: priority++,
|
|
664
|
+
...Security.ManagedRuleGroups.AmazonIpReputation,
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
// Add Anonymous IP protection
|
|
668
|
+
webAcl = Security.addManagedRules(webAcl, {
|
|
669
|
+
name: 'AWSAnonymousIPList',
|
|
670
|
+
priority: priority++,
|
|
671
|
+
...Security.ManagedRuleGroups.AnonymousIpList,
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
// Add Core Rule Set
|
|
675
|
+
webAcl = Security.addManagedRules(webAcl, {
|
|
676
|
+
name: 'AWSCoreRuleSet',
|
|
677
|
+
priority: priority++,
|
|
678
|
+
...Security.ManagedRuleGroups.CoreRuleSet,
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
// Add Known Bad Inputs
|
|
682
|
+
webAcl = Security.addManagedRules(webAcl, {
|
|
683
|
+
name: 'AWSKnownBadInputs',
|
|
684
|
+
priority: priority++,
|
|
685
|
+
...Security.ManagedRuleGroups.KnownBadInputs,
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
// Add SQL Injection protection
|
|
689
|
+
webAcl = Security.addManagedRules(webAcl, {
|
|
690
|
+
name: 'AWSSQLi',
|
|
691
|
+
priority: priority++,
|
|
692
|
+
...Security.ManagedRuleGroups.SqlDatabase,
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
// Optionally add Bot Control (additional cost)
|
|
696
|
+
if (enableBotControl) {
|
|
697
|
+
webAcl = Security.addManagedRules(webAcl, {
|
|
698
|
+
name: 'AWSBotControl',
|
|
699
|
+
priority: priority++,
|
|
700
|
+
...Security.ManagedRuleGroups.BotControl,
|
|
701
|
+
})
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Add global rate limiting
|
|
705
|
+
if (enableRateLimiting) {
|
|
706
|
+
webAcl = Security.setRateLimit(webAcl, {
|
|
707
|
+
name: 'GlobalRateLimit',
|
|
708
|
+
priority: priority++,
|
|
709
|
+
requestsPerWindow: rateLimitPerMinute * 5, // AWS uses 5-minute windows
|
|
710
|
+
})
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return { webAcl, logicalId }
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Common rate limit presets
|
|
718
|
+
*/
|
|
719
|
+
static readonly RateLimitPresets = {
|
|
720
|
+
/** Standard website: 2000 requests per minute per IP */
|
|
721
|
+
STANDARD: 2000,
|
|
722
|
+
/** High-traffic API: 10000 requests per minute per IP */
|
|
723
|
+
HIGH_TRAFFIC: 10000,
|
|
724
|
+
/** Aggressive protection: 100 requests per minute per IP */
|
|
725
|
+
STRICT: 100,
|
|
726
|
+
/** Login protection: 10 requests per minute per IP */
|
|
727
|
+
LOGIN: 10,
|
|
728
|
+
/** API endpoint: 100 requests per minute per IP */
|
|
729
|
+
API: 100,
|
|
730
|
+
} as const
|
|
731
|
+
}
|