@stacksjs/cloud 0.67.0 → 0.68.0
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/dist/ai.d.ts +20 -0
- package/dist/cdn.d.ts +271 -0
- package/dist/cli.d.ts +12 -0
- package/dist/compute.d.ts +221 -0
- package/dist/database.d.ts +29 -0
- package/dist/deployment.d.ts +60 -0
- package/dist/dns.d.ts +34 -0
- package/dist/docs.d.ts +34 -0
- package/dist/email.d.ts +336 -0
- package/dist/file-system.d.ts +32 -0
- package/dist/helpers.d.ts +50 -50
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -18
- package/dist/jump-box.d.ts +42 -0
- package/dist/network.d.ts +29 -0
- package/dist/origin-request.d.ts +5 -0
- package/dist/permissions.d.ts +25 -0
- package/dist/queue.d.ts +144 -0
- package/dist/redirects.d.ts +45 -0
- package/dist/security.d.ts +149 -0
- package/dist/storage.d.ts +189 -0
- package/dist/types.d.ts +26 -25
- package/package.json +18 -18
- package/dist/cloud/ai.d.ts +0 -6
- package/dist/cloud/cache.d.ts +0 -0
- package/dist/cloud/cdn.d.ts +0 -34
- package/dist/cloud/cli.d.ts +0 -6
- package/dist/cloud/compute.d.ts +0 -17
- package/dist/cloud/dashboard.d.ts +0 -0
- package/dist/cloud/database.d.ts +0 -11
- package/dist/cloud/deployment.d.ts +0 -17
- package/dist/cloud/dns.d.ts +0 -7
- package/dist/cloud/docs.d.ts +0 -8
- package/dist/cloud/email.d.ts +0 -10
- package/dist/cloud/file-system.d.ts +0 -12
- package/dist/cloud/index.d.ts +0 -42
- package/dist/cloud/jump-box.d.ts +0 -12
- package/dist/cloud/network.d.ts +0 -8
- package/dist/cloud/permissions.d.ts +0 -6
- package/dist/cloud/queue.d.ts +0 -22
- package/dist/cloud/redirects.d.ts +0 -8
- package/dist/cloud/search-engine.d.ts +0 -0
- package/dist/cloud/security.d.ts +0 -15
- package/dist/cloud/storage.d.ts +0 -21
- package/dist/edge/origin-request.d.ts +0 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Construct } from 'constructs';
|
|
2
|
+
import type { NestedCloudProps } from '../types';
|
|
3
|
+
|
|
4
|
+
export declare interface JumpBoxStackProps extends NestedCloudProps {
|
|
5
|
+
vpc: ec2.Vpc
|
|
6
|
+
fileSystem: efs.FileSystem
|
|
7
|
+
}
|
|
8
|
+
export declare class JumpBoxStack {
|
|
9
|
+
jumpBox?: ec2.Instance
|
|
10
|
+
|
|
11
|
+
constructor(scope: Construct, props: JumpBoxStackProps) {
|
|
12
|
+
const role = new iam.Role(scope, 'JumpBoxInstanceRole', {
|
|
13
|
+
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
|
|
14
|
+
managedPolicies: [
|
|
15
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
|
|
16
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy'),
|
|
17
|
+
],
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
this.jumpBox = new ec2.Instance(scope, 'JumpBox', {
|
|
21
|
+
vpc: props.vpc,
|
|
22
|
+
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO),
|
|
23
|
+
machineImage: new ec2.AmazonLinuxImage(),
|
|
24
|
+
role,
|
|
25
|
+
userData: ec2.UserData.custom(`
|
|
26
|
+
#!/bin/bash
|
|
27
|
+
yum update -y
|
|
28
|
+
yum install -y amazon-efs-utils
|
|
29
|
+
yum install -y git
|
|
30
|
+
yum install -y https:
|
|
31
|
+
mkdir /mnt/efs
|
|
32
|
+
mount -t efs ${props.fileSystem.fileSystemId}:/ /mnt/efs
|
|
33
|
+
git clone https:
|
|
34
|
+
`),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
new Output(scope, 'JumpBoxInstanceId', {
|
|
38
|
+
value: this.jumpBox.instanceId,
|
|
39
|
+
description: 'The ID of the EC2 instance that can be used to SSH into the Stacks Cloud.',
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Construct } from 'constructs';
|
|
2
|
+
import type { NestedCloudProps } from '../types';
|
|
3
|
+
|
|
4
|
+
export declare interface NetworkStackProps extends NestedCloudProps {
|
|
5
|
+
}
|
|
6
|
+
export declare class NetworkStack {
|
|
7
|
+
vpc: ec2.Vpc
|
|
8
|
+
|
|
9
|
+
constructor(scope: Construct, props: NetworkStackProps) {
|
|
10
|
+
this.vpc = new ec2.Vpc(scope, 'Network', {
|
|
11
|
+
vpcName: `${props.slug}-${props.appEnv}-vpc`,
|
|
12
|
+
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
|
|
13
|
+
maxAzs: 3,
|
|
14
|
+
natGateways: 0,
|
|
15
|
+
subnetConfiguration: [
|
|
16
|
+
{
|
|
17
|
+
name: 'public-subnet-1',
|
|
18
|
+
subnetType: ec2.SubnetType.PUBLIC,
|
|
19
|
+
cidrMask: 24,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'private-subnet-1',
|
|
23
|
+
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
|
|
24
|
+
cidrMask: 28,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Construct } from 'constructs';
|
|
2
|
+
import type { NestedCloudProps } from '../types';
|
|
3
|
+
|
|
4
|
+
export declare interface PermissionsStackProps extends NestedCloudProps {
|
|
5
|
+
}
|
|
6
|
+
export declare class PermissionsStack {
|
|
7
|
+
constructor(scope: Construct) {
|
|
8
|
+
const teamName = config.team.name
|
|
9
|
+
const users = config.team.members
|
|
10
|
+
const password = env.AWS_DEFAULT_PASSWORD || string.random()
|
|
11
|
+
|
|
12
|
+
for (const name in users) {
|
|
13
|
+
const id = `User${string.pascalCase(teamName)}${string.pascalCase(name)}`
|
|
14
|
+
const userName = string.slug(`${teamName}-${name}`)
|
|
15
|
+
const user = new iam.User(scope, id, {
|
|
16
|
+
userName,
|
|
17
|
+
password: SecretValue.unsafePlainText(password),
|
|
18
|
+
passwordResetRequired: true,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
user.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'))
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/queue.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { Cluster, TaskDefinition } from 'aws-cdk-lib/aws-ecs';
|
|
2
|
+
import type { Construct } from 'constructs';
|
|
3
|
+
import type { NestedCloudProps } from '../types';
|
|
4
|
+
|
|
5
|
+
export declare interface QueueStackProps extends NestedCloudProps {
|
|
6
|
+
cluster: Cluster
|
|
7
|
+
taskDefinition: TaskDefinition
|
|
8
|
+
}
|
|
9
|
+
export declare class QueueStack {
|
|
10
|
+
scope: Construct
|
|
11
|
+
props: QueueStackProps
|
|
12
|
+
|
|
13
|
+
constructor(scope: Construct, props: QueueStackProps) {
|
|
14
|
+
this.scope = scope
|
|
15
|
+
this.props = props
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async init(): Promise<void> {
|
|
19
|
+
const jobsDir = path.jobsPath()
|
|
20
|
+
const actionsDir = path.appPath('Actions')
|
|
21
|
+
const ormActionDir = path.builtUserActionsPath('src')
|
|
22
|
+
|
|
23
|
+
const jobFiles = await fs.readdir(jobsDir)
|
|
24
|
+
const actionFiles = await fs.readdir(actionsDir)
|
|
25
|
+
const ormActionFiles = await fs.readdir(ormActionDir)
|
|
26
|
+
const jobs = []
|
|
27
|
+
|
|
28
|
+
for (const file of jobFiles) {
|
|
29
|
+
if (!file.endsWith('.ts'))
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
const jobPath = path.jobsPath(file)
|
|
33
|
+
|
|
34
|
+
const job = await this.loadModule(jobPath)
|
|
35
|
+
this.createQueueRule(job, file)
|
|
36
|
+
jobs.push(job)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const ormFile of ormActionFiles) {
|
|
40
|
+
if (!ormFile.endsWith('.ts'))
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
const ormActionPath = path.builtUserActionsPath(`src/${ormFile}`)
|
|
44
|
+
|
|
45
|
+
const ormAction = await this.loadModule(ormActionPath)
|
|
46
|
+
this.createQueueRule(ormAction, ormFile)
|
|
47
|
+
jobs.push(ormAction)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const file of actionFiles) {
|
|
51
|
+
if (!file.endsWith('.ts'))
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
const actionPath = path.appPath(`Actions/${file}`)
|
|
55
|
+
|
|
56
|
+
const action = await this.loadModule(actionPath)
|
|
57
|
+
this.createQueueRule(action, file)
|
|
58
|
+
jobs.push(action)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
cronScheduleFromRate(rate: string): {
|
|
63
|
+
minute?: string
|
|
64
|
+
hour?: string
|
|
65
|
+
month?: string
|
|
66
|
+
weekDay?: string
|
|
67
|
+
year?: string
|
|
68
|
+
} {
|
|
69
|
+
const parts = rate.split(' ')
|
|
70
|
+
return {
|
|
71
|
+
minute: parts[0],
|
|
72
|
+
hour: parts[1],
|
|
73
|
+
month: parts[2],
|
|
74
|
+
weekDay: parts[3],
|
|
75
|
+
year: parts[4],
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async loadModule(filePath: string): Promise<{ default: any }> {
|
|
80
|
+
const jobModule = await import(filePath)
|
|
81
|
+
|
|
82
|
+
return jobModule
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
createQueueRule(module: { default: any }, file: string): void {
|
|
86
|
+
const rate = module.default?.rate
|
|
87
|
+
|
|
88
|
+
if (!rate || module.default?.enabled === false)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
const schedule = Schedule.cron(this.cronScheduleFromRate(rate))
|
|
92
|
+
|
|
93
|
+
const id = `QueueRule${pascalCase(file.replace('.ts', ''))}`
|
|
94
|
+
|
|
95
|
+
const rule = new Rule(this.scope, id, {
|
|
96
|
+
ruleName: `${this.props.appName}-${this.props.appEnv}-queue-rule-${slug(file.replace('.ts', ''))}`,
|
|
97
|
+
schedule,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
rule.addTarget(
|
|
101
|
+
new EcsTask({
|
|
102
|
+
cluster: this.props.cluster,
|
|
103
|
+
taskDefinition: this.props.taskDefinition,
|
|
104
|
+
containerOverrides: [
|
|
105
|
+
{
|
|
106
|
+
containerName: `${this.props.appName}-${this.props.appEnv}-api`,
|
|
107
|
+
environment: [
|
|
108
|
+
{
|
|
109
|
+
name: 'QUEUE_WORKER',
|
|
110
|
+
value: 'true',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'JOB',
|
|
114
|
+
value: file,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'JOB_BACKOFF_FACTOR',
|
|
118
|
+
value: module.default?.backoffFactor,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'JOB_RETRIES',
|
|
122
|
+
value: module.default?.tries,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'JOB_INITIAL_DELAY',
|
|
126
|
+
value: module.default?.initialDelay,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'JOB_JITTER',
|
|
130
|
+
value: module.default?.jitter,
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
|
|
136
|
+
retryAttempts: 1,
|
|
137
|
+
|
|
138
|
+
subnetSelection: {
|
|
139
|
+
subnetType: ec2.SubnetType.PUBLIC,
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Construct } from 'constructs';
|
|
2
|
+
import type { NestedCloudProps } from '../types';
|
|
3
|
+
|
|
4
|
+
export declare interface RedirectsStackProps extends NestedCloudProps {
|
|
5
|
+
}
|
|
6
|
+
export declare class RedirectsStack {
|
|
7
|
+
redirectZones: route53.IHostedZone[] = []
|
|
8
|
+
|
|
9
|
+
constructor(scope: Construct, props: RedirectsStackProps) {
|
|
10
|
+
config.dns.redirects?.forEach((redirect: string) => {
|
|
11
|
+
const slug = redirect
|
|
12
|
+
.split('.')
|
|
13
|
+
.map((part: string, index: number) => (index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)))
|
|
14
|
+
.join('')
|
|
15
|
+
const hostedZone = route53.HostedZone.fromLookup(scope, 'HostedZone', {
|
|
16
|
+
domainName: redirect,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const redirectBucket = new s3.Bucket(scope, `RedirectBucket${slug}`, {
|
|
20
|
+
bucketName: `${redirect}-redirect`,
|
|
21
|
+
websiteRedirect: {
|
|
22
|
+
hostName: props.domain,
|
|
23
|
+
protocol: s3.RedirectProtocol.HTTPS,
|
|
24
|
+
},
|
|
25
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
26
|
+
autoDeleteObjects: true,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
new route53.CnameRecord(scope, `RedirectRecord${slug}`, {
|
|
30
|
+
zone: hostedZone,
|
|
31
|
+
recordName: 'redirect',
|
|
32
|
+
domainName: redirectBucket.bucketWebsiteDomainName,
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
config.dns.redirects?.forEach((redirect: string) => {
|
|
37
|
+
const slug = redirect
|
|
38
|
+
.split('.')
|
|
39
|
+
.map((part: string, index: number) => (index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)))
|
|
40
|
+
.join('')
|
|
41
|
+
const hostedZone = route53.HostedZone.fromLookup(scope, `RedirectHostedZone${slug}`, { domainName: redirect })
|
|
42
|
+
this.redirectZones.push(hostedZone)
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { Construct } from 'constructs';
|
|
2
|
+
import type { NestedCloudProps } from '../types';
|
|
3
|
+
|
|
4
|
+
export declare interface StorageStackProps extends NestedCloudProps {
|
|
5
|
+
zone: route53.IHostedZone
|
|
6
|
+
}
|
|
7
|
+
export declare class SecurityStack {
|
|
8
|
+
firewall: wafv2.CfnWebACL
|
|
9
|
+
kmsKey: kms.Key
|
|
10
|
+
certificate: acm.Certificate
|
|
11
|
+
originAccessIdentity: cloudfront.OriginAccessIdentity
|
|
12
|
+
|
|
13
|
+
constructor(scope: Construct, props: StorageStackProps) {
|
|
14
|
+
const firewallOptions = config.cloud.firewall
|
|
15
|
+
|
|
16
|
+
if (!firewallOptions)
|
|
17
|
+
throw new Error('No firewall options found in config')
|
|
18
|
+
|
|
19
|
+
const options = {
|
|
20
|
+
defaultAction: { allow: {} },
|
|
21
|
+
scope: 'CLOUDFRONT',
|
|
22
|
+
visibilityConfig: {
|
|
23
|
+
sampledRequestsEnabled: true,
|
|
24
|
+
cloudWatchMetricsEnabled: true,
|
|
25
|
+
metricName: 'firewallMetric',
|
|
26
|
+
},
|
|
27
|
+
rules: this.getFirewallRules(scope),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.firewall = new wafv2.CfnWebACL(scope, 'StacksWebFirewall', options)
|
|
31
|
+
Tags.of(this.firewall).add('Name', 'waf-cloudfront', { priority: 300 })
|
|
32
|
+
Tags.of(this.firewall).add('Purpose', 'CloudFront', { priority: 300 })
|
|
33
|
+
Tags.of(this.firewall).add('CreatedBy', 'CloudFormation', {
|
|
34
|
+
priority: 300,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
this.kmsKey = new kms.Key(scope, 'EncryptionKey', {
|
|
38
|
+
alias: 'stacks-encryption-key',
|
|
39
|
+
description: 'KMS key for Stacks Cloud',
|
|
40
|
+
enableKeyRotation: true,
|
|
41
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
42
|
+
pendingWindow: Duration.days(30),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
this.certificate = new acm.Certificate(scope, 'Certificate', {
|
|
46
|
+
domainName: props.domain,
|
|
47
|
+
validation: acm.CertificateValidation.fromDns(props.zone),
|
|
48
|
+
subjectAlternativeNames: [`www.${props.domain}`, `api.${props.domain}`, `docs.${props.domain}`],
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
this.originAccessIdentity = new cloudfront.OriginAccessIdentity(scope, 'OAI')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getFirewallRules(scope: Construct): wafv2.CfnWebACL.RuleProperty[] {
|
|
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 as string[],
|
|
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(scope, 'IpSet', {
|
|
81
|
+
name: 'IpSet',
|
|
82
|
+
description: 'IP Set',
|
|
83
|
+
scope: 'CLOUDFRONT',
|
|
84
|
+
addresses: config.security.firewall.ipAddresses as string[],
|
|
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: string | undefined, index: number) => {
|
|
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: 0,
|
|
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
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
return rules
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { Construct } from 'constructs';
|
|
2
|
+
import type { NestedCloudProps } from '../types';
|
|
3
|
+
|
|
4
|
+
export declare interface StorageStackProps extends NestedCloudProps {
|
|
5
|
+
kmsKey: kms.Key
|
|
6
|
+
originAccessIdentity: cloudfront.OriginAccessIdentity
|
|
7
|
+
}
|
|
8
|
+
export declare class StorageStack {
|
|
9
|
+
publicBucket: s3.Bucket
|
|
10
|
+
privateBucket: s3.Bucket
|
|
11
|
+
docsBucket?: s3.Bucket
|
|
12
|
+
logBucket: s3.Bucket
|
|
13
|
+
bucketPrefix: string
|
|
14
|
+
vault: backup.BackupVault
|
|
15
|
+
backupPlan: backup.BackupPlan
|
|
16
|
+
backupRole: iam.Role
|
|
17
|
+
|
|
18
|
+
constructor(scope: Construct, props: StorageStackProps) {
|
|
19
|
+
this.bucketPrefix = `${props.slug}-${props.appEnv}`
|
|
20
|
+
|
|
21
|
+
this.publicBucket = new s3.Bucket(scope, 'PublicBucket', {
|
|
22
|
+
bucketName: `${this.bucketPrefix}-public-${props.timestamp}`,
|
|
23
|
+
versioned: true,
|
|
24
|
+
autoDeleteObjects: true,
|
|
25
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
26
|
+
encryption: s3.BucketEncryption.S3_MANAGED,
|
|
27
|
+
websiteIndexDocument: 'index.html',
|
|
28
|
+
websiteErrorDocument: 'index.html',
|
|
29
|
+
publicReadAccess: true,
|
|
30
|
+
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
Tags.of(this.publicBucket).add('daily-backup', 'true')
|
|
34
|
+
|
|
35
|
+
if (this.shouldDeployDocs()) {
|
|
36
|
+
this.docsBucket = new s3.Bucket(scope, 'DocsBucket', {
|
|
37
|
+
bucketName: `${this.bucketPrefix}-docs-${props.timestamp}`,
|
|
38
|
+
versioned: true,
|
|
39
|
+
autoDeleteObjects: true,
|
|
40
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
41
|
+
encryption: s3.BucketEncryption.S3_MANAGED,
|
|
42
|
+
websiteIndexDocument: 'index.html',
|
|
43
|
+
websiteErrorDocument: 'index.html',
|
|
44
|
+
publicReadAccess: true,
|
|
45
|
+
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
Tags.of(this.docsBucket).add('weekly-backup', 'true')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.privateBucket = new s3.Bucket(scope, 'PrivateBucket', {
|
|
52
|
+
bucketName: `${this.bucketPrefix}-private-${props.timestamp}`,
|
|
53
|
+
versioned: true,
|
|
54
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
55
|
+
autoDeleteObjects: true,
|
|
56
|
+
encryption: s3.BucketEncryption.S3_MANAGED,
|
|
57
|
+
enforceSSL: true,
|
|
58
|
+
publicReadAccess: false,
|
|
59
|
+
blockPublicAccess: {
|
|
60
|
+
blockPublicAcls: true,
|
|
61
|
+
blockPublicPolicy: true,
|
|
62
|
+
ignorePublicAcls: true,
|
|
63
|
+
restrictPublicBuckets: true,
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
Tags.of(this.privateBucket).add('daily-backup', 'true')
|
|
68
|
+
|
|
69
|
+
this.logBucket = new s3.Bucket(scope, 'LogsBucket', {
|
|
70
|
+
bucketName: `${this.bucketPrefix}-logs-${props.timestamp}`,
|
|
71
|
+
removalPolicy: RemovalPolicy.RETAIN,
|
|
72
|
+
blockPublicAccess: new s3.BlockPublicAccess({
|
|
73
|
+
blockPublicAcls: false,
|
|
74
|
+
ignorePublicAcls: true,
|
|
75
|
+
blockPublicPolicy: true,
|
|
76
|
+
restrictPublicBuckets: true,
|
|
77
|
+
}),
|
|
78
|
+
objectOwnership: s3.ObjectOwnership.BUCKET_OWNER_PREFERRED,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
Tags.of(this.logBucket).add('daily-backup', 'true')
|
|
82
|
+
|
|
83
|
+
this.backupRole = this.createBackupRole(scope)
|
|
84
|
+
|
|
85
|
+
this.vault = new backup.BackupVault(scope, 'BackupVault', {
|
|
86
|
+
backupVaultName: `${props.slug}-${props.appEnv}-daily-backup-vault`,
|
|
87
|
+
encryptionKey: props.kmsKey,
|
|
88
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
this.backupPlan = backup.BackupPlan.daily35DayRetention(scope, 'BackupPlan', this.vault)
|
|
92
|
+
|
|
93
|
+
this.backupPlan.addSelection('Selection', {
|
|
94
|
+
role: this.backupRole,
|
|
95
|
+
resources: [backup.BackupResource.fromTag('daily-backup', 'true')],
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
createBackupRole(scope: Construct): iam.Role {
|
|
100
|
+
const backupRole = new iam.Role(scope, 'BackupRole', {
|
|
101
|
+
assumedBy: new iam.ServicePrincipal('backup.amazonaws.com'),
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
backupRole.addToPolicy(
|
|
105
|
+
new iam.PolicyStatement({
|
|
106
|
+
actions: [
|
|
107
|
+
's3:GetInventoryConfiguration',
|
|
108
|
+
's3:PutInventoryConfiguration',
|
|
109
|
+
's3:ListBucketVersions',
|
|
110
|
+
's3:ListBucket',
|
|
111
|
+
's3:GetBucketVersioning',
|
|
112
|
+
's3:GetBucketNotification',
|
|
113
|
+
's3:PutBucketNotification',
|
|
114
|
+
's3:GetBucketLocation',
|
|
115
|
+
's3:GetBucketTagging',
|
|
116
|
+
],
|
|
117
|
+
resources: ['arn:aws:s3:::*'],
|
|
118
|
+
sid: 'S3BucketBackupPermissions',
|
|
119
|
+
}),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
backupRole.addToPolicy(
|
|
123
|
+
new iam.PolicyStatement({
|
|
124
|
+
actions: [
|
|
125
|
+
's3:GetObjectAcl',
|
|
126
|
+
's3:GetObject',
|
|
127
|
+
's3:GetObjectVersionTagging',
|
|
128
|
+
's3:GetObjectVersionAcl',
|
|
129
|
+
's3:GetObjectTagging',
|
|
130
|
+
's3:GetObjectVersion',
|
|
131
|
+
],
|
|
132
|
+
resources: ['arn:aws:s3:::*/*'],
|
|
133
|
+
sid: 'S3ObjectBackupPermissions',
|
|
134
|
+
}),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
backupRole.addToPolicy(
|
|
138
|
+
new iam.PolicyStatement({
|
|
139
|
+
actions: ['s3:ListAllMyBuckets'],
|
|
140
|
+
resources: ['*'],
|
|
141
|
+
sid: 'S3GlobalPermissions',
|
|
142
|
+
}),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
backupRole.addToPolicy(
|
|
146
|
+
new iam.PolicyStatement({
|
|
147
|
+
actions: ['kms:Decrypt', 'kms:DescribeKey'],
|
|
148
|
+
resources: ['*'],
|
|
149
|
+
sid: 'KMSBackupPermissions',
|
|
150
|
+
conditions: {
|
|
151
|
+
StringLike: {
|
|
152
|
+
'kms:ViaService': 's3.*.amazonaws.com',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
}),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
backupRole.addToPolicy(
|
|
159
|
+
new iam.PolicyStatement({
|
|
160
|
+
actions: [
|
|
161
|
+
'events:DescribeRule',
|
|
162
|
+
'events:EnableRule',
|
|
163
|
+
'events:PutRule',
|
|
164
|
+
'events:DeleteRule',
|
|
165
|
+
'events:PutTargets',
|
|
166
|
+
'events:RemoveTargets',
|
|
167
|
+
'events:ListTargetsByRule',
|
|
168
|
+
'events:DisableRule',
|
|
169
|
+
],
|
|
170
|
+
resources: ['arn:aws:events:*:*:rule/AwsBackupManagedRule*'],
|
|
171
|
+
sid: 'EventsPermissions',
|
|
172
|
+
}),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
backupRole.addToPolicy(
|
|
176
|
+
new iam.PolicyStatement({
|
|
177
|
+
actions: ['cloudwatch:GetMetricData', 'events:ListRules'],
|
|
178
|
+
resources: ['*'],
|
|
179
|
+
sid: 'EventsMetricsGlobalPermissions',
|
|
180
|
+
}),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return backupRole
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
shouldDeployDocs(): boolean {
|
|
187
|
+
return (hasFiles(p.projectPath('docs')) || config.app.docMode) ?? false
|
|
188
|
+
}
|
|
189
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
import type { AppEnvType } from
|
|
2
|
-
import type { StackProps } from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export interface NestedCloudProps {
|
|
16
|
-
name: string;
|
|
17
|
-
env: {
|
|
18
|
-
account: string;
|
|
19
|
-
region: string;
|
|
20
|
-
};
|
|
21
|
-
slug: string;
|
|
22
|
-
appEnv: AppEnvType;
|
|
23
|
-
appName: string;
|
|
24
|
-
domain: string;
|
|
25
|
-
timestamp: string;
|
|
1
|
+
import type { AppEnvType } from '@stacksjs/types';
|
|
2
|
+
import type { StackProps } from 'aws-cdk-lib';
|
|
3
|
+
|
|
4
|
+
export declare interface CloudOptions extends StackProps {
|
|
5
|
+
name: string
|
|
6
|
+
env: {
|
|
7
|
+
account: string
|
|
8
|
+
region: string
|
|
9
|
+
}
|
|
10
|
+
slug: string
|
|
11
|
+
appEnv: AppEnvType
|
|
12
|
+
appName: string
|
|
13
|
+
domain: string
|
|
14
|
+
timestamp: string
|
|
26
15
|
}
|
|
16
|
+
export declare interface NestedCloudProps {
|
|
17
|
+
name: string
|
|
18
|
+
env: {
|
|
19
|
+
account: string
|
|
20
|
+
region: string
|
|
21
|
+
}
|
|
22
|
+
slug: string
|
|
23
|
+
appEnv: AppEnvType
|
|
24
|
+
appName: string
|
|
25
|
+
domain: string
|
|
26
|
+
timestamp: string
|
|
27
|
+
}
|