@dga-itc/aws-cdk-constructs 1.0.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/README.md +219 -0
- package/dist/aws-cdk/constructs/acm.d.ts +28 -0
- package/dist/aws-cdk/constructs/acm.js +239 -0
- package/dist/aws-cdk/constructs/alb.d.ts +28 -0
- package/dist/aws-cdk/constructs/alb.js +304 -0
- package/dist/aws-cdk/constructs/bastion.d.ts +46 -0
- package/dist/aws-cdk/constructs/bastion.js +332 -0
- package/dist/aws-cdk/constructs/cloudfront.d.ts +45 -0
- package/dist/aws-cdk/constructs/cloudfront.js +261 -0
- package/dist/aws-cdk/constructs/ecr.d.ts +17 -0
- package/dist/aws-cdk/constructs/ecr.js +143 -0
- package/dist/aws-cdk/constructs/ecs-cluster.d.ts +21 -0
- package/dist/aws-cdk/constructs/ecs-cluster.js +124 -0
- package/dist/aws-cdk/constructs/ecs-service.d.ts +72 -0
- package/dist/aws-cdk/constructs/ecs-service.js +682 -0
- package/dist/aws-cdk/constructs/efs.d.ts +31 -0
- package/dist/aws-cdk/constructs/efs.js +241 -0
- package/dist/aws-cdk/constructs/elasticache.d.ts +35 -0
- package/dist/aws-cdk/constructs/elasticache.js +210 -0
- package/dist/aws-cdk/constructs/nacl.d.ts +37 -0
- package/dist/aws-cdk/constructs/nacl.js +88 -0
- package/dist/aws-cdk/constructs/nlb.d.ts +39 -0
- package/dist/aws-cdk/constructs/nlb.js +276 -0
- package/dist/aws-cdk/constructs/rds.d.ts +40 -0
- package/dist/aws-cdk/constructs/rds.js +320 -0
- package/dist/aws-cdk/constructs/self-signed-cert.d.ts +83 -0
- package/dist/aws-cdk/constructs/self-signed-cert.js +215 -0
- package/dist/aws-cdk/constructs/sqs.d.ts +30 -0
- package/dist/aws-cdk/constructs/sqs.js +268 -0
- package/dist/aws-cdk/constructs/vpc.d.ts +30 -0
- package/dist/aws-cdk/constructs/vpc.js +423 -0
- package/dist/aws-cdk/constructs/waf.d.ts +37 -0
- package/dist/aws-cdk/constructs/waf.js +350 -0
- package/dist/aws-cdk/interfaces/account-config.d.ts +18 -0
- package/dist/aws-cdk/interfaces/account-config.js +2 -0
- package/dist/aws-cdk/interfaces/acm-config.d.ts +94 -0
- package/dist/aws-cdk/interfaces/acm-config.js +14 -0
- package/dist/aws-cdk/interfaces/alb-config.d.ts +72 -0
- package/dist/aws-cdk/interfaces/alb-config.js +2 -0
- package/dist/aws-cdk/interfaces/bastion-config.d.ts +77 -0
- package/dist/aws-cdk/interfaces/bastion-config.js +10 -0
- package/dist/aws-cdk/interfaces/cloudfront-config.d.ts +154 -0
- package/dist/aws-cdk/interfaces/cloudfront-config.js +15 -0
- package/dist/aws-cdk/interfaces/ecr-config.d.ts +40 -0
- package/dist/aws-cdk/interfaces/ecr-config.js +2 -0
- package/dist/aws-cdk/interfaces/ecs-cluster-config.d.ts +30 -0
- package/dist/aws-cdk/interfaces/ecs-cluster-config.js +2 -0
- package/dist/aws-cdk/interfaces/ecs-service-config.d.ts +237 -0
- package/dist/aws-cdk/interfaces/ecs-service-config.js +2 -0
- package/dist/aws-cdk/interfaces/efs-config.d.ts +56 -0
- package/dist/aws-cdk/interfaces/efs-config.js +7 -0
- package/dist/aws-cdk/interfaces/elasticache-config.d.ts +56 -0
- package/dist/aws-cdk/interfaces/elasticache-config.js +7 -0
- package/dist/aws-cdk/interfaces/nacl-config.d.ts +1 -0
- package/dist/aws-cdk/interfaces/nacl-config.js +3 -0
- package/dist/aws-cdk/interfaces/nlb-config.d.ts +69 -0
- package/dist/aws-cdk/interfaces/nlb-config.js +2 -0
- package/dist/aws-cdk/interfaces/rds-config.d.ts +84 -0
- package/dist/aws-cdk/interfaces/rds-config.js +7 -0
- package/dist/aws-cdk/interfaces/sqs-config.d.ts +145 -0
- package/dist/aws-cdk/interfaces/sqs-config.js +12 -0
- package/dist/aws-cdk/interfaces/tag-config.d.ts +18 -0
- package/dist/aws-cdk/interfaces/tag-config.js +2 -0
- package/dist/aws-cdk/interfaces/vpc-config.d.ts +72 -0
- package/dist/aws-cdk/interfaces/vpc-config.js +2 -0
- package/dist/aws-cdk/interfaces/waf-config.d.ts +180 -0
- package/dist/aws-cdk/interfaces/waf-config.js +2 -0
- package/dist/aws-cdk/utils/priority-tracker.d.ts +60 -0
- package/dist/aws-cdk/utils/priority-tracker.js +131 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +55 -0
- package/dist/terraform-cdk/constructs/alb-listener-rule.d.ts +33 -0
- package/dist/terraform-cdk/constructs/alb-listener-rule.js +81 -0
- package/dist/terraform-cdk/constructs/ecs-service.d.ts +29 -0
- package/dist/terraform-cdk/constructs/ecs-service.js +238 -0
- package/dist/terraform-cdk/interfaces/ecs-service-config.d.ts +53 -0
- package/dist/terraform-cdk/interfaces/ecs-service-config.js +25 -0
- package/dist/terraform-cdk/interfaces/infrastructure-refs.d.ts +16 -0
- package/dist/terraform-cdk/interfaces/infrastructure-refs.js +8 -0
- package/dist/terraform-cdk/utils/priority-tracker.d.ts +60 -0
- package/dist/terraform-cdk/utils/priority-tracker.js +131 -0
- package/package.json +46 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Construct } from 'constructs';
|
|
2
|
+
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
|
|
3
|
+
export interface SelfSignedCertificateConstructProps {
|
|
4
|
+
/**
|
|
5
|
+
* Domain name for the certificate (CN)
|
|
6
|
+
* Example: 'example.local', '*.example.local'
|
|
7
|
+
*/
|
|
8
|
+
domainName: string;
|
|
9
|
+
/**
|
|
10
|
+
* Certificate validity in days
|
|
11
|
+
* Default: 365
|
|
12
|
+
*/
|
|
13
|
+
validityDays?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Organization name
|
|
16
|
+
* Default: 'Demo'
|
|
17
|
+
*/
|
|
18
|
+
organization?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Country code (2 letters)
|
|
21
|
+
* Default: 'TH'
|
|
22
|
+
*/
|
|
23
|
+
country?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Removal policy
|
|
26
|
+
* Default: 'destroy'
|
|
27
|
+
*/
|
|
28
|
+
removalPolicy?: 'destroy' | 'retain';
|
|
29
|
+
/**
|
|
30
|
+
* Stack name for CloudFormation outputs
|
|
31
|
+
*/
|
|
32
|
+
stackName: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Self-Signed Certificate Construct
|
|
36
|
+
*
|
|
37
|
+
* Generates a self-signed SSL/TLS certificate and imports it to ACM.
|
|
38
|
+
*
|
|
39
|
+
* Features:
|
|
40
|
+
* - Generates certificate during CDK synth (no runtime dependencies)
|
|
41
|
+
* - Imports to ACM using Custom Resource
|
|
42
|
+
* - Auto-deletes certificate when stack is destroyed
|
|
43
|
+
* - Suitable for dev/test environments
|
|
44
|
+
*
|
|
45
|
+
* ⚠️ WARNING: Self-signed certificates are NOT trusted by browsers.
|
|
46
|
+
* Use only for development/testing. For production, use ACM request mode
|
|
47
|
+
* or import certificates from a trusted CA.
|
|
48
|
+
*
|
|
49
|
+
* Example:
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const cert = new SelfSignedCertificateConstruct(this, 'SelfSignedCert', {
|
|
52
|
+
* stackName: 'my-stack',
|
|
53
|
+
* domainName: 'app.local',
|
|
54
|
+
* validityDays: 365,
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* // Use with ALB
|
|
58
|
+
* const alb = new AlbConstruct(this, 'Alb', {
|
|
59
|
+
* config: {
|
|
60
|
+
* enableHttps: true,
|
|
61
|
+
* certificateArn: cert.certificateArn,
|
|
62
|
+
* }
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare class SelfSignedCertificateConstruct extends Construct {
|
|
67
|
+
readonly certificate: acm.ICertificate;
|
|
68
|
+
readonly certificateArn: string;
|
|
69
|
+
private readonly removalPolicy;
|
|
70
|
+
constructor(scope: Construct, id: string, props: SelfSignedCertificateConstructProps);
|
|
71
|
+
/**
|
|
72
|
+
* Generate self-signed certificate using OpenSSL
|
|
73
|
+
*
|
|
74
|
+
* Certificate is cached in .cdk-selfsigned/ directory to ensure
|
|
75
|
+
* consistent values across synth runs (prevents cross-stack export changes).
|
|
76
|
+
* Delete the cache directory to force regeneration.
|
|
77
|
+
*/
|
|
78
|
+
private generateCertificate;
|
|
79
|
+
/**
|
|
80
|
+
* Create CloudFormation outputs
|
|
81
|
+
*/
|
|
82
|
+
private createOutputs;
|
|
83
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SelfSignedCertificateConstruct = void 0;
|
|
37
|
+
const constructs_1 = require("constructs");
|
|
38
|
+
const cdk = __importStar(require("aws-cdk-lib"));
|
|
39
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
40
|
+
const acm = __importStar(require("aws-cdk-lib/aws-certificatemanager"));
|
|
41
|
+
const cr = __importStar(require("aws-cdk-lib/custom-resources"));
|
|
42
|
+
const iam = __importStar(require("aws-cdk-lib/aws-iam"));
|
|
43
|
+
const child_process_1 = require("child_process");
|
|
44
|
+
const fs_1 = require("fs");
|
|
45
|
+
const path_1 = require("path");
|
|
46
|
+
const os_1 = require("os");
|
|
47
|
+
/**
|
|
48
|
+
* Self-Signed Certificate Construct
|
|
49
|
+
*
|
|
50
|
+
* Generates a self-signed SSL/TLS certificate and imports it to ACM.
|
|
51
|
+
*
|
|
52
|
+
* Features:
|
|
53
|
+
* - Generates certificate during CDK synth (no runtime dependencies)
|
|
54
|
+
* - Imports to ACM using Custom Resource
|
|
55
|
+
* - Auto-deletes certificate when stack is destroyed
|
|
56
|
+
* - Suitable for dev/test environments
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ WARNING: Self-signed certificates are NOT trusted by browsers.
|
|
59
|
+
* Use only for development/testing. For production, use ACM request mode
|
|
60
|
+
* or import certificates from a trusted CA.
|
|
61
|
+
*
|
|
62
|
+
* Example:
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const cert = new SelfSignedCertificateConstruct(this, 'SelfSignedCert', {
|
|
65
|
+
* stackName: 'my-stack',
|
|
66
|
+
* domainName: 'app.local',
|
|
67
|
+
* validityDays: 365,
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* // Use with ALB
|
|
71
|
+
* const alb = new AlbConstruct(this, 'Alb', {
|
|
72
|
+
* config: {
|
|
73
|
+
* enableHttps: true,
|
|
74
|
+
* certificateArn: cert.certificateArn,
|
|
75
|
+
* }
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
class SelfSignedCertificateConstruct extends constructs_1.Construct {
|
|
80
|
+
constructor(scope, id, props) {
|
|
81
|
+
super(scope, id);
|
|
82
|
+
this.removalPolicy = props.removalPolicy === 'retain'
|
|
83
|
+
? aws_cdk_lib_1.RemovalPolicy.RETAIN
|
|
84
|
+
: aws_cdk_lib_1.RemovalPolicy.DESTROY;
|
|
85
|
+
// Generate certificate during synth
|
|
86
|
+
const { certBody, privateKey } = this.generateCertificate(props);
|
|
87
|
+
// Import to ACM using Custom Resource
|
|
88
|
+
const importCert = new cr.AwsCustomResource(this, 'ImportCertificate', {
|
|
89
|
+
onCreate: {
|
|
90
|
+
service: 'ACM',
|
|
91
|
+
action: 'importCertificate',
|
|
92
|
+
parameters: {
|
|
93
|
+
Certificate: certBody,
|
|
94
|
+
PrivateKey: privateKey,
|
|
95
|
+
},
|
|
96
|
+
physicalResourceId: cr.PhysicalResourceId.fromResponse('CertificateArn'),
|
|
97
|
+
},
|
|
98
|
+
onUpdate: {
|
|
99
|
+
service: 'ACM',
|
|
100
|
+
action: 'importCertificate',
|
|
101
|
+
parameters: {
|
|
102
|
+
Certificate: certBody,
|
|
103
|
+
PrivateKey: privateKey,
|
|
104
|
+
},
|
|
105
|
+
physicalResourceId: cr.PhysicalResourceId.fromResponse('CertificateArn'),
|
|
106
|
+
},
|
|
107
|
+
onDelete: {
|
|
108
|
+
service: 'ACM',
|
|
109
|
+
action: 'deleteCertificate',
|
|
110
|
+
parameters: {
|
|
111
|
+
CertificateArn: new cr.PhysicalResourceIdReference(),
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
policy: cr.AwsCustomResourcePolicy.fromStatements([
|
|
115
|
+
new iam.PolicyStatement({
|
|
116
|
+
actions: [
|
|
117
|
+
'acm:ImportCertificate',
|
|
118
|
+
'acm:DeleteCertificate',
|
|
119
|
+
'acm:DescribeCertificate',
|
|
120
|
+
],
|
|
121
|
+
resources: ['*'],
|
|
122
|
+
}),
|
|
123
|
+
]),
|
|
124
|
+
});
|
|
125
|
+
// Get certificate ARN from response
|
|
126
|
+
this.certificateArn = importCert.getResponseField('CertificateArn');
|
|
127
|
+
this.certificate = acm.Certificate.fromCertificateArn(this, 'Certificate', this.certificateArn);
|
|
128
|
+
// Outputs
|
|
129
|
+
this.createOutputs(props);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Generate self-signed certificate using OpenSSL
|
|
133
|
+
*
|
|
134
|
+
* Certificate is cached in .cdk-selfsigned/ directory to ensure
|
|
135
|
+
* consistent values across synth runs (prevents cross-stack export changes).
|
|
136
|
+
* Delete the cache directory to force regeneration.
|
|
137
|
+
*/
|
|
138
|
+
generateCertificate(props) {
|
|
139
|
+
const validityDays = props.validityDays ?? 365;
|
|
140
|
+
const organization = props.organization ?? 'Demo';
|
|
141
|
+
const country = props.country ?? 'TH';
|
|
142
|
+
// Cache directory: .cdk-selfsigned/<stackName>/ in project root
|
|
143
|
+
const projectRoot = process.cwd();
|
|
144
|
+
const cacheDir = (0, path_1.join)(projectRoot, '.cdk-selfsigned', props.stackName);
|
|
145
|
+
const cachedKeyFile = (0, path_1.join)(cacheDir, 'key.pem');
|
|
146
|
+
const cachedCertFile = (0, path_1.join)(cacheDir, 'cert.pem');
|
|
147
|
+
// Use cached certificate if exists
|
|
148
|
+
if ((0, fs_1.existsSync)(cachedKeyFile) && (0, fs_1.existsSync)(cachedCertFile)) {
|
|
149
|
+
console.log(`🔐 Using cached self-signed certificate for ${props.domainName}`);
|
|
150
|
+
console.log(` Cache: ${cacheDir}`);
|
|
151
|
+
const certBody = (0, fs_1.readFileSync)(cachedCertFile, 'utf8');
|
|
152
|
+
const privateKey = (0, fs_1.readFileSync)(cachedKeyFile, 'utf8');
|
|
153
|
+
return { certBody, privateKey };
|
|
154
|
+
}
|
|
155
|
+
// Generate new certificate
|
|
156
|
+
const tmpDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'cdk-selfsigned-'));
|
|
157
|
+
const keyFile = (0, path_1.join)(tmpDir, 'key.pem');
|
|
158
|
+
const certFile = (0, path_1.join)(tmpDir, 'cert.pem');
|
|
159
|
+
try {
|
|
160
|
+
console.log(`🔐 Generating self-signed certificate for ${props.domainName}...`);
|
|
161
|
+
// Generate private key (2048-bit RSA)
|
|
162
|
+
(0, child_process_1.execSync)(`openssl genrsa 2048 > "${keyFile}"`, { stdio: 'pipe' });
|
|
163
|
+
// Generate self-signed certificate
|
|
164
|
+
const subject = `/CN=${props.domainName}/O=${organization}/C=${country}`;
|
|
165
|
+
(0, child_process_1.execSync)(`openssl req -new -x509 -key "${keyFile}" -out "${certFile}" -days ${validityDays} -subj "${subject}"`, { stdio: 'pipe' });
|
|
166
|
+
// Read certificate and key
|
|
167
|
+
const certBody = (0, fs_1.readFileSync)(certFile, 'utf8');
|
|
168
|
+
const privateKey = (0, fs_1.readFileSync)(keyFile, 'utf8');
|
|
169
|
+
// Save to cache
|
|
170
|
+
(0, fs_1.mkdirSync)(cacheDir, { recursive: true });
|
|
171
|
+
(0, fs_1.writeFileSync)(cachedKeyFile, privateKey, { mode: 0o600 });
|
|
172
|
+
(0, fs_1.writeFileSync)(cachedCertFile, certBody);
|
|
173
|
+
console.log(`✅ Certificate generated and cached`);
|
|
174
|
+
console.log(` Domain: ${props.domainName}`);
|
|
175
|
+
console.log(` Valid for: ${validityDays} days`);
|
|
176
|
+
console.log(` Cache: ${cacheDir}`);
|
|
177
|
+
console.log(` ⚠️ Delete cache to regenerate: rm -rf ${cacheDir}`);
|
|
178
|
+
return { certBody, privateKey };
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
throw new Error(`Failed to generate self-signed certificate: ${error}\n` +
|
|
182
|
+
`Make sure OpenSSL is installed and available in PATH.`);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
// Cleanup temp files
|
|
186
|
+
try {
|
|
187
|
+
(0, fs_1.rmSync)(tmpDir, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
// Ignore cleanup errors
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Create CloudFormation outputs
|
|
196
|
+
*/
|
|
197
|
+
createOutputs(props) {
|
|
198
|
+
const stack = cdk.Stack.of(this);
|
|
199
|
+
new cdk.CfnOutput(stack, 'SelfSignedCertificateArn', {
|
|
200
|
+
value: this.certificateArn,
|
|
201
|
+
description: `Self-signed certificate ARN for ${props.domainName}`,
|
|
202
|
+
exportName: `${props.stackName}-SelfSignedCert-Arn`,
|
|
203
|
+
});
|
|
204
|
+
new cdk.CfnOutput(stack, 'SelfSignedCertificateDomain', {
|
|
205
|
+
value: props.domainName,
|
|
206
|
+
description: 'Certificate domain name',
|
|
207
|
+
exportName: `${props.stackName}-SelfSignedCert-Domain`,
|
|
208
|
+
});
|
|
209
|
+
new cdk.CfnOutput(stack, 'SelfSignedCertificateValidity', {
|
|
210
|
+
value: `${props.validityDays ?? 365} days`,
|
|
211
|
+
description: 'Certificate validity period',
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.SelfSignedCertificateConstruct = SelfSignedCertificateConstruct;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Construct } from 'constructs';
|
|
2
|
+
import * as sqs from 'aws-cdk-lib/aws-sqs';
|
|
3
|
+
import { SqsConfig } from '../interfaces/sqs-config';
|
|
4
|
+
export interface SqsConstructProps {
|
|
5
|
+
config: SqsConfig;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* SQS Construct — สร้าง SQS Queue + Dead Letter Queue
|
|
9
|
+
*
|
|
10
|
+
* สร้าง:
|
|
11
|
+
* 1. Main Queue (Standard หรือ FIFO)
|
|
12
|
+
* 2. Dead Letter Queue (optional) — รับ message ที่ process ไม่สำเร็จ
|
|
13
|
+
* 3. Access Policy (optional) — cross-account permissions
|
|
14
|
+
* 4. Redrive Allow Policy (optional) — สำหรับ DLQ
|
|
15
|
+
*/
|
|
16
|
+
export declare class SqsConstruct extends Construct {
|
|
17
|
+
readonly queue: sqs.Queue;
|
|
18
|
+
readonly deadLetterQueue?: sqs.Queue;
|
|
19
|
+
readonly queueArn: string;
|
|
20
|
+
readonly queueUrl: string;
|
|
21
|
+
readonly deadLetterQueueArn?: string;
|
|
22
|
+
readonly deadLetterQueueUrl?: string;
|
|
23
|
+
private readonly removalPolicy;
|
|
24
|
+
constructor(scope: Construct, id: string, props: SqsConstructProps);
|
|
25
|
+
private createDeadLetterQueue;
|
|
26
|
+
private createMainQueue;
|
|
27
|
+
private resolveEncryption;
|
|
28
|
+
private addAccessPolicies;
|
|
29
|
+
private createOutputs;
|
|
30
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SqsConstruct = void 0;
|
|
37
|
+
const constructs_1 = require("constructs");
|
|
38
|
+
const cdk = __importStar(require("aws-cdk-lib"));
|
|
39
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
40
|
+
const sqs = __importStar(require("aws-cdk-lib/aws-sqs"));
|
|
41
|
+
const kms = __importStar(require("aws-cdk-lib/aws-kms"));
|
|
42
|
+
const iam = __importStar(require("aws-cdk-lib/aws-iam"));
|
|
43
|
+
/**
|
|
44
|
+
* SQS Construct — สร้าง SQS Queue + Dead Letter Queue
|
|
45
|
+
*
|
|
46
|
+
* สร้าง:
|
|
47
|
+
* 1. Main Queue (Standard หรือ FIFO)
|
|
48
|
+
* 2. Dead Letter Queue (optional) — รับ message ที่ process ไม่สำเร็จ
|
|
49
|
+
* 3. Access Policy (optional) — cross-account permissions
|
|
50
|
+
* 4. Redrive Allow Policy (optional) — สำหรับ DLQ
|
|
51
|
+
*/
|
|
52
|
+
class SqsConstruct extends constructs_1.Construct {
|
|
53
|
+
constructor(scope, id, props) {
|
|
54
|
+
super(scope, id);
|
|
55
|
+
const { config } = props;
|
|
56
|
+
this.removalPolicy = config.removalPolicy === 'retain'
|
|
57
|
+
? aws_cdk_lib_1.RemovalPolicy.RETAIN
|
|
58
|
+
: aws_cdk_lib_1.RemovalPolicy.DESTROY;
|
|
59
|
+
// ------------------------------------------
|
|
60
|
+
// A. Create Dead Letter Queue (ต้องสร้างก่อน Main Queue)
|
|
61
|
+
// ------------------------------------------
|
|
62
|
+
if (config.deadLetterQueue) {
|
|
63
|
+
this.deadLetterQueue = this.createDeadLetterQueue(config);
|
|
64
|
+
this.deadLetterQueueArn = this.deadLetterQueue.queueArn;
|
|
65
|
+
this.deadLetterQueueUrl = this.deadLetterQueue.queueUrl;
|
|
66
|
+
}
|
|
67
|
+
// ------------------------------------------
|
|
68
|
+
// B. Create Main Queue
|
|
69
|
+
// ------------------------------------------
|
|
70
|
+
this.queue = this.createMainQueue(config);
|
|
71
|
+
this.queueArn = this.queue.queueArn;
|
|
72
|
+
this.queueUrl = this.queue.queueUrl;
|
|
73
|
+
// ------------------------------------------
|
|
74
|
+
// C. Access Policies
|
|
75
|
+
// ------------------------------------------
|
|
76
|
+
this.addAccessPolicies(config);
|
|
77
|
+
// ------------------------------------------
|
|
78
|
+
// D. Apply Removal Policy
|
|
79
|
+
// ------------------------------------------
|
|
80
|
+
this.queue.applyRemovalPolicy(this.removalPolicy);
|
|
81
|
+
if (this.deadLetterQueue) {
|
|
82
|
+
this.deadLetterQueue.applyRemovalPolicy(this.removalPolicy);
|
|
83
|
+
}
|
|
84
|
+
// ------------------------------------------
|
|
85
|
+
// E. Outputs
|
|
86
|
+
// ------------------------------------------
|
|
87
|
+
this.createOutputs(config);
|
|
88
|
+
}
|
|
89
|
+
// ==========================================
|
|
90
|
+
// A. Create Dead Letter Queue
|
|
91
|
+
// ==========================================
|
|
92
|
+
createDeadLetterQueue(config) {
|
|
93
|
+
const dlqConfig = config.deadLetterQueue;
|
|
94
|
+
const fifo = config.fifo ?? false;
|
|
95
|
+
// DLQ name: custom หรือ <queueName>-dlq
|
|
96
|
+
let dlqName = dlqConfig.queueName ?? `${config.queueName}-dlq`;
|
|
97
|
+
if (fifo && !dlqName.endsWith('.fifo')) {
|
|
98
|
+
dlqName += '.fifo';
|
|
99
|
+
}
|
|
100
|
+
// Encryption
|
|
101
|
+
const encryption = this.resolveEncryption(config);
|
|
102
|
+
const dlq = new sqs.Queue(this, 'DeadLetterQueue', {
|
|
103
|
+
queueName: dlqName,
|
|
104
|
+
fifo,
|
|
105
|
+
retentionPeriod: cdk.Duration.seconds(dlqConfig.retentionPeriod ?? 1209600), // 14 วัน
|
|
106
|
+
visibilityTimeout: cdk.Duration.seconds(dlqConfig.visibilityTimeout ?? config.visibilityTimeout ?? 30),
|
|
107
|
+
encryption: encryption.type,
|
|
108
|
+
encryptionMasterKey: encryption.key,
|
|
109
|
+
dataKeyReuse: encryption.dataKeyReuse,
|
|
110
|
+
});
|
|
111
|
+
return dlq;
|
|
112
|
+
}
|
|
113
|
+
// ==========================================
|
|
114
|
+
// B. Create Main Queue
|
|
115
|
+
// ==========================================
|
|
116
|
+
createMainQueue(config) {
|
|
117
|
+
const fifo = config.fifo ?? false;
|
|
118
|
+
// Queue name
|
|
119
|
+
let queueName = config.queueName;
|
|
120
|
+
if (fifo && !queueName.endsWith('.fifo')) {
|
|
121
|
+
queueName += '.fifo';
|
|
122
|
+
}
|
|
123
|
+
// Encryption
|
|
124
|
+
const encryption = this.resolveEncryption(config);
|
|
125
|
+
// Dead Letter Queue settings
|
|
126
|
+
let deadLetterQueue;
|
|
127
|
+
if (this.deadLetterQueue && config.deadLetterQueue) {
|
|
128
|
+
deadLetterQueue = {
|
|
129
|
+
queue: this.deadLetterQueue,
|
|
130
|
+
maxReceiveCount: config.deadLetterQueue.maxReceiveCount ?? 3,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const queue = new sqs.Queue(this, 'Queue', {
|
|
134
|
+
queueName,
|
|
135
|
+
fifo,
|
|
136
|
+
contentBasedDeduplication: fifo ? (config.contentBasedDeduplication ?? false) : undefined,
|
|
137
|
+
visibilityTimeout: cdk.Duration.seconds(config.visibilityTimeout ?? 30),
|
|
138
|
+
retentionPeriod: cdk.Duration.seconds(config.retentionPeriod ?? 345600), // 4 วัน
|
|
139
|
+
deliveryDelay: cdk.Duration.seconds(config.deliveryDelay ?? 0),
|
|
140
|
+
receiveMessageWaitTime: cdk.Duration.seconds(config.receiveMessageWaitTime ?? 0),
|
|
141
|
+
maxMessageSizeBytes: config.maxMessageSize ?? 262144, // 256 KB
|
|
142
|
+
deadLetterQueue,
|
|
143
|
+
encryption: encryption.type,
|
|
144
|
+
encryptionMasterKey: encryption.key,
|
|
145
|
+
dataKeyReuse: encryption.dataKeyReuse,
|
|
146
|
+
});
|
|
147
|
+
// FIFO High Throughput — ตั้งค่าผ่าน L1 escape hatch
|
|
148
|
+
if (fifo && config.highThroughputFifo) {
|
|
149
|
+
const cfnQueue = queue.node.defaultChild;
|
|
150
|
+
cfnQueue.addPropertyOverride('DeduplicationScope', 'messageGroup');
|
|
151
|
+
cfnQueue.addPropertyOverride('FifoThroughputLimit', 'perMessageGroupId');
|
|
152
|
+
}
|
|
153
|
+
else if (fifo && config.fifoThroughputLimit === 'perMessageGroupId') {
|
|
154
|
+
const cfnQueue = queue.node.defaultChild;
|
|
155
|
+
cfnQueue.addPropertyOverride('FifoThroughputLimit', 'perMessageGroupId');
|
|
156
|
+
}
|
|
157
|
+
return queue;
|
|
158
|
+
}
|
|
159
|
+
// ==========================================
|
|
160
|
+
// Encryption Helper
|
|
161
|
+
// ==========================================
|
|
162
|
+
resolveEncryption(config) {
|
|
163
|
+
const encConfig = config.encryption;
|
|
164
|
+
const encType = encConfig?.type ?? 'sqs-managed';
|
|
165
|
+
if (encType === 'kms' && encConfig?.kmsKeyArn) {
|
|
166
|
+
return {
|
|
167
|
+
type: sqs.QueueEncryption.KMS,
|
|
168
|
+
key: kms.Key.fromKeyArn(this, 'EncryptionKey', encConfig.kmsKeyArn),
|
|
169
|
+
dataKeyReuse: cdk.Duration.seconds(encConfig.kmsDataKeyReusePeriod ?? 300),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (encType === 'none') {
|
|
173
|
+
return { type: sqs.QueueEncryption.UNENCRYPTED };
|
|
174
|
+
}
|
|
175
|
+
// Default: SQS managed
|
|
176
|
+
return { type: sqs.QueueEncryption.SQS_MANAGED };
|
|
177
|
+
}
|
|
178
|
+
// ==========================================
|
|
179
|
+
// C. Access Policies
|
|
180
|
+
// ==========================================
|
|
181
|
+
addAccessPolicies(config) {
|
|
182
|
+
// Allow SendMessage from specific accounts
|
|
183
|
+
if (config.allowSendFromAccounts && config.allowSendFromAccounts.length > 0) {
|
|
184
|
+
const principals = config.allowSendFromAccounts.map((accountId) => new iam.AccountPrincipal(accountId));
|
|
185
|
+
this.queue.addToResourcePolicy(new iam.PolicyStatement({
|
|
186
|
+
sid: 'AllowSendFromAccounts',
|
|
187
|
+
effect: iam.Effect.ALLOW,
|
|
188
|
+
principals,
|
|
189
|
+
actions: ['sqs:SendMessage'],
|
|
190
|
+
resources: [this.queue.queueArn],
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
// Allow ReceiveMessage + DeleteMessage from specific accounts
|
|
194
|
+
if (config.allowReceiveFromAccounts && config.allowReceiveFromAccounts.length > 0) {
|
|
195
|
+
const principals = config.allowReceiveFromAccounts.map((accountId) => new iam.AccountPrincipal(accountId));
|
|
196
|
+
this.queue.addToResourcePolicy(new iam.PolicyStatement({
|
|
197
|
+
sid: 'AllowReceiveFromAccounts',
|
|
198
|
+
effect: iam.Effect.ALLOW,
|
|
199
|
+
principals,
|
|
200
|
+
actions: [
|
|
201
|
+
'sqs:ReceiveMessage',
|
|
202
|
+
'sqs:DeleteMessage',
|
|
203
|
+
'sqs:GetQueueAttributes',
|
|
204
|
+
],
|
|
205
|
+
resources: [this.queue.queueArn],
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
// Redrive allow policy on DLQ
|
|
209
|
+
if (this.deadLetterQueue && config.redriveAllowPolicy) {
|
|
210
|
+
const rap = config.redriveAllowPolicy;
|
|
211
|
+
if (rap.redrivePermission === 'denyAll') {
|
|
212
|
+
// Deny all queues from using this as DLQ
|
|
213
|
+
const cfnDlq = this.deadLetterQueue.node.defaultChild;
|
|
214
|
+
cfnDlq.addPropertyOverride('RedriveAllowPolicy', {
|
|
215
|
+
redrivePermission: 'denyAll',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
else if (rap.redrivePermission === 'byQueue' && rap.sourceQueueArns) {
|
|
219
|
+
const cfnDlq = this.deadLetterQueue.node.defaultChild;
|
|
220
|
+
cfnDlq.addPropertyOverride('RedriveAllowPolicy', {
|
|
221
|
+
redrivePermission: 'byQueue',
|
|
222
|
+
sourceQueueArns: rap.sourceQueueArns,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
// 'allowAll' is the default — ไม่ต้องตั้งค่า
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// ==========================================
|
|
229
|
+
// E. Outputs
|
|
230
|
+
// ==========================================
|
|
231
|
+
createOutputs(config) {
|
|
232
|
+
const stack = cdk.Stack.of(this);
|
|
233
|
+
const prefix = config.stackName;
|
|
234
|
+
new cdk.CfnOutput(stack, 'QueueUrl', {
|
|
235
|
+
value: this.queue.queueUrl,
|
|
236
|
+
description: 'SQS Queue URL',
|
|
237
|
+
exportName: `${prefix}-SQS-QueueUrl`,
|
|
238
|
+
});
|
|
239
|
+
new cdk.CfnOutput(stack, 'QueueArn', {
|
|
240
|
+
value: this.queue.queueArn,
|
|
241
|
+
description: 'SQS Queue ARN',
|
|
242
|
+
exportName: `${prefix}-SQS-QueueArn`,
|
|
243
|
+
});
|
|
244
|
+
new cdk.CfnOutput(stack, 'QueueName', {
|
|
245
|
+
value: this.queue.queueName,
|
|
246
|
+
description: 'SQS Queue Name',
|
|
247
|
+
exportName: `${prefix}-SQS-QueueName`,
|
|
248
|
+
});
|
|
249
|
+
if (this.deadLetterQueue) {
|
|
250
|
+
new cdk.CfnOutput(stack, 'DlqUrl', {
|
|
251
|
+
value: this.deadLetterQueue.queueUrl,
|
|
252
|
+
description: 'Dead Letter Queue URL',
|
|
253
|
+
exportName: `${prefix}-SQS-DlqUrl`,
|
|
254
|
+
});
|
|
255
|
+
new cdk.CfnOutput(stack, 'DlqArn', {
|
|
256
|
+
value: this.deadLetterQueue.queueArn,
|
|
257
|
+
description: 'Dead Letter Queue ARN',
|
|
258
|
+
exportName: `${prefix}-SQS-DlqArn`,
|
|
259
|
+
});
|
|
260
|
+
new cdk.CfnOutput(stack, 'DlqName', {
|
|
261
|
+
value: this.deadLetterQueue.queueName,
|
|
262
|
+
description: 'Dead Letter Queue Name',
|
|
263
|
+
exportName: `${prefix}-SQS-DlqName`,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
exports.SqsConstruct = SqsConstruct;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Construct } from 'constructs';
|
|
2
|
+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
3
|
+
import { VpcConfig } from '../interfaces/vpc-config';
|
|
4
|
+
export interface VpcConstructProps {
|
|
5
|
+
config: VpcConfig;
|
|
6
|
+
}
|
|
7
|
+
export declare class VpcConstruct extends Construct {
|
|
8
|
+
readonly vpc: ec2.CfnVPC;
|
|
9
|
+
readonly subnets: Map<string, ec2.CfnSubnet>;
|
|
10
|
+
readonly subnetsByName: Map<string, ec2.CfnSubnet[]>;
|
|
11
|
+
readonly routeTables: Map<string, ec2.CfnRouteTable>;
|
|
12
|
+
/** Map: subnetKey (name-az) → routeTableId (CfnRouteTable.ref) */
|
|
13
|
+
readonly subnetRouteTableMap: Map<string, string>;
|
|
14
|
+
readonly natGateways: ec2.CfnNatGateway[];
|
|
15
|
+
private igw?;
|
|
16
|
+
private readonly removalPolicy;
|
|
17
|
+
constructor(scope: Construct, id: string, props: VpcConstructProps);
|
|
18
|
+
private createRoutesForRouteTable;
|
|
19
|
+
private createDefaultRoutes;
|
|
20
|
+
private createRoute;
|
|
21
|
+
/**
|
|
22
|
+
* Get routeTableId for a subnet by name and az
|
|
23
|
+
* ลองหา key "name-az" ก่อน, ถ้าไม่เจอ fallback ไป "name" (routeTablePerAz: false)
|
|
24
|
+
*/
|
|
25
|
+
getRouteTableId(subnetName: string, az: string): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Export VPC and subnet IDs for cross-stack references (e.g., Network Security Stack)
|
|
28
|
+
*/
|
|
29
|
+
exportForSecurityStack(stackName: string): void;
|
|
30
|
+
}
|