@fjall/components-infrastructure 2.24.0 → 2.25.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.
@@ -56,6 +56,7 @@ export declare class RdsAurora extends Construct implements IConnectable {
56
56
  connections: Connections;
57
57
  private readonly constructId;
58
58
  private port;
59
+ private clientPort;
59
60
  private engineConfig;
60
61
  private databaseCluster;
61
62
  private databaseProxy?;
@@ -1,4 +1,5 @@
1
- import { Duration, RemovalPolicy } from "aws-cdk-lib";
1
+ import { Annotations, Duration, RemovalPolicy } from "aws-cdk-lib";
2
+ import { ENGINE_DEFAULT_DATABASE_PORTS } from "@fjall/generator";
2
3
  import { Connections, Peer, Port, SubnetType } from "aws-cdk-lib/aws-ec2";
3
4
  import { AuroraPostgresEngineVersion, CaCertificate, ClusterInstance, Credentials, DatabaseCluster, DatabaseClusterFromSnapshot, DatabaseClusterEngine, DatabaseProxy, ParameterGroup, ProxyTarget, SnapshotCredentials } from "aws-cdk-lib/aws-rds";
4
5
  import { Construct } from "constructs";
@@ -14,6 +15,7 @@ export class RdsAurora extends Construct {
14
15
  connections;
15
16
  constructId;
16
17
  port;
18
+ clientPort;
17
19
  engineConfig;
18
20
  databaseCluster;
19
21
  databaseProxy;
@@ -23,10 +25,16 @@ export class RdsAurora extends Construct {
23
25
  constructor(scope, id, props) {
24
26
  super(scope, id);
25
27
  this.constructId = id;
26
- this.port = props.port ?? RDS_DEFAULTS.DEFAULT_PORT;
27
28
  this.databaseNameValue = props.databaseName ?? id;
28
29
  // PostgreSQL fallback for direct usage - ensure engine and engineConfig match
29
30
  this.engineConfig = props.engineConfig ?? DEFAULT_POSTGRES_ENGINE_CONFIG;
31
+ const engineDefaultPort = ENGINE_DEFAULT_DATABASE_PORTS[this.engineConfig.family];
32
+ this.port = props.port ?? engineDefaultPort;
33
+ // RDS Proxy listens exclusively on the engine-default port (CreateDBProxy
34
+ // has no port parameter), so the client-facing surface must advertise it
35
+ // whenever a proxy fronts the cluster.
36
+ const proxyEnabled = props.proxy !== undefined && props.proxy !== false;
37
+ this.clientPort = proxyEnabled ? engineDefaultPort : this.port;
30
38
  const { piEnabled, piConfig } = resolveDatabaseInsights(props.databaseInsights);
31
39
  // Username priority: snapshotUsername > credentials.username > engine default
32
40
  const username = props.snapshotIdentifier && props.snapshotUsername
@@ -158,7 +166,6 @@ export class RdsAurora extends Construct {
158
166
  this.cfnCluster.masterUserPassword = undefined;
159
167
  this.cfnCluster.databaseName = undefined;
160
168
  }
161
- const proxyEnabled = props.proxy !== undefined && props.proxy !== false;
162
169
  if (proxyEnabled) {
163
170
  this.addProxy(props, clusterSecurityGroup);
164
171
  }
@@ -214,18 +221,26 @@ export class RdsAurora extends Construct {
214
221
  });
215
222
  });
216
223
  }
217
- addProxy(props, securityGroup) {
224
+ addProxy(props, clusterSecurityGroup) {
218
225
  if (!props.proxy)
219
226
  return;
220
227
  const proxyConfig = props.proxy;
221
228
  const vpcSubnets = proxyConfig.vpcSubnets ?? {
222
229
  subnetType: SubnetType.PRIVATE_WITH_EGRESS
223
230
  };
231
+ const proxySecurityGroup = new SecurityGroup(this, `${this.databaseNameValue}ProxySecurityGroup`, {
232
+ vpc: props.vpc,
233
+ description: `Security group for RDS Proxy ${this.databaseNameValue}`
234
+ });
235
+ clusterSecurityGroup.addIngressRule(proxySecurityGroup, Port.tcp(this.port), "Allow RDS Proxy to connect to Aurora cluster");
236
+ if (props.allowVpcAccess) {
237
+ proxySecurityGroup.addIngressRule(Peer.ipv4(props.vpc.vpcCidrBlock), Port.tcp(this.clientPort), "Allow access from VPC CIDR");
238
+ }
224
239
  this.databaseProxy = new DatabaseProxy(this, `${this.databaseNameValue}DatabaseProxy`, {
225
240
  dbProxyName: ResourceNaming.proxyName(this.constructId),
226
241
  proxyTarget: ProxyTarget.fromCluster(this.databaseCluster),
227
242
  secrets: [this.databaseCredentials.secret],
228
- securityGroups: [securityGroup],
243
+ securityGroups: [proxySecurityGroup],
229
244
  vpc: props.vpc,
230
245
  vpcSubnets,
231
246
  requireTLS: proxyConfig.requireTLS ?? true,
@@ -235,6 +250,19 @@ export class RdsAurora extends Construct {
235
250
  maxConnectionsPercent: proxyConfig.maxConnections,
236
251
  maxIdleConnectionsPercent: proxyConfig.maxIdleConnections
237
252
  });
253
+ // The proxy is now the client-facing surface: grantConnect /
254
+ // allowDefaultPortFrom must open the PROXY security group on the proxy's
255
+ // fixed engine-default listener port, not the cluster SG on the DB port.
256
+ this.connections = new Connections({
257
+ securityGroups: [proxySecurityGroup],
258
+ defaultPort: Port.tcp(this.clientPort)
259
+ });
260
+ if (this.port !== this.clientPort) {
261
+ Annotations.of(this).addWarning(`Database '${this.databaseNameValue}' listens on port ${this.port}, but its ` +
262
+ `RDS Proxy accepts client connections only on ${this.clientPort} (the engine ` +
263
+ `default — AWS does not allow configuring the proxy listener port). Fjall ` +
264
+ `advertises ${this.clientPort}; direct-to-database access uses ${this.port}.`);
265
+ }
238
266
  addProxyCfnOutput(this, this.constructId, this.databaseNameValue, this.databaseProxy);
239
267
  }
240
268
  addSecretRotation(props) {
@@ -257,7 +285,7 @@ export class RdsAurora extends Construct {
257
285
  return this.databaseCluster.clusterEndpoint.hostname;
258
286
  }
259
287
  getHostPort() {
260
- return String(this.port);
288
+ return String(this.clientPort);
261
289
  }
262
290
  getCredentials() {
263
291
  return this.databaseCredentials;
@@ -43,7 +43,7 @@ interface RdsAuroraGlobalProps {
43
43
  monitoringInterval?: Duration;
44
44
  /** Preferred maintenance window. Default: "Sat:12:30-Sat:20:30" */
45
45
  preferredMaintenanceWindow?: string;
46
- /** Database port. Default: RDS_DEFAULTS.DEFAULT_PORT */
46
+ /** Database port. Default: engine default (5432 postgresql / 3306 mysql) */
47
47
  port?: number;
48
48
  databaseInsights?: DatabaseInsightsConfig | false;
49
49
  deletionProtection?: boolean;
@@ -1,7 +1,5 @@
1
1
  import { Duration } from "aws-cdk-lib";
2
2
  export declare const RDS_DEFAULTS: Readonly<{
3
- /** Default database port: FJALL on phone keypad */
4
- readonly DEFAULT_PORT: 35255;
5
3
  /** Saturday window in UTC — minimises weekday impact for scheduled maintenance */
6
4
  readonly PREFERRED_MAINTENANCE_WINDOW: "Sat:12:30-Sat:20:30";
7
5
  /** 1 minute — balances monitoring granularity against CloudWatch costs */
@@ -1,7 +1,5 @@
1
1
  import { Duration } from "aws-cdk-lib";
2
2
  export const RDS_DEFAULTS = Object.freeze({
3
- /** Default database port: FJALL on phone keypad */
4
- DEFAULT_PORT: 35255,
5
3
  /** Saturday window in UTC — minimises weekday impact for scheduled maintenance */
6
4
  PREFERRED_MAINTENANCE_WINDOW: "Sat:12:30-Sat:20:30",
7
5
  /** 1 minute — balances monitoring granularity against CloudWatch costs */
@@ -57,6 +57,7 @@ export declare class RdsInstance extends Construct implements IConnectable {
57
57
  databaseSecurityGroup: SecurityGroup;
58
58
  vpc: IVpc;
59
59
  private port;
60
+ private clientPort;
60
61
  private engineConfig;
61
62
  private databaseCredentials;
62
63
  private database;
@@ -1,4 +1,5 @@
1
- import { Duration, RemovalPolicy } from "aws-cdk-lib";
1
+ import { Annotations, Duration, RemovalPolicy } from "aws-cdk-lib";
2
+ import { ENGINE_DEFAULT_DATABASE_PORTS } from "@fjall/generator";
2
3
  import { Connections, InstanceType, Port, SubnetType } from "aws-cdk-lib/aws-ec2";
3
4
  import { CaCertificate, Credentials, DatabaseInstance, DatabaseInstanceFromSnapshot, DatabaseInstanceEngine, DatabaseInstanceReadReplica, DatabaseProxy, ParameterGroup, PostgresEngineVersion, ProxyTarget, SnapshotCredentials, StorageType } from "aws-cdk-lib/aws-rds";
4
5
  import { Runtime } from "aws-cdk-lib/aws-lambda";
@@ -19,6 +20,7 @@ export class RdsInstance extends Construct {
19
20
  databaseSecurityGroup;
20
21
  vpc;
21
22
  port;
23
+ clientPort;
22
24
  engineConfig;
23
25
  databaseCredentials;
24
26
  database;
@@ -31,11 +33,17 @@ export class RdsInstance extends Construct {
31
33
  constructor(scope, id, props) {
32
34
  super(scope, id);
33
35
  this.constructId = id;
34
- this.port = props.port ?? RDS_DEFAULTS.DEFAULT_PORT;
35
36
  this.vpc = props.vpc;
36
37
  this.databaseNameValue = props.databaseName ?? id.replace("Rds", "");
37
38
  // PostgreSQL fallback for direct usage - ensure engine and engineConfig match
38
39
  this.engineConfig = props.engineConfig ?? DEFAULT_POSTGRES_ENGINE_CONFIG;
40
+ const engineDefaultPort = ENGINE_DEFAULT_DATABASE_PORTS[this.engineConfig.family];
41
+ this.port = props.port ?? engineDefaultPort;
42
+ // RDS Proxy listens exclusively on the engine-default port (CreateDBProxy
43
+ // has no port parameter), so the client-facing surface must advertise it
44
+ // whenever a proxy fronts the database.
45
+ const proxyEnabled = props.proxy !== undefined && props.proxy !== false;
46
+ this.clientPort = proxyEnabled ? engineDefaultPort : this.port;
39
47
  this.addDatabase(props);
40
48
  // Secret rotation enabled by default (opt-out with secretRotation: false)
41
49
  const secretRotationDisabled = props.credentials?.secretRotation === false;
@@ -196,6 +204,19 @@ export class RdsInstance extends Construct {
196
204
  maxConnectionsPercent: proxyConfig.maxConnections,
197
205
  maxIdleConnectionsPercent: proxyConfig.maxIdleConnections
198
206
  });
207
+ // The proxy is now the client-facing surface: grantConnect /
208
+ // allowDefaultPortFrom must open the PROXY security group on the proxy's
209
+ // fixed engine-default listener port, not the database SG on the DB port.
210
+ this.connections = new Connections({
211
+ securityGroups: [this.databaseProxySecurityGroup],
212
+ defaultPort: Port.tcp(this.clientPort)
213
+ });
214
+ if (this.port !== this.clientPort) {
215
+ Annotations.of(this).addWarning(`Database '${this.databaseNameValue}' listens on port ${this.port}, but its ` +
216
+ `RDS Proxy accepts client connections only on ${this.clientPort} (the engine ` +
217
+ `default — AWS does not allow configuring the proxy listener port). Fjall ` +
218
+ `advertises ${this.clientPort}; direct-to-database access uses ${this.port}.`);
219
+ }
199
220
  addProxyCfnOutput(this, this.constructId, this.databaseNameValue, this.databaseProxy);
200
221
  }
201
222
  addReadReplica(props) {
@@ -312,7 +333,7 @@ exports.handler = async (event) => {
312
333
  }
313
334
  }
314
335
  getHostPort() {
315
- return String(this.port);
336
+ return String(this.clientPort);
316
337
  }
317
338
  getCredentials() {
318
339
  return this.databaseCredentials;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/components-infrastructure",
3
- "version": "2.24.0",
3
+ "version": "2.25.0",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "type": "module",
6
6
  "bin": {
@@ -64,8 +64,8 @@
64
64
  },
65
65
  "dependencies": {
66
66
  "@aws-sdk/client-organizations": "^3.1038.0",
67
- "@fjall/generator": "^2.24.0",
68
- "@fjall/util": "^2.24.0",
67
+ "@fjall/generator": "^2.25.0",
68
+ "@fjall/util": "^2.25.0",
69
69
  "constructs": "^10.6.0"
70
70
  },
71
71
  "overrides": {
@@ -79,5 +79,5 @@
79
79
  "engines": {
80
80
  "node": ">=18.0.0"
81
81
  },
82
- "gitHead": "616d7af846f0eebfa3409f5f47c98fbee836bad9"
82
+ "gitHead": "7c1a329184064aefa557c2c09de0965c4f8cd4fb"
83
83
  }