feat: add more bps
This commit is contained in:
15
package.json
15
package.json
@ -10,13 +10,28 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-apigatewayv2": "^3.716.0",
|
"@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-auto-scaling": "^3.716.0",
|
||||||
"@aws-sdk/client-backup": "^3.716.0",
|
"@aws-sdk/client-backup": "^3.716.0",
|
||||||
"@aws-sdk/client-cloudfront": "^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-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-lambda": "^3.716.0",
|
||||||
|
"@aws-sdk/client-rds": "^3.716.0",
|
||||||
"@aws-sdk/client-s3": "^3.717.0",
|
"@aws-sdk/client-s3": "^3.717.0",
|
||||||
"@aws-sdk/client-s3-control": "^3.716.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-sts": "^3.716.0",
|
||||||
"@aws-sdk/client-wafv2": "^3.716.0",
|
"@aws-sdk/client-wafv2": "^3.716.0",
|
||||||
"@smithy/smithy-client": "^3.5.1",
|
"@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