@stacksjs/cloud 0.58.48 → 0.58.49
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/package.json +3 -2
- package/src/cloud/ai.ts +94 -0
- package/src/cloud/aws-sdk-layer/nodejs/package-lock.json +386 -0
- package/src/cloud/aws-sdk-layer/nodejs/package.json +15 -0
- package/src/cloud/cache.ts +0 -0
- package/src/cloud/cdn.ts +374 -0
- package/src/cloud/cli.ts +45 -0
- package/src/cloud/compute.ts +80 -0
- package/src/cloud/dashboard.ts +85 -0
- package/src/cloud/database.ts +0 -0
- package/src/cloud/deployment.ts +41 -0
- package/src/cloud/dns.ts +34 -0
- package/src/cloud/docs.ts +51 -0
- package/src/cloud/email.ts +339 -0
- package/src/cloud/file-system.ts +35 -0
- package/src/cloud/index.ts +99 -0
- package/src/cloud/jump-box.ts +48 -0
- package/src/cloud/lambda/ask/index.js +35 -0
- package/src/cloud/lambda/cli-setup/index.js +67 -0
- package/src/cloud/lambda/summarize/index.js +35 -0
- package/src/cloud/network.ts +52 -0
- package/src/cloud/package/README.md +59 -0
- package/src/cloud/package/package.json +63 -0
- package/src/cloud/permissions.ts +33 -0
- package/src/cloud/queue.ts +0 -0
- package/src/cloud/redirects.ts +45 -0
- package/src/cloud/router-layer/nodejs/package.json +15 -0
- package/src/cloud/search-engine.ts +108 -0
- package/src/cloud/security.ts +259 -0
- package/src/cloud/stacksjs-router-0.58.48.tgz +0 -0
- package/src/cloud/storage.ts +168 -0
- package/src/edge/origin-request.ts +49 -0
- package/src/helpers.ts +597 -0
- package/src/index.ts +3 -0
- package/src/runtime/README.md +116 -0
- package/src/runtime/bootstrap +3 -0
- package/src/runtime/example/lambda.ts +36 -0
- package/src/runtime/runtime.ts +830 -0
- package/src/runtime/scripts/build-layer.ts +104 -0
- package/src/runtime/scripts/publish-layer.ts +110 -0
- package/src/runtime/server.ts +39 -0
- package/src/types.ts +28 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const AWS = require('aws-sdk')
|
|
2
|
+
|
|
3
|
+
async function handler(event) {
|
|
4
|
+
const requestBody = JSON.parse(event.body)
|
|
5
|
+
|
|
6
|
+
// Extract the 'question' property from the request body
|
|
7
|
+
const text = requestBody.text
|
|
8
|
+
// eslint-disable-next-line no-console
|
|
9
|
+
console.log(`Text received: ${text}`)
|
|
10
|
+
|
|
11
|
+
const bedrockRuntime = new AWS.BedrockRuntime({ apiVersion: '2023-09-30' })
|
|
12
|
+
const res = await bedrockRuntime.invokeModel({
|
|
13
|
+
modelId: 'amazon.titan-text-express-v1',
|
|
14
|
+
contentType: 'application/json',
|
|
15
|
+
accept: '*/*',
|
|
16
|
+
body: JSON.stringify({
|
|
17
|
+
inputText: `Summarize the following text: ${text}`,
|
|
18
|
+
textGenerationConfig: {
|
|
19
|
+
maxTokenCount: 512,
|
|
20
|
+
stopSequences: [],
|
|
21
|
+
temperature: 0,
|
|
22
|
+
topP: 0.9,
|
|
23
|
+
},
|
|
24
|
+
}),
|
|
25
|
+
}).promise()
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
statusCode: 200,
|
|
29
|
+
body: res.body.toString(),
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
handler,
|
|
35
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { aws_ec2 as ec2 } from 'aws-cdk-lib'
|
|
2
|
+
import type { Construct } from 'constructs'
|
|
3
|
+
import type { NestedCloudProps } from '../types'
|
|
4
|
+
|
|
5
|
+
export interface NetworkStackProps extends NestedCloudProps {
|
|
6
|
+
//
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class NetworkStack {
|
|
10
|
+
vpc: ec2.Vpc
|
|
11
|
+
|
|
12
|
+
constructor(scope: Construct, props: NetworkStackProps) {
|
|
13
|
+
this.vpc = new ec2.Vpc(scope, 'Network', {
|
|
14
|
+
vpcName: `${props.slug}-${props.appEnv}-vpc`,
|
|
15
|
+
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
|
|
16
|
+
maxAzs: 3,
|
|
17
|
+
natGateways: 0,
|
|
18
|
+
subnetConfiguration: [
|
|
19
|
+
{
|
|
20
|
+
cidrMask: 21,
|
|
21
|
+
name: `${props.slug}-${props.appEnv}-public-subnet-1`,
|
|
22
|
+
subnetType: ec2.SubnetType.PUBLIC,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
cidrMask: 21,
|
|
26
|
+
name: `${props.slug}-${props.appEnv}-public-subnet-2`,
|
|
27
|
+
subnetType: ec2.SubnetType.PUBLIC,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
cidrMask: 21,
|
|
31
|
+
name: `${props.slug}-${props.appEnv}-public-subnet-3`,
|
|
32
|
+
subnetType: ec2.SubnetType.PUBLIC,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
cidrMask: 21,
|
|
36
|
+
name: `${props.slug}-${props.appEnv}-private-subnet-1`,
|
|
37
|
+
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
cidrMask: 21,
|
|
41
|
+
name: `${props.slug}-${props.appEnv}-private-subnet-2`,
|
|
42
|
+
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
cidrMask: 21,
|
|
46
|
+
name: `${props.slug}-${props.appEnv}-private-subnet-3`,
|
|
47
|
+
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Stacks Router
|
|
2
|
+
|
|
3
|
+
This package contains the Stacks Router.
|
|
4
|
+
|
|
5
|
+
## ☘️ Features
|
|
6
|
+
|
|
7
|
+
wip
|
|
8
|
+
|
|
9
|
+
- ⚡️
|
|
10
|
+
|
|
11
|
+
wip
|
|
12
|
+
|
|
13
|
+
## 🤖 Usage
|
|
14
|
+
|
|
15
|
+
wip
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun install -d @stacksjs/actions
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Now, you can use it in your project:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
import * as router from '@stacksjs/router'
|
|
25
|
+
|
|
26
|
+
// wip
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Learn more in the docs.
|
|
30
|
+
|
|
31
|
+
## 🧪 Testing
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
bun test
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 📈 Changelog
|
|
38
|
+
|
|
39
|
+
Please see our [releases](https://github.com/stacksjs/stacks/releases) page for more information on what has changed recently.
|
|
40
|
+
|
|
41
|
+
## 🚜 Contributing
|
|
42
|
+
|
|
43
|
+
Please review the [Contributing Guide](https://github.com/stacksjs/contributing) for details.
|
|
44
|
+
|
|
45
|
+
## 🏝 Community
|
|
46
|
+
|
|
47
|
+
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
|
|
48
|
+
|
|
49
|
+
[Discussions on GitHub](https://github.com/stacksjs/stacks/discussions)
|
|
50
|
+
|
|
51
|
+
For casual chit-chat with others using this package:
|
|
52
|
+
|
|
53
|
+
[Join the Stacks Discord Server](https://discord.gg/stacksjs)
|
|
54
|
+
|
|
55
|
+
## 📄 License
|
|
56
|
+
|
|
57
|
+
The MIT License (MIT). Please see [LICENSE](https://github.com/stacksjs/stacks/tree/main/LICENSE.md) for more information.
|
|
58
|
+
|
|
59
|
+
Made with 💙
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stacksjs/router",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.58.49",
|
|
5
|
+
"description": "The Stacks framework router.",
|
|
6
|
+
"author": "Chris Breuer",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"funding": "https://github.com/sponsors/chrisbbreuer",
|
|
9
|
+
"homepage": "https://github.com/stacksjs/stacks/tree/main/storage/framework/core/router#readme",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
13
|
+
"directory": "./storage/framework/core/router"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/stacksjs/stacks/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"router",
|
|
20
|
+
"stacks",
|
|
21
|
+
"framework",
|
|
22
|
+
"typescript",
|
|
23
|
+
"javascript"
|
|
24
|
+
],
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"bun": "./src/index.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./*": {
|
|
31
|
+
"bun": "./src/*",
|
|
32
|
+
"import": "./dist/*"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"module": "dist/index.js",
|
|
36
|
+
"types": "dist/index.d.ts",
|
|
37
|
+
"contributors": [
|
|
38
|
+
"Chris Breuer <chris@stacksjs.org>"
|
|
39
|
+
],
|
|
40
|
+
"files": [
|
|
41
|
+
"README.md",
|
|
42
|
+
"dist",
|
|
43
|
+
"src"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "bun --bun build.ts",
|
|
47
|
+
"typecheck": "bun --bun tsc --noEmit",
|
|
48
|
+
"prepublishOnly": "bun --bun run build"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@stacksjs/config": "latest",
|
|
52
|
+
"unplugin-vue-router": "^0.7.0",
|
|
53
|
+
"vue-router": "^4.2.5"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@stacksjs/config": "latest",
|
|
57
|
+
"unplugin-vue-router": "^0.7.0",
|
|
58
|
+
"vue-router": "^4.2.5"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@stacksjs/development": "latest"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SecretValue, aws_iam as iam } from 'aws-cdk-lib'
|
|
2
|
+
import type { Construct } from 'constructs'
|
|
3
|
+
import { config } from '@stacksjs/config'
|
|
4
|
+
import { string } from '@stacksjs/strings'
|
|
5
|
+
import { env } from '@stacksjs/env'
|
|
6
|
+
import type { NestedCloudProps } from '../types'
|
|
7
|
+
|
|
8
|
+
export interface PermissionsStackProps extends NestedCloudProps {
|
|
9
|
+
//
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class PermissionsStack {
|
|
13
|
+
constructor(scope: Construct) {
|
|
14
|
+
const teamName = config.team.name
|
|
15
|
+
const users = config.team.members
|
|
16
|
+
const password = env.AWS_DEFAULT_PASSWORD || string.random()
|
|
17
|
+
|
|
18
|
+
for (const name in users) {
|
|
19
|
+
// const userEmail = users[userName]
|
|
20
|
+
const id = `User${string.pascalCase(teamName)}${string.pascalCase(name)}`
|
|
21
|
+
const userName = string.slug(`${teamName}-${name}`)
|
|
22
|
+
const user = new iam.User(scope, id, {
|
|
23
|
+
userName,
|
|
24
|
+
password: SecretValue.unsafePlainText(password),
|
|
25
|
+
passwordResetRequired: true,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
user.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'))
|
|
29
|
+
|
|
30
|
+
// TODO: email the userEmail their credentials
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* eslint-disable no-new */
|
|
2
|
+
import { config } from '@stacksjs/config'
|
|
3
|
+
import { RemovalPolicy, aws_route53 as route53, aws_s3 as s3 } from 'aws-cdk-lib'
|
|
4
|
+
import type { Construct } from 'constructs'
|
|
5
|
+
import type { NestedCloudProps } from '../types'
|
|
6
|
+
|
|
7
|
+
export interface RedirectsStackProps extends NestedCloudProps {
|
|
8
|
+
//
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class RedirectsStack {
|
|
12
|
+
redirectZones: route53.IHostedZone[] = []
|
|
13
|
+
|
|
14
|
+
constructor(scope: Construct, props: RedirectsStackProps) {
|
|
15
|
+
// for each redirect, create a bucket & redirect it to the APP_URL
|
|
16
|
+
config.dns.redirects?.forEach((redirect) => {
|
|
17
|
+
// TODO: use string-ts function here instead
|
|
18
|
+
const slug = redirect.split('.').map((part, index) => index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)).join('') // creates a CamelCase slug from the redirect
|
|
19
|
+
const hostedZone = route53.HostedZone.fromLookup(scope, 'HostedZone', { domainName: redirect })
|
|
20
|
+
|
|
21
|
+
const redirectBucket = new s3.Bucket(scope, `RedirectBucket${slug}`, {
|
|
22
|
+
bucketName: `${redirect}-redirect`,
|
|
23
|
+
websiteRedirect: {
|
|
24
|
+
hostName: props.domain,
|
|
25
|
+
protocol: s3.RedirectProtocol.HTTPS,
|
|
26
|
+
},
|
|
27
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
28
|
+
autoDeleteObjects: true,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
new route53.CnameRecord(scope, `RedirectRecord${slug}`, {
|
|
32
|
+
zone: hostedZone,
|
|
33
|
+
recordName: 'redirect',
|
|
34
|
+
domainName: redirectBucket.bucketWebsiteDomainName,
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// TODO: fix this – redirects do not work yet
|
|
39
|
+
config.dns.redirects?.forEach((redirect) => {
|
|
40
|
+
const slug = redirect.split('.').map((part, index) => index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)).join('') // creates a CamelCase slug from the redirect
|
|
41
|
+
const hostedZone = route53.HostedZone.fromLookup(scope, `RedirectHostedZone${slug}`, { domainName: redirect })
|
|
42
|
+
this.redirectZones.push(hostedZone)
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "stacks-router-layer",
|
|
3
|
+
"version": "0.58.49",
|
|
4
|
+
"description": "",
|
|
5
|
+
"author": "",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [],
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@stacksjs/router": "^0.58.48"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// async manageSearchEngine() {
|
|
2
|
+
// const vpc = this.vpc
|
|
3
|
+
|
|
4
|
+
// // Security Group
|
|
5
|
+
// const bastionSecurityGroup = new ec2.SecurityGroup(this, 'BastionSecurityGroup', {
|
|
6
|
+
// vpc,
|
|
7
|
+
// allowAllOutbound: true,
|
|
8
|
+
// securityGroupName: `${this.appName}-${appEnv}-bastion-sg`,
|
|
9
|
+
// })
|
|
10
|
+
|
|
11
|
+
// const opensearchSecurityGroup = new ec2.SecurityGroup(this, 'OpenSearchSecurityGroup', {
|
|
12
|
+
// vpc,
|
|
13
|
+
// securityGroupName: `${this.appName}-${appEnv}-opensearch-sg`,
|
|
14
|
+
// })
|
|
15
|
+
|
|
16
|
+
// opensearchSecurityGroup.addIngressRule(bastionSecurityGroup, ec2.Port.tcp(443))
|
|
17
|
+
|
|
18
|
+
// // Service-linked role that Amazon OpenSearch Service will use
|
|
19
|
+
// const iamClient = new IAMClient({})
|
|
20
|
+
// const response = await iamClient.send(
|
|
21
|
+
// new ListRolesCommand({
|
|
22
|
+
// PathPrefix: '/aws-service-role/opensearchservice.amazonaws.com/',
|
|
23
|
+
// }),
|
|
24
|
+
// )
|
|
25
|
+
|
|
26
|
+
// // Only if the role for OpenSearch Service doesn't exist, it will be created.
|
|
27
|
+
// if (response.Roles && response.Roles?.length === 0) {
|
|
28
|
+
// new iam.CfnServiceLinkedRole(this, 'OpenSearchServiceLinkedRole', {
|
|
29
|
+
// awsServiceName: 'es.amazonaws.com',
|
|
30
|
+
// })
|
|
31
|
+
// }
|
|
32
|
+
|
|
33
|
+
// // Bastion host to access Opensearch Dashboards
|
|
34
|
+
// new ec2.BastionHostLinux(this, 'BastionHost', {
|
|
35
|
+
// vpc,
|
|
36
|
+
// securityGroup: bastionSecurityGroup,
|
|
37
|
+
// machineImage: ec2.MachineImage.latestAmazonLinux2023(),
|
|
38
|
+
// blockDevices: [
|
|
39
|
+
// {
|
|
40
|
+
// deviceName: '/dev/xvda',
|
|
41
|
+
// volume: ec2.BlockDeviceVolume.ebs(10, {
|
|
42
|
+
// encrypted: true,
|
|
43
|
+
// }),
|
|
44
|
+
// },
|
|
45
|
+
// ],
|
|
46
|
+
// })
|
|
47
|
+
|
|
48
|
+
// // OpenSearch domain
|
|
49
|
+
// const domain = new opensearch.Domain(this, 'OpenSearchDomain', {
|
|
50
|
+
// version: opensearch.EngineVersion.OPENSEARCH_2_9,
|
|
51
|
+
// nodeToNodeEncryption: true,
|
|
52
|
+
// enforceHttps: true,
|
|
53
|
+
// encryptionAtRest: {
|
|
54
|
+
// enabled: true,
|
|
55
|
+
// },
|
|
56
|
+
// vpc,
|
|
57
|
+
// // unsure if there are "better" ways to do this
|
|
58
|
+
// vpcSubnets: [
|
|
59
|
+
// { subnetGroupName: `${this.appName}-${appEnv}-private-subnet-1` },
|
|
60
|
+
// { subnetGroupName: `${this.appName}-${appEnv}-private-subnet-2` },
|
|
61
|
+
// ],
|
|
62
|
+
|
|
63
|
+
// capacity: {
|
|
64
|
+
// masterNodes: 2,
|
|
65
|
+
// dataNodes: 2,
|
|
66
|
+
// multiAzWithStandbyEnabled: true,
|
|
67
|
+
// },
|
|
68
|
+
// ebs: {
|
|
69
|
+
// volumeSize: 10,
|
|
70
|
+
// volumeType: ec2.EbsDeviceVolumeType.GP3, // or opensearch.EbsVolumeType.IO1
|
|
71
|
+
// },
|
|
72
|
+
// removalPolicy: RemovalPolicy.DESTROY,
|
|
73
|
+
// zoneAwareness: {
|
|
74
|
+
// enabled: true,
|
|
75
|
+
// availabilityZoneCount: 2,
|
|
76
|
+
// },
|
|
77
|
+
// securityGroups: [opensearchSecurityGroup],
|
|
78
|
+
// })
|
|
79
|
+
|
|
80
|
+
// domain.addAccessPolicies(
|
|
81
|
+
// new iam.PolicyStatement({
|
|
82
|
+
// principals: [new iam.AnyPrincipal()],
|
|
83
|
+
// actions: ['es:ESHttp*'],
|
|
84
|
+
// resources: [`${domain.domainArn}/*`],
|
|
85
|
+
// }),
|
|
86
|
+
// )
|
|
87
|
+
|
|
88
|
+
// // // Lambda
|
|
89
|
+
// // const dataIndexFunction = PythonFunction(this, 'DataIndex', {
|
|
90
|
+
// // runtime: lambda.Runtime.PYTHON_3_10,
|
|
91
|
+
// // entry: 'lambda',
|
|
92
|
+
// // vpc,
|
|
93
|
+
// // environment: {
|
|
94
|
+
// // OPENSEARCH_HOST: domain.domainEndpoint,
|
|
95
|
+
// // },
|
|
96
|
+
// // })
|
|
97
|
+
|
|
98
|
+
// // domain.connections.allowFrom(dataIndexFunction, Port.tcp(443))
|
|
99
|
+
|
|
100
|
+
// // Outputs
|
|
101
|
+
// new Output(this, 'OpenSearchDomainHost', {
|
|
102
|
+
// value: domain.domainEndpoint,
|
|
103
|
+
// })
|
|
104
|
+
|
|
105
|
+
// // new Output(this, 'IndexingFunctionName', {
|
|
106
|
+
// // value: dataIndexFunction.functionName,
|
|
107
|
+
// // })
|
|
108
|
+
// }
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// waf and encryption
|
|
2
|
+
import { config } from '@stacksjs/config'
|
|
3
|
+
import type { aws_route53 as route53 } from 'aws-cdk-lib'
|
|
4
|
+
import { Duration, RemovalPolicy, Tags, aws_certificatemanager as acm, aws_kms as kms, aws_wafv2 as wafv2 } from 'aws-cdk-lib'
|
|
5
|
+
import type { Construct } from 'constructs'
|
|
6
|
+
import type { NestedCloudProps } from '../types'
|
|
7
|
+
|
|
8
|
+
export interface StorageStackProps extends NestedCloudProps {
|
|
9
|
+
zone: route53.IHostedZone
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class SecurityStack {
|
|
13
|
+
firewall: wafv2.CfnWebACL
|
|
14
|
+
kmsKey: kms.Key
|
|
15
|
+
certificate: acm.Certificate
|
|
16
|
+
|
|
17
|
+
constructor(scope: Construct, props: StorageStackProps) {
|
|
18
|
+
const firewallOptions = config.cloud.firewall
|
|
19
|
+
|
|
20
|
+
if (!firewallOptions)
|
|
21
|
+
throw new Error('No firewall options found in config')
|
|
22
|
+
|
|
23
|
+
const options = {
|
|
24
|
+
defaultAction: { allow: {} },
|
|
25
|
+
scope: 'CLOUDFRONT',
|
|
26
|
+
visibilityConfig: {
|
|
27
|
+
sampledRequestsEnabled: true,
|
|
28
|
+
cloudWatchMetricsEnabled: true,
|
|
29
|
+
metricName: 'firewallMetric',
|
|
30
|
+
},
|
|
31
|
+
rules: this.getFirewallRules(),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.firewall = new wafv2.CfnWebACL(scope, 'WebFirewall', options)
|
|
35
|
+
Tags.of(this.firewall).add('Name', 'waf-cloudfront', { priority: 300 })
|
|
36
|
+
Tags.of(this.firewall).add('Purpose', 'CloudFront', { priority: 300 })
|
|
37
|
+
Tags.of(this.firewall).add('CreatedBy', 'CloudFormation', { priority: 300 })
|
|
38
|
+
|
|
39
|
+
this.kmsKey = new kms.Key(scope, 'EncryptionKey', {
|
|
40
|
+
alias: 'stacks-encryption-key',
|
|
41
|
+
description: 'KMS key for Stacks Cloud',
|
|
42
|
+
enableKeyRotation: true,
|
|
43
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
44
|
+
pendingWindow: Duration.days(30),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
this.certificate = new acm.Certificate(scope, 'Certificate', {
|
|
48
|
+
domainName: props.domain,
|
|
49
|
+
validation: acm.CertificateValidation.fromDns(props.zone),
|
|
50
|
+
subjectAlternativeNames: [`www.${props.domain}`, `api.${props.domain}`],
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getFirewallRules() {
|
|
55
|
+
const rules: wafv2.CfnWebACL.RuleProperty[] = []
|
|
56
|
+
const priorities = []
|
|
57
|
+
|
|
58
|
+
if (config.security.firewall?.countryCodes?.length) {
|
|
59
|
+
priorities.push(1)
|
|
60
|
+
rules.push({
|
|
61
|
+
name: 'CountryRule',
|
|
62
|
+
priority: priorities.length,
|
|
63
|
+
statement: {
|
|
64
|
+
geoMatchStatement: {
|
|
65
|
+
countryCodes: config.security.firewall.countryCodes,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
action: {
|
|
69
|
+
block: {},
|
|
70
|
+
},
|
|
71
|
+
visibilityConfig: {
|
|
72
|
+
sampledRequestsEnabled: true,
|
|
73
|
+
cloudWatchMetricsEnabled: true,
|
|
74
|
+
metricName: 'CountryRule',
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (config.security.firewall?.ipAddresses?.length) {
|
|
80
|
+
const ipSet = new wafv2.CfnIPSet(this, 'IpSet', {
|
|
81
|
+
name: 'IpSet',
|
|
82
|
+
description: 'IP Set',
|
|
83
|
+
scope: 'CLOUDFRONT',
|
|
84
|
+
addresses: config.security.firewall.ipAddresses,
|
|
85
|
+
ipAddressVersion: 'IPV4',
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
priorities.push(1)
|
|
89
|
+
rules.push({
|
|
90
|
+
name: 'IpAddressRule',
|
|
91
|
+
priority: priorities.length,
|
|
92
|
+
statement: {
|
|
93
|
+
ipSetReferenceStatement: {
|
|
94
|
+
arn: ipSet.attrArn,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
action: {
|
|
98
|
+
block: {},
|
|
99
|
+
},
|
|
100
|
+
visibilityConfig: {
|
|
101
|
+
sampledRequestsEnabled: true,
|
|
102
|
+
cloudWatchMetricsEnabled: true,
|
|
103
|
+
metricName: 'IpAddressRule',
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (config.security.firewall?.httpHeaders?.length) {
|
|
109
|
+
config.security.firewall.httpHeaders.forEach((header, index) => {
|
|
110
|
+
priorities.push(1)
|
|
111
|
+
rules.push({
|
|
112
|
+
name: `HttpHeaderRule${index}`,
|
|
113
|
+
priority: priorities.length,
|
|
114
|
+
statement: {
|
|
115
|
+
byteMatchStatement: {
|
|
116
|
+
fieldToMatch: {
|
|
117
|
+
singleHeader: {
|
|
118
|
+
name: header,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
positionalConstraint: 'EXACTLY',
|
|
122
|
+
searchString: 'true',
|
|
123
|
+
textTransformations: [
|
|
124
|
+
{
|
|
125
|
+
priority: index,
|
|
126
|
+
type: 'NONE',
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
action: {
|
|
132
|
+
block: {},
|
|
133
|
+
},
|
|
134
|
+
visibilityConfig: {
|
|
135
|
+
sampledRequestsEnabled: true,
|
|
136
|
+
cloudWatchMetricsEnabled: true,
|
|
137
|
+
metricName: `HttpHeaderRule${index}`,
|
|
138
|
+
},
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// if (config.security.firewall?.queryString?.length) {
|
|
144
|
+
// priorities.push(1)
|
|
145
|
+
// rules.push({
|
|
146
|
+
// name: 'QueryStringRule',
|
|
147
|
+
// priority: priorities.length,
|
|
148
|
+
// statement: {
|
|
149
|
+
// byteMatchStatement: {
|
|
150
|
+
// fieldToMatch: {
|
|
151
|
+
// queryString: {},
|
|
152
|
+
// },
|
|
153
|
+
// positionalConstraint: 'EXACTLY',
|
|
154
|
+
// searchString: config.security.firewall.queryString.join(', '),
|
|
155
|
+
// textTransformations: [
|
|
156
|
+
// {
|
|
157
|
+
// priority: 0,
|
|
158
|
+
// type: 'NONE',
|
|
159
|
+
// },
|
|
160
|
+
// ],
|
|
161
|
+
// },
|
|
162
|
+
// },
|
|
163
|
+
// action: {
|
|
164
|
+
// block: {},
|
|
165
|
+
// },
|
|
166
|
+
// visibilityConfig: {
|
|
167
|
+
// sampledRequestsEnabled: true,
|
|
168
|
+
// cloudWatchMetricsEnabled: true,
|
|
169
|
+
// metricName: 'QueryStringRule',
|
|
170
|
+
// },
|
|
171
|
+
// })
|
|
172
|
+
// }
|
|
173
|
+
|
|
174
|
+
// if (config.security.firewall?.rateLimitPerMinute) {
|
|
175
|
+
// priorities.push(1)
|
|
176
|
+
// rules.push({
|
|
177
|
+
// name: 'RateLimitRule',
|
|
178
|
+
// priority: priorities.length,
|
|
179
|
+
// statement: {
|
|
180
|
+
// rateBasedStatement: {
|
|
181
|
+
// limit: config.security.firewall.rateLimitPerMinute,
|
|
182
|
+
// aggregateKeyType: 'IP',
|
|
183
|
+
// },
|
|
184
|
+
// },
|
|
185
|
+
// action: {
|
|
186
|
+
// block: {},
|
|
187
|
+
// },
|
|
188
|
+
// visibilityConfig: {
|
|
189
|
+
// sampledRequestsEnabled: true,
|
|
190
|
+
// cloudWatchMetricsEnabled: true,
|
|
191
|
+
// metricName: 'RateLimitRule',
|
|
192
|
+
// },
|
|
193
|
+
// })
|
|
194
|
+
// }
|
|
195
|
+
|
|
196
|
+
// if (config.security.firewall?.useIpReputationLists) {
|
|
197
|
+
// priorities.push(1)
|
|
198
|
+
// rules.push({
|
|
199
|
+
// name: 'IpReputationRule',
|
|
200
|
+
// priority: priorities.length,
|
|
201
|
+
// statement: {
|
|
202
|
+
// managedRuleGroupStatement: {
|
|
203
|
+
// vendorName: 'AWS',
|
|
204
|
+
// name: 'AWSManagedRulesAmazonIpReputationList',
|
|
205
|
+
// },
|
|
206
|
+
// },
|
|
207
|
+
// action: {
|
|
208
|
+
// block: {},
|
|
209
|
+
// },
|
|
210
|
+
// visibilityConfig: {
|
|
211
|
+
// sampledRequestsEnabled: true,
|
|
212
|
+
// cloudWatchMetricsEnabled: true,
|
|
213
|
+
// metricName: 'IpReputationRule',
|
|
214
|
+
// },
|
|
215
|
+
// })
|
|
216
|
+
// }
|
|
217
|
+
|
|
218
|
+
// if (config.security.firewall?.useKnownBadInputsRuleSet) {
|
|
219
|
+
// priorities.push(1)
|
|
220
|
+
// rules.push({
|
|
221
|
+
// name: 'KnownBadInputsRule',
|
|
222
|
+
// priority: priorities.length,
|
|
223
|
+
// statement: {
|
|
224
|
+
// managedRuleGroupStatement: {
|
|
225
|
+
// vendorName: 'AWS',
|
|
226
|
+
// name: 'AWSManagedRulesKnownBadInputsRuleSet',
|
|
227
|
+
// },
|
|
228
|
+
// },
|
|
229
|
+
// action: {
|
|
230
|
+
// block: {},
|
|
231
|
+
// },
|
|
232
|
+
// visibilityConfig: {
|
|
233
|
+
// sampledRequestsEnabled: true,
|
|
234
|
+
// cloudWatchMetricsEnabled: true,
|
|
235
|
+
// metricName: 'KnownBadInputsRule',
|
|
236
|
+
// },
|
|
237
|
+
// })
|
|
238
|
+
// }
|
|
239
|
+
// also add
|
|
240
|
+
// }, {
|
|
241
|
+
// "name": "AWSManagedRulesAnonymousIpList",
|
|
242
|
+
// "priority": 40,
|
|
243
|
+
// "overrideAction": "none",
|
|
244
|
+
// "excludedRules": []
|
|
245
|
+
// }, {
|
|
246
|
+
// "name": "AWSManagedRulesLinuxRuleSet",
|
|
247
|
+
// "priority": 50,
|
|
248
|
+
// "overrideAction": "none",
|
|
249
|
+
// "excludedRules": []
|
|
250
|
+
// }, {
|
|
251
|
+
// "name": "AWSManagedRulesUnixRuleSet",
|
|
252
|
+
// "priority": 60,
|
|
253
|
+
// "overrideAction": "none",
|
|
254
|
+
// "excludedRules": [],
|
|
255
|
+
// }];
|
|
256
|
+
|
|
257
|
+
return rules
|
|
258
|
+
}
|
|
259
|
+
}
|
|
Binary file
|