@ndlib/ndlib-cdk2 1.0.29 → 1.0.31
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 +15 -9
- package/lib/ec2s/ec2-with-database.d.ts +14 -8
- package/lib/ec2s/ec2-with-database.js +95 -15
- package/lib/rds/postgres-stack.js +2 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -362,9 +362,9 @@ new SourceWatcher(stack, 'TestProject', {
|
|
|
362
362
|
NOTE: `webhookResourceStackName` refers to a stack which will manage contains the backend for a CustomResource webhook. Prior to using this construct, an instance of [ndlib/aws-github-webhook](https://github.com/ndlib/aws-github-webhook) should be deployed to the AWS account. One stack can be used for any number of SourceWatcher constructs.
|
|
363
363
|
|
|
364
364
|
|
|
365
|
-
## EC2 server with access
|
|
365
|
+
## EC2 server with access rules
|
|
366
366
|
|
|
367
|
-
The EC2withDatabase construct builds an EC2 server
|
|
367
|
+
The EC2withDatabase construct builds an EC2 server. The basic concept is to build the server with security group access to, possibly, several AWS RDS database servers. The server is built within an existing VPC. Parameters allow for AMI ID, instance type, root disk storage, networking, and security group rules for the server upon build. The server is created with the OS only; further configuration will need to be performed, often using ansible.
|
|
368
368
|
|
|
369
369
|
Example usage:
|
|
370
370
|
|
|
@@ -380,22 +380,28 @@ new EC2withDatabase (app, 'StackName', {
|
|
|
380
380
|
env: { account: 'AccountID', region: 'us-east-1' },
|
|
381
381
|
amiId: "Valid AMI Id",
|
|
382
382
|
availabilityZones: [ "us-east-1c" ],
|
|
383
|
+
backup: 'True',
|
|
383
384
|
instanceClass: ec2.InstanceClass.T3A,
|
|
384
385
|
instanceSize: ec2.InstanceSize.MEDIUM,
|
|
385
386
|
instanceName: "InstanceName",
|
|
386
387
|
keyName: "libnd",
|
|
387
388
|
privateIpAddress: "IPAddress",
|
|
388
|
-
publicSubnetIds: [ "ValidSubnet in VPC" ],
|
|
389
|
+
publicSubnetIds: [ "ValidSubnet in VPC that matches up with AvailabilityZone" ],
|
|
389
390
|
domainName: "Local domain name",
|
|
390
|
-
|
|
391
|
+
CnameList: [ Array of cnames/additional IPs to be added ],
|
|
391
392
|
volumeSize: Number (in GB),
|
|
392
393
|
vpcId: "Valid VPC ID",
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
394
|
+
SGDBAccessRules: [
|
|
395
|
+
{
|
|
396
|
+
"database": "sg-088a1b9d4918effb3", // Security groups that provide access to an RDS database
|
|
397
|
+
"port": 3306, // Can be multiple or none at all
|
|
398
|
+
"description": "MySQL access from jumpbox"
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
SGIngressRules: [
|
|
396
402
|
{
|
|
397
|
-
"ipv4": "10.32.0.0/11",
|
|
398
|
-
"port": 22,
|
|
403
|
+
"ipv4": "10.32.0.0/11", // Additional access rules to allow connection to the server
|
|
404
|
+
"port": 22, // Can be multiple or none at all
|
|
399
405
|
"description": "SSH access from campus"
|
|
400
406
|
},
|
|
401
407
|
],
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Stack, StackProps } from 'aws-cdk-lib';
|
|
2
|
+
import { InstanceClass, InstanceSize } from 'aws-cdk-lib/aws-ec2';
|
|
2
3
|
import { Construct } from 'constructs';
|
|
3
|
-
export interface
|
|
4
|
+
export interface AccessRule {
|
|
5
|
+
readonly database: string;
|
|
6
|
+
readonly port: number;
|
|
7
|
+
readonly description: string;
|
|
8
|
+
}
|
|
9
|
+
export interface IIngressRule {
|
|
4
10
|
readonly ipv4: string;
|
|
5
11
|
readonly port: number;
|
|
6
12
|
readonly description: string;
|
|
@@ -8,17 +14,17 @@ export interface IIngress_Rule {
|
|
|
8
14
|
export interface ServiceStackProps extends StackProps {
|
|
9
15
|
readonly amiId: string;
|
|
10
16
|
readonly availabilityZones: string[];
|
|
11
|
-
readonly
|
|
12
|
-
readonly
|
|
17
|
+
readonly backup?: string;
|
|
18
|
+
readonly backupAccountId?: string;
|
|
19
|
+
readonly instanceClass: InstanceClass;
|
|
20
|
+
readonly instanceSize: InstanceSize;
|
|
13
21
|
readonly instanceName: string;
|
|
14
22
|
readonly keyName: string;
|
|
15
|
-
readonly privateIpAddress: string;
|
|
16
23
|
readonly publicSubnetIds: string[];
|
|
17
24
|
readonly volumeSize: number;
|
|
18
25
|
readonly vpcId: string;
|
|
19
|
-
readonly
|
|
20
|
-
readonly
|
|
21
|
-
readonly sg_ingress_db_port: number;
|
|
26
|
+
readonly SGIngressRules?: IIngressRule[];
|
|
27
|
+
readonly SGDBAccessRules?: AccessRule[];
|
|
22
28
|
readonly domainName: string;
|
|
23
29
|
readonly privateDomainName: string;
|
|
24
30
|
readonly CnameList: string[];
|
|
@@ -4,11 +4,26 @@ exports.EC2withDatabase = void 0;
|
|
|
4
4
|
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
5
5
|
const aws_cdk_lib_2 = require("aws-cdk-lib");
|
|
6
6
|
const ssm = require("aws-cdk-lib/aws-ssm");
|
|
7
|
+
const aws_kms_1 = require("aws-cdk-lib/aws-kms");
|
|
8
|
+
const aws_iam_1 = require("aws-cdk-lib/aws-iam");
|
|
7
9
|
const aws_route53_1 = require("aws-cdk-lib/aws-route53");
|
|
8
10
|
class EC2withDatabase extends aws_cdk_lib_1.Stack {
|
|
9
11
|
constructor(scope, id, props) {
|
|
12
|
+
var _a, _b, _c, _d;
|
|
10
13
|
super(scope, id, props);
|
|
11
|
-
|
|
14
|
+
/*
|
|
15
|
+
* Security Group setup and usage are optional parameters
|
|
16
|
+
* SGDBAccessRules = RDS database Access group rules (can be multiple)
|
|
17
|
+
* SGIngressRules = Ingress rules for additional access (can be multiple)
|
|
18
|
+
*/
|
|
19
|
+
props = {
|
|
20
|
+
...props,
|
|
21
|
+
backup: (_a = props.backup) !== null && _a !== void 0 ? _a : 'False',
|
|
22
|
+
backupAccountId: (_b = props.backupAccountId) !== null && _b !== void 0 ? _b : '140023380087',
|
|
23
|
+
SGDBAccessRules: (_c = props.SGDBAccessRules) !== null && _c !== void 0 ? _c : [],
|
|
24
|
+
SGIngressRules: (_d = props.SGIngressRules) !== null && _d !== void 0 ? _d : [],
|
|
25
|
+
};
|
|
26
|
+
// Determine the VPC from the availability zone and subnet passed in. These are expected to align within the VPC
|
|
12
27
|
const vpc = aws_cdk_lib_1.aws_ec2.Vpc.fromVpcAttributes(this, 'Vpc', {
|
|
13
28
|
vpcId: props.vpcId,
|
|
14
29
|
availabilityZones: props.availabilityZones,
|
|
@@ -18,23 +33,78 @@ class EC2withDatabase extends aws_cdk_lib_1.Stack {
|
|
|
18
33
|
const linux_build = aws_cdk_lib_1.aws_ec2.MachineImage.genericLinux({
|
|
19
34
|
'us-east-1': props.amiId,
|
|
20
35
|
});
|
|
21
|
-
// Need to look up the role name and then get the ARN value
|
|
36
|
+
// Need to look up the basic role name and then get the ARN value
|
|
22
37
|
const basic_role = ssm.StringParameter.valueFromLookup(this, '/esu/ec2/basic_role_name');
|
|
23
38
|
const roleName = `arn:aws:iam::${this.account}:${basic_role}`;
|
|
24
39
|
const ec2role = aws_cdk_lib_1.aws_iam.Role.fromRoleArn(this, 'Role', roleName);
|
|
25
|
-
|
|
40
|
+
/*
|
|
41
|
+
* Create a security group for this specific server and then add the SGIngressRules passed in
|
|
42
|
+
*/
|
|
26
43
|
const ec2SecurityGroup = new aws_cdk_lib_1.aws_ec2.SecurityGroup(this, props.instanceName, {
|
|
27
44
|
description: 'Security Group for $(props.instanceName)',
|
|
28
45
|
allowAllOutbound: true,
|
|
29
46
|
vpc: vpc,
|
|
30
47
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
if (props.SGIngressRules) {
|
|
49
|
+
props.SGIngressRules.forEach((rule) => {
|
|
50
|
+
ec2SecurityGroup.addIngressRule(aws_cdk_lib_1.aws_ec2.Peer.ipv4(rule.ipv4), aws_cdk_lib_1.aws_ec2.Port.tcp(rule.port), rule.description);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Create a key to allow volume and instance to be backed up
|
|
54
|
+
const backupKey = new aws_kms_1.Key(this, 'BackupKey', {
|
|
55
|
+
enableKeyRotation: true,
|
|
56
|
+
alias: props.instanceName,
|
|
57
|
+
});
|
|
58
|
+
const backupAccountId = props.backupAccountId; // Our backup account ID
|
|
59
|
+
const currentAccountId = this.account; // Current account ID
|
|
60
|
+
const policyStatement = new aws_iam_1.PolicyStatement({
|
|
61
|
+
actions: [
|
|
62
|
+
'kms:CreateGrant',
|
|
63
|
+
'kms:ListGrants',
|
|
64
|
+
'kms:RevokeGrant',
|
|
65
|
+
'kms:Encrypt',
|
|
66
|
+
'kms:Decrypt',
|
|
67
|
+
'kms:ReEncrypt*',
|
|
68
|
+
'kms:GenerateDataKey',
|
|
69
|
+
'kms:GenerateDataKeyPair',
|
|
70
|
+
'kms:DescribeKey',
|
|
71
|
+
'kms:GenerateDataKeyWithoutPlaintext',
|
|
72
|
+
'kms:GenerateDataKeyPairWithoutPlaintext',
|
|
73
|
+
],
|
|
74
|
+
principals: [
|
|
75
|
+
new aws_iam_1.AccountPrincipal(backupAccountId),
|
|
76
|
+
new aws_iam_1.AccountPrincipal(currentAccountId),
|
|
77
|
+
],
|
|
78
|
+
resources: ['*'],
|
|
37
79
|
});
|
|
80
|
+
backupKey.addToResourcePolicy(policyStatement);
|
|
81
|
+
// Allow attachment of persistent resources for cross-account backup
|
|
82
|
+
backupKey.addToResourcePolicy(new aws_iam_1.PolicyStatement({
|
|
83
|
+
sid: 'Allow attachment of persistent resources',
|
|
84
|
+
principals: [
|
|
85
|
+
new aws_iam_1.AccountPrincipal(`${backupAccountId}`),
|
|
86
|
+
new aws_iam_1.AccountPrincipal(`${currentAccountId}`),
|
|
87
|
+
],
|
|
88
|
+
actions: [
|
|
89
|
+
'kms:CreateGrant',
|
|
90
|
+
'kms:ListGrants',
|
|
91
|
+
'kms:RevokeGrant',
|
|
92
|
+
],
|
|
93
|
+
effect: aws_iam_1.Effect.ALLOW,
|
|
94
|
+
resources: ['*'],
|
|
95
|
+
conditions: { 'Bool': { 'kms:GrantIsForAWSResource': 'true' } },
|
|
96
|
+
}));
|
|
97
|
+
/*
|
|
98
|
+
* Add connections from the new security group to the RDS which will be used by this server using
|
|
99
|
+
* SGDBAccessRules passed in
|
|
100
|
+
*/
|
|
101
|
+
if (props.SGDBAccessRules) {
|
|
102
|
+
props.SGDBAccessRules.forEach((rule) => {
|
|
103
|
+
const db_connect_SG = aws_cdk_lib_1.aws_ec2.SecurityGroup.fromSecurityGroupId(this, `SGAccess${rule.database}`, rule.database);
|
|
104
|
+
ec2SecurityGroup.connections.allowFrom(db_connect_SG, aws_cdk_lib_1.aws_ec2.Port.tcp(rule.port), rule.description);
|
|
105
|
+
ec2SecurityGroup.connections.allowTo(db_connect_SG, aws_cdk_lib_1.aws_ec2.Port.tcp(rule.port), rule.description);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
38
108
|
// With all of the information gathered, as well as values passed in, create the server
|
|
39
109
|
const ec2_server = new aws_cdk_lib_1.aws_ec2.Instance(this, 'ec2-server', {
|
|
40
110
|
blockDevices: [{
|
|
@@ -43,6 +113,7 @@ class EC2withDatabase extends aws_cdk_lib_1.Stack {
|
|
|
43
113
|
ebsDevice: {
|
|
44
114
|
volumeSize: props.volumeSize,
|
|
45
115
|
encrypted: true,
|
|
116
|
+
kmsKey: backupKey,
|
|
46
117
|
},
|
|
47
118
|
},
|
|
48
119
|
}],
|
|
@@ -50,24 +121,33 @@ class EC2withDatabase extends aws_cdk_lib_1.Stack {
|
|
|
50
121
|
role: ec2role,
|
|
51
122
|
securityGroup: ec2SecurityGroup,
|
|
52
123
|
machineImage: linux_build,
|
|
53
|
-
|
|
124
|
+
propagateTagsToVolumeOnCreation: true,
|
|
54
125
|
keyPair: aws_cdk_lib_1.aws_ec2.KeyPair.fromKeyPairName(this, 'KeyPair', props.keyName),
|
|
55
126
|
instanceType: aws_cdk_lib_1.aws_ec2.InstanceType.of(props.instanceClass, props.instanceSize),
|
|
56
127
|
instanceName: props.instanceName,
|
|
57
|
-
privateIpAddress: props.privateIpAddress,
|
|
58
128
|
});
|
|
59
129
|
// Add an Elastic IP Address to the server
|
|
60
130
|
const IPAddr = new aws_cdk_lib_1.aws_ec2.CfnEIP(this, 'Ip', {
|
|
61
131
|
instanceId: ec2_server.instanceId,
|
|
62
132
|
});
|
|
63
|
-
//
|
|
133
|
+
// Tag the server and the EIP with the instance name and FQDN
|
|
134
|
+
const FQDN = `${props.instanceName}.${props.domainName}`;
|
|
135
|
+
const schedule = '*'; // Initially the schedule is always up
|
|
136
|
+
const service = props.instanceName; // Initially the service is the instance name
|
|
137
|
+
// Then tag the EIP and the server
|
|
64
138
|
aws_cdk_lib_1.Tags.of(IPAddr).add('Name', props.instanceName);
|
|
139
|
+
aws_cdk_lib_1.Tags.of(ec2_server).add('FQDN', FQDN);
|
|
140
|
+
aws_cdk_lib_1.Tags.of(ec2_server).add('Schedule', schedule);
|
|
141
|
+
aws_cdk_lib_1.Tags.of(ec2_server).add('Service', service);
|
|
142
|
+
if (props.backup) {
|
|
143
|
+
aws_cdk_lib_1.Tags.of(ec2_server).add('Backup', props.backup);
|
|
144
|
+
}
|
|
65
145
|
// Create DNS for libnd.nd.edu connection
|
|
66
146
|
const privateZoneId = ssm.StringParameter.fromStringParameterAttributes(this, 'PrivateZoneId', {
|
|
67
147
|
parameterName: `/all/dns/${props.keyName}/private/zoneId`,
|
|
68
148
|
}).stringValue;
|
|
69
149
|
new aws_route53_1.ARecord(this, 'PrivateARec', {
|
|
70
|
-
target: aws_route53_1.RecordTarget.fromIpAddresses(
|
|
150
|
+
target: aws_route53_1.RecordTarget.fromIpAddresses(ec2_server.instancePrivateIp),
|
|
71
151
|
recordName: props.instanceName,
|
|
72
152
|
zone: aws_route53_1.HostedZone.fromHostedZoneAttributes(this, 'PrivateHostedZone', {
|
|
73
153
|
hostedZoneId: privateZoneId,
|
|
@@ -109,7 +189,7 @@ class EC2withDatabase extends aws_cdk_lib_1.Stack {
|
|
|
109
189
|
parameterName: `/all/dns/${props.domainName}/${direction}/zoneId`,
|
|
110
190
|
}).stringValue;
|
|
111
191
|
new aws_route53_1.ARecord(this, `${direction}ServiceARec`, {
|
|
112
|
-
target: aws_route53_1.RecordTarget.fromIpAddresses(
|
|
192
|
+
target: aws_route53_1.RecordTarget.fromIpAddresses(ec2_server.instancePrivateIp),
|
|
113
193
|
recordName: props.instanceName,
|
|
114
194
|
zone: aws_route53_1.HostedZone.fromHostedZoneAttributes(this, `${direction}ImportedHostedZone`, {
|
|
115
195
|
hostedZoneId: hostedZoneId,
|
|
@@ -120,7 +200,7 @@ class EC2withDatabase extends aws_cdk_lib_1.Stack {
|
|
|
120
200
|
if (props.CnameList.length != 0) {
|
|
121
201
|
props.CnameList.forEach(cname => {
|
|
122
202
|
new aws_route53_1.ARecord(this, `${direction}${cname}`, {
|
|
123
|
-
target: aws_route53_1.RecordTarget.fromIpAddresses(
|
|
203
|
+
target: aws_route53_1.RecordTarget.fromIpAddresses(ec2_server.instancePrivateIp),
|
|
124
204
|
recordName: cname,
|
|
125
205
|
zone: aws_route53_1.HostedZone.fromHostedZoneAttributes(this, `${direction}${cname}HostedZone`, {
|
|
126
206
|
hostedZoneId: hostedZoneId,
|
|
@@ -37,7 +37,7 @@ class PostgresRDSConstruct extends constructs_1.Construct {
|
|
|
37
37
|
},
|
|
38
38
|
});
|
|
39
39
|
const parameterGroupDescription = `Parameter Group for ${props.dbClusterIdentifier}`;
|
|
40
|
-
|
|
40
|
+
const auroraParameters = {};
|
|
41
41
|
auroraParameters['timezone'] = 'US/Eastern';
|
|
42
42
|
auroraParameters['idle_session_timeout'] = '60000'; // 1 minute idle timeout for any given session
|
|
43
43
|
const libParameterGroup = new aws_rds_1.ParameterGroup(this, 'LibParameterGroup', {
|
|
@@ -87,6 +87,7 @@ class PostgresRDSConstruct extends constructs_1.Construct {
|
|
|
87
87
|
storageEncrypted: true,
|
|
88
88
|
parameterGroup: libParameterGroup,
|
|
89
89
|
vpc: vpc,
|
|
90
|
+
enableDataApi: true,
|
|
90
91
|
storageType: aws_rds_1.DBClusterStorageType.AURORA_IOPT1,
|
|
91
92
|
deletionProtection: true,
|
|
92
93
|
writer: aws_rds_1.ClusterInstance.serverlessV2('writer'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ndlib/ndlib-cdk2",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.31",
|
|
4
4
|
"description": "Reusable CDK2 modules used within Hesburgh Libraries of Notre Dame",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -41,20 +41,20 @@
|
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/jest": "^29.5.14",
|
|
44
|
-
"@types/node": "^22.10.
|
|
45
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
46
|
-
"@typescript-eslint/parser": "^8.
|
|
44
|
+
"@types/node": "^22.10.2",
|
|
45
|
+
"@typescript-eslint/eslint-plugin": "^8.18.1",
|
|
46
|
+
"@typescript-eslint/parser": "^8.18.1",
|
|
47
47
|
"aws-sdk-client-mock": "^4.1.0",
|
|
48
|
-
"eslint": "^9.
|
|
48
|
+
"eslint": "^9.17.0",
|
|
49
49
|
"eslint-plugin-import": "^2.31.0",
|
|
50
50
|
"eslint-plugin-jest": "^28.9.0",
|
|
51
|
-
"eslint-plugin-n": "^17.
|
|
51
|
+
"eslint-plugin-n": "^17.15.0",
|
|
52
52
|
"eslint-plugin-node": "^11.1.0",
|
|
53
53
|
"eslint-plugin-promise": "^7.2.1",
|
|
54
54
|
"github-changes": "^2.0.3",
|
|
55
55
|
"jest": "^29.7.0",
|
|
56
56
|
"jest-mock": "^29.7.0",
|
|
57
|
-
"prettier": "^3.4.
|
|
57
|
+
"prettier": "^3.4.2",
|
|
58
58
|
"subpackage": "^1.1.0",
|
|
59
59
|
"ts-jest": "^29.2.5",
|
|
60
60
|
"tsc-watch": "^6.2.1",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"lib/**/*"
|
|
65
65
|
],
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"aws-cdk-lib": "^2.
|
|
67
|
+
"aws-cdk-lib": "^2.173.2",
|
|
68
68
|
"constructs": "^10.4.2",
|
|
69
69
|
"node-fetch": "^3.3.2"
|
|
70
70
|
},
|