Skip to content

Commit 9910bb7

Browse files
committed
Merge remote-tracking branch 'origin/main' into codex/aws-discovery-nat-sagemaker-checks
* origin/main: feat(rules): add AWS IaC cost review rules (#52) # Conflicts: # docs/reference/rule-ids.md # packages/rules/src/aws/ec2/index.ts # packages/rules/test/exports.test.ts # packages/rules/test/rule-metadata.test.ts # packages/sdk/test/exports.test.ts
2 parents edf0fc6 + 8cd3b28 commit 9910bb7

File tree

58 files changed

+3032
-573
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+3032
-573
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudburn/rules": minor
3+
---
4+
5+
Add new AWS IaC cost review rules for versioned S3 cleanup, ECR lifecycle quality, gp3 tuning, EC2 detailed monitoring, DynamoDB autoscaling ranges, Lambda provisioned concurrency, and RDS Performance Insights retention, and extend ECS and Redshift rules to support IaC.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudburn/sdk": minor
3+
---
4+
5+
Add AWS static IaC dataset support for new cost review rules across S3, ECR, EBS, EC2, DynamoDB, ECS, Lambda, RDS, and Redshift.

docs/architecture/rules.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ Rule evaluators consume static and live datasets through `context.resources.get(
102102
| `CLDBRN-AWS-EC2-3` | Elastic IP Address Unassociated | ec2 | discovery | Implemented |
103103
| `CLDBRN-AWS-EC2-4` | VPC Interface Endpoint Inactive | ec2 | discovery | Implemented |
104104
| `CLDBRN-AWS-EC2-5` | EC2 Instance Low Utilization | ec2 | discovery | Implemented |
105-
| `CLDBRN-AWS-EC2-10` | NAT Gateway Idle | ec2 | discovery | Implemented |
105+
| `CLDBRN-AWS-EC2-10` | EC2 Instance Detailed Monitoring Enabled | ec2 | iac | Implemented |
106+
| `CLDBRN-AWS-EC2-11` | NAT Gateway Idle | ec2 | discovery | Implemented |
106107
| `CLDBRN-AWS-EBS-1` | EBS Volume Type Not Current Generation | ebs | discovery, iac | Implemented |
107108
| `CLDBRN-AWS-EBS-2` | EBS Volume Unattached | ebs | discovery | Implemented |
108109
| `CLDBRN-AWS-EBS-3` | EBS Volume Attached To Stopped Instances | ebs | discovery | Implemented |

docs/reference/rule-ids.md

Lines changed: 96 additions & 69 deletions
Large diffs are not rendered by default.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { createFinding, createFindingMatch, createRule } from '../../shared/helpers.js';
2+
3+
const RULE_ID = 'CLDBRN-AWS-DYNAMODB-4';
4+
const RULE_SERVICE = 'dynamodb';
5+
const RULE_MESSAGE = 'Provisioned DynamoDB autoscaling should allow capacity to change.';
6+
7+
const hasFixedRange = (minCapacity: number | null | undefined, maxCapacity: number | null | undefined): boolean =>
8+
typeof minCapacity === 'number' && typeof maxCapacity === 'number' && minCapacity === maxCapacity;
9+
10+
/** Flag provisioned-capacity DynamoDB tables whose table autoscaling min and max capacity are identical. */
11+
export const dynamoDbAutoscalingRangeFixedRule = createRule({
12+
id: RULE_ID,
13+
name: 'DynamoDB Autoscaling Range Fixed',
14+
description: 'Flag provisioned-capacity DynamoDB tables whose table autoscaling min and max capacity are identical.',
15+
message: RULE_MESSAGE,
16+
provider: 'aws',
17+
service: RULE_SERVICE,
18+
supports: ['iac'],
19+
staticDependencies: ['aws-dynamodb-tables', 'aws-dynamodb-autoscaling'],
20+
evaluateStatic: ({ resources }) => {
21+
const autoscalingByTable = new Map(
22+
resources
23+
.get('aws-dynamodb-autoscaling')
24+
.filter((table) => table.tableName !== null)
25+
.map((table) => [table.tableName, table] as const),
26+
);
27+
const findings = resources
28+
.get('aws-dynamodb-tables')
29+
.filter((table) => table.billingMode === 'PROVISIONED' && table.tableName !== null)
30+
.filter((table) => {
31+
const autoscaling = autoscalingByTable.get(table.tableName);
32+
33+
if (!autoscaling) {
34+
return false;
35+
}
36+
37+
return (
38+
hasFixedRange(autoscaling.readMinCapacity, autoscaling.readMaxCapacity) ||
39+
hasFixedRange(autoscaling.writeMinCapacity, autoscaling.writeMaxCapacity)
40+
);
41+
})
42+
.map((table) => createFindingMatch(table.resourceId, undefined, undefined, table.location));
43+
44+
return createFinding({ id: RULE_ID, service: RULE_SERVICE, message: RULE_MESSAGE }, 'iac', findings);
45+
},
46+
});
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
import { dynamoDbAutoscalingRangeFixedRule } from './autoscaling-range-fixed.js';
12
import { dynamoDbStaleTableDataRule } from './stale-table-data.js';
23
import { dynamoDbTableWithoutAutoscalingRule } from './table-without-autoscaling.js';
34
import { dynamoDbUnusedTableRule } from './unused-table.js';
45

56
// Intent: aggregate AWS DynamoDB rule definitions.
6-
export const dynamodbRules = [dynamoDbStaleTableDataRule, dynamoDbTableWithoutAutoscalingRule, dynamoDbUnusedTableRule];
7+
export const dynamodbRules = [
8+
dynamoDbStaleTableDataRule,
9+
dynamoDbTableWithoutAutoscalingRule,
10+
dynamoDbUnusedTableRule,
11+
dynamoDbAutoscalingRangeFixedRule,
12+
];
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { createFinding, createFindingMatch, createRule } from '../../shared/helpers.js';
2+
3+
const RULE_ID = 'CLDBRN-AWS-EBS-9';
4+
const RULE_SERVICE = 'ebs';
5+
const RULE_MESSAGE = 'EBS gp3 volumes should avoid paid IOPS above the included baseline unless required.';
6+
7+
/** Flag gp3 volumes that provision IOPS above the included 3000 baseline. */
8+
export const ebsGp3ExtraIopsRule = createRule({
9+
id: RULE_ID,
10+
name: 'EBS gp3 Volume Extra IOPS Provisioned',
11+
description: 'Flag gp3 volumes that provision IOPS above the included 3000 baseline.',
12+
message: RULE_MESSAGE,
13+
provider: 'aws',
14+
service: RULE_SERVICE,
15+
supports: ['iac'],
16+
staticDependencies: ['aws-ebs-volumes'],
17+
evaluateStatic: ({ resources }) => {
18+
const findings = resources
19+
.get('aws-ebs-volumes')
20+
.filter((volume) => volume.volumeType === 'gp3' && volume.iops !== null && volume.iops > 3000)
21+
.map((volume) => createFindingMatch(volume.resourceId, undefined, undefined, volume.location));
22+
23+
return createFinding({ id: RULE_ID, service: RULE_SERVICE, message: RULE_MESSAGE }, 'iac', findings);
24+
},
25+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { createFinding, createFindingMatch, createRule } from '../../shared/helpers.js';
2+
3+
const RULE_ID = 'CLDBRN-AWS-EBS-8';
4+
const RULE_SERVICE = 'ebs';
5+
const RULE_MESSAGE = 'EBS gp3 volumes should avoid paid throughput above the included baseline unless required.';
6+
7+
/** Flag gp3 volumes that provision throughput above the included 125 MiB/s baseline. */
8+
export const ebsGp3ExtraThroughputRule = createRule({
9+
id: RULE_ID,
10+
name: 'EBS gp3 Volume Extra Throughput Provisioned',
11+
description: 'Flag gp3 volumes that provision throughput above the included 125 MiB/s baseline.',
12+
message: RULE_MESSAGE,
13+
provider: 'aws',
14+
service: RULE_SERVICE,
15+
supports: ['iac'],
16+
staticDependencies: ['aws-ebs-volumes'],
17+
evaluateStatic: ({ resources }) => {
18+
const findings = resources
19+
.get('aws-ebs-volumes')
20+
.filter(
21+
(volume) =>
22+
volume.volumeType === 'gp3' && typeof volume.throughputMiBps === 'number' && volume.throughputMiBps > 125,
23+
)
24+
.map((volume) => createFindingMatch(volume.resourceId, undefined, undefined, volume.location));
25+
26+
return createFinding({ id: RULE_ID, service: RULE_SERVICE, message: RULE_MESSAGE }, 'iac', findings);
27+
},
28+
});

packages/rules/src/aws/ebs/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { ebsAttachedToStoppedInstancesRule } from './attached-to-stopped-instances.js';
2+
import { ebsGp3ExtraIopsRule } from './gp3-extra-iops.js';
3+
import { ebsGp3ExtraThroughputRule } from './gp3-extra-throughput.js';
24
import { ebsHighIopsVolumeRule } from './high-iops-volume.js';
35
import { ebsLargeVolumeRule } from './large-volume.js';
46
import { ebsLowIopsVolumeRule } from './low-iops-volume.js';
@@ -15,4 +17,6 @@ export const ebsRules = [
1517
ebsHighIopsVolumeRule,
1618
ebsLowIopsVolumeRule,
1719
ebsSnapshotMaxAgeRule,
20+
ebsGp3ExtraThroughputRule,
21+
ebsGp3ExtraIopsRule,
1822
];
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { createFinding, createFindingMatch, createRule } from '../../shared/helpers.js';
2+
3+
const RULE_ID = 'CLDBRN-AWS-EC2-10';
4+
const RULE_SERVICE = 'ec2';
5+
const RULE_MESSAGE = 'EC2 instances should review detailed monitoring because it adds CloudWatch cost.';
6+
7+
/** Flag EC2 instances that explicitly enable detailed monitoring. */
8+
export const ec2DetailedMonitoringEnabledRule = createRule({
9+
id: RULE_ID,
10+
name: 'EC2 Instance Detailed Monitoring Enabled',
11+
description: 'Flag EC2 instances that explicitly enable detailed monitoring.',
12+
message: RULE_MESSAGE,
13+
provider: 'aws',
14+
service: RULE_SERVICE,
15+
supports: ['iac'],
16+
staticDependencies: ['aws-ec2-instances'],
17+
evaluateStatic: ({ resources }) => {
18+
const findings = resources
19+
.get('aws-ec2-instances')
20+
.filter((instance) => instance.detailedMonitoringEnabled)
21+
.map((instance) => createFindingMatch(instance.resourceId, undefined, undefined, instance.location));
22+
23+
return createFinding({ id: RULE_ID, service: RULE_SERVICE, message: RULE_MESSAGE }, 'iac', findings);
24+
},
25+
});

0 commit comments

Comments
 (0)