feat: add more bps
This commit is contained in:
15
package.json
15
package.json
@ -10,13 +10,28 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-apigatewayv2": "^3.716.0",
|
||||
"@aws-sdk/client-application-auto-scaling": "^3.716.0",
|
||||
"@aws-sdk/client-auto-scaling": "^3.716.0",
|
||||
"@aws-sdk/client-backup": "^3.716.0",
|
||||
"@aws-sdk/client-cloudfront": "^3.716.0",
|
||||
"@aws-sdk/client-cloudwatch": "^3.716.0",
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.716.0",
|
||||
"@aws-sdk/client-codebuild": "^3.716.0",
|
||||
"@aws-sdk/client-codedeploy": "^3.716.0",
|
||||
"@aws-sdk/client-dynamodb": "^3.716.0",
|
||||
"@aws-sdk/client-ec2": "^3.716.0",
|
||||
"@aws-sdk/client-ecr": "^3.718.0",
|
||||
"@aws-sdk/client-ecs": "^3.716.0",
|
||||
"@aws-sdk/client-efs": "^3.716.0",
|
||||
"@aws-sdk/client-eks": "^3.718.0",
|
||||
"@aws-sdk/client-elastic-load-balancing-v2": "^3.716.0",
|
||||
"@aws-sdk/client-elasticache": "^3.716.0",
|
||||
"@aws-sdk/client-iam": "^3.716.0",
|
||||
"@aws-sdk/client-lambda": "^3.716.0",
|
||||
"@aws-sdk/client-rds": "^3.716.0",
|
||||
"@aws-sdk/client-s3": "^3.717.0",
|
||||
"@aws-sdk/client-s3-control": "^3.716.0",
|
||||
"@aws-sdk/client-ssm": "^3.716.0",
|
||||
"@aws-sdk/client-sts": "^3.716.0",
|
||||
"@aws-sdk/client-wafv2": "^3.716.0",
|
||||
"@smithy/smithy-client": "^3.5.1",
|
||||
|
965
pnpm-lock.yaml
generated
965
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
60
src/bpsets/cloudwatch/CWLogGroupRetentionPeriodCheck.ts
Normal file
60
src/bpsets/cloudwatch/CWLogGroupRetentionPeriodCheck.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import {
|
||||
CloudWatchLogsClient,
|
||||
DescribeLogGroupsCommand,
|
||||
PutRetentionPolicyCommand
|
||||
} from '@aws-sdk/client-cloudwatch-logs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class CWLogGroupRetentionPeriodCheck implements BPSet {
|
||||
private readonly client = new CloudWatchLogsClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getLogGroups = async () => {
|
||||
const response = await this.memoClient.send(new DescribeLogGroupsCommand({}))
|
||||
return response.logGroups || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const logGroups = await this.getLogGroups()
|
||||
|
||||
for (const logGroup of logGroups) {
|
||||
if (logGroup.retentionInDays) {
|
||||
compliantResources.push(logGroup.logGroupArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(logGroup.logGroupArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'retention-period-days' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const retentionPeriod = requiredParametersForFix.find(
|
||||
param => param.name === 'retention-period-days'
|
||||
)?.value
|
||||
|
||||
if (!retentionPeriod) {
|
||||
throw new Error("Required parameter 'retention-period-days' is missing.")
|
||||
}
|
||||
|
||||
for (const logGroupArn of nonCompliantResources) {
|
||||
const logGroupName = logGroupArn.split(':').pop()!
|
||||
await this.client.send(
|
||||
new PutRetentionPolicyCommand({
|
||||
logGroupName,
|
||||
retentionInDays: parseInt(retentionPeriod, 10)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
85
src/bpsets/cloudwatch/CloudWatchAlarmSettingsCheck.ts
Normal file
85
src/bpsets/cloudwatch/CloudWatchAlarmSettingsCheck.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import {
|
||||
CloudWatchClient,
|
||||
DescribeAlarmsCommand,
|
||||
PutMetricAlarmCommand
|
||||
} from '@aws-sdk/client-cloudwatch'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class CloudWatchAlarmSettingsCheck implements BPSet {
|
||||
private readonly client = new CloudWatchClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getAlarms = async () => {
|
||||
const response = await this.memoClient.send(new DescribeAlarmsCommand({}))
|
||||
return response.MetricAlarms || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const parameters = {
|
||||
MetricName: '', // Required
|
||||
Threshold: null,
|
||||
EvaluationPeriods: null,
|
||||
Period: null,
|
||||
ComparisonOperator: null,
|
||||
Statistic: null
|
||||
}
|
||||
|
||||
const alarms = await this.getAlarms()
|
||||
|
||||
for (const alarm of alarms) {
|
||||
for (const parameter of Object.keys(parameters).filter(key => (parameters as any)[key] !== null)) {
|
||||
if (alarm.MetricName !== parameters.MetricName) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (alarm[parameter as keyof typeof alarm] !== parameters[parameter as keyof typeof parameters]) {
|
||||
nonCompliantResources.push(alarm.AlarmArn!)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
compliantResources.push(alarm.AlarmArn!)
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [
|
||||
{ name: 'metric-name' },
|
||||
{ name: 'threshold' },
|
||||
{ name: 'evaluation-periods' },
|
||||
{ name: 'period' },
|
||||
{ name: 'comparison-operator' },
|
||||
{ name: 'statistic' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const requiredSettings = Object.fromEntries(
|
||||
requiredParametersForFix.map(param => [param.name, param.value])
|
||||
)
|
||||
|
||||
for (const alarmArn of nonCompliantResources) {
|
||||
const alarmName = alarmArn.split(':').pop()!
|
||||
|
||||
await this.client.send(
|
||||
new PutMetricAlarmCommand({
|
||||
AlarmName: alarmName,
|
||||
MetricName: requiredSettings['metric-name'],
|
||||
Threshold: parseFloat(requiredSettings['threshold']),
|
||||
EvaluationPeriods: parseInt(requiredSettings['evaluation-periods'], 10),
|
||||
Period: parseInt(requiredSettings['period'], 10),
|
||||
ComparisonOperator: requiredSettings['comparison-operator'] as any,
|
||||
Statistic: requiredSettings['statistic'] as any
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
import {
|
||||
CodeBuildClient,
|
||||
ListProjectsCommand,
|
||||
BatchGetProjectsCommand,
|
||||
UpdateProjectCommand
|
||||
} from '@aws-sdk/client-codebuild'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class CodeBuildProjectEnvironmentPrivilegedCheck implements BPSet {
|
||||
private readonly client = new CodeBuildClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getProjects = async () => {
|
||||
const projectNames = await this.memoClient.send(new ListProjectsCommand({}))
|
||||
if (!projectNames.projects?.length) {
|
||||
return []
|
||||
}
|
||||
const response = await this.memoClient.send(
|
||||
new BatchGetProjectsCommand({ names: projectNames.projects })
|
||||
)
|
||||
return response.projects || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const projects = await this.getProjects()
|
||||
|
||||
for (const project of projects) {
|
||||
if (!project.environment?.privilegedMode) {
|
||||
compliantResources.push(project.arn!)
|
||||
} else {
|
||||
nonCompliantResources.push(project.arn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const projectName = arn.split(':').pop()!
|
||||
const projects = await this.getProjects()
|
||||
const projectToFix = projects.find(project => project.arn === arn)
|
||||
|
||||
if (!projectToFix) {
|
||||
continue
|
||||
}
|
||||
|
||||
await this.client.send(
|
||||
new UpdateProjectCommand({
|
||||
name: projectName,
|
||||
environment: {
|
||||
...projectToFix.environment as any,
|
||||
privilegedMode: false
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
74
src/bpsets/codeseries/CodeBuildProjectLoggingEnabled.ts
Normal file
74
src/bpsets/codeseries/CodeBuildProjectLoggingEnabled.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import {
|
||||
CodeBuildClient,
|
||||
ListProjectsCommand,
|
||||
BatchGetProjectsCommand,
|
||||
UpdateProjectCommand
|
||||
} from '@aws-sdk/client-codebuild'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class CodeBuildProjectLoggingEnabled implements BPSet {
|
||||
private readonly client = new CodeBuildClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getProjects = async () => {
|
||||
const projectNames = await this.memoClient.send(new ListProjectsCommand({}))
|
||||
if (!projectNames.projects?.length) {
|
||||
return []
|
||||
}
|
||||
const response = await this.memoClient.send(
|
||||
new BatchGetProjectsCommand({ names: projectNames.projects })
|
||||
)
|
||||
return response.projects || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const projects = await this.getProjects()
|
||||
|
||||
for (const project of projects) {
|
||||
const logsConfig = project.logsConfig
|
||||
if (
|
||||
logsConfig?.cloudWatchLogs?.status === 'ENABLED' ||
|
||||
logsConfig?.s3Logs?.status === 'ENABLED'
|
||||
) {
|
||||
compliantResources.push(project.arn!)
|
||||
} else {
|
||||
nonCompliantResources.push(project.arn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const projectName = arn.split(':').pop()!
|
||||
const projects = await this.getProjects()
|
||||
const projectToFix = projects.find(project => project.arn === arn)
|
||||
|
||||
if (!projectToFix) {
|
||||
continue
|
||||
}
|
||||
|
||||
await this.client.send(
|
||||
new UpdateProjectCommand({
|
||||
name: projectName,
|
||||
logsConfig: {
|
||||
...projectToFix.logsConfig,
|
||||
cloudWatchLogs: {
|
||||
status: 'ENABLED',
|
||||
groupName: 'default-cloudwatch-group',
|
||||
streamName: 'default-stream'
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
import {
|
||||
CodeDeployClient,
|
||||
ListApplicationsCommand,
|
||||
ListDeploymentGroupsCommand,
|
||||
BatchGetDeploymentGroupsCommand,
|
||||
UpdateDeploymentGroupCommand
|
||||
} from '@aws-sdk/client-codedeploy'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class CodeDeployAutoRollbackMonitorEnabled implements BPSet {
|
||||
private readonly client = new CodeDeployClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDeploymentGroups = async () => {
|
||||
const applications = await this.memoClient.send(new ListApplicationsCommand({}))
|
||||
const deploymentGroupsInfo = []
|
||||
|
||||
for (const application of applications.applications || []) {
|
||||
const deploymentGroups = await this.memoClient.send(
|
||||
new ListDeploymentGroupsCommand({ applicationName: application })
|
||||
)
|
||||
if (!deploymentGroups.deploymentGroups?.length) {
|
||||
continue
|
||||
}
|
||||
const batchResponse = await this.memoClient.send(
|
||||
new BatchGetDeploymentGroupsCommand({
|
||||
applicationName: application,
|
||||
deploymentGroupNames: deploymentGroups.deploymentGroups
|
||||
})
|
||||
)
|
||||
deploymentGroupsInfo.push(...(batchResponse.deploymentGroupsInfo || []))
|
||||
}
|
||||
|
||||
return deploymentGroupsInfo
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const deploymentGroups = await this.getDeploymentGroups()
|
||||
|
||||
for (const deploymentGroup of deploymentGroups) {
|
||||
if (
|
||||
deploymentGroup.alarmConfiguration?.enabled &&
|
||||
deploymentGroup.autoRollbackConfiguration?.enabled
|
||||
) {
|
||||
compliantResources.push(deploymentGroup.deploymentGroupId!)
|
||||
} else {
|
||||
nonCompliantResources.push(deploymentGroup.deploymentGroupId!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const groupId of nonCompliantResources) {
|
||||
const deploymentGroups = await this.getDeploymentGroups()
|
||||
const deploymentGroupToFix = deploymentGroups.find(
|
||||
group => group.deploymentGroupId === groupId
|
||||
)
|
||||
|
||||
if (!deploymentGroupToFix) {
|
||||
continue
|
||||
}
|
||||
|
||||
await this.client.send(
|
||||
new UpdateDeploymentGroupCommand({
|
||||
applicationName: deploymentGroupToFix.applicationName!,
|
||||
currentDeploymentGroupName: deploymentGroupToFix.deploymentGroupName!,
|
||||
alarmConfiguration: {
|
||||
...deploymentGroupToFix.alarmConfiguration,
|
||||
enabled: true
|
||||
},
|
||||
autoRollbackConfiguration: {
|
||||
...deploymentGroupToFix.autoRollbackConfiguration,
|
||||
enabled: true
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
133
src/bpsets/dynamodb/DynamoDBAutoscalingEnabled.ts
Normal file
133
src/bpsets/dynamodb/DynamoDBAutoscalingEnabled.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import {
|
||||
DynamoDBClient,
|
||||
ListTablesCommand,
|
||||
DescribeTableCommand
|
||||
} from '@aws-sdk/client-dynamodb'
|
||||
import {
|
||||
ApplicationAutoScalingClient,
|
||||
RegisterScalableTargetCommand,
|
||||
PutScalingPolicyCommand,
|
||||
DescribeScalingPoliciesCommand
|
||||
} from '@aws-sdk/client-application-auto-scaling'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class DynamoDBAutoscalingEnabled implements BPSet {
|
||||
private readonly client = new DynamoDBClient({})
|
||||
private readonly autoScalingClient = new ApplicationAutoScalingClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getTables = async () => {
|
||||
const tableNames = await this.memoClient.send(new ListTablesCommand({}))
|
||||
const tables = []
|
||||
for (const tableName of tableNames.TableNames || []) {
|
||||
const tableDetails = await this.memoClient.send(
|
||||
new DescribeTableCommand({ TableName: tableName })
|
||||
)
|
||||
tables.push(tableDetails.Table!)
|
||||
}
|
||||
return tables
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const tables = await this.getTables()
|
||||
|
||||
for (const table of tables) {
|
||||
if (table.BillingModeSummary?.BillingMode === 'PAY_PER_REQUEST') {
|
||||
compliantResources.push(table.TableArn!)
|
||||
continue
|
||||
}
|
||||
|
||||
const scalingPolicies = await this.autoScalingClient.send(
|
||||
new DescribeScalingPoliciesCommand({
|
||||
ServiceNamespace: 'dynamodb',
|
||||
ResourceId: `table/${table.TableName}`
|
||||
})
|
||||
)
|
||||
const scalingPolicyDimensions = scalingPolicies.ScalingPolicies?.map(
|
||||
policy => policy.ScalableDimension
|
||||
)
|
||||
|
||||
if (
|
||||
scalingPolicyDimensions?.includes('dynamodb:table:ReadCapacityUnits') &&
|
||||
scalingPolicyDimensions?.includes('dynamodb:table:WriteCapacityUnits')
|
||||
) {
|
||||
compliantResources.push(table.TableArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(table.TableArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const tableName = arn.split('/').pop()!
|
||||
|
||||
// Register scalable targets for read and write capacity
|
||||
await this.autoScalingClient.send(
|
||||
new RegisterScalableTargetCommand({
|
||||
ServiceNamespace: 'dynamodb',
|
||||
ResourceId: `table/${tableName}`,
|
||||
ScalableDimension: 'dynamodb:table:ReadCapacityUnits',
|
||||
MinCapacity: 1,
|
||||
MaxCapacity: 100
|
||||
})
|
||||
)
|
||||
|
||||
await this.autoScalingClient.send(
|
||||
new RegisterScalableTargetCommand({
|
||||
ServiceNamespace: 'dynamodb',
|
||||
ResourceId: `table/${tableName}`,
|
||||
ScalableDimension: 'dynamodb:table:WriteCapacityUnits',
|
||||
MinCapacity: 1,
|
||||
MaxCapacity: 100
|
||||
})
|
||||
)
|
||||
|
||||
// Put scaling policies for read and write capacity
|
||||
await this.autoScalingClient.send(
|
||||
new PutScalingPolicyCommand({
|
||||
ServiceNamespace: 'dynamodb',
|
||||
ResourceId: `table/${tableName}`,
|
||||
ScalableDimension: 'dynamodb:table:ReadCapacityUnits',
|
||||
PolicyName: `${tableName}-ReadPolicy`,
|
||||
PolicyType: 'TargetTrackingScaling',
|
||||
TargetTrackingScalingPolicyConfiguration: {
|
||||
TargetValue: 70.0,
|
||||
ScaleInCooldown: 60,
|
||||
ScaleOutCooldown: 60,
|
||||
PredefinedMetricSpecification: {
|
||||
PredefinedMetricType: 'DynamoDBReadCapacityUtilization'
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
await this.autoScalingClient.send(
|
||||
new PutScalingPolicyCommand({
|
||||
ServiceNamespace: 'dynamodb',
|
||||
ResourceId: `table/${tableName}`,
|
||||
ScalableDimension: 'dynamodb:table:WriteCapacityUnits',
|
||||
PolicyName: `${tableName}-WritePolicy`,
|
||||
PolicyType: 'TargetTrackingScaling',
|
||||
TargetTrackingScalingPolicyConfiguration: {
|
||||
TargetValue: 70.0,
|
||||
ScaleInCooldown: 60,
|
||||
ScaleOutCooldown: 60,
|
||||
PredefinedMetricSpecification: {
|
||||
PredefinedMetricType: 'DynamoDBWriteCapacityUtilization'
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
import {
|
||||
DynamoDBClient,
|
||||
ListTablesCommand,
|
||||
DescribeTableCommand
|
||||
} from '@aws-sdk/client-dynamodb'
|
||||
import {
|
||||
BackupClient,
|
||||
ListRecoveryPointsByResourceCommand,
|
||||
StartBackupJobCommand
|
||||
} from '@aws-sdk/client-backup'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class DynamoDBLastBackupRecoveryPointCreated implements BPSet {
|
||||
private readonly client = new DynamoDBClient({})
|
||||
private readonly backupClient = new BackupClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getTables = async () => {
|
||||
const tableNames = await this.memoClient.send(new ListTablesCommand({}))
|
||||
const tables = []
|
||||
for (const tableName of tableNames.TableNames || []) {
|
||||
const tableDetails = await this.memoClient.send(
|
||||
new DescribeTableCommand({ TableName: tableName })
|
||||
)
|
||||
tables.push(tableDetails.Table!)
|
||||
}
|
||||
return tables
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const tables = await this.getTables()
|
||||
|
||||
for (const table of tables) {
|
||||
const recoveryPointsResponse = await this.backupClient.send(
|
||||
new ListRecoveryPointsByResourceCommand({
|
||||
ResourceArn: table.TableArn
|
||||
})
|
||||
)
|
||||
const recoveryPoints = recoveryPointsResponse.RecoveryPoints || []
|
||||
|
||||
if (recoveryPoints.length === 0) {
|
||||
nonCompliantResources.push(table.TableArn!)
|
||||
continue
|
||||
}
|
||||
|
||||
const latestRecoveryPoint = recoveryPoints
|
||||
.map(point => new Date(point.CreationDate!))
|
||||
.sort((a, b) => b.getTime() - a.getTime())[0]
|
||||
|
||||
if (new Date().getTime() - latestRecoveryPoint.getTime() > 86400000) {
|
||||
nonCompliantResources.push(table.TableArn!)
|
||||
} else {
|
||||
compliantResources.push(table.TableArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
await this.backupClient.send(
|
||||
new StartBackupJobCommand({
|
||||
ResourceArn: arn,
|
||||
BackupVaultName: 'Default',
|
||||
IamRoleArn: 'arn:aws:iam::account-id:role/service-role/BackupDefaultServiceRole',
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
70
src/bpsets/dynamodb/DynamoDBPITREnabled.ts
Normal file
70
src/bpsets/dynamodb/DynamoDBPITREnabled.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import {
|
||||
DynamoDBClient,
|
||||
ListTablesCommand,
|
||||
DescribeTableCommand,
|
||||
DescribeContinuousBackupsCommand,
|
||||
UpdateContinuousBackupsCommand
|
||||
} from '@aws-sdk/client-dynamodb'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class DynamoDBPITREnabled implements BPSet {
|
||||
private readonly client = new DynamoDBClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getTables = async () => {
|
||||
const tableNames = await this.memoClient.send(new ListTablesCommand({}))
|
||||
const tables = []
|
||||
for (const tableName of tableNames.TableNames || []) {
|
||||
const tableDetails = await this.memoClient.send(
|
||||
new DescribeTableCommand({ TableName: tableName })
|
||||
)
|
||||
tables.push(tableDetails.Table!)
|
||||
}
|
||||
return tables
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const tables = await this.getTables()
|
||||
|
||||
for (const table of tables) {
|
||||
const backupStatus = await this.memoClient.send(
|
||||
new DescribeContinuousBackupsCommand({
|
||||
TableName: table.TableName!
|
||||
})
|
||||
)
|
||||
|
||||
if (
|
||||
backupStatus.ContinuousBackupsDescription?.PointInTimeRecoveryDescription
|
||||
?.PointInTimeRecoveryStatus === 'ENABLED'
|
||||
) {
|
||||
compliantResources.push(table.TableArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(table.TableArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const tableName = arn.split('/').pop()!
|
||||
|
||||
await this.client.send(
|
||||
new UpdateContinuousBackupsCommand({
|
||||
TableName: tableName,
|
||||
PointInTimeRecoverySpecification: {
|
||||
PointInTimeRecoveryEnabled: true
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
import {
|
||||
DynamoDBClient,
|
||||
ListTablesCommand,
|
||||
DescribeTableCommand,
|
||||
UpdateTableCommand
|
||||
} from '@aws-sdk/client-dynamodb'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class DynamoDBTableDeletionProtectionEnabled implements BPSet {
|
||||
private readonly client = new DynamoDBClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getTables = async () => {
|
||||
const tableNames = await this.memoClient.send(new ListTablesCommand({}))
|
||||
const tables = []
|
||||
for (const tableName of tableNames.TableNames || []) {
|
||||
const tableDetails = await this.memoClient.send(
|
||||
new DescribeTableCommand({ TableName: tableName })
|
||||
)
|
||||
tables.push(tableDetails.Table!)
|
||||
}
|
||||
return tables
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const tables = await this.getTables()
|
||||
|
||||
for (const table of tables) {
|
||||
if (table.DeletionProtectionEnabled) {
|
||||
compliantResources.push(table.TableArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(table.TableArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const tableName = arn.split('/').pop()!
|
||||
|
||||
await this.client.send(
|
||||
new UpdateTableCommand({
|
||||
TableName: tableName,
|
||||
DeletionProtectionEnabled: true
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
74
src/bpsets/dynamodb/DynamoDBTableEncryptedKMS.ts
Normal file
74
src/bpsets/dynamodb/DynamoDBTableEncryptedKMS.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import {
|
||||
DynamoDBClient,
|
||||
ListTablesCommand,
|
||||
DescribeTableCommand,
|
||||
UpdateTableCommand
|
||||
} from '@aws-sdk/client-dynamodb'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class DynamoDBTableEncryptedKMS implements BPSet {
|
||||
private readonly client = new DynamoDBClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getTables = async () => {
|
||||
const tableNames = await this.memoClient.send(new ListTablesCommand({}))
|
||||
const tables = []
|
||||
for (const tableName of tableNames.TableNames || []) {
|
||||
const tableDetails = await this.memoClient.send(
|
||||
new DescribeTableCommand({ TableName: tableName })
|
||||
)
|
||||
tables.push(tableDetails.Table!)
|
||||
}
|
||||
return tables
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const tables = await this.getTables()
|
||||
|
||||
for (const table of tables) {
|
||||
if (
|
||||
table.SSEDescription?.Status === 'ENABLED' &&
|
||||
table.SSEDescription?.SSEType === 'KMS'
|
||||
) {
|
||||
compliantResources.push(table.TableArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(table.TableArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'kms-key-id' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const kmsKeyId = requiredParametersForFix.find(param => param.name === 'kms-key-id')?.value
|
||||
|
||||
if (!kmsKeyId) {
|
||||
throw new Error("Required parameter 'kms-key-id' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const tableName = arn.split('/').pop()!
|
||||
|
||||
await this.client.send(
|
||||
new UpdateTableCommand({
|
||||
TableName: tableName,
|
||||
SSESpecification: {
|
||||
Enabled: true,
|
||||
SSEType: 'KMS',
|
||||
KMSMasterKeyId: kmsKeyId
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
60
src/bpsets/dynamodb/DynamoDBTableEncryptionEnabled.ts
Normal file
60
src/bpsets/dynamodb/DynamoDBTableEncryptionEnabled.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import {
|
||||
DynamoDBClient,
|
||||
ListTablesCommand,
|
||||
DescribeTableCommand,
|
||||
UpdateTableCommand
|
||||
} from '@aws-sdk/client-dynamodb'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class DynamoDBTableEncryptionEnabled implements BPSet {
|
||||
private readonly client = new DynamoDBClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getTables = async () => {
|
||||
const tableNames = await this.memoClient.send(new ListTablesCommand({}))
|
||||
const tables = []
|
||||
for (const tableName of tableNames.TableNames || []) {
|
||||
const tableDetails = await this.memoClient.send(
|
||||
new DescribeTableCommand({ TableName: tableName })
|
||||
)
|
||||
tables.push(tableDetails.Table!)
|
||||
}
|
||||
return tables
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const tables = await this.getTables()
|
||||
|
||||
for (const table of tables) {
|
||||
if (table.SSEDescription?.Status === 'ENABLED') {
|
||||
compliantResources.push(table.TableArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(table.TableArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const tableName = arn.split('/').pop()!
|
||||
|
||||
await this.client.send(
|
||||
new UpdateTableCommand({
|
||||
TableName: tableName,
|
||||
SSESpecification: {
|
||||
Enabled: true
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
36
src/bpsets/ec2/EC2EbsEncryptionByDefault.ts
Normal file
36
src/bpsets/ec2/EC2EbsEncryptionByDefault.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import {
|
||||
EC2Client,
|
||||
DescribeVolumesCommand,
|
||||
EnableEbsEncryptionByDefaultCommand
|
||||
} from '@aws-sdk/client-ec2'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EC2EbsEncryptionByDefault implements BPSet {
|
||||
private readonly client = new EC2Client({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const response = await this.memoClient.send(new DescribeVolumesCommand({}))
|
||||
|
||||
for (const volume of response.Volumes || []) {
|
||||
if (volume.Encrypted) {
|
||||
compliantResources.push(volume.VolumeId!)
|
||||
} else {
|
||||
nonCompliantResources.push(volume.VolumeId!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async () => {
|
||||
await this.client.send(new EnableEbsEncryptionByDefaultCommand({}))
|
||||
}
|
||||
}
|
45
src/bpsets/ec2/EC2Imdsv2Check.ts
Normal file
45
src/bpsets/ec2/EC2Imdsv2Check.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import {
|
||||
DescribeInstancesCommand,
|
||||
EC2Client,
|
||||
ModifyInstanceMetadataOptionsCommand
|
||||
} from '@aws-sdk/client-ec2'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EC2Imdsv2Check implements BPSet {
|
||||
private readonly client = new EC2Client({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||
|
||||
for (const reservation of response.Reservations || []) {
|
||||
for (const instance of reservation.Instances || []) {
|
||||
if (instance.MetadataOptions?.HttpTokens === 'required') {
|
||||
compliantResources.push(instance.InstanceId!)
|
||||
} else {
|
||||
nonCompliantResources.push(instance.InstanceId!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const instanceId of nonCompliantResources) {
|
||||
await this.client.send(
|
||||
new ModifyInstanceMetadataOptionsCommand({
|
||||
InstanceId: instanceId,
|
||||
HttpTokens: 'required'
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
42
src/bpsets/ec2/EC2InstanceDetailedMonitoringEnabled.ts
Normal file
42
src/bpsets/ec2/EC2InstanceDetailedMonitoringEnabled.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import {
|
||||
DescribeInstancesCommand,
|
||||
EC2Client,
|
||||
MonitorInstancesCommand
|
||||
} from '@aws-sdk/client-ec2'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EC2InstanceDetailedMonitoringEnabled implements BPSet {
|
||||
private readonly client = new EC2Client({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||
|
||||
for (const reservation of response.Reservations || []) {
|
||||
for (const instance of reservation.Instances || []) {
|
||||
if (instance.Monitoring?.State === 'enabled') {
|
||||
compliantResources.push(instance.InstanceId!)
|
||||
} else {
|
||||
nonCompliantResources.push(instance.InstanceId!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
await this.client.send(
|
||||
new MonitorInstancesCommand({
|
||||
InstanceIds: nonCompliantResources
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
48
src/bpsets/ec2/EC2InstanceManagedBySystemsManager.ts
Normal file
48
src/bpsets/ec2/EC2InstanceManagedBySystemsManager.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import {
|
||||
EC2Client,
|
||||
DescribeInstancesCommand
|
||||
} from '@aws-sdk/client-ec2'
|
||||
import { SSMClient, DescribeInstanceInformationCommand } from '@aws-sdk/client-ssm'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EC2InstanceManagedBySystemsManager implements BPSet {
|
||||
private readonly client = new EC2Client({})
|
||||
private readonly ssmClient = new SSMClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||
const ssmResponse = await this.ssmClient.send(
|
||||
new DescribeInstanceInformationCommand({})
|
||||
)
|
||||
|
||||
const managedInstanceIds = ssmResponse.InstanceInformationList?.map(
|
||||
info => info.InstanceId
|
||||
)
|
||||
|
||||
for (const reservation of response.Reservations || []) {
|
||||
for (const instance of reservation.Instances || []) {
|
||||
if (managedInstanceIds?.includes(instance.InstanceId!)) {
|
||||
compliantResources.push(instance.InstanceId!)
|
||||
} else {
|
||||
nonCompliantResources.push(instance.InstanceId!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async () => {
|
||||
throw new Error(
|
||||
'Fix logic for EC2InstanceManagedBySystemsManager is not directly applicable. Systems Manager Agent setup requires manual intervention.'
|
||||
)
|
||||
}
|
||||
}
|
56
src/bpsets/ec2/EC2InstanceProfileAttached,ts
Normal file
56
src/bpsets/ec2/EC2InstanceProfileAttached,ts
Normal file
@ -0,0 +1,56 @@
|
||||
import {
|
||||
EC2Client,
|
||||
DescribeInstancesCommand,
|
||||
AssociateIamInstanceProfileCommand
|
||||
} from '@aws-sdk/client-ec2'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EC2InstanceProfileAttached implements BPSet {
|
||||
private readonly client = new EC2Client({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources: string[] = []
|
||||
const nonCompliantResources: string[] = []
|
||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||
|
||||
for (const reservation of response.Reservations || []) {
|
||||
for (const instance of reservation.Instances || []) {
|
||||
if (instance.IamInstanceProfile) {
|
||||
compliantResources.push(instance.InstanceId!)
|
||||
} else {
|
||||
nonCompliantResources.push(instance.InstanceId!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'iam-instance-profile' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const iamInstanceProfile = requiredParametersForFix.find(
|
||||
param => param.name === 'iam-instance-profile'
|
||||
)?.value
|
||||
|
||||
if (!iamInstanceProfile) {
|
||||
throw new Error("Required parameter 'iam-instance-profile' is missing.")
|
||||
}
|
||||
|
||||
for (const instanceId of nonCompliantResources) {
|
||||
await this.client.send(
|
||||
new AssociateIamInstanceProfileCommand({
|
||||
InstanceId: instanceId,
|
||||
IamInstanceProfile: { Name: iamInstanceProfile }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
39
src/bpsets/ec2/EC2NoAmazonKeyPair.ts
Normal file
39
src/bpsets/ec2/EC2NoAmazonKeyPair.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import {
|
||||
EC2Client,
|
||||
DescribeInstancesCommand
|
||||
} from '@aws-sdk/client-ec2'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EC2NoAmazonKeyPair implements BPSet {
|
||||
private readonly client = new EC2Client({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||
|
||||
for (const reservation of response.Reservations || []) {
|
||||
for (const instance of reservation.Instances || []) {
|
||||
if (instance.KeyName) {
|
||||
nonCompliantResources.push(instance.InstanceId!)
|
||||
} else {
|
||||
compliantResources.push(instance.InstanceId!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async () => {
|
||||
throw new Error(
|
||||
'Fix logic for EC2NoAmazonKeyPair is not applicable. Key pairs must be removed manually or during instance creation.'
|
||||
)
|
||||
}
|
||||
}
|
46
src/bpsets/ec2/EC2StoppedInstance.ts
Normal file
46
src/bpsets/ec2/EC2StoppedInstance.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import {
|
||||
EC2Client,
|
||||
DescribeInstancesCommand,
|
||||
TerminateInstancesCommand
|
||||
} from '@aws-sdk/client-ec2'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EC2StoppedInstance implements BPSet {
|
||||
private readonly client = new EC2Client({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||
|
||||
for (const reservation of response.Reservations || []) {
|
||||
for (const instance of reservation.Instances || []) {
|
||||
if (instance.State?.Name === 'stopped') {
|
||||
nonCompliantResources.push(instance.InstanceId!)
|
||||
} else {
|
||||
compliantResources.push(instance.InstanceId!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
if (nonCompliantResources.length === 0) {
|
||||
return // No stopped instances to terminate
|
||||
}
|
||||
|
||||
await this.client.send(
|
||||
new TerminateInstancesCommand({
|
||||
InstanceIds: nonCompliantResources
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
48
src/bpsets/ec2/EC2TokenHopLimitCheck.ts
Normal file
48
src/bpsets/ec2/EC2TokenHopLimitCheck.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import {
|
||||
EC2Client,
|
||||
DescribeInstancesCommand,
|
||||
ModifyInstanceMetadataOptionsCommand
|
||||
} from '@aws-sdk/client-ec2'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EC2TokenHopLimitCheck implements BPSet {
|
||||
private readonly client = new EC2Client({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||
|
||||
for (const reservation of response.Reservations || []) {
|
||||
for (const instance of reservation.Instances || []) {
|
||||
if (
|
||||
instance.MetadataOptions?.HttpPutResponseHopLimit &&
|
||||
instance.MetadataOptions.HttpPutResponseHopLimit < 2
|
||||
) {
|
||||
compliantResources.push(instance.InstanceId!)
|
||||
} else {
|
||||
nonCompliantResources.push(instance.InstanceId!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const instanceId of nonCompliantResources) {
|
||||
await this.client.send(
|
||||
new ModifyInstanceMetadataOptionsCommand({
|
||||
InstanceId: instanceId,
|
||||
HttpPutResponseHopLimit: 1
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
99
src/bpsets/ecr/ECRKmsEncryption1.ts
Normal file
99
src/bpsets/ecr/ECRKmsEncryption1.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import {
|
||||
ECRClient,
|
||||
DescribeRepositoriesCommand,
|
||||
CreateRepositoryCommand,
|
||||
ListImagesCommand,
|
||||
BatchGetImageCommand,
|
||||
PutImageCommand,
|
||||
DeleteRepositoryCommand
|
||||
} from '@aws-sdk/client-ecr'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ECRKmsEncryption1 implements BPSet {
|
||||
private readonly client = new ECRClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getRepositories = async () => {
|
||||
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}))
|
||||
return response.repositories || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const repositories = await this.getRepositories()
|
||||
|
||||
for (const repository of repositories) {
|
||||
if (repository.encryptionConfiguration?.encryptionType === 'KMS') {
|
||||
compliantResources.push(repository.repositoryArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(repository.repositoryArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'kms-key-id' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const kmsKeyId = requiredParametersForFix.find(param => param.name === 'kms-key-id')?.value
|
||||
|
||||
if (!kmsKeyId) {
|
||||
throw new Error("Required parameter 'kms-key-id' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const repositoryName = arn.split('/').pop()!
|
||||
|
||||
// Create a new repository with KMS encryption
|
||||
const newRepositoryName = `${repositoryName}-kms`
|
||||
await this.client.send(
|
||||
new CreateRepositoryCommand({
|
||||
repositoryName: newRepositoryName,
|
||||
encryptionConfiguration: {
|
||||
encryptionType: 'KMS',
|
||||
kmsKey: kmsKeyId
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Get all images in the existing repository
|
||||
const listImagesResponse = await this.client.send(
|
||||
new ListImagesCommand({ repositoryName })
|
||||
)
|
||||
const imageIds = listImagesResponse.imageIds || []
|
||||
|
||||
if (imageIds.length > 0) {
|
||||
const batchGetImageResponse = await this.client.send(
|
||||
new BatchGetImageCommand({ repositoryName, imageIds })
|
||||
)
|
||||
|
||||
// Push images to the new repository
|
||||
for (const image of batchGetImageResponse.images || []) {
|
||||
await this.client.send(
|
||||
new PutImageCommand({
|
||||
repositoryName: newRepositoryName,
|
||||
imageManifest: image.imageManifest,
|
||||
imageTag: image.imageId?.imageTag
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the old repository
|
||||
await this.client.send(
|
||||
new DeleteRepositoryCommand({
|
||||
repositoryName,
|
||||
force: true
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
50
src/bpsets/ecr/ECRPrivateImageScanningEnabled.ts
Normal file
50
src/bpsets/ecr/ECRPrivateImageScanningEnabled.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
ECRClient,
|
||||
DescribeRepositoriesCommand,
|
||||
PutImageScanningConfigurationCommand
|
||||
} from '@aws-sdk/client-ecr'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ECRPrivateImageScanningEnabled implements BPSet {
|
||||
private readonly client = new ECRClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getRepositories = async () => {
|
||||
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}))
|
||||
return response.repositories || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const repositories = await this.getRepositories()
|
||||
|
||||
for (const repository of repositories) {
|
||||
if (repository.imageScanningConfiguration?.scanOnPush) {
|
||||
compliantResources.push(repository.repositoryArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(repository.repositoryArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const repositoryName = arn.split('/').pop()!
|
||||
|
||||
await this.client.send(
|
||||
new PutImageScanningConfigurationCommand({
|
||||
repositoryName,
|
||||
imageScanningConfiguration: { scanOnPush: true }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
72
src/bpsets/ecr/ECRPrivateLifecyclePolicyConfigured.ts
Normal file
72
src/bpsets/ecr/ECRPrivateLifecyclePolicyConfigured.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import {
|
||||
ECRClient,
|
||||
DescribeRepositoriesCommand,
|
||||
PutLifecyclePolicyCommand,
|
||||
GetLifecyclePolicyCommand
|
||||
} from '@aws-sdk/client-ecr'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ECRPrivateLifecyclePolicyConfigured implements BPSet {
|
||||
private readonly client = new ECRClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getRepositories = async () => {
|
||||
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}))
|
||||
return response.repositories || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const repositories = await this.getRepositories()
|
||||
|
||||
for (const repository of repositories) {
|
||||
try {
|
||||
await this.client.send(
|
||||
new GetLifecyclePolicyCommand({
|
||||
registryId: repository.registryId,
|
||||
repositoryName: repository.repositoryName
|
||||
})
|
||||
)
|
||||
compliantResources.push(repository.repositoryArn!)
|
||||
} catch (error: any) {
|
||||
if (error.name === 'LifecyclePolicyNotFoundException') {
|
||||
nonCompliantResources.push(repository.repositoryArn!)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'lifecycle-policy' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const lifecyclePolicy = requiredParametersForFix.find(
|
||||
param => param.name === 'lifecycle-policy'
|
||||
)?.value
|
||||
|
||||
if (!lifecyclePolicy) {
|
||||
throw new Error("Required parameter 'lifecycle-policy' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const repositoryName = arn.split('/').pop()!
|
||||
|
||||
await this.client.send(
|
||||
new PutLifecyclePolicyCommand({
|
||||
repositoryName,
|
||||
lifecyclePolicyText: lifecyclePolicy
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
50
src/bpsets/ecr/ECRPrivateTagImmutabilityEnabled.ts
Normal file
50
src/bpsets/ecr/ECRPrivateTagImmutabilityEnabled.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
ECRClient,
|
||||
DescribeRepositoriesCommand,
|
||||
PutImageTagMutabilityCommand
|
||||
} from '@aws-sdk/client-ecr'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ECRPrivateTagImmutabilityEnabled implements BPSet {
|
||||
private readonly client = new ECRClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getRepositories = async () => {
|
||||
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}))
|
||||
return response.repositories || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const repositories = await this.getRepositories()
|
||||
|
||||
for (const repository of repositories) {
|
||||
if (repository.imageTagMutability === 'IMMUTABLE') {
|
||||
compliantResources.push(repository.repositoryArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(repository.repositoryArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const repositoryName = arn.split('/').pop()!
|
||||
|
||||
await this.client.send(
|
||||
new PutImageTagMutabilityCommand({
|
||||
repositoryName,
|
||||
imageTagMutability: 'IMMUTABLE'
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
67
src/bpsets/ecs/ECSAwsVpcNetworkingEnabled.ts
Normal file
67
src/bpsets/ecs/ECSAwsVpcNetworkingEnabled.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
ECSClient,
|
||||
ListTaskDefinitionsCommand,
|
||||
DescribeTaskDefinitionCommand,
|
||||
RegisterTaskDefinitionCommand
|
||||
} from '@aws-sdk/client-ecs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ECSAwsVpcNetworkingEnabled implements BPSet {
|
||||
private readonly client = new ECSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getTaskDefinitions = async () => {
|
||||
const taskDefinitionArns = await this.memoClient.send(
|
||||
new ListTaskDefinitionsCommand({ status: 'ACTIVE' })
|
||||
)
|
||||
const taskDefinitions = []
|
||||
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
||||
const taskDefinition = await this.memoClient.send(
|
||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
||||
)
|
||||
taskDefinitions.push(taskDefinition.taskDefinition!)
|
||||
}
|
||||
return taskDefinitions
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const taskDefinitions = await this.getTaskDefinitions()
|
||||
|
||||
for (const taskDefinition of taskDefinitions) {
|
||||
if (taskDefinition.networkMode === 'awsvpc') {
|
||||
compliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const taskDefinition = await this.memoClient.send(
|
||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
||||
)
|
||||
const family = taskDefinition.taskDefinition?.family
|
||||
|
||||
await this.client.send(
|
||||
new RegisterTaskDefinitionCommand({
|
||||
family,
|
||||
containerDefinitions: taskDefinition.taskDefinition?.containerDefinitions,
|
||||
networkMode: 'awsvpc',
|
||||
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
||||
cpu: taskDefinition.taskDefinition?.cpu,
|
||||
memory: taskDefinition.taskDefinition?.memory
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
51
src/bpsets/ecs/ECSContainerInsightsEnabled.ts
Normal file
51
src/bpsets/ecs/ECSContainerInsightsEnabled.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import {
|
||||
ECSClient,
|
||||
DescribeClustersCommand,
|
||||
UpdateClusterSettingsCommand
|
||||
} from '@aws-sdk/client-ecs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ECSContainerInsightsEnabled implements BPSet {
|
||||
private readonly client = new ECSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getClusters = async () => {
|
||||
const response = await this.memoClient.send(new DescribeClustersCommand({ include: ['SETTINGS'] }))
|
||||
return response.clusters || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const clusters = await this.getClusters()
|
||||
|
||||
for (const cluster of clusters) {
|
||||
const containerInsightsSetting = cluster.settings?.find(
|
||||
setting => setting.name === 'containerInsights'
|
||||
)
|
||||
if (containerInsightsSetting?.value === 'enabled') {
|
||||
compliantResources.push(cluster.clusterArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.clusterArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
await this.client.send(
|
||||
new UpdateClusterSettingsCommand({
|
||||
cluster: arn,
|
||||
settings: [{ name: 'containerInsights', value: 'enabled' }]
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
77
src/bpsets/ecs/ECSContainersNonPrivileged.ts
Normal file
77
src/bpsets/ecs/ECSContainersNonPrivileged.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import {
|
||||
ECSClient,
|
||||
DescribeTaskDefinitionCommand,
|
||||
RegisterTaskDefinitionCommand,
|
||||
ListTaskDefinitionsCommand
|
||||
} from '@aws-sdk/client-ecs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ECSContainersNonPrivileged implements BPSet {
|
||||
private readonly client = new ECSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getTaskDefinitions = async () => {
|
||||
const taskDefinitionArns = await this.memoClient.send(
|
||||
new ListTaskDefinitionsCommand({ status: 'ACTIVE' })
|
||||
)
|
||||
const taskDefinitions = []
|
||||
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
||||
const taskDefinition = await this.memoClient.send(
|
||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
||||
)
|
||||
taskDefinitions.push(taskDefinition.taskDefinition!)
|
||||
}
|
||||
return taskDefinitions
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const taskDefinitions = await this.getTaskDefinitions()
|
||||
|
||||
for (const taskDefinition of taskDefinitions) {
|
||||
const privilegedContainers = taskDefinition.containerDefinitions?.filter(
|
||||
container => container.privileged
|
||||
)
|
||||
if (privilegedContainers?.length) {
|
||||
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||
} else {
|
||||
compliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const taskDefinition = await this.memoClient.send(
|
||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
||||
)
|
||||
const family = taskDefinition.taskDefinition?.family
|
||||
|
||||
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map(
|
||||
container => ({
|
||||
...container,
|
||||
privileged: false
|
||||
})
|
||||
)
|
||||
|
||||
await this.client.send(
|
||||
new RegisterTaskDefinitionCommand({
|
||||
family,
|
||||
containerDefinitions: updatedContainers,
|
||||
networkMode: taskDefinition.taskDefinition?.networkMode,
|
||||
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
||||
cpu: taskDefinition.taskDefinition?.cpu,
|
||||
memory: taskDefinition.taskDefinition?.memory
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
77
src/bpsets/ecs/ECSContainersReadonlyAccess.ts
Normal file
77
src/bpsets/ecs/ECSContainersReadonlyAccess.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import {
|
||||
ECSClient,
|
||||
DescribeTaskDefinitionCommand,
|
||||
RegisterTaskDefinitionCommand,
|
||||
ListTaskDefinitionsCommand
|
||||
} from '@aws-sdk/client-ecs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ECSContainersReadonlyAccess implements BPSet {
|
||||
private readonly client = new ECSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getTaskDefinitions = async () => {
|
||||
const taskDefinitionArns = await this.memoClient.send(
|
||||
new ListTaskDefinitionsCommand({ status: 'ACTIVE' })
|
||||
)
|
||||
const taskDefinitions = []
|
||||
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
||||
const taskDefinition = await this.memoClient.send(
|
||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
||||
)
|
||||
taskDefinitions.push(taskDefinition.taskDefinition!)
|
||||
}
|
||||
return taskDefinitions
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const taskDefinitions = await this.getTaskDefinitions()
|
||||
|
||||
for (const taskDefinition of taskDefinitions) {
|
||||
const notReadonlyContainers = taskDefinition.containerDefinitions?.filter(
|
||||
container => !container.readonlyRootFilesystem
|
||||
)
|
||||
if (notReadonlyContainers?.length) {
|
||||
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||
} else {
|
||||
compliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const taskDefinition = await this.memoClient.send(
|
||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
||||
)
|
||||
const family = taskDefinition.taskDefinition?.family
|
||||
|
||||
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map(
|
||||
container => ({
|
||||
...container,
|
||||
readonlyRootFilesystem: true
|
||||
})
|
||||
)
|
||||
|
||||
await this.client.send(
|
||||
new RegisterTaskDefinitionCommand({
|
||||
family,
|
||||
containerDefinitions: updatedContainers,
|
||||
networkMode: taskDefinition.taskDefinition?.networkMode,
|
||||
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
||||
cpu: taskDefinition.taskDefinition?.cpu,
|
||||
memory: taskDefinition.taskDefinition?.memory
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
70
src/bpsets/ecs/ECSFargateLatestPlatformVersion.ts
Normal file
70
src/bpsets/ecs/ECSFargateLatestPlatformVersion.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import {
|
||||
ECSClient,
|
||||
ListClustersCommand,
|
||||
ListServicesCommand,
|
||||
DescribeServicesCommand,
|
||||
UpdateServiceCommand
|
||||
} from '@aws-sdk/client-ecs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ECSFargateLatestPlatformVersion implements BPSet {
|
||||
private readonly client = new ECSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getServices = async () => {
|
||||
const clustersResponse = await this.memoClient.send(new ListClustersCommand({}))
|
||||
const clusterArns = clustersResponse.clusterArns || []
|
||||
const services: { clusterArn: string; serviceArn: string }[] = []
|
||||
|
||||
for (const clusterArn of clusterArns) {
|
||||
const servicesResponse = await this.memoClient.send(
|
||||
new ListServicesCommand({ cluster: clusterArn })
|
||||
)
|
||||
for (const serviceArn of servicesResponse.serviceArns || []) {
|
||||
services.push({ clusterArn, serviceArn })
|
||||
}
|
||||
}
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const services = await this.getServices()
|
||||
|
||||
for (const { clusterArn, serviceArn } of services) {
|
||||
const serviceResponse = await this.memoClient.send(
|
||||
new DescribeServicesCommand({ cluster: clusterArn, services: [serviceArn] })
|
||||
)
|
||||
|
||||
const service = serviceResponse.services?.[0]
|
||||
if (service?.platformVersion === 'LATEST') {
|
||||
compliantResources.push(service.serviceArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(service?.serviceArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const serviceArn of nonCompliantResources) {
|
||||
const clusterArn = serviceArn.split(':cluster/')[1].split(':service/')[0]
|
||||
|
||||
await this.client.send(
|
||||
new UpdateServiceCommand({
|
||||
cluster: clusterArn,
|
||||
service: serviceArn,
|
||||
platformVersion: 'LATEST'
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
88
src/bpsets/ecs/ECSTaskDefinitionLogConfiguration.ts
Normal file
88
src/bpsets/ecs/ECSTaskDefinitionLogConfiguration.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import {
|
||||
ECSClient,
|
||||
ListTaskDefinitionsCommand,
|
||||
DescribeTaskDefinitionCommand,
|
||||
RegisterTaskDefinitionCommand
|
||||
} from '@aws-sdk/client-ecs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ECSTaskDefinitionLogConfiguration implements BPSet {
|
||||
private readonly client = new ECSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getTaskDefinitions = async () => {
|
||||
const taskDefinitionArns = await this.memoClient.send(
|
||||
new ListTaskDefinitionsCommand({ status: 'ACTIVE' })
|
||||
)
|
||||
const taskDefinitions = []
|
||||
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
||||
const taskDefinition = await this.memoClient.send(
|
||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
||||
)
|
||||
taskDefinitions.push(taskDefinition.taskDefinition!)
|
||||
}
|
||||
return taskDefinitions
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const taskDefinitions = await this.getTaskDefinitions()
|
||||
|
||||
for (const taskDefinition of taskDefinitions) {
|
||||
const logDisabledContainers = taskDefinition.containerDefinitions?.filter(
|
||||
container => !container.logConfiguration
|
||||
)
|
||||
if (logDisabledContainers?.length) {
|
||||
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||
} else {
|
||||
compliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'log-configuration' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const logConfiguration = requiredParametersForFix.find(
|
||||
param => param.name === 'log-configuration'
|
||||
)?.value
|
||||
|
||||
if (!logConfiguration) {
|
||||
throw new Error("Required parameter 'log-configuration' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const taskDefinition = await this.memoClient.send(
|
||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
||||
)
|
||||
const family = taskDefinition.taskDefinition?.family
|
||||
|
||||
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map(
|
||||
container => ({
|
||||
...container,
|
||||
logConfiguration: JSON.parse(logConfiguration)
|
||||
})
|
||||
)
|
||||
|
||||
await this.client.send(
|
||||
new RegisterTaskDefinitionCommand({
|
||||
family,
|
||||
containerDefinitions: updatedContainers,
|
||||
networkMode: taskDefinition.taskDefinition?.networkMode,
|
||||
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
||||
cpu: taskDefinition.taskDefinition?.cpu,
|
||||
memory: taskDefinition.taskDefinition?.memory
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
77
src/bpsets/ecs/ECSTaskDefinitionMemoryHardLimit.ts
Normal file
77
src/bpsets/ecs/ECSTaskDefinitionMemoryHardLimit.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import {
|
||||
ECSClient,
|
||||
ListTaskDefinitionsCommand,
|
||||
DescribeTaskDefinitionCommand,
|
||||
RegisterTaskDefinitionCommand
|
||||
} from '@aws-sdk/client-ecs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ECSTaskDefinitionMemoryHardLimit implements BPSet {
|
||||
private readonly client = new ECSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getTaskDefinitions = async () => {
|
||||
const taskDefinitionArns = await this.memoClient.send(
|
||||
new ListTaskDefinitionsCommand({ status: 'ACTIVE' })
|
||||
)
|
||||
const taskDefinitions = []
|
||||
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
||||
const taskDefinition = await this.memoClient.send(
|
||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
||||
)
|
||||
taskDefinitions.push(taskDefinition.taskDefinition!)
|
||||
}
|
||||
return taskDefinitions
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const taskDefinitions = await this.getTaskDefinitions()
|
||||
|
||||
for (const taskDefinition of taskDefinitions) {
|
||||
const containersWithoutMemoryLimit = taskDefinition.containerDefinitions?.filter(
|
||||
container => !container.memory
|
||||
)
|
||||
if (containersWithoutMemoryLimit?.length) {
|
||||
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||
} else {
|
||||
compliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const taskDefinition = await this.memoClient.send(
|
||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
||||
)
|
||||
const family = taskDefinition.taskDefinition?.family
|
||||
|
||||
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map(
|
||||
container => ({
|
||||
...container,
|
||||
memory: container.memory || 512 // Default hard limit memory value
|
||||
})
|
||||
)
|
||||
|
||||
await this.client.send(
|
||||
new RegisterTaskDefinitionCommand({
|
||||
family,
|
||||
containerDefinitions: updatedContainers,
|
||||
networkMode: taskDefinition.taskDefinition?.networkMode,
|
||||
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
||||
cpu: taskDefinition.taskDefinition?.cpu,
|
||||
memory: taskDefinition.taskDefinition?.memory
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
77
src/bpsets/ecs/ECSTaskDefinitionNonRootUser.ts
Normal file
77
src/bpsets/ecs/ECSTaskDefinitionNonRootUser.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import {
|
||||
ECSClient,
|
||||
ListTaskDefinitionsCommand,
|
||||
DescribeTaskDefinitionCommand,
|
||||
RegisterTaskDefinitionCommand
|
||||
} from '@aws-sdk/client-ecs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ECSTaskDefinitionNonRootUser implements BPSet {
|
||||
private readonly client = new ECSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getTaskDefinitions = async () => {
|
||||
const taskDefinitionArns = await this.memoClient.send(
|
||||
new ListTaskDefinitionsCommand({ status: 'ACTIVE' })
|
||||
)
|
||||
const taskDefinitions = []
|
||||
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
||||
const taskDefinition = await this.memoClient.send(
|
||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
||||
)
|
||||
taskDefinitions.push(taskDefinition.taskDefinition!)
|
||||
}
|
||||
return taskDefinitions
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const taskDefinitions = await this.getTaskDefinitions()
|
||||
|
||||
for (const taskDefinition of taskDefinitions) {
|
||||
const privilegedContainers = taskDefinition.containerDefinitions?.filter(
|
||||
container => !container.user || container.user === 'root'
|
||||
)
|
||||
if (privilegedContainers?.length) {
|
||||
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||
} else {
|
||||
compliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const taskDefinition = await this.memoClient.send(
|
||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
||||
)
|
||||
const family = taskDefinition.taskDefinition?.family
|
||||
|
||||
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map(
|
||||
container => ({
|
||||
...container,
|
||||
user: container.user || 'ecs-user' // Default non-root user
|
||||
})
|
||||
)
|
||||
|
||||
await this.client.send(
|
||||
new RegisterTaskDefinitionCommand({
|
||||
family,
|
||||
containerDefinitions: updatedContainers,
|
||||
networkMode: taskDefinition.taskDefinition?.networkMode,
|
||||
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
||||
cpu: taskDefinition.taskDefinition?.cpu,
|
||||
memory: taskDefinition.taskDefinition?.memory
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
71
src/bpsets/efs/EFSAccessPointEnforceRootDirectory.ts
Normal file
71
src/bpsets/efs/EFSAccessPointEnforceRootDirectory.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import {
|
||||
EFSClient,
|
||||
DescribeAccessPointsCommand,
|
||||
DeleteAccessPointCommand,
|
||||
CreateAccessPointCommand
|
||||
} from '@aws-sdk/client-efs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EFSAccessPointEnforceRootDirectory implements BPSet {
|
||||
private readonly client = new EFSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getAccessPoints = async () => {
|
||||
const response = await this.memoClient.send(new DescribeAccessPointsCommand({}))
|
||||
return response.AccessPoints || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const accessPoints = await this.getAccessPoints()
|
||||
|
||||
for (const accessPoint of accessPoints) {
|
||||
if (accessPoint.RootDirectory?.Path !== '/') {
|
||||
compliantResources.push(accessPoint.AccessPointArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(accessPoint.AccessPointArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'root-directory-path' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const rootDirectoryPath = requiredParametersForFix.find(
|
||||
param => param.name === 'root-directory-path'
|
||||
)?.value
|
||||
|
||||
if (!rootDirectoryPath) {
|
||||
throw new Error("Required parameter 'root-directory-path' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const accessPointId = arn.split('/').pop()!
|
||||
const fileSystemId = arn.split(':file-system/')[1].split('/')[0]
|
||||
|
||||
// Delete the existing access point
|
||||
await this.client.send(
|
||||
new DeleteAccessPointCommand({
|
||||
AccessPointId: accessPointId
|
||||
})
|
||||
)
|
||||
|
||||
// Recreate the access point with the desired root directory
|
||||
await this.client.send(
|
||||
new CreateAccessPointCommand({
|
||||
FileSystemId: fileSystemId,
|
||||
RootDirectory: { Path: rootDirectoryPath }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
69
src/bpsets/efs/EFSAccessPointEnforceUserIdentity.ts
Normal file
69
src/bpsets/efs/EFSAccessPointEnforceUserIdentity.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import {
|
||||
EFSClient,
|
||||
DescribeAccessPointsCommand,
|
||||
DeleteAccessPointCommand,
|
||||
CreateAccessPointCommand
|
||||
} from '@aws-sdk/client-efs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EFSAccessPointEnforceUserIdentity implements BPSet {
|
||||
private readonly client = new EFSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getAccessPoints = async () => {
|
||||
const response = await this.memoClient.send(new DescribeAccessPointsCommand({}))
|
||||
return response.AccessPoints || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const accessPoints = await this.getAccessPoints()
|
||||
|
||||
for (const accessPoint of accessPoints) {
|
||||
if (accessPoint.PosixUser) {
|
||||
compliantResources.push(accessPoint.AccessPointArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(accessPoint.AccessPointArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'posix-user' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const posixUser = requiredParametersForFix.find(param => param.name === 'posix-user')?.value
|
||||
|
||||
if (!posixUser) {
|
||||
throw new Error("Required parameter 'posix-user' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const accessPointId = arn.split('/').pop()!
|
||||
const fileSystemId = arn.split(':file-system/')[1].split('/')[0]
|
||||
|
||||
// Delete the existing access point
|
||||
await this.client.send(
|
||||
new DeleteAccessPointCommand({
|
||||
AccessPointId: accessPointId
|
||||
})
|
||||
)
|
||||
|
||||
// Recreate the access point with the desired PosixUser
|
||||
await this.client.send(
|
||||
new CreateAccessPointCommand({
|
||||
FileSystemId: fileSystemId,
|
||||
PosixUser: JSON.parse(posixUser)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
55
src/bpsets/efs/EFSAutomaticBackupsEnabled.ts
Normal file
55
src/bpsets/efs/EFSAutomaticBackupsEnabled.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import {
|
||||
EFSClient,
|
||||
DescribeFileSystemsCommand,
|
||||
PutBackupPolicyCommand,
|
||||
DescribeBackupPolicyCommand
|
||||
} from '@aws-sdk/client-efs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EFSAutomaticBackupsEnabled implements BPSet {
|
||||
private readonly client = new EFSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getFileSystems = async () => {
|
||||
const response = await this.memoClient.send(new DescribeFileSystemsCommand({}))
|
||||
return response.FileSystems || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const fileSystems = await this.getFileSystems()
|
||||
|
||||
for (const fileSystem of fileSystems) {
|
||||
const response = await this.client.send(
|
||||
new DescribeBackupPolicyCommand({ FileSystemId: fileSystem.FileSystemId! })
|
||||
)
|
||||
|
||||
if (response.BackupPolicy?.Status === 'ENABLED') {
|
||||
compliantResources.push(fileSystem.FileSystemArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(fileSystem.FileSystemArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const fileSystemId = arn.split('/').pop()!
|
||||
|
||||
await this.client.send(
|
||||
new PutBackupPolicyCommand({
|
||||
FileSystemId: fileSystemId,
|
||||
BackupPolicy: { Status: 'ENABLED' }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
64
src/bpsets/efs/EFSEncryptedCheck.ts
Normal file
64
src/bpsets/efs/EFSEncryptedCheck.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import {
|
||||
EFSClient,
|
||||
DescribeFileSystemsCommand,
|
||||
CreateFileSystemCommand,
|
||||
DeleteFileSystemCommand
|
||||
} from '@aws-sdk/client-efs'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EFSEncryptedCheck implements BPSet {
|
||||
private readonly client = new EFSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getFileSystems = async () => {
|
||||
const response = await this.memoClient.send(new DescribeFileSystemsCommand({}))
|
||||
return response.FileSystems || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const fileSystems = await this.getFileSystems()
|
||||
|
||||
for (const fileSystem of fileSystems) {
|
||||
if (fileSystem.Encrypted) {
|
||||
compliantResources.push(fileSystem.FileSystemArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(fileSystem.FileSystemArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const fileSystemId = arn.split('/').pop()!
|
||||
const fileSystem = await this.memoClient.send(
|
||||
new DescribeFileSystemsCommand({ FileSystemId: fileSystemId })
|
||||
)
|
||||
|
||||
// Delete the non-compliant file system
|
||||
await this.client.send(
|
||||
new DeleteFileSystemCommand({
|
||||
FileSystemId: fileSystemId
|
||||
})
|
||||
)
|
||||
|
||||
// Recreate the file system with encryption enabled
|
||||
await this.client.send(
|
||||
new CreateFileSystemCommand({
|
||||
Encrypted: true,
|
||||
PerformanceMode: fileSystem.FileSystems?.[0]?.PerformanceMode,
|
||||
ThroughputMode: fileSystem.FileSystems?.[0]?.ThroughputMode,
|
||||
ProvisionedThroughputInMibps: fileSystem.FileSystems?.[0]?.ProvisionedThroughputInMibps
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
71
src/bpsets/efs/EFSMountTargetPublicAccessible.ts
Normal file
71
src/bpsets/efs/EFSMountTargetPublicAccessible.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import {
|
||||
EFSClient,
|
||||
DescribeFileSystemsCommand,
|
||||
DescribeMountTargetsCommand
|
||||
} from '@aws-sdk/client-efs'
|
||||
import { EC2Client, DescribeRouteTablesCommand } from '@aws-sdk/client-ec2'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EFSMountTargetPublicAccessible implements BPSet {
|
||||
private readonly efsClient = new EFSClient({})
|
||||
private readonly ec2Client = new EC2Client({})
|
||||
private readonly memoEFSClient = Memorizer.memo(this.efsClient)
|
||||
private readonly memoEC2Client = Memorizer.memo(this.ec2Client)
|
||||
|
||||
private readonly getFileSystems = async () => {
|
||||
const response = await this.memoEFSClient.send(new DescribeFileSystemsCommand({}))
|
||||
return response.FileSystems || []
|
||||
}
|
||||
|
||||
private readonly getRoutesForSubnet = async (subnetId: string) => {
|
||||
const response = await this.memoEC2Client.send(
|
||||
new DescribeRouteTablesCommand({
|
||||
Filters: [{ Name: 'association.subnet-id', Values: [subnetId] }]
|
||||
})
|
||||
)
|
||||
return response.RouteTables?.[0]?.Routes || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const fileSystems = await this.getFileSystems()
|
||||
|
||||
for (const fileSystem of fileSystems) {
|
||||
const mountTargets = await this.memoEFSClient.send(
|
||||
new DescribeMountTargetsCommand({ FileSystemId: fileSystem.FileSystemId! })
|
||||
)
|
||||
|
||||
for (const mountTarget of mountTargets.MountTargets || []) {
|
||||
const routes = await this.getRoutesForSubnet(mountTarget.SubnetId!)
|
||||
|
||||
for (const route of routes) {
|
||||
if (
|
||||
route.DestinationCidrBlock === '0.0.0.0/0' &&
|
||||
route.GatewayId?.startsWith('igw-')
|
||||
) {
|
||||
nonCompliantResources.push(fileSystem.FileSystemArn!)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!nonCompliantResources.includes(fileSystem.FileSystemArn!)) {
|
||||
compliantResources.push(fileSystem.FileSystemArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
throw new Error(
|
||||
'Fixing public accessibility for mount targets requires manual network reconfiguration.'
|
||||
)
|
||||
}
|
||||
}
|
67
src/bpsets/eks/EKSClusterLoggingEnabled.ts
Normal file
67
src/bpsets/eks/EKSClusterLoggingEnabled.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
EKSClient,
|
||||
ListClustersCommand,
|
||||
DescribeClusterCommand,
|
||||
UpdateClusterConfigCommand
|
||||
} from '@aws-sdk/client-eks'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EKSClusterLoggingEnabled implements BPSet {
|
||||
private readonly client = new EKSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getClusters = async () => {
|
||||
const clusterNamesResponse = await this.memoClient.send(new ListClustersCommand({}))
|
||||
const clusterNames = clusterNamesResponse.clusters || []
|
||||
const clusters = []
|
||||
for (const clusterName of clusterNames) {
|
||||
const cluster = await this.memoClient.send(
|
||||
new DescribeClusterCommand({ name: clusterName })
|
||||
)
|
||||
clusters.push(cluster.cluster!)
|
||||
}
|
||||
return clusters
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const clusters = await this.getClusters()
|
||||
|
||||
for (const cluster of clusters) {
|
||||
const clusterLogging = cluster.logging?.clusterLogging?.[0]
|
||||
if (clusterLogging?.enabled && clusterLogging.types?.length === 5) {
|
||||
compliantResources.push(cluster.arn!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.arn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterName = arn.split(':cluster/')[1]
|
||||
|
||||
await this.client.send(
|
||||
new UpdateClusterConfigCommand({
|
||||
name: clusterName,
|
||||
logging: {
|
||||
clusterLogging: [
|
||||
{
|
||||
enabled: true,
|
||||
types: ['api', 'audit', 'authenticator', 'controllerManager', 'scheduler']
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
74
src/bpsets/eks/EKSClusterSecretsEncrypted.ts
Normal file
74
src/bpsets/eks/EKSClusterSecretsEncrypted.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import {
|
||||
EKSClient,
|
||||
ListClustersCommand,
|
||||
DescribeClusterCommand,
|
||||
AssociateEncryptionConfigCommand
|
||||
} from '@aws-sdk/client-eks'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EKSClusterSecretsEncrypted implements BPSet {
|
||||
private readonly client = new EKSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getClusters = async () => {
|
||||
const clusterNamesResponse = await this.memoClient.send(new ListClustersCommand({}))
|
||||
const clusterNames = clusterNamesResponse.clusters || []
|
||||
const clusters = []
|
||||
for (const clusterName of clusterNames) {
|
||||
const cluster = await this.memoClient.send(
|
||||
new DescribeClusterCommand({ name: clusterName })
|
||||
)
|
||||
clusters.push(cluster.cluster!)
|
||||
}
|
||||
return clusters
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const clusters = await this.getClusters()
|
||||
|
||||
for (const cluster of clusters) {
|
||||
const encryptionConfig = cluster.encryptionConfig?.[0]
|
||||
if (encryptionConfig?.resources?.includes('secrets')) {
|
||||
compliantResources.push(cluster.arn!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.arn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'kms-key-id' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const kmsKeyId = requiredParametersForFix.find(param => param.name === 'kms-key-id')?.value
|
||||
|
||||
if (!kmsKeyId) {
|
||||
throw new Error("Required parameter 'kms-key-id' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterName = arn.split(':cluster/')[1]
|
||||
|
||||
await this.client.send(
|
||||
new AssociateEncryptionConfigCommand({
|
||||
clusterName,
|
||||
encryptionConfig: [
|
||||
{
|
||||
resources: ['secrets'],
|
||||
provider: { keyArn: kmsKeyId }
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
62
src/bpsets/eks/EKSEndpointNoPublicAccess.ts
Normal file
62
src/bpsets/eks/EKSEndpointNoPublicAccess.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import {
|
||||
EKSClient,
|
||||
ListClustersCommand,
|
||||
DescribeClusterCommand,
|
||||
UpdateClusterConfigCommand
|
||||
} from '@aws-sdk/client-eks'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class EKSEndpointNoPublicAccess implements BPSet {
|
||||
private readonly client = new EKSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getClusters = async () => {
|
||||
const clusterNamesResponse = await this.memoClient.send(new ListClustersCommand({}))
|
||||
const clusterNames = clusterNamesResponse.clusters || []
|
||||
const clusters = []
|
||||
for (const clusterName of clusterNames) {
|
||||
const cluster = await this.memoClient.send(
|
||||
new DescribeClusterCommand({ name: clusterName })
|
||||
)
|
||||
clusters.push(cluster.cluster!)
|
||||
}
|
||||
return clusters
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const clusters = await this.getClusters()
|
||||
|
||||
for (const cluster of clusters) {
|
||||
const endpointPublicAccess = cluster.resourcesVpcConfig?.endpointPublicAccess
|
||||
if (endpointPublicAccess) {
|
||||
nonCompliantResources.push(cluster.arn!)
|
||||
} else {
|
||||
compliantResources.push(cluster.arn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterName = arn.split(':cluster/')[1]
|
||||
|
||||
await this.client.send(
|
||||
new UpdateClusterConfigCommand({
|
||||
name: clusterName,
|
||||
resourcesVpcConfig: {
|
||||
endpointPublicAccess: false
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import {
|
||||
ElastiCacheClient,
|
||||
DescribeCacheClustersCommand,
|
||||
ModifyCacheClusterCommand
|
||||
} from '@aws-sdk/client-elasticache'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ElastiCacheAutoMinorVersionUpgradeCheck implements BPSet {
|
||||
private readonly client = new ElastiCacheClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getClusters = async () => {
|
||||
const response = await this.memoClient.send(new DescribeCacheClustersCommand({}))
|
||||
return response.CacheClusters || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const clusters = await this.getClusters()
|
||||
|
||||
for (const cluster of clusters) {
|
||||
if (cluster.AutoMinorVersionUpgrade) {
|
||||
compliantResources.push(cluster.ARN!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.ARN!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterId = arn.split(':cluster:')[1]
|
||||
await this.client.send(
|
||||
new ModifyCacheClusterCommand({
|
||||
CacheClusterId: clusterId,
|
||||
AutoMinorVersionUpgrade: true
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
import {
|
||||
ElastiCacheClient,
|
||||
DescribeReplicationGroupsCommand,
|
||||
ModifyReplicationGroupCommand
|
||||
} from '@aws-sdk/client-elasticache'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ElastiCacheRedisClusterAutomaticBackupCheck implements BPSet {
|
||||
private readonly client = new ElastiCacheClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getReplicationGroups = async () => {
|
||||
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}))
|
||||
return response.ReplicationGroups || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const replicationGroups = await this.getReplicationGroups()
|
||||
|
||||
for (const group of replicationGroups) {
|
||||
if (group.SnapshottingClusterId) {
|
||||
compliantResources.push(group.ARN!)
|
||||
} else {
|
||||
nonCompliantResources.push(group.ARN!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'snapshot-retention-period' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const retentionPeriod = requiredParametersForFix.find(
|
||||
param => param.name === 'snapshot-retention-period'
|
||||
)?.value
|
||||
|
||||
if (!retentionPeriod) {
|
||||
throw new Error("Required parameter 'snapshot-retention-period' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const groupId = arn.split(':replication-group:')[1]
|
||||
await this.client.send(
|
||||
new ModifyReplicationGroupCommand({
|
||||
ReplicationGroupId: groupId,
|
||||
SnapshotRetentionLimit: parseInt(retentionPeriod, 10)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import {
|
||||
ElastiCacheClient,
|
||||
DescribeReplicationGroupsCommand,
|
||||
ModifyReplicationGroupCommand
|
||||
} from '@aws-sdk/client-elasticache'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ElastiCacheReplGrpAutoFailoverEnabled implements BPSet {
|
||||
private readonly client = new ElastiCacheClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getReplicationGroups = async () => {
|
||||
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}))
|
||||
return response.ReplicationGroups || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const replicationGroups = await this.getReplicationGroups()
|
||||
|
||||
for (const group of replicationGroups) {
|
||||
if (group.AutomaticFailover === 'enabled') {
|
||||
compliantResources.push(group.ARN!)
|
||||
} else {
|
||||
nonCompliantResources.push(group.ARN!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const groupId = arn.split(':replication-group:')[1]
|
||||
await this.client.send(
|
||||
new ModifyReplicationGroupCommand({
|
||||
ReplicationGroupId: groupId,
|
||||
AutomaticFailoverEnabled: true
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
43
src/bpsets/elasticache/ElastiCacheReplGrpEncryptedAtRest.ts
Normal file
43
src/bpsets/elasticache/ElastiCacheReplGrpEncryptedAtRest.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import {
|
||||
ElastiCacheClient,
|
||||
DescribeReplicationGroupsCommand,
|
||||
ModifyReplicationGroupCommand
|
||||
} from '@aws-sdk/client-elasticache'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ElastiCacheReplGrpEncryptedAtRest implements BPSet {
|
||||
private readonly client = new ElastiCacheClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getReplicationGroups = async () => {
|
||||
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}))
|
||||
return response.ReplicationGroups || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const replicationGroups = await this.getReplicationGroups()
|
||||
|
||||
for (const group of replicationGroups) {
|
||||
if (group.AtRestEncryptionEnabled) {
|
||||
compliantResources.push(group.ARN!)
|
||||
} else {
|
||||
nonCompliantResources.push(group.ARN!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
throw new Error(
|
||||
'Fixing encryption at rest for replication groups requires recreation. Please create a new replication group with AtRestEncryptionEnabled set to true.'
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import {
|
||||
ElastiCacheClient,
|
||||
DescribeReplicationGroupsCommand,
|
||||
ModifyReplicationGroupCommand
|
||||
} from '@aws-sdk/client-elasticache'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ElastiCacheReplGrpEncryptedInTransit implements BPSet {
|
||||
private readonly client = new ElastiCacheClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getReplicationGroups = async () => {
|
||||
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}))
|
||||
return response.ReplicationGroups || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const replicationGroups = await this.getReplicationGroups()
|
||||
|
||||
for (const group of replicationGroups) {
|
||||
if (group.TransitEncryptionEnabled) {
|
||||
compliantResources.push(group.ARN!)
|
||||
} else {
|
||||
nonCompliantResources.push(group.ARN!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
throw new Error(
|
||||
'Fixing in-transit encryption for replication groups requires recreation. Please create a new replication group with TransitEncryptionEnabled set to true.'
|
||||
)
|
||||
}
|
||||
}
|
84
src/bpsets/elasticache/ElastiCacheSubnetGroupCheck.ts
Normal file
84
src/bpsets/elasticache/ElastiCacheSubnetGroupCheck.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import {
|
||||
ElastiCacheClient,
|
||||
DescribeCacheClustersCommand,
|
||||
DeleteCacheClusterCommand,
|
||||
CreateCacheClusterCommand
|
||||
} from '@aws-sdk/client-elasticache'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class ElastiCacheSubnetGroupCheck implements BPSet {
|
||||
private readonly client = new ElastiCacheClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getClusters = async () => {
|
||||
const response = await this.memoClient.send(new DescribeCacheClustersCommand({}))
|
||||
return response.CacheClusters || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const clusters = await this.getClusters()
|
||||
|
||||
for (const cluster of clusters) {
|
||||
if (cluster.CacheSubnetGroupName !== 'default') {
|
||||
compliantResources.push(cluster.ARN!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.ARN!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'subnet-group-name' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const subnetGroupName = requiredParametersForFix.find(
|
||||
param => param.name === 'subnet-group-name'
|
||||
)?.value
|
||||
|
||||
if (!subnetGroupName) {
|
||||
throw new Error("Required parameter 'subnet-group-name' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterId = arn.split(':cluster:')[1]
|
||||
const cluster = await this.memoClient.send(
|
||||
new DescribeCacheClustersCommand({ CacheClusterId: clusterId })
|
||||
)
|
||||
const clusterDetails = cluster.CacheClusters?.[0]
|
||||
|
||||
if (!clusterDetails) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Delete the non-compliant cluster
|
||||
await this.client.send(
|
||||
new DeleteCacheClusterCommand({
|
||||
CacheClusterId: clusterId
|
||||
})
|
||||
)
|
||||
|
||||
// Recreate the cluster with the desired subnet group
|
||||
await this.client.send(
|
||||
new CreateCacheClusterCommand({
|
||||
CacheClusterId: clusterDetails.CacheClusterId!,
|
||||
Engine: clusterDetails.Engine!,
|
||||
CacheNodeType: clusterDetails.CacheNodeType!,
|
||||
NumCacheNodes: clusterDetails.NumCacheNodes!,
|
||||
CacheSubnetGroupName: subnetGroupName,
|
||||
SecurityGroupIds: clusterDetails.SecurityGroups?.map(group => group.SecurityGroupId) as string[],
|
||||
PreferredMaintenanceWindow: clusterDetails.PreferredMaintenanceWindow,
|
||||
EngineVersion: clusterDetails.EngineVersion
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
67
src/bpsets/iam/IAMPolicyNoStatementsWithAdminAccess.ts
Normal file
67
src/bpsets/iam/IAMPolicyNoStatementsWithAdminAccess.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
IAMClient,
|
||||
ListPoliciesCommand,
|
||||
GetPolicyVersionCommand,
|
||||
DeletePolicyCommand
|
||||
} from '@aws-sdk/client-iam'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class IAMPolicyNoStatementsWithAdminAccess implements BPSet {
|
||||
private readonly client = new IAMClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getPolicies = async () => {
|
||||
const response = await this.memoClient.send(new ListPoliciesCommand({ Scope: 'Local' }))
|
||||
return response.Policies || []
|
||||
}
|
||||
|
||||
private readonly getPolicyDefaultVersions = async (policyArn: string, versionId: string) => {
|
||||
const response = await this.memoClient.send(
|
||||
new GetPolicyVersionCommand({ PolicyArn: policyArn, VersionId: versionId })
|
||||
)
|
||||
return response.PolicyVersion!
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const policies = await this.getPolicies()
|
||||
|
||||
for (const policy of policies) {
|
||||
const policyVersion = await this.getPolicyDefaultVersions(policy.Arn!, policy.DefaultVersionId!)
|
||||
|
||||
const policyDocument = JSON.parse(JSON.stringify(policyVersion.Document)) // Parse Document JSON string
|
||||
const statements = Array.isArray(policyDocument.Statement)
|
||||
? policyDocument.Statement
|
||||
: [policyDocument.Statement]
|
||||
|
||||
for (const statement of statements) {
|
||||
if (
|
||||
statement.Action === '*' &&
|
||||
statement.Resource === '*' &&
|
||||
statement.Effect === 'Allow'
|
||||
) {
|
||||
nonCompliantResources.push(policy.Arn!)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!nonCompliantResources.includes(policy.Arn!)) {
|
||||
compliantResources.push(policy.Arn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
await this.client.send(new DeletePolicyCommand({ PolicyArn: arn }))
|
||||
}
|
||||
}
|
||||
}
|
67
src/bpsets/iam/IAMPolicyNoStatementsWithFullAccess.ts
Normal file
67
src/bpsets/iam/IAMPolicyNoStatementsWithFullAccess.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
IAMClient,
|
||||
ListPoliciesCommand,
|
||||
GetPolicyVersionCommand,
|
||||
DeletePolicyCommand
|
||||
} from '@aws-sdk/client-iam'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class IAMPolicyNoStatementsWithAdminAccess implements BPSet {
|
||||
private readonly client = new IAMClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getPolicies = async () => {
|
||||
const response = await this.memoClient.send(new ListPoliciesCommand({ Scope: 'Local' }))
|
||||
return response.Policies || []
|
||||
}
|
||||
|
||||
private readonly getPolicyDefaultVersions = async (policyArn: string, versionId: string) => {
|
||||
const response = await this.memoClient.send(
|
||||
new GetPolicyVersionCommand({ PolicyArn: policyArn, VersionId: versionId })
|
||||
)
|
||||
return response.PolicyVersion!
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const policies = await this.getPolicies()
|
||||
|
||||
for (const policy of policies) {
|
||||
const policyVersion = await this.getPolicyDefaultVersions(policy.Arn!, policy.DefaultVersionId!)
|
||||
|
||||
const policyDocument = JSON.parse(JSON.stringify(policyVersion.Document)) // Parse Document JSON string
|
||||
const statements = Array.isArray(policyDocument.Statement)
|
||||
? policyDocument.Statement
|
||||
: [policyDocument.Statement]
|
||||
|
||||
for (const statement of statements) {
|
||||
if (
|
||||
statement.Action === '*' &&
|
||||
statement.Resource === '*' &&
|
||||
statement.Effect === 'Allow'
|
||||
) {
|
||||
nonCompliantResources.push(policy.Arn!)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!nonCompliantResources.includes(policy.Arn!)) {
|
||||
compliantResources.push(policy.Arn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
await this.client.send(new DeletePolicyCommand({ PolicyArn: arn }))
|
||||
}
|
||||
}
|
||||
}
|
56
src/bpsets/iam/IAMRoleManagedPolicyCheck.ts
Normal file
56
src/bpsets/iam/IAMRoleManagedPolicyCheck.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import {
|
||||
IAMClient,
|
||||
ListPoliciesCommand,
|
||||
ListEntitiesForPolicyCommand
|
||||
} from '@aws-sdk/client-iam'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class IAMRoleManagedPolicyCheck implements BPSet {
|
||||
private readonly client = new IAMClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getPolicies = async () => {
|
||||
const response = await this.memoClient.send(new ListPoliciesCommand({ Scope: 'Local' }))
|
||||
return response.Policies || []
|
||||
}
|
||||
|
||||
private readonly checkEntitiesForPolicy = async (policyArn: string) => {
|
||||
const response = await this.memoClient.send(
|
||||
new ListEntitiesForPolicyCommand({ PolicyArn: policyArn })
|
||||
)
|
||||
return {
|
||||
attached: Boolean(
|
||||
response.PolicyGroups?.length || response.PolicyUsers?.length || response.PolicyRoles?.length
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const policies = await this.getPolicies()
|
||||
|
||||
for (const policy of policies) {
|
||||
const { attached } = await this.checkEntitiesForPolicy(policy.Arn!)
|
||||
|
||||
if (attached) {
|
||||
compliantResources.push(policy.Arn!)
|
||||
} else {
|
||||
nonCompliantResources.push(policy.Arn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
throw new Error(
|
||||
'Fixing orphaned managed policies requires manual review and removal. Ensure these policies are no longer needed.'
|
||||
)
|
||||
}
|
||||
}
|
80
src/bpsets/rds/AuroraLastBackupRecoveryPointCreated.ts
Normal file
80
src/bpsets/rds/AuroraLastBackupRecoveryPointCreated.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBClustersCommand,
|
||||
ModifyDBClusterCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import {
|
||||
BackupClient,
|
||||
ListRecoveryPointsByResourceCommand
|
||||
} from '@aws-sdk/client-backup'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class AuroraLastBackupRecoveryPointCreated implements BPSet {
|
||||
private readonly rdsClient = new RDSClient({})
|
||||
private readonly backupClient = new BackupClient({})
|
||||
private readonly memoRdsClient = Memorizer.memo(this.rdsClient)
|
||||
|
||||
private readonly getDBClusters = async () => {
|
||||
const response = await this.memoRdsClient.send(new DescribeDBClustersCommand({}))
|
||||
return response.DBClusters || []
|
||||
}
|
||||
|
||||
private readonly getRecoveryPoints = async (resourceArn: string) => {
|
||||
const response = await this.backupClient.send(
|
||||
new ListRecoveryPointsByResourceCommand({ ResourceArn: resourceArn })
|
||||
)
|
||||
return response.RecoveryPoints || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const dbClusters = await this.getDBClusters()
|
||||
|
||||
for (const cluster of dbClusters) {
|
||||
const recoveryPoints = await this.getRecoveryPoints(cluster.DBClusterArn!)
|
||||
const recoveryDates = recoveryPoints.map(rp => new Date(rp.CreationDate!))
|
||||
recoveryDates.sort((a, b) => b.getTime() - a.getTime())
|
||||
|
||||
if (
|
||||
recoveryDates.length > 0 &&
|
||||
new Date().getTime() - recoveryDates[0].getTime() < 24 * 60 * 60 * 1000
|
||||
) {
|
||||
compliantResources.push(cluster.DBClusterArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'backup-retention-period', value: '7' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const retentionPeriod = requiredParametersForFix.find(
|
||||
param => param.name === 'backup-retention-period'
|
||||
)?.value
|
||||
|
||||
if (!retentionPeriod) {
|
||||
throw new Error("Required parameter 'backup-retention-period' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterId = arn.split(':cluster/')[1]
|
||||
|
||||
await this.rdsClient.send(
|
||||
new ModifyDBClusterCommand({
|
||||
DBClusterIdentifier: clusterId,
|
||||
BackupRetentionPeriod: parseInt(retentionPeriod, 10)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
52
src/bpsets/rds/AuroraMySQLBacktrackingEnabled.ts
Normal file
52
src/bpsets/rds/AuroraMySQLBacktrackingEnabled.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBClustersCommand,
|
||||
ModifyDBClusterCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class AuroraMySQLBacktrackingEnabled implements BPSet {
|
||||
private readonly client = new RDSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDBClusters = async () => {
|
||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||
return response.DBClusters || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const dbClusters = await this.getDBClusters()
|
||||
|
||||
for (const cluster of dbClusters) {
|
||||
if (
|
||||
cluster.Engine === 'aurora-mysql' &&
|
||||
(!cluster.EarliestBacktrackTime || cluster.EarliestBacktrackTime === null)
|
||||
) {
|
||||
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||
} else {
|
||||
compliantResources.push(cluster.DBClusterArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterId = arn.split(':cluster/')[1]
|
||||
await this.client.send(
|
||||
new ModifyDBClusterCommand({
|
||||
DBClusterIdentifier: clusterId,
|
||||
BacktrackWindow: 3600 // Set backtracking window to 1 hour
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
60
src/bpsets/rds/DBInstanceBackupEnabled.ts
Normal file
60
src/bpsets/rds/DBInstanceBackupEnabled.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBInstancesCommand,
|
||||
ModifyDBInstanceCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class DBInstanceBackupEnabled implements BPSet {
|
||||
private readonly client = new RDSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDBInstances = async () => {
|
||||
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}))
|
||||
return response.DBInstances || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const dbInstances = await this.getDBInstances()
|
||||
|
||||
for (const instance of dbInstances) {
|
||||
if (instance.BackupRetentionPeriod && instance.BackupRetentionPeriod > 0) {
|
||||
compliantResources.push(instance.DBInstanceArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(instance.DBInstanceArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'retention-period', value: '7' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const retentionPeriod = requiredParametersForFix.find(
|
||||
param => param.name === 'retention-period'
|
||||
)?.value
|
||||
|
||||
if (!retentionPeriod) {
|
||||
throw new Error("Required parameter 'retention-period' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const instanceId = arn.split(':instance/')[1]
|
||||
await this.client.send(
|
||||
new ModifyDBInstanceCommand({
|
||||
DBInstanceIdentifier: instanceId,
|
||||
BackupRetentionPeriod: parseInt(retentionPeriod, 10)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
50
src/bpsets/rds/RDSClusterAutoMinorVersionUpgradeEnabled.ts
Normal file
50
src/bpsets/rds/RDSClusterAutoMinorVersionUpgradeEnabled.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBClustersCommand,
|
||||
ModifyDBClusterCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class RDSClusterAutoMinorVersionUpgradeEnabled implements BPSet {
|
||||
private readonly client = new RDSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDBClusters = async () => {
|
||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||
return response.DBClusters || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const dbClusters = await this.getDBClusters()
|
||||
|
||||
for (const cluster of dbClusters) {
|
||||
if (cluster.Engine === 'docdb' || cluster.AutoMinorVersionUpgrade) {
|
||||
compliantResources.push(cluster.DBClusterArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterId = arn.split(':cluster/')[1]
|
||||
|
||||
await this.client.send(
|
||||
new ModifyDBClusterCommand({
|
||||
DBClusterIdentifier: clusterId,
|
||||
AutoMinorVersionUpgrade: true
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
67
src/bpsets/rds/RDSClusterDefaultAdminCheck.ts
Normal file
67
src/bpsets/rds/RDSClusterDefaultAdminCheck.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBClustersCommand,
|
||||
ModifyDBClusterCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class RDSClusterDefaultAdminCheck implements BPSet {
|
||||
private readonly client = new RDSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDBClusters = async () => {
|
||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||
return response.DBClusters || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const dbClusters = await this.getDBClusters()
|
||||
|
||||
for (const cluster of dbClusters) {
|
||||
if (!['admin', 'postgres'].includes(cluster.MasterUsername!)) {
|
||||
compliantResources.push(cluster.DBClusterArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [
|
||||
{ name: 'new-master-username', value: '<NEW_MASTER_USERNAME>' },
|
||||
{ name: 'new-master-password', value: '<NEW_MASTER_PASSWORD>' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const newMasterUsername = requiredParametersForFix.find(
|
||||
param => param.name === 'new-master-username'
|
||||
)?.value
|
||||
const newMasterPassword = requiredParametersForFix.find(
|
||||
param => param.name === 'new-master-password'
|
||||
)?.value
|
||||
|
||||
if (!newMasterUsername || !newMasterPassword) {
|
||||
throw new Error("Required parameters 'new-master-username' and 'new-master-password' are missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterId = arn.split(':cluster/')[1]
|
||||
|
||||
await this.client.send(
|
||||
new ModifyDBClusterCommand({
|
||||
DBClusterIdentifier: clusterId,
|
||||
MasterUserPassword: newMasterPassword
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
50
src/bpsets/rds/RDSClusterDeletionProtectionEnabled.ts
Normal file
50
src/bpsets/rds/RDSClusterDeletionProtectionEnabled.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBClustersCommand,
|
||||
ModifyDBClusterCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class RDSClusterDeletionProtectionEnabled implements BPSet {
|
||||
private readonly client = new RDSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDBClusters = async () => {
|
||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||
return response.DBClusters || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const dbClusters = await this.getDBClusters()
|
||||
|
||||
for (const cluster of dbClusters) {
|
||||
if (cluster.DeletionProtection) {
|
||||
compliantResources.push(cluster.DBClusterArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterId = arn.split(':cluster/')[1]
|
||||
|
||||
await this.client.send(
|
||||
new ModifyDBClusterCommand({
|
||||
DBClusterIdentifier: clusterId,
|
||||
DeletionProtection: true
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
43
src/bpsets/rds/RDSClusterEncryptedAtRest.ts
Normal file
43
src/bpsets/rds/RDSClusterEncryptedAtRest.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBClustersCommand,
|
||||
ModifyDBClusterCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class RDSClusterEncryptedAtRest implements BPSet {
|
||||
private readonly client = new RDSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDBClusters = async () => {
|
||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||
return response.DBClusters || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const dbClusters = await this.getDBClusters()
|
||||
|
||||
for (const cluster of dbClusters) {
|
||||
if (cluster.StorageEncrypted) {
|
||||
compliantResources.push(cluster.DBClusterArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
throw new Error(
|
||||
'Fixing encryption at rest requires recreating the cluster. Please manually recreate the cluster with encryption enabled.'
|
||||
)
|
||||
}
|
||||
}
|
53
src/bpsets/rds/RDSClusterIAMAuthenticationEnabled.ts
Normal file
53
src/bpsets/rds/RDSClusterIAMAuthenticationEnabled.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBClustersCommand,
|
||||
ModifyDBClusterCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class RDSClusterIAMAuthenticationEnabled implements BPSet {
|
||||
private readonly client = new RDSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDBClusters = async () => {
|
||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||
return response.DBClusters || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const dbClusters = await this.getDBClusters()
|
||||
|
||||
for (const cluster of dbClusters) {
|
||||
if (
|
||||
cluster.Engine === 'docdb' ||
|
||||
cluster.IAMDatabaseAuthenticationEnabled
|
||||
) {
|
||||
compliantResources.push(cluster.DBClusterArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterId = arn.split(':cluster/')[1]
|
||||
|
||||
await this.client.send(
|
||||
new ModifyDBClusterCommand({
|
||||
DBClusterIdentifier: clusterId,
|
||||
EnableIAMDatabaseAuthentication: true
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
43
src/bpsets/rds/RDSClusterMultiAZEnabled.ts
Normal file
43
src/bpsets/rds/RDSClusterMultiAZEnabled.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBClustersCommand,
|
||||
ModifyDBClusterCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class RDSClusterMultiAZEnabled implements BPSet {
|
||||
private readonly client = new RDSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDBClusters = async () => {
|
||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||
return response.DBClusters || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const dbClusters = await this.getDBClusters()
|
||||
|
||||
for (const cluster of dbClusters) {
|
||||
if ((cluster.AvailabilityZones || []).length > 1) {
|
||||
compliantResources.push(cluster.DBClusterArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'additional-azs', value: '2' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
throw new Error(
|
||||
'Enabling Multi-AZ requires cluster reconfiguration. This must be performed manually.'
|
||||
)
|
||||
}
|
||||
}
|
64
src/bpsets/rds/RDSDBSecurityGroupNotAllowed.ts
Normal file
64
src/bpsets/rds/RDSDBSecurityGroupNotAllowed.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBClustersCommand,
|
||||
ModifyDBClusterCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { EC2Client, DescribeSecurityGroupsCommand } from '@aws-sdk/client-ec2'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class RDSDBSecurityGroupNotAllowed implements BPSet {
|
||||
private readonly rdsClient = new RDSClient({})
|
||||
private readonly ec2Client = new EC2Client({})
|
||||
private readonly memoRdsClient = Memorizer.memo(this.rdsClient)
|
||||
private readonly memoEc2Client = Memorizer.memo(this.ec2Client)
|
||||
|
||||
private readonly getDBClusters = async () => {
|
||||
const response = await this.memoRdsClient.send(new DescribeDBClustersCommand({}))
|
||||
return response.DBClusters || []
|
||||
}
|
||||
|
||||
private readonly getDefaultSecurityGroups = async () => {
|
||||
const response = await this.memoEc2Client.send(
|
||||
new DescribeSecurityGroupsCommand({ Filters: [{ Name: 'group-name', Values: ['default'] }] })
|
||||
)
|
||||
return response.SecurityGroups || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const dbClusters = await this.getDBClusters()
|
||||
const defaultSecurityGroupIds = (await this.getDefaultSecurityGroups()).map(sg => sg.GroupId!)
|
||||
|
||||
for (const cluster of dbClusters) {
|
||||
const activeSecurityGroups = cluster.VpcSecurityGroups?.filter(sg => sg.Status === 'active') || []
|
||||
|
||||
if (activeSecurityGroups.some(sg => defaultSecurityGroupIds.includes(sg.VpcSecurityGroupId!))) {
|
||||
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||
} else {
|
||||
compliantResources.push(cluster.DBClusterArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterId = arn.split(':cluster/')[1]
|
||||
|
||||
// Remove default security groups by modifying the cluster's security group configuration
|
||||
await this.rdsClient.send(
|
||||
new ModifyDBClusterCommand({
|
||||
DBClusterIdentifier: clusterId,
|
||||
VpcSecurityGroupIds: [] // Update to valid non-default security groups
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
61
src/bpsets/rds/RDSEnhancedMonitoringEnabled.ts
Normal file
61
src/bpsets/rds/RDSEnhancedMonitoringEnabled.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBInstancesCommand,
|
||||
ModifyDBInstanceCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class RDSEnhancedMonitoringEnabled implements BPSet {
|
||||
private readonly client = new RDSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDBInstances = async () => {
|
||||
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}))
|
||||
return response.DBInstances || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const dbInstances = await this.getDBInstances()
|
||||
|
||||
for (const instance of dbInstances) {
|
||||
if (instance.MonitoringInterval && instance.MonitoringInterval > 0) {
|
||||
compliantResources.push(instance.DBInstanceArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(instance.DBInstanceArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [{ name: 'monitoring-interval', value: '60' }]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const monitoringInterval = requiredParametersForFix.find(
|
||||
param => param.name === 'monitoring-interval'
|
||||
)?.value
|
||||
|
||||
if (!monitoringInterval) {
|
||||
throw new Error("Required parameter 'monitoring-interval' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const instanceId = arn.split(':instance/')[1]
|
||||
|
||||
await this.client.send(
|
||||
new ModifyDBInstanceCommand({
|
||||
DBInstanceIdentifier: instanceId,
|
||||
MonitoringInterval: parseInt(monitoringInterval, 10)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
50
src/bpsets/rds/RDSInstancePublicAccessCheck.ts
Normal file
50
src/bpsets/rds/RDSInstancePublicAccessCheck.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBInstancesCommand,
|
||||
ModifyDBInstanceCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class RDSInstancePublicAccessCheck implements BPSet {
|
||||
private readonly client = new RDSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDBInstances = async () => {
|
||||
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}))
|
||||
return response.DBInstances || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const dbInstances = await this.getDBInstances()
|
||||
|
||||
for (const instance of dbInstances) {
|
||||
if (instance.PubliclyAccessible) {
|
||||
nonCompliantResources.push(instance.DBInstanceArn!)
|
||||
} else {
|
||||
compliantResources.push(instance.DBInstanceArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const instanceId = arn.split(':instance/')[1]
|
||||
|
||||
await this.client.send(
|
||||
new ModifyDBInstanceCommand({
|
||||
DBInstanceIdentifier: instanceId,
|
||||
PubliclyAccessible: false
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
70
src/bpsets/rds/RDSLoggingEnabled.ts
Normal file
70
src/bpsets/rds/RDSLoggingEnabled.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBClustersCommand,
|
||||
ModifyDBClusterCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class RDSLoggingEnabled implements BPSet {
|
||||
private readonly client = new RDSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDBClusters = async () => {
|
||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||
return response.DBClusters || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const logsForEngine = {
|
||||
'aurora-mysql': ['audit', 'error', 'general', 'slowquery'],
|
||||
'aurora-postgresql': ['postgresql'],
|
||||
'docdb': ['audit', 'profiler']
|
||||
}
|
||||
const dbClusters = await this.getDBClusters()
|
||||
|
||||
for (const cluster of dbClusters) {
|
||||
if (
|
||||
JSON.stringify(cluster.EnabledCloudwatchLogsExports || []) ===
|
||||
JSON.stringify((logsForEngine as any)[cluster.Engine!] || [])
|
||||
) {
|
||||
compliantResources.push(cluster.DBClusterArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: []
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||
for (const arn of nonCompliantResources) {
|
||||
const clusterId = arn.split(':cluster/')[1]
|
||||
const logsForEngine = {
|
||||
'aurora-mysql': ['audit', 'error', 'general', 'slowquery'],
|
||||
'aurora-postgresql': ['postgresql'],
|
||||
'docdb': ['audit', 'profiler']
|
||||
}
|
||||
|
||||
const dbClusters = await this.getDBClusters()
|
||||
const cluster = dbClusters.find(c => c.DBClusterArn === arn)
|
||||
|
||||
if (cluster) {
|
||||
const logsToEnable = (logsForEngine as any)[cluster.Engine!]
|
||||
|
||||
await this.client.send(
|
||||
new ModifyDBClusterCommand({
|
||||
DBClusterIdentifier: clusterId,
|
||||
CloudwatchLogsExportConfiguration: { EnableLogTypes: logsToEnable }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
src/bpsets/rds/RDSSnapshotEncrypted.ts
Normal file
62
src/bpsets/rds/RDSSnapshotEncrypted.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import {
|
||||
RDSClient,
|
||||
DescribeDBClusterSnapshotsCommand,
|
||||
CopyDBClusterSnapshotCommand
|
||||
} from '@aws-sdk/client-rds'
|
||||
import { BPSet } from '../BPSet'
|
||||
import { Memorizer } from '../../Memorizer'
|
||||
|
||||
export class RDSSnapshotEncrypted implements BPSet {
|
||||
private readonly client = new RDSClient({})
|
||||
private readonly memoClient = Memorizer.memo(this.client)
|
||||
|
||||
private readonly getDBClusterSnapshots = async () => {
|
||||
const response = await this.memoClient.send(new DescribeDBClusterSnapshotsCommand({}))
|
||||
return response.DBClusterSnapshots || []
|
||||
}
|
||||
|
||||
public readonly check = async () => {
|
||||
const compliantResources = []
|
||||
const nonCompliantResources = []
|
||||
const snapshots = await this.getDBClusterSnapshots()
|
||||
|
||||
for (const snapshot of snapshots) {
|
||||
if (snapshot.StorageEncrypted) {
|
||||
compliantResources.push(snapshot.DBClusterSnapshotArn!)
|
||||
} else {
|
||||
nonCompliantResources.push(snapshot.DBClusterSnapshotArn!)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compliantResources,
|
||||
nonCompliantResources,
|
||||
requiredParametersForFix: [
|
||||
{ name: 'kms-key-id', value: '<KMS_KEY_ID>' } // Replace with your KMS key ID
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fix = async (
|
||||
nonCompliantResources: string[],
|
||||
requiredParametersForFix: { name: string; value: string }[]
|
||||
) => {
|
||||
const kmsKeyId = requiredParametersForFix.find(param => param.name === 'kms-key-id')?.value
|
||||
|
||||
if (!kmsKeyId) {
|
||||
throw new Error("Required parameter 'kms-key-id' is missing.")
|
||||
}
|
||||
|
||||
for (const arn of nonCompliantResources) {
|
||||
const snapshotId = arn.split(':snapshot:')[1]
|
||||
|
||||
await this.client.send(
|
||||
new CopyDBClusterSnapshotCommand({
|
||||
SourceDBClusterSnapshotIdentifier: arn,
|
||||
TargetDBClusterSnapshotIdentifier: `${snapshotId}-encrypted`,
|
||||
KmsKeyId: kmsKeyId
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user