@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 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 to RDS
365
+ ## EC2 server with access rules
366
366
 
367
- The EC2withDatabase construct builds an EC2 server similar to those used in a large number of our services. The basic concept is to build the server with security group access to an AWS RDS database server. The server is built within an existing VPC. Parameters allow for AMI ID, instance type, root disk storage, IP address, 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.
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
- cnameList: [ Array of cnames/additional IPs to be added ],
391
+ CnameList: [ Array of cnames/additional IPs to be added ],
391
392
  volumeSize: Number (in GB),
392
393
  vpcId: "Valid VPC ID",
393
- sg_ingress_db: 'Security Group of database for access',
394
- sg_ingress_db_port: PortNumber,
395
- sg_ingress_rules: [
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 { aws_ec2, Stack, StackProps } from 'aws-cdk-lib';
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 IIngress_Rule {
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 instanceClass: aws_ec2.InstanceClass;
12
- readonly instanceSize: aws_ec2.InstanceSize;
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 sg_ingress_rules: IIngress_Rule[];
20
- readonly sg_ingress_db: string;
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
- // Determine the VPC from the availability zones and subnets passed in
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
- // Create a security group for this specific server using rules passed in
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
- // Add connections from the new security group to the RDS which will be used by this server
32
- const db_connect_SG = aws_cdk_lib_1.aws_ec2.SecurityGroup.fromSecurityGroupId(this, 'SG', props.sg_ingress_db);
33
- ec2SecurityGroup.connections.allowFrom(db_connect_SG, aws_cdk_lib_1.aws_ec2.Port.tcp(props.sg_ingress_db_port), 'Database SG Access');
34
- ec2SecurityGroup.connections.allowTo(db_connect_SG, aws_cdk_lib_1.aws_ec2.Port.tcp(props.sg_ingress_db_port), 'Database SG Access');
35
- props.sg_ingress_rules.forEach((rule) => {
36
- ec2SecurityGroup.addIngressRule(aws_cdk_lib_1.aws_ec2.Peer.ipv4(rule.ipv4), aws_cdk_lib_1.aws_ec2.Port.tcp(rule.port), rule.description);
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
- // keyName: props.keyName, // deprecated, replaced with keyPair below.
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
- // Then tag the EIP
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(props.privateIpAddress),
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(props.privateIpAddress),
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(props.privateIpAddress),
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
- let auroraParameters = {};
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.29",
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.0",
45
- "@typescript-eslint/eslint-plugin": "^8.16.0",
46
- "@typescript-eslint/parser": "^8.16.0",
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.15.0",
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.14.0",
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.1",
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.171.0",
67
+ "aws-cdk-lib": "^2.173.2",
68
68
  "constructs": "^10.4.2",
69
69
  "node-fetch": "^3.3.2"
70
70
  },