style: add eslint styling

This commit is contained in:
2025-01-16 15:48:55 +09:00
parent eba8fd3b1a
commit ee95323ce2
122 changed files with 6890 additions and 6658 deletions

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"singleQuote": true,
"trailingComma": "none",
"semi": false,
"printWidth": 120,
"tabWidth": 2,
"useTabs": false
}

37
eslint.config.mjs Normal file
View File

@ -0,0 +1,37 @@
import globals from 'globals'
import pluginJs from '@eslint/js'
import tseslint from 'typescript-eslint'
import prettierPlugin from 'eslint-plugin-prettier/recommended'
import prettierConfig from 'eslint-config-prettier'
/** @type {import('eslint').Linter.Config[]} */
export default [
{ files: ['**/*.{js,mjs,cjs,ts}'] },
{ languageOptions: { globals: globals.node } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
prettierConfig,
prettierPlugin,
{
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true
}
],
'@typescript-eslint/no-explicit-any': [
'error',
{
fixToUnknown: true
}
]
}
}
]

View File

@ -47,12 +47,18 @@
"sha.js": "^2.4.11"
},
"devDependencies": {
"@eslint/js": "^9.18.0",
"@types/express": "^5.0.0",
"@types/node": "^22.10.7",
"@types/sha.js": "^2.4.4",
"@yao-pkg/pkg": "^6.2.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^15.14.0",
"nodemon": "^3.1.9",
"typescript": "^5.7.3"
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"pkg": {
"scripts": "build/**/*",

831
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,15 @@
import { BPSet } from "./types";
import { BPSet } from './types'
import { readdir } from 'node:fs/promises'
import path from 'node:path'
export class BPManager {
private static _instance = new BPManager()
public static getInstance = () =>
this._instance
public static getInstance = () => this._instance
// ---
private readonly bpSets:
Record<string, BPSet> = {}
private readonly bpSets: Record<string, BPSet> = {}
private constructor() {
this.loadBPSets()
@ -19,14 +17,14 @@ export class BPManager {
private async loadBPSets() {
const bpSetFolders = await readdir(path.join(__dirname, 'bpsets'))
for (const bpSetFolder of bpSetFolders) {
const bpSetFiles = await readdir(path.join(__dirname, 'bpsets', bpSetFolder))
for (const bpSetFile of bpSetFiles) {
const bpSetPath = path.join(__dirname, 'bpsets', bpSetFolder, bpSetFile)
const bpSetClasses = await import(bpSetPath) as Record<string, new () => BPSet>
const bpSetClasses = (await import(bpSetPath)) as Record<string, new () => BPSet>
for (const bpSetClass of Object.keys(bpSetClasses)) {
this.bpSets[bpSetClass] = new bpSetClasses[bpSetClass]()
console.log('BPSet implement,', bpSetClass, 'loaded')
@ -39,29 +37,18 @@ export class BPManager {
return this.bpSets[name].check()
}
public runCheckAll(finished = (name: string) => {}) {
public runCheckAll(finished = (_: string) => {}) {
const checkJobs: Promise<void>[] = []
for (const bpset of Object.values(this.bpSets))
checkJobs.push(
bpset
.check()
.then(() =>
finished(bpset.getMetadata().name))
)
checkJobs.push(bpset.check().then(() => finished(bpset.getMetadata().name)))
return Promise.all(checkJobs)
}
public runFix(name: string, requiredParametersForFix: { name: string, value: string }[]) {
return this
.bpSets[name]
.fix(
this.bpSets[name].getStats().nonCompliantResources,
requiredParametersForFix
)
public runFix(name: string, requiredParametersForFix: { name: string; value: string }[]) {
return this.bpSets[name].fix(this.bpSets[name].getStats().nonCompliantResources, requiredParametersForFix)
}
public readonly getBPSets = () =>
Object.values(this.bpSets)
public readonly getBPSets = () => Object.values(this.bpSets)
}

View File

@ -5,24 +5,23 @@ import shajs from 'sha.js'
* Memorize AWS SDK operation results.
* This util class tend to be always re-use AWS SDK Client
* which makes operation more faster and optimize memory usage.
*
*
* All results will be store as Key-Value hash map.
* * Key: sha256(serialize([OPERATION_NAME, OPERATION_INPUT_PARAMETER]))
* * Value: OPERATION_OUTPUT
*
*
* @author Minhyeok Park <pmh_only@pmh.codes>
*/
export class Memorizer {
private static memorized = new Map<string, Memorizer>()
public static memo(client: Client<any, any, any, any>, salt = '') {
public static memo(client: Client<unknown, unknown, unknown, unknown>, salt = '') {
const serialized = JSON.stringify([client.constructor.name, salt])
const hashed = shajs('sha256').update(serialized).digest('hex')
const memorized = this.memorized.get(hashed)
if (memorized !== undefined)
return memorized
if (memorized !== undefined) return memorized
const newMemo = new Memorizer(client)
this.memorized.set(hashed, newMemo)
@ -31,23 +30,19 @@ export class Memorizer {
}
public static reset() {
for (const memorized of Memorizer.memorized.values())
memorized.reset()
for (const memorized of Memorizer.memorized.values()) memorized.reset()
}
private memorized = new Map<string, any>()
private memorized = new Map<string, unknown>()
private constructor (
private client: Client<any, any, any, any>
) {}
private constructor(private client: Client<unknown, unknown, unknown, unknown>) {}
public readonly send: typeof this.client.send = async (command) => {
const serialized = JSON.stringify([command.constructor.name, command.input])
const hashed = shajs('sha256').update(serialized).digest('hex')
const memorized = this.memorized.get(hashed)
if (memorized !== undefined)
return memorized
if (memorized !== undefined) return memorized
console.log(command.constructor.name, 'Executed.')
@ -57,6 +52,5 @@ export class Memorizer {
return newMemo
}
private readonly reset = () =>
this.memorized.clear()
private readonly reset = () => this.memorized.clear()
}

View File

@ -1,20 +1,17 @@
import express, { Request, Response } from 'express'
import { BPManager } from './BPManager'
import { BPSet, BPSetMetadata, BPSetStats } from './types'
import { BPSetMetadata, BPSetStats } from './types'
import { Memorizer } from './Memorizer'
import path from 'path'
export class WebServer {
export class WebServer {
private readonly app = express()
private readonly bpManager =
BPManager.getInstance()
private readonly bpManager = BPManager.getInstance()
constructor (
private readonly port = 2424
) {
constructor(private readonly port = 2424) {
this.app.set('view engine', 'ejs')
this.app.set('views', path.join(__dirname, '../views'));
this.app.set('views', path.join(__dirname, '../views'))
this.app.get('/', this.getMainPage.bind(this))
this.app.get('/check', this.runCheckOnce.bind(this))
this.app.get('/check_all', this.runCheckAll.bind(this))
@ -23,15 +20,15 @@ export class WebServer {
this.app.post('/fix', this.runFix.bind(this))
this.app.use(this.error404)
this.app.listen(this.port, this.showBanner.bind(this))
}
private getMainPage(req: Request, res: Response) {
const hidePass = req.query['hidePass'] === 'true'
const bpStatus: {
category: string,
metadatas: (BPSetMetadata&BPSetStats)[]
category: string
metadatas: (BPSetMetadata & BPSetStats)[]
}[] = []
const bpMetadatas = this.bpManager.getBPSets().map((v, idx) => ({ ...v, idx }))
@ -40,11 +37,11 @@ export class WebServer {
for (const category of categories)
bpStatus.push({
category,
metadatas:
bpMetadatas
.filter((v) =>
v.getMetadata().awsService === category &&
(!hidePass || v.getStats().nonCompliantResources.length > 0))
metadatas: bpMetadatas
.filter(
(v) =>
v.getMetadata().awsService === category && (!hidePass || v.getStats().nonCompliantResources.length > 0)
)
.map((v) => ({ ...v.getMetadata(), ...v.getStats(), idx: v.idx }))
})
@ -93,8 +90,7 @@ export class WebServer {
res.write('<pre>Start Checking....\n')
Memorizer.reset()
await this.bpManager.runCheckAll((name) =>
res.write(`${name} - FINISHED\n`))
await this.bpManager.runCheckAll((name) => res.write(`${name} - FINISHED\n`))
res.write(`<a href="/?hidePass=${hidePass}">Done. Return to Report Page</a>`)
res.write(`<script>window.location.replace('/?hidePass=${hidePass}')</script>`)
@ -109,7 +105,7 @@ export class WebServer {
res.write('<body class="bg-gray-100 text-gray-800">')
res.write('<div class="container mx-auto p-4">')
res.write('<pre>Start Fixing....\n')
const { name, hidePass } = req.query
if (typeof name !== 'string' || name.length < 1) {
res.write('<a href="/">Failed. name not found. Return to Report Page')
@ -117,27 +113,24 @@ export class WebServer {
return
}
const requiredParametersForFix =
Object
.keys(req.body)
.map((k) => ({ name: k, value: req.body[k] }))
const requiredParametersForFix = Object.keys(req.body).map((k) => ({ name: k, value: req.body[k] }))
await this.bpManager.runFix(name, requiredParametersForFix)
.catch((error) => {
res.write(error.toString() + '\n')
})
await this.bpManager.runFix(name, requiredParametersForFix).catch((error) => {
res.write(error.toString() + '\n')
})
res.write(`<a href="/?hidePass=${hidePass}">Done. Return to Report Page`)
res.write(`<script>window.location.replace('/?hidePass=${hidePass}')</script>`)
res.end()
}
private error404 (_: Request, res: Response) {
private error404(_: Request, res: Response) {
res.status(404).send({ success: false, message: 'Page not found' })
}
private showBanner() {
console.log(`
console.log(
`
_______ _______ _______ _______ _______ _______
| _ || || || || || |
@ -151,9 +144,9 @@ export class WebServer {
Server is now on http://127.0.0.1:${this.port}
`
.split('\n')
.map((v) => v.replace(/ /, ''))
.join('\n')
.split('\n')
.map((v) => v.replace(/ {6}/, ''))
.join('\n')
)
}
}

View File

@ -2,26 +2,26 @@ import {
ElasticLoadBalancingV2Client,
DescribeLoadBalancersCommand,
DescribeLoadBalancerAttributesCommand,
ModifyLoadBalancerAttributesCommand,
} from '@aws-sdk/client-elastic-load-balancing-v2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
ModifyLoadBalancerAttributesCommand
} from '@aws-sdk/client-elastic-load-balancing-v2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ALBHttpDropInvalidHeaderEnabled implements BPSet {
private readonly client = new ElasticLoadBalancingV2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new ElasticLoadBalancingV2Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getLoadBalancers = async () => {
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}));
return response.LoadBalancers || [];
};
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}))
return response.LoadBalancers || []
}
private readonly getLoadBalancerAttributes = async (loadBalancerArn: string) => {
const response = await this.memoClient.send(
new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: loadBalancerArn })
);
return response.Attributes || [];
};
)
return response.Attributes || []
}
public readonly getMetadata = () => ({
name: 'ALBHttpDropInvalidHeaderEnabled',
@ -36,95 +36,92 @@ export class ALBHttpDropInvalidHeaderEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeLoadBalancerAttributesCommand',
reason: 'Verify if invalid headers are dropped for ALBs.',
},
reason: 'Verify if invalid headers are dropped for ALBs.'
}
],
commandUsedInFixFunction: [
{
name: 'ModifyLoadBalancerAttributesCommand',
reason: 'Enable the invalid header drop attribute on ALBs.',
},
reason: 'Enable the invalid header drop attribute on ALBs.'
}
],
adviseBeforeFixFunction: 'Ensure enabling this setting aligns with your application requirements.',
});
adviseBeforeFixFunction: 'Ensure enabling this setting aligns with your application requirements.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const loadBalancers = await this.getLoadBalancers();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const loadBalancers = await this.getLoadBalancers()
for (const lb of loadBalancers) {
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!);
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!)
const isEnabled = attributes.some(
(attr) =>
attr.Key === 'routing.http.drop_invalid_header_fields.enabled' && attr.Value === 'true'
);
(attr) => attr.Key === 'routing.http.drop_invalid_header_fields.enabled' && attr.Value === 'true'
)
if (isEnabled) {
compliantResources.push(lb.LoadBalancerArn!);
compliantResources.push(lb.LoadBalancerArn!)
} else {
nonCompliantResources.push(lb.LoadBalancerArn!);
nonCompliantResources.push(lb.LoadBalancerArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const lbArn of nonCompliantResources) {
await this.client.send(
new ModifyLoadBalancerAttributesCommand({
LoadBalancerArn: lbArn,
Attributes: [
{ Key: 'routing.http.drop_invalid_header_fields.enabled', Value: 'true' },
],
Attributes: [{ Key: 'routing.http.drop_invalid_header_fields.enabled', Value: 'true' }]
})
);
)
}
};
}
}

View File

@ -13,37 +13,36 @@ export class ALBWAFEnabled implements BPSet {
return response.LoadBalancers || []
}
public readonly getMetadata = () => (
{
name: 'ALBWAFEnabled',
description: 'Ensures that WAF is associated with ALBs.',
priority: 1,
priorityReason: 'Associating WAF with ALBs protects against common web attacks.',
awsService: 'Elastic Load Balancing',
awsServiceCategory: 'Application Load Balancer',
bestPracticeCategory: 'Security',
requiredParametersForFix: [
{
name: 'web-acl-arn',
description: 'The ARN of the WAF ACL to associate with the ALB.',
default: '',
example: 'arn:aws:wafv2:us-east-1:123456789012:regional/webacl/example'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'GetWebAclForResourceCommand',
reason: 'Check if a WAF is associated with the ALB.'
}
],
commandUsedInFixFunction: [
{
name: 'AssociateWebAclCommand',
reason: 'Associate a WAF ACL with the ALB.'
}
],
adviseBeforeFixFunction: 'Ensure the WAF ACL has the appropriate rules for the application\'s requirements.'
public readonly getMetadata = () => ({
name: 'ALBWAFEnabled',
description: 'Ensures that WAF is associated with ALBs.',
priority: 1,
priorityReason: 'Associating WAF with ALBs protects against common web attacks.',
awsService: 'Elastic Load Balancing',
awsServiceCategory: 'Application Load Balancer',
bestPracticeCategory: 'Security',
requiredParametersForFix: [
{
name: 'web-acl-arn',
description: 'The ARN of the WAF ACL to associate with the ALB.',
default: '',
example: 'arn:aws:wafv2:us-east-1:123456789012:regional/webacl/example'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'GetWebAclForResourceCommand',
reason: 'Check if a WAF is associated with the ALB.'
}
],
commandUsedInFixFunction: [
{
name: 'AssociateWebAclCommand',
reason: 'Associate a WAF ACL with the ALB.'
}
],
adviseBeforeFixFunction: "Ensure the WAF ACL has the appropriate rules for the application's requirements."
})
private readonly stats: BPSetStats = {
@ -53,8 +52,7 @@ export class ALBWAFEnabled implements BPSet {
errorMessage: []
}
public readonly getStats = () =>
this.stats
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = []
@ -62,21 +60,20 @@ export class ALBWAFEnabled implements BPSet {
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(
() => this.stats.status = 'FINISHED',
(err) => {
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err
}
)
})
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err
})
}
)
}
private readonly checkImpl = async () => {
@ -85,9 +82,7 @@ export class ALBWAFEnabled implements BPSet {
const loadBalancers = await this.getLoadBalancers()
for (const lb of loadBalancers) {
const response = await this.wafClient.send(
new GetWebACLForResourceCommand({ ResourceArn: lb.LoadBalancerArn })
)
const response = await this.wafClient.send(new GetWebACLForResourceCommand({ ResourceArn: lb.LoadBalancerArn }))
if (response.WebACL) {
compliantResources.push(lb.LoadBalancerArn!)
} else {
@ -100,23 +95,21 @@ export class ALBWAFEnabled implements BPSet {
this.stats.status = 'FINISHED'
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args)
.then(
() => this.stats.status = 'FINISHED',
(err) => {
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err
}
)
})
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err
})
}
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const webAclArn = requiredParametersForFix.find(param => param.name === 'web-acl-arn')?.value
const webAclArn = requiredParametersForFix.find((param) => param.name === 'web-acl-arn')?.value
if (!webAclArn) {
throw new Error("Required parameter 'web-acl-arn' is missing.")

View File

@ -2,26 +2,26 @@ import {
ElasticLoadBalancingV2Client,
DescribeLoadBalancersCommand,
DescribeLoadBalancerAttributesCommand,
ModifyLoadBalancerAttributesCommand,
} from '@aws-sdk/client-elastic-load-balancing-v2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
ModifyLoadBalancerAttributesCommand
} from '@aws-sdk/client-elastic-load-balancing-v2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ELBCrossZoneLoadBalancingEnabled implements BPSet {
private readonly client = new ElasticLoadBalancingV2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new ElasticLoadBalancingV2Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getLoadBalancers = async () => {
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}));
return response.LoadBalancers || [];
};
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}))
return response.LoadBalancers || []
}
private readonly getLoadBalancerAttributes = async (loadBalancerArn: string) => {
const response = await this.memoClient.send(
new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: loadBalancerArn })
);
return response.Attributes || [];
};
)
return response.Attributes || []
}
public readonly getMetadata = () => ({
name: 'ELBCrossZoneLoadBalancingEnabled',
@ -36,93 +36,92 @@ export class ELBCrossZoneLoadBalancingEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeLoadBalancerAttributesCommand',
reason: 'Verify if cross-zone load balancing is enabled for ELBs.',
},
reason: 'Verify if cross-zone load balancing is enabled for ELBs.'
}
],
commandUsedInFixFunction: [
{
name: 'ModifyLoadBalancerAttributesCommand',
reason: 'Enable cross-zone load balancing for ELBs.',
},
reason: 'Enable cross-zone load balancing for ELBs.'
}
],
adviseBeforeFixFunction: 'Ensure this setting aligns with your traffic distribution requirements.',
});
adviseBeforeFixFunction: 'Ensure this setting aligns with your traffic distribution requirements.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const loadBalancers = await this.getLoadBalancers();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const loadBalancers = await this.getLoadBalancers()
for (const lb of loadBalancers) {
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!);
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!)
const isEnabled = attributes.some(
(attr) =>
attr.Key === 'load_balancing.cross_zone.enabled' && attr.Value === 'true'
);
(attr) => attr.Key === 'load_balancing.cross_zone.enabled' && attr.Value === 'true'
)
if (isEnabled) {
compliantResources.push(lb.LoadBalancerArn!);
compliantResources.push(lb.LoadBalancerArn!)
} else {
nonCompliantResources.push(lb.LoadBalancerArn!);
nonCompliantResources.push(lb.LoadBalancerArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const lbArn of nonCompliantResources) {
await this.client.send(
new ModifyLoadBalancerAttributesCommand({
LoadBalancerArn: lbArn,
Attributes: [{ Key: 'load_balancing.cross_zone.enabled', Value: 'true' }],
Attributes: [{ Key: 'load_balancing.cross_zone.enabled', Value: 'true' }]
})
);
)
}
};
}
}

View File

@ -2,26 +2,26 @@ import {
ElasticLoadBalancingV2Client,
DescribeLoadBalancersCommand,
DescribeLoadBalancerAttributesCommand,
ModifyLoadBalancerAttributesCommand,
} from '@aws-sdk/client-elastic-load-balancing-v2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
ModifyLoadBalancerAttributesCommand
} from '@aws-sdk/client-elastic-load-balancing-v2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ELBDeletionProtectionEnabled implements BPSet {
private readonly client = new ElasticLoadBalancingV2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new ElasticLoadBalancingV2Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getLoadBalancers = async () => {
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}));
return response.LoadBalancers || [];
};
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}))
return response.LoadBalancers || []
}
private readonly getLoadBalancerAttributes = async (loadBalancerArn: string) => {
const response = await this.memoClient.send(
new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: loadBalancerArn })
);
return response.Attributes || [];
};
)
return response.Attributes || []
}
public readonly getMetadata = () => ({
name: 'ELBDeletionProtectionEnabled',
@ -36,93 +36,90 @@ export class ELBDeletionProtectionEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeLoadBalancerAttributesCommand',
reason: 'Verify if deletion protection is enabled for ELBs.',
},
reason: 'Verify if deletion protection is enabled for ELBs.'
}
],
commandUsedInFixFunction: [
{
name: 'ModifyLoadBalancerAttributesCommand',
reason: 'Enable deletion protection for ELBs.',
},
reason: 'Enable deletion protection for ELBs.'
}
],
adviseBeforeFixFunction: 'Ensure enabling deletion protection aligns with resource management policies.',
});
adviseBeforeFixFunction: 'Ensure enabling deletion protection aligns with resource management policies.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const loadBalancers = await this.getLoadBalancers();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const loadBalancers = await this.getLoadBalancers()
for (const lb of loadBalancers) {
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!);
const isEnabled = attributes.some(
(attr) =>
attr.Key === 'deletion_protection.enabled' && attr.Value === 'true'
);
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!)
const isEnabled = attributes.some((attr) => attr.Key === 'deletion_protection.enabled' && attr.Value === 'true')
if (isEnabled) {
compliantResources.push(lb.LoadBalancerArn!);
compliantResources.push(lb.LoadBalancerArn!)
} else {
nonCompliantResources.push(lb.LoadBalancerArn!);
nonCompliantResources.push(lb.LoadBalancerArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const lbArn of nonCompliantResources) {
await this.client.send(
new ModifyLoadBalancerAttributesCommand({
LoadBalancerArn: lbArn,
Attributes: [{ Key: 'deletion_protection.enabled', Value: 'true' }],
Attributes: [{ Key: 'deletion_protection.enabled', Value: 'true' }]
})
);
)
}
};
}
}

View File

@ -2,26 +2,26 @@ import {
ElasticLoadBalancingV2Client,
DescribeLoadBalancersCommand,
DescribeLoadBalancerAttributesCommand,
ModifyLoadBalancerAttributesCommand,
} from '@aws-sdk/client-elastic-load-balancing-v2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
ModifyLoadBalancerAttributesCommand
} from '@aws-sdk/client-elastic-load-balancing-v2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ELBLoggingEnabled implements BPSet {
private readonly client = new ElasticLoadBalancingV2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new ElasticLoadBalancingV2Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getLoadBalancers = async () => {
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}));
return response.LoadBalancers || [];
};
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}))
return response.LoadBalancers || []
}
private readonly getLoadBalancerAttributes = async (loadBalancerArn: string) => {
const response = await this.memoClient.send(
new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: loadBalancerArn })
);
return response.Attributes || [];
};
)
return response.Attributes || []
}
public readonly getMetadata = () => ({
name: 'ELBLoggingEnabled',
@ -32,97 +32,100 @@ export class ELBLoggingEnabled implements BPSet {
awsServiceCategory: 'Classic Load Balancer',
bestPracticeCategory: 'Logging and Monitoring',
requiredParametersForFix: [
{ name: 's3-bucket-name', description: 'The S3 bucket for storing access logs.', default: '', example: 'my-log-bucket' },
{ name: 's3-prefix', description: 'The S3 prefix for the access logs.', default: '', example: 'elb/logs/' },
{
name: 's3-bucket-name',
description: 'The S3 bucket for storing access logs.',
default: '',
example: 'my-log-bucket'
},
{ name: 's3-prefix', description: 'The S3 prefix for the access logs.', default: '', example: 'elb/logs/' }
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'DescribeLoadBalancerAttributesCommand',
reason: 'Verify if access logging is enabled for ELBs.',
},
reason: 'Verify if access logging is enabled for ELBs.'
}
],
commandUsedInFixFunction: [
{
name: 'ModifyLoadBalancerAttributesCommand',
reason: 'Enable access logging for ELBs and set S3 bucket and prefix.',
},
reason: 'Enable access logging for ELBs and set S3 bucket and prefix.'
}
],
adviseBeforeFixFunction: 'Ensure the specified S3 bucket and prefix exist and are accessible.',
});
adviseBeforeFixFunction: 'Ensure the specified S3 bucket and prefix exist and are accessible.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const loadBalancers = await this.getLoadBalancers();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const loadBalancers = await this.getLoadBalancers()
for (const lb of loadBalancers) {
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!);
const isEnabled = attributes.some(
(attr) => attr.Key === 'access_logs.s3.enabled' && attr.Value === 'true'
);
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!)
const isEnabled = attributes.some((attr) => attr.Key === 'access_logs.s3.enabled' && attr.Value === 'true')
if (isEnabled) {
compliantResources.push(lb.LoadBalancerArn!);
compliantResources.push(lb.LoadBalancerArn!)
} else {
nonCompliantResources.push(lb.LoadBalancerArn!);
nonCompliantResources.push(lb.LoadBalancerArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const bucketName = requiredParametersForFix.find((param) => param.name === 's3-bucket-name')?.value;
const bucketPrefix = requiredParametersForFix.find((param) => param.name === 's3-prefix')?.value;
const bucketName = requiredParametersForFix.find((param) => param.name === 's3-bucket-name')?.value
const bucketPrefix = requiredParametersForFix.find((param) => param.name === 's3-prefix')?.value
if (!bucketName || !bucketPrefix) {
throw new Error("Required parameters 's3-bucket-name' and/or 's3-prefix' are missing.");
throw new Error("Required parameters 's3-bucket-name' and/or 's3-prefix' are missing.")
}
for (const lbArn of nonCompliantResources) {
@ -132,10 +135,10 @@ export class ELBLoggingEnabled implements BPSet {
Attributes: [
{ Key: 'access_logs.s3.enabled', Value: 'true' },
{ Key: 'access_logs.s3.bucket', Value: bucketName },
{ Key: 'access_logs.s3.prefix', Value: bucketPrefix },
],
{ Key: 'access_logs.s3.prefix', Value: bucketPrefix }
]
})
);
)
}
};
}
}

View File

@ -1,30 +1,22 @@
import {
ApiGatewayV2Client,
GetApisCommand,
GetStagesCommand,
} from '@aws-sdk/client-apigatewayv2';
import {
WAFV2Client,
GetWebACLForResourceCommand,
AssociateWebACLCommand,
} from '@aws-sdk/client-wafv2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { ApiGatewayV2Client, GetApisCommand, GetStagesCommand } from '@aws-sdk/client-apigatewayv2'
import { WAFV2Client, GetWebACLForResourceCommand, AssociateWebACLCommand } from '@aws-sdk/client-wafv2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class APIGatewayAssociatedWithWAF implements BPSet {
private readonly client = new ApiGatewayV2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly wafClient = Memorizer.memo(new WAFV2Client({}));
private readonly client = new ApiGatewayV2Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly wafClient = Memorizer.memo(new WAFV2Client({}))
private readonly getHttpApis = async () => {
const response = await this.memoClient.send(new GetApisCommand({}));
return response.Items || [];
};
const response = await this.memoClient.send(new GetApisCommand({}))
return response.Items || []
}
private readonly getStages = async (apiId: string) => {
const response = await this.memoClient.send(new GetStagesCommand({ ApiId: apiId }));
return response.Items || [];
};
const response = await this.memoClient.send(new GetStagesCommand({ ApiId: apiId }))
return response.Items || []
}
public readonly getMetadata = () => ({
name: 'APIGatewayAssociatedWithWAF',
@ -39,108 +31,106 @@ export class APIGatewayAssociatedWithWAF implements BPSet {
name: 'web-acl-arn',
description: 'The ARN of the WAF ACL to associate with the API Gateway stage.',
default: '',
example: 'arn:aws:wafv2:us-east-1:123456789012:regional/webacl/example',
},
example: 'arn:aws:wafv2:us-east-1:123456789012:regional/webacl/example'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'GetWebACLForResourceCommand',
reason: 'Verify if a WAF is associated with the API Gateway stage.',
},
reason: 'Verify if a WAF is associated with the API Gateway stage.'
}
],
commandUsedInFixFunction: [
{
name: 'AssociateWebACLCommand',
reason: 'Associate a WAF ACL with the API Gateway stage.',
},
reason: 'Associate a WAF ACL with the API Gateway stage.'
}
],
adviseBeforeFixFunction: 'Ensure the WAF ACL has the appropriate rules for the application\'s requirements.',
});
adviseBeforeFixFunction: "Ensure the WAF ACL has the appropriate rules for the application's requirements."
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const apis = await this.getHttpApis();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const apis = await this.getHttpApis()
for (const api of apis) {
const stages = await this.getStages(api.ApiId!);
const stages = await this.getStages(api.ApiId!)
for (const stage of stages) {
const stageArn = `arn:aws:apigateway:${this.client.config.region}::/apis/${api.ApiId}/stages/${stage.StageName}`;
const response = await this.wafClient.send(
new GetWebACLForResourceCommand({ ResourceArn: stageArn })
);
const stageArn = `arn:aws:apigateway:${this.client.config.region}::/apis/${api.ApiId}/stages/${stage.StageName}`
const response = await this.wafClient.send(new GetWebACLForResourceCommand({ ResourceArn: stageArn }))
if (response.WebACL) {
compliantResources.push(stageArn);
compliantResources.push(stageArn)
} else {
nonCompliantResources.push(stageArn);
nonCompliantResources.push(stageArn)
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const webAclArn = requiredParametersForFix.find((param) => param.name === 'web-acl-arn')?.value;
const webAclArn = requiredParametersForFix.find((param) => param.name === 'web-acl-arn')?.value
if (!webAclArn) {
throw new Error("Required parameter 'web-acl-arn' is missing.");
throw new Error("Required parameter 'web-acl-arn' is missing.")
}
for (const stageArn of nonCompliantResources) {
await this.wafClient.send(
new AssociateWebACLCommand({
ResourceArn: stageArn,
WebACLArn: webAclArn,
WebACLArn: webAclArn
})
);
)
}
};
}
}

View File

@ -1,25 +1,20 @@
import {
ApiGatewayV2Client,
GetApisCommand,
GetStagesCommand,
UpdateStageCommand,
} from '@aws-sdk/client-apigatewayv2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { ApiGatewayV2Client, GetApisCommand, GetStagesCommand, UpdateStageCommand } from '@aws-sdk/client-apigatewayv2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class APIGatewayExecutionLoggingEnabled implements BPSet {
private readonly client = new ApiGatewayV2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new ApiGatewayV2Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getHttpApis = async () => {
const response = await this.memoClient.send(new GetApisCommand({}));
return response.Items || [];
};
const response = await this.memoClient.send(new GetApisCommand({}))
return response.Items || []
}
private readonly getStages = async (apiId: string) => {
const response = await this.memoClient.send(new GetStagesCommand({ ApiId: apiId }));
return response.Items || [];
};
const response = await this.memoClient.send(new GetStagesCommand({ ApiId: apiId }))
return response.Items || []
}
public readonly getMetadata = () => ({
name: 'APIGatewayExecutionLoggingEnabled',
@ -34,103 +29,101 @@ export class APIGatewayExecutionLoggingEnabled implements BPSet {
name: 'log-destination-arn',
description: 'The ARN of the CloudWatch log group for storing API Gateway logs.',
default: '',
example: 'arn:aws:logs:us-east-1:123456789012:log-group:/aws/apigateway/logs',
},
example: 'arn:aws:logs:us-east-1:123456789012:log-group:/aws/apigateway/logs'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'GetStagesCommand',
reason: 'Verify if execution logging is enabled for API Gateway stages.',
},
reason: 'Verify if execution logging is enabled for API Gateway stages.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateStageCommand',
reason: 'Enable execution logging for API Gateway stages and set the destination log group.',
},
reason: 'Enable execution logging for API Gateway stages and set the destination log group.'
}
],
adviseBeforeFixFunction: 'Ensure the CloudWatch log group exists and has the appropriate permissions.',
});
adviseBeforeFixFunction: 'Ensure the CloudWatch log group exists and has the appropriate permissions.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const apis = await this.getHttpApis();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const apis = await this.getHttpApis()
for (const api of apis) {
const stages = await this.getStages(api.ApiId!);
const stages = await this.getStages(api.ApiId!)
for (const stage of stages) {
const stageArn = `arn:aws:apigateway:${this.client.config.region}::/apis/${api.ApiId}/stages/${stage.StageName}`;
const loggingLevel = stage.AccessLogSettings?.Format;
const stageArn = `arn:aws:apigateway:${this.client.config.region}::/apis/${api.ApiId}/stages/${stage.StageName}`
const loggingLevel = stage.AccessLogSettings?.Format
if (loggingLevel && loggingLevel !== 'OFF') {
compliantResources.push(stageArn);
compliantResources.push(stageArn)
} else {
nonCompliantResources.push(stageArn);
nonCompliantResources.push(stageArn)
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const logDestinationArn = requiredParametersForFix.find(
(param) => param.name === 'log-destination-arn'
)?.value;
const logDestinationArn = requiredParametersForFix.find((param) => param.name === 'log-destination-arn')?.value
if (!logDestinationArn) {
throw new Error("Required parameter 'log-destination-arn' is missing.");
throw new Error("Required parameter 'log-destination-arn' is missing.")
}
for (const stageArn of nonCompliantResources) {
const [apiId, stageName] = stageArn.split('/').slice(-2);
const [apiId, stageName] = stageArn.split('/').slice(-2)
await this.client.send(
new UpdateStageCommand({
@ -138,10 +131,10 @@ export class APIGatewayExecutionLoggingEnabled implements BPSet {
StageName: stageName,
AccessLogSettings: {
DestinationArn: logDestinationArn,
Format: '$context.requestId',
},
Format: '$context.requestId'
}
})
);
)
}
};
}
}

View File

@ -1,25 +1,20 @@
import {
ApiGatewayV2Client,
GetApisCommand,
GetStagesCommand,
UpdateStageCommand,
} from '@aws-sdk/client-apigatewayv2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { ApiGatewayV2Client, GetApisCommand, GetStagesCommand, UpdateStageCommand } from '@aws-sdk/client-apigatewayv2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class APIGatewayV2AccessLogsEnabled implements BPSet {
private readonly client = new ApiGatewayV2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new ApiGatewayV2Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getHttpApis = async () => {
const response = await this.memoClient.send(new GetApisCommand({}));
return response.Items || [];
};
const response = await this.memoClient.send(new GetApisCommand({}))
return response.Items || []
}
private readonly getStages = async (apiId: string) => {
const response = await this.memoClient.send(new GetStagesCommand({ ApiId: apiId }));
return response.Items || [];
};
const response = await this.memoClient.send(new GetStagesCommand({ ApiId: apiId }))
return response.Items || []
}
public readonly getMetadata = () => ({
name: 'APIGatewayV2AccessLogsEnabled',
@ -34,104 +29,102 @@ export class APIGatewayV2AccessLogsEnabled implements BPSet {
name: 'log-destination-arn',
description: 'The ARN of the CloudWatch log group for storing API Gateway logs.',
default: '',
example: 'arn:aws:logs:us-east-1:123456789012:log-group:/aws/apigateway/logs',
},
example: 'arn:aws:logs:us-east-1:123456789012:log-group:/aws/apigateway/logs'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'GetStagesCommand',
reason: 'Verify if access logging is enabled for API Gateway stages.',
},
reason: 'Verify if access logging is enabled for API Gateway stages.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateStageCommand',
reason: 'Enable access logging for API Gateway stages and set the destination log group.',
},
reason: 'Enable access logging for API Gateway stages and set the destination log group.'
}
],
adviseBeforeFixFunction: 'Ensure the CloudWatch log group exists and has the appropriate permissions.',
});
adviseBeforeFixFunction: 'Ensure the CloudWatch log group exists and has the appropriate permissions.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const apis = await this.getHttpApis();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const apis = await this.getHttpApis()
for (const api of apis) {
const stages = await this.getStages(api.ApiId!);
const stages = await this.getStages(api.ApiId!)
for (const stage of stages) {
const stageIdentifier = `${api.Name!} / ${stage.StageName!}`;
const stageIdentifier = `${api.Name!} / ${stage.StageName!}`
if (!stage.AccessLogSettings) {
nonCompliantResources.push(stageIdentifier);
nonCompliantResources.push(stageIdentifier)
} else {
compliantResources.push(stageIdentifier);
compliantResources.push(stageIdentifier)
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const logDestinationArn = requiredParametersForFix.find(
(param) => param.name === 'log-destination-arn'
)?.value;
const logDestinationArn = requiredParametersForFix.find((param) => param.name === 'log-destination-arn')?.value
if (!logDestinationArn) {
throw new Error("Required parameter 'log-destination-arn' is missing.");
throw new Error("Required parameter 'log-destination-arn' is missing.")
}
for (const resource of nonCompliantResources) {
const [apiName, stageName] = resource.split(' / ');
const api = (await this.getHttpApis()).find((a) => a.Name === apiName);
const [apiName, stageName] = resource.split(' / ')
const api = (await this.getHttpApis()).find((a) => a.Name === apiName)
if (!api) continue;
if (!api) continue
await this.client.send(
new UpdateStageCommand({
@ -139,10 +132,10 @@ export class APIGatewayV2AccessLogsEnabled implements BPSet {
StageName: stageName,
AccessLogSettings: {
DestinationArn: logDestinationArn,
Format: '$context.requestId',
},
Format: '$context.requestId'
}
})
);
)
}
};
}
}

View File

@ -1,25 +1,20 @@
import {
ApiGatewayV2Client,
GetApisCommand,
GetRoutesCommand,
UpdateRouteCommand,
} from '@aws-sdk/client-apigatewayv2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { ApiGatewayV2Client, GetApisCommand, GetRoutesCommand, UpdateRouteCommand } from '@aws-sdk/client-apigatewayv2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class APIGatewayV2AuthorizationTypeConfigured implements BPSet {
private readonly client = new ApiGatewayV2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new ApiGatewayV2Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getHttpApis = async () => {
const response = await this.memoClient.send(new GetApisCommand({}));
return response.Items || [];
};
const response = await this.memoClient.send(new GetApisCommand({}))
return response.Items || []
}
private readonly getRoutes = async (apiId: string) => {
const response = await this.memoClient.send(new GetRoutesCommand({ ApiId: apiId }));
return response.Items || [];
};
const response = await this.memoClient.send(new GetRoutesCommand({ ApiId: apiId }))
return response.Items || []
}
public readonly getMetadata = () => ({
name: 'APIGatewayV2AuthorizationTypeConfigured',
@ -34,117 +29,115 @@ export class APIGatewayV2AuthorizationTypeConfigured implements BPSet {
name: 'authorization-type',
description: 'The authorization type to configure for the routes.',
default: '',
example: 'JWT',
},
example: 'JWT'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'GetRoutesCommand',
reason: 'Verify if authorization type is configured for API Gateway routes.',
},
reason: 'Verify if authorization type is configured for API Gateway routes.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateRouteCommand',
reason: 'Set the authorization type for API Gateway routes.',
},
reason: 'Set the authorization type for API Gateway routes.'
}
],
adviseBeforeFixFunction: 'Ensure the chosen authorization type aligns with application requirements.',
});
adviseBeforeFixFunction: 'Ensure the chosen authorization type aligns with application requirements.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const apis = await this.getHttpApis();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const apis = await this.getHttpApis()
for (const api of apis) {
const routes = await this.getRoutes(api.ApiId!);
const routes = await this.getRoutes(api.ApiId!)
for (const route of routes) {
const routeIdentifier = `${api.Name!} / ${route.RouteKey!}`;
const routeIdentifier = `${api.Name!} / ${route.RouteKey!}`
if (route.AuthorizationType === 'NONE') {
nonCompliantResources.push(routeIdentifier);
nonCompliantResources.push(routeIdentifier)
} else {
compliantResources.push(routeIdentifier);
compliantResources.push(routeIdentifier)
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const authorizationType = requiredParametersForFix.find(
(param) => param.name === 'authorization-type'
)?.value;
const authorizationType = requiredParametersForFix.find((param) => param.name === 'authorization-type')?.value
if (!authorizationType) {
throw new Error("Required parameter 'authorization-type' is missing.");
throw new Error("Required parameter 'authorization-type' is missing.")
}
for (const resource of nonCompliantResources) {
const [apiName, routeKey] = resource.split(' / ');
const api = (await this.getHttpApis()).find((a) => a.Name === apiName);
const [apiName, routeKey] = resource.split(' / ')
const api = (await this.getHttpApis()).find((a) => a.Name === apiName)
if (!api) continue;
if (!api) continue
const routes = await this.getRoutes(api.ApiId!);
const route = routes.find((r) => r.RouteKey === routeKey);
const routes = await this.getRoutes(api.ApiId!)
const route = routes.find((r) => r.RouteKey === routeKey)
if (!route) continue;
if (!route) continue
await this.client.send(
new UpdateRouteCommand({
ApiId: api.ApiId!,
RouteId: route.RouteId!,
AuthorizationType: authorizationType as any,
AuthorizationType: authorizationType as unknown
})
);
)
}
};
}
}

View File

@ -1,19 +1,19 @@
import {
AutoScalingClient,
DescribeAutoScalingGroupsCommand,
UpdateAutoScalingGroupCommand,
} from '@aws-sdk/client-auto-scaling';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateAutoScalingGroupCommand
} from '@aws-sdk/client-auto-scaling'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class AutoScalingGroupELBHealthCheckRequired implements BPSet {
private readonly client = new AutoScalingClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new AutoScalingClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getAutoScalingGroups = async () => {
const response = await this.memoClient.send(new DescribeAutoScalingGroupsCommand({}));
return response.AutoScalingGroups || [];
};
const response = await this.memoClient.send(new DescribeAutoScalingGroupsCommand({}))
return response.AutoScalingGroups || []
}
public readonly getMetadata = () => ({
name: 'AutoScalingGroupELBHealthCheckRequired',
@ -28,91 +28,88 @@ export class AutoScalingGroupELBHealthCheckRequired implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeAutoScalingGroupsCommand',
reason: 'Retrieve Auto Scaling groups to check health check type.',
},
reason: 'Retrieve Auto Scaling groups to check health check type.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateAutoScalingGroupCommand',
reason: 'Set the health check type to ELB for Auto Scaling groups.',
},
reason: 'Set the health check type to ELB for Auto Scaling groups.'
}
],
adviseBeforeFixFunction: 'Ensure that the Auto Scaling group is associated with a functional ELB or Target Group.',
});
adviseBeforeFixFunction: 'Ensure that the Auto Scaling group is associated with a functional ELB or Target Group.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const asgs = await this.getAutoScalingGroups();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const asgs = await this.getAutoScalingGroups()
for (const asg of asgs) {
if (
(asg.LoadBalancerNames?.length || asg.TargetGroupARNs?.length) &&
asg.HealthCheckType !== 'ELB'
) {
nonCompliantResources.push(asg.AutoScalingGroupARN!);
if ((asg.LoadBalancerNames?.length || asg.TargetGroupARNs?.length) && asg.HealthCheckType !== 'ELB') {
nonCompliantResources.push(asg.AutoScalingGroupARN!)
} else {
compliantResources.push(asg.AutoScalingGroupARN!);
compliantResources.push(asg.AutoScalingGroupARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const asgArn of nonCompliantResources) {
const asgName = asgArn.split(':').pop()!;
const asgName = asgArn.split(':').pop()!
await this.client.send(
new UpdateAutoScalingGroupCommand({
AutoScalingGroupName: asgName,
HealthCheckType: 'ELB',
HealthCheckType: 'ELB'
})
);
)
}
};
}
}

View File

@ -1,19 +1,19 @@
import {
AutoScalingClient,
DescribeAutoScalingGroupsCommand,
UpdateAutoScalingGroupCommand,
} from '@aws-sdk/client-auto-scaling';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateAutoScalingGroupCommand
} from '@aws-sdk/client-auto-scaling'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class AutoScalingLaunchTemplate implements BPSet {
private readonly client = new AutoScalingClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new AutoScalingClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getAutoScalingGroups = async () => {
const response = await this.memoClient.send(new DescribeAutoScalingGroupsCommand({}));
return response.AutoScalingGroups || [];
};
const response = await this.memoClient.send(new DescribeAutoScalingGroupsCommand({}))
return response.AutoScalingGroups || []
}
public readonly getMetadata = () => ({
name: 'AutoScalingLaunchTemplate',
@ -24,107 +24,116 @@ export class AutoScalingLaunchTemplate implements BPSet {
awsServiceCategory: 'Compute',
bestPracticeCategory: 'Management',
requiredParametersForFix: [
{ name: 'launch-template-id', description: 'The ID of the launch template to associate.', default: '', example: 'lt-0abcd1234efgh5678' },
{ name: 'version', description: 'The version of the launch template to use.', default: '$Default', example: '$Default' },
{
name: 'launch-template-id',
description: 'The ID of the launch template to associate.',
default: '',
example: 'lt-0abcd1234efgh5678'
},
{
name: 'version',
description: 'The version of the launch template to use.',
default: '$Default',
example: '$Default'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'DescribeAutoScalingGroupsCommand',
reason: 'Retrieve Auto Scaling groups to check for launch template usage.',
},
reason: 'Retrieve Auto Scaling groups to check for launch template usage.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateAutoScalingGroupCommand',
reason: 'Associate the Auto Scaling group with the specified launch template.',
},
reason: 'Associate the Auto Scaling group with the specified launch template.'
}
],
adviseBeforeFixFunction: 'Ensure the launch template is configured properly before associating it with an Auto Scaling group.',
});
adviseBeforeFixFunction:
'Ensure the launch template is configured properly before associating it with an Auto Scaling group.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const asgs = await this.getAutoScalingGroups();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const asgs = await this.getAutoScalingGroups()
for (const asg of asgs) {
if (asg.LaunchConfigurationName) {
nonCompliantResources.push(asg.AutoScalingGroupARN!);
nonCompliantResources.push(asg.AutoScalingGroupARN!)
} else {
compliantResources.push(asg.AutoScalingGroupARN!);
compliantResources.push(asg.AutoScalingGroupARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const launchTemplateId = requiredParametersForFix.find(
(param) => param.name === 'launch-template-id'
)?.value;
const version = requiredParametersForFix.find((param) => param.name === 'version')?.value;
const launchTemplateId = requiredParametersForFix.find((param) => param.name === 'launch-template-id')?.value
const version = requiredParametersForFix.find((param) => param.name === 'version')?.value
if (!launchTemplateId || !version) {
throw new Error("Required parameters 'launch-template-id' and/or 'version' are missing.");
throw new Error("Required parameters 'launch-template-id' and/or 'version' are missing.")
}
for (const asgArn of nonCompliantResources) {
const asgName = asgArn.split(':').pop()!;
const asgName = asgArn.split(':').pop()!
await this.client.send(
new UpdateAutoScalingGroupCommand({
AutoScalingGroupName: asgName,
LaunchTemplate: {
LaunchTemplateId: launchTemplateId,
Version: version,
},
Version: version
}
})
);
)
}
};
}
}

View File

@ -1,19 +1,19 @@
import {
AutoScalingClient,
DescribeAutoScalingGroupsCommand,
UpdateAutoScalingGroupCommand,
} from '@aws-sdk/client-auto-scaling';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateAutoScalingGroupCommand
} from '@aws-sdk/client-auto-scaling'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class AutoScalingMultipleAZ implements BPSet {
private readonly client = new AutoScalingClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new AutoScalingClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getAutoScalingGroups = async () => {
const response = await this.memoClient.send(new DescribeAutoScalingGroupsCommand({}));
return response.AutoScalingGroups || [];
};
const response = await this.memoClient.send(new DescribeAutoScalingGroupsCommand({}))
return response.AutoScalingGroups || []
}
public readonly getMetadata = () => ({
name: 'AutoScalingMultipleAZ',
@ -28,103 +28,102 @@ export class AutoScalingMultipleAZ implements BPSet {
name: 'availability-zones',
description: 'Comma-separated list of Availability Zones to assign to the Auto Scaling group.',
default: '',
example: 'us-east-1a,us-east-1b',
},
example: 'us-east-1a,us-east-1b'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'DescribeAutoScalingGroupsCommand',
reason: 'Retrieve Auto Scaling groups to verify their Availability Zone configuration.',
},
reason: 'Retrieve Auto Scaling groups to verify their Availability Zone configuration.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateAutoScalingGroupCommand',
reason: 'Update the Auto Scaling group to use the specified Availability Zones.',
},
reason: 'Update the Auto Scaling group to use the specified Availability Zones.'
}
],
adviseBeforeFixFunction: 'Ensure that the specified Availability Zones are correctly configured in your infrastructure.',
});
adviseBeforeFixFunction:
'Ensure that the specified Availability Zones are correctly configured in your infrastructure.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const asgs = await this.getAutoScalingGroups();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const asgs = await this.getAutoScalingGroups()
for (const asg of asgs) {
if (asg.AvailabilityZones?.length! > 1) {
compliantResources.push(asg.AutoScalingGroupARN!);
if ((asg.AvailabilityZones?.length ?? 0) > 1) {
compliantResources.push(asg.AutoScalingGroupARN!)
} else {
nonCompliantResources.push(asg.AutoScalingGroupARN!);
nonCompliantResources.push(asg.AutoScalingGroupARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const availabilityZones = requiredParametersForFix.find(
(param) => param.name === 'availability-zones'
)?.value;
const availabilityZones = requiredParametersForFix.find((param) => param.name === 'availability-zones')?.value
if (!availabilityZones) {
throw new Error("Required parameter 'availability-zones' is missing.");
throw new Error("Required parameter 'availability-zones' is missing.")
}
for (const asgArn of nonCompliantResources) {
const asgName = asgArn.split(':').pop()!;
const asgName = asgArn.split(':').pop()!
await this.client.send(
new UpdateAutoScalingGroupCommand({
AutoScalingGroupName: asgName,
AvailabilityZones: availabilityZones.split(','),
AvailabilityZones: availabilityZones.split(',')
})
);
)
}
};
}
}

View File

@ -2,29 +2,27 @@ import {
CloudFrontClient,
ListDistributionsCommand,
GetDistributionCommand,
UpdateDistributionCommand,
} from '@aws-sdk/client-cloudfront';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateDistributionCommand
} from '@aws-sdk/client-cloudfront'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class CloudFrontAccessLogsEnabled implements BPSet {
private readonly client = new CloudFrontClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new CloudFrontClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getDistributions = async () => {
const response = await this.memoClient.send(new ListDistributionsCommand({}));
return response.DistributionList?.Items || [];
};
const response = await this.memoClient.send(new ListDistributionsCommand({}))
return response.DistributionList?.Items || []
}
private readonly getDistributionDetails = async (distributionId: string) => {
const response = await this.memoClient.send(
new GetDistributionCommand({ Id: distributionId })
);
const response = await this.memoClient.send(new GetDistributionCommand({ Id: distributionId }))
return {
distribution: response.Distribution!,
etag: response.ETag!,
};
};
etag: response.ETag!
}
}
public readonly getMetadata = () => ({
name: 'CloudFrontAccessLogsEnabled',
@ -39,103 +37,101 @@ export class CloudFrontAccessLogsEnabled implements BPSet {
name: 'log-bucket-name',
description: 'The S3 bucket name for storing access logs.',
default: '',
example: 'my-cloudfront-logs-bucket.s3.amazonaws.com',
},
example: 'my-cloudfront-logs-bucket.s3.amazonaws.com'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'ListDistributionsCommand',
reason: 'List all CloudFront distributions.',
reason: 'List all CloudFront distributions.'
},
{
name: 'GetDistributionCommand',
reason: 'Retrieve distribution details to check logging settings.',
},
reason: 'Retrieve distribution details to check logging settings.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateDistributionCommand',
reason: 'Enable logging and update distribution settings.',
},
reason: 'Enable logging and update distribution settings.'
}
],
adviseBeforeFixFunction: 'Ensure the specified S3 bucket exists and has proper permissions for CloudFront logging.',
});
adviseBeforeFixFunction: 'Ensure the specified S3 bucket exists and has proper permissions for CloudFront logging.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const distributions = await this.getDistributions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const distributions = await this.getDistributions()
for (const distribution of distributions) {
const { distribution: details } = await this.getDistributionDetails(distribution.Id!);
const { distribution: details } = await this.getDistributionDetails(distribution.Id!)
if (details.DistributionConfig?.Logging?.Enabled) {
compliantResources.push(details.ARN!);
compliantResources.push(details.ARN!)
} else {
nonCompliantResources.push(details.ARN!);
nonCompliantResources.push(details.ARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const logBucketName = requiredParametersForFix.find(
(param) => param.name === 'log-bucket-name'
)?.value;
const logBucketName = requiredParametersForFix.find((param) => param.name === 'log-bucket-name')?.value
if (!logBucketName) {
throw new Error("Required parameter 'log-bucket-name' is missing.");
throw new Error("Required parameter 'log-bucket-name' is missing.")
}
for (const arn of nonCompliantResources) {
const distributionId = arn.split('/').pop()!;
const { distribution, etag } = await this.getDistributionDetails(distributionId);
const distributionId = arn.split('/').pop()!
const { distribution, etag } = await this.getDistributionDetails(distributionId)
const updatedConfig = {
...distribution.DistributionConfig,
@ -143,17 +139,17 @@ export class CloudFrontAccessLogsEnabled implements BPSet {
Enabled: true,
Bucket: logBucketName,
IncludeCookies: false,
Prefix: '',
},
};
Prefix: ''
}
}
await this.client.send(
new UpdateDistributionCommand({
Id: distributionId,
IfMatch: etag,
DistributionConfig: updatedConfig as any,
DistributionConfig: updatedConfig as unknown
})
);
)
}
};
}
}

View File

@ -2,29 +2,27 @@ import {
CloudFrontClient,
ListDistributionsCommand,
GetDistributionCommand,
UpdateDistributionCommand,
} from '@aws-sdk/client-cloudfront';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateDistributionCommand
} from '@aws-sdk/client-cloudfront'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class CloudFrontAssociatedWithWAF implements BPSet {
private readonly client = new CloudFrontClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new CloudFrontClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getDistributions = async () => {
const response = await this.memoClient.send(new ListDistributionsCommand({}));
return response.DistributionList?.Items || [];
};
const response = await this.memoClient.send(new ListDistributionsCommand({}))
return response.DistributionList?.Items || []
}
private readonly getDistributionDetails = async (distributionId: string) => {
const response = await this.memoClient.send(
new GetDistributionCommand({ Id: distributionId })
);
const response = await this.memoClient.send(new GetDistributionCommand({ Id: distributionId }))
return {
distribution: response.Distribution!,
etag: response.ETag!,
};
};
etag: response.ETag!
}
}
public readonly getMetadata = () => ({
name: 'CloudFrontAssociatedWithWAF',
@ -39,111 +37,109 @@ export class CloudFrontAssociatedWithWAF implements BPSet {
name: 'web-acl-id',
description: 'The ID of the Web ACL to associate with the CloudFront distribution.',
default: '',
example: 'arn:aws:wafv2:us-east-1:123456789012:regional/webacl/example',
},
example: 'arn:aws:wafv2:us-east-1:123456789012:regional/webacl/example'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'ListDistributionsCommand',
reason: 'List all CloudFront distributions to check for WAF association.',
},
reason: 'List all CloudFront distributions to check for WAF association.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateDistributionCommand',
reason: 'Associate the specified WAF with the CloudFront distribution.',
},
reason: 'Associate the specified WAF with the CloudFront distribution.'
}
],
adviseBeforeFixFunction: 'Ensure the Web ACL is configured correctly for the applications security requirements.',
});
adviseBeforeFixFunction: 'Ensure the Web ACL is configured correctly for the applications security requirements.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const distributions = await this.getDistributions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const distributions = await this.getDistributions()
for (const distribution of distributions) {
if (distribution.WebACLId && distribution.WebACLId !== '') {
compliantResources.push(distribution.ARN!);
compliantResources.push(distribution.ARN!)
} else {
nonCompliantResources.push(distribution.ARN!);
nonCompliantResources.push(distribution.ARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const webAclId = requiredParametersForFix.find(
(param) => param.name === 'web-acl-id'
)?.value;
const webAclId = requiredParametersForFix.find((param) => param.name === 'web-acl-id')?.value
if (!webAclId) {
throw new Error("Required parameter 'web-acl-id' is missing.");
throw new Error("Required parameter 'web-acl-id' is missing.")
}
for (const arn of nonCompliantResources) {
const distributionId = arn.split('/').pop()!;
const { distribution, etag } = await this.getDistributionDetails(distributionId);
const distributionId = arn.split('/').pop()!
const { distribution, etag } = await this.getDistributionDetails(distributionId)
const updatedConfig = {
...distribution.DistributionConfig,
WebACLId: webAclId,
};
WebACLId: webAclId
}
await this.client.send(
new UpdateDistributionCommand({
Id: distributionId,
IfMatch: etag,
DistributionConfig: updatedConfig as any,
DistributionConfig: updatedConfig as unknown
})
);
)
}
};
}
}

View File

@ -2,35 +2,34 @@ import {
CloudFrontClient,
ListDistributionsCommand,
GetDistributionCommand,
UpdateDistributionCommand,
} from '@aws-sdk/client-cloudfront';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateDistributionCommand
} from '@aws-sdk/client-cloudfront'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class CloudFrontDefaultRootObjectConfigured implements BPSet {
private readonly client = new CloudFrontClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new CloudFrontClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getDistributions = async () => {
const response = await this.memoClient.send(new ListDistributionsCommand({}));
return response.DistributionList?.Items || [];
};
const response = await this.memoClient.send(new ListDistributionsCommand({}))
return response.DistributionList?.Items || []
}
private readonly getDistributionDetails = async (distributionId: string) => {
const response = await this.memoClient.send(
new GetDistributionCommand({ Id: distributionId })
);
const response = await this.memoClient.send(new GetDistributionCommand({ Id: distributionId }))
return {
distribution: response.Distribution!,
etag: response.ETag!,
};
};
etag: response.ETag!
}
}
public readonly getMetadata = () => ({
name: 'CloudFrontDefaultRootObjectConfigured',
description: 'Ensures that CloudFront distributions have a default root object configured.',
priority: 3,
priorityReason: 'A default root object ensures users access the correct content when navigating to the distribution domain.',
priorityReason:
'A default root object ensures users access the correct content when navigating to the distribution domain.',
awsService: 'CloudFront',
awsServiceCategory: 'CDN',
bestPracticeCategory: 'Configuration',
@ -39,116 +38,114 @@ export class CloudFrontDefaultRootObjectConfigured implements BPSet {
name: 'default-root-object',
description: 'The default root object for the CloudFront distribution.',
default: '',
example: 'index.html',
},
example: 'index.html'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'ListDistributionsCommand',
reason: 'List all CloudFront distributions to check for a default root object.',
reason: 'List all CloudFront distributions to check for a default root object.'
},
{
name: 'GetDistributionCommand',
reason: 'Retrieve distribution details to verify the default root object setting.',
},
reason: 'Retrieve distribution details to verify the default root object setting.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateDistributionCommand',
reason: 'Set the default root object for the CloudFront distribution.',
},
reason: 'Set the default root object for the CloudFront distribution.'
}
],
adviseBeforeFixFunction: 'Ensure the default root object exists in the origin to avoid 404 errors.',
});
adviseBeforeFixFunction: 'Ensure the default root object exists in the origin to avoid 404 errors.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const distributions = await this.getDistributions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const distributions = await this.getDistributions()
for (const distribution of distributions) {
const { distribution: details } = await this.getDistributionDetails(distribution.Id!);
const { distribution: details } = await this.getDistributionDetails(distribution.Id!)
if (details.DistributionConfig?.DefaultRootObject !== '') {
compliantResources.push(details.ARN!);
compliantResources.push(details.ARN!)
} else {
nonCompliantResources.push(details.ARN!);
nonCompliantResources.push(details.ARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const defaultRootObject = requiredParametersForFix.find(
(param) => param.name === 'default-root-object'
)?.value;
const defaultRootObject = requiredParametersForFix.find((param) => param.name === 'default-root-object')?.value
if (!defaultRootObject) {
throw new Error("Required parameter 'default-root-object' is missing.");
throw new Error("Required parameter 'default-root-object' is missing.")
}
for (const arn of nonCompliantResources) {
const distributionId = arn.split('/').pop()!;
const { distribution, etag } = await this.getDistributionDetails(distributionId);
const distributionId = arn.split('/').pop()!
const { distribution, etag } = await this.getDistributionDetails(distributionId)
const updatedConfig = {
...distribution.DistributionConfig,
DefaultRootObject: defaultRootObject,
};
DefaultRootObject: defaultRootObject
}
await this.client.send(
new UpdateDistributionCommand({
Id: distributionId,
IfMatch: etag,
DistributionConfig: updatedConfig as any,
DistributionConfig: updatedConfig as unknown
})
);
)
}
};
}
}

View File

@ -2,29 +2,27 @@ import {
CloudFrontClient,
ListDistributionsCommand,
GetDistributionCommand,
UpdateDistributionCommand,
} from '@aws-sdk/client-cloudfront';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateDistributionCommand
} from '@aws-sdk/client-cloudfront'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class CloudFrontNoDeprecatedSSLProtocols implements BPSet {
private readonly client = new CloudFrontClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new CloudFrontClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getDistributions = async () => {
const response = await this.memoClient.send(new ListDistributionsCommand({}));
return response.DistributionList?.Items || [];
};
const response = await this.memoClient.send(new ListDistributionsCommand({}))
return response.DistributionList?.Items || []
}
private readonly getDistributionDetails = async (distributionId: string) => {
const response = await this.memoClient.send(
new GetDistributionCommand({ Id: distributionId })
);
const response = await this.memoClient.send(new GetDistributionCommand({ Id: distributionId }))
return {
distribution: response.Distribution!,
etag: response.ETag!,
};
};
etag: response.ETag!
}
}
public readonly getMetadata = () => ({
name: 'CloudFrontNoDeprecatedSSLProtocols',
@ -39,94 +37,92 @@ export class CloudFrontNoDeprecatedSSLProtocols implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListDistributionsCommand',
reason: 'List all CloudFront distributions to check for deprecated SSL protocols.',
reason: 'List all CloudFront distributions to check for deprecated SSL protocols.'
},
{
name: 'GetDistributionCommand',
reason: 'Retrieve distribution details to identify deprecated SSL protocols in origin configuration.',
},
reason: 'Retrieve distribution details to identify deprecated SSL protocols in origin configuration.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateDistributionCommand',
reason: 'Remove deprecated SSL protocols from the origin configuration of the distribution.',
},
reason: 'Remove deprecated SSL protocols from the origin configuration of the distribution.'
}
],
adviseBeforeFixFunction: 'Ensure the origins are configured to support only secure and modern SSL protocols.',
});
adviseBeforeFixFunction: 'Ensure the origins are configured to support only secure and modern SSL protocols.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const distributions = await this.getDistributions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const distributions = await this.getDistributions()
for (const distribution of distributions) {
const { distribution: details } = await this.getDistributionDetails(distribution.Id!);
const { distribution: details } = await this.getDistributionDetails(distribution.Id!)
const hasDeprecatedSSL = details.DistributionConfig?.Origins?.Items?.some(
(origin) =>
origin.CustomOriginConfig &&
origin.CustomOriginConfig.OriginSslProtocols?.Items?.includes('SSLv3')
);
(origin) => origin.CustomOriginConfig && origin.CustomOriginConfig.OriginSslProtocols?.Items?.includes('SSLv3')
)
if (hasDeprecatedSSL) {
nonCompliantResources.push(details.ARN!);
nonCompliantResources.push(details.ARN!)
} else {
compliantResources.push(details.ARN!);
compliantResources.push(details.ARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const distributionId = arn.split('/').pop()!;
const { distribution, etag } = await this.getDistributionDetails(distributionId);
const distributionId = arn.split('/').pop()!
const { distribution, etag } = await this.getDistributionDetails(distributionId)
const updatedConfig = {
...distribution.DistributionConfig,
@ -141,23 +137,23 @@ export class CloudFrontNoDeprecatedSSLProtocols implements BPSet {
...origin.CustomOriginConfig.OriginSslProtocols,
Items: origin.CustomOriginConfig.OriginSslProtocols?.Items?.filter(
(protocol) => protocol !== 'SSLv3'
),
},
},
};
)
}
}
}
}
return origin;
}),
},
};
return origin
})
}
}
await this.client.send(
new UpdateDistributionCommand({
Id: distributionId,
IfMatch: etag,
DistributionConfig: updatedConfig as any,
DistributionConfig: updatedConfig as unknown
})
);
)
}
};
}
}

View File

@ -2,35 +2,34 @@ import {
CloudFrontClient,
ListDistributionsCommand,
GetDistributionCommand,
UpdateDistributionCommand,
} from '@aws-sdk/client-cloudfront';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateDistributionCommand
} from '@aws-sdk/client-cloudfront'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class CloudFrontS3OriginAccessControlEnabled implements BPSet {
private readonly client = new CloudFrontClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new CloudFrontClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getDistributions = async () => {
const response = await this.memoClient.send(new ListDistributionsCommand({}));
return response.DistributionList?.Items || [];
};
const response = await this.memoClient.send(new ListDistributionsCommand({}))
return response.DistributionList?.Items || []
}
private readonly getDistributionDetails = async (distributionId: string) => {
const response = await this.memoClient.send(
new GetDistributionCommand({ Id: distributionId })
);
const response = await this.memoClient.send(new GetDistributionCommand({ Id: distributionId }))
return {
distribution: response.Distribution!,
etag: response.ETag!,
};
};
etag: response.ETag!
}
}
public readonly getMetadata = () => ({
name: 'CloudFrontS3OriginAccessControlEnabled',
description: 'Ensures that CloudFront distributions with S3 origins have Origin Access Control (OAC) enabled.',
priority: 3,
priorityReason: 'Using Origin Access Control enhances security by ensuring only CloudFront can access the S3 origin.',
priorityReason:
'Using Origin Access Control enhances security by ensuring only CloudFront can access the S3 origin.',
awsService: 'CloudFront',
awsServiceCategory: 'CDN',
bestPracticeCategory: 'Security',
@ -39,109 +38,108 @@ export class CloudFrontS3OriginAccessControlEnabled implements BPSet {
name: 'origin-access-control-id',
description: 'The ID of the Origin Access Control to associate with the S3 origin.',
default: '',
example: 'oac-0abcd1234efgh5678',
},
example: 'oac-0abcd1234efgh5678'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'ListDistributionsCommand',
reason: 'List all CloudFront distributions to check for S3 origins.',
reason: 'List all CloudFront distributions to check for S3 origins.'
},
{
name: 'GetDistributionCommand',
reason: 'Retrieve distribution details to verify Origin Access Control configuration.',
},
reason: 'Retrieve distribution details to verify Origin Access Control configuration.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateDistributionCommand',
reason: 'Enable Origin Access Control for S3 origins in the distribution.',
},
reason: 'Enable Origin Access Control for S3 origins in the distribution.'
}
],
adviseBeforeFixFunction: 'Ensure the specified Origin Access Control is correctly configured and applied to the S3 bucket.',
});
adviseBeforeFixFunction:
'Ensure the specified Origin Access Control is correctly configured and applied to the S3 bucket.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const distributions = await this.getDistributions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const distributions = await this.getDistributions()
for (const distribution of distributions) {
const { distribution: details } = await this.getDistributionDetails(distribution.Id!);
const { distribution: details } = await this.getDistributionDetails(distribution.Id!)
const hasNonCompliantOrigin = details.DistributionConfig?.Origins?.Items?.some(
(origin) =>
origin.S3OriginConfig &&
(!origin.OriginAccessControlId || origin.OriginAccessControlId === '')
);
(origin) => origin.S3OriginConfig && (!origin.OriginAccessControlId || origin.OriginAccessControlId === '')
)
if (hasNonCompliantOrigin) {
nonCompliantResources.push(details.ARN!);
nonCompliantResources.push(details.ARN!)
} else {
compliantResources.push(details.ARN!);
compliantResources.push(details.ARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const originAccessControlId = requiredParametersForFix.find(
(param) => param.name === 'origin-access-control-id'
)?.value;
)?.value
if (!originAccessControlId) {
throw new Error("Required parameter 'origin-access-control-id' is missing.");
throw new Error("Required parameter 'origin-access-control-id' is missing.")
}
for (const arn of nonCompliantResources) {
const distributionId = arn.split('/').pop()!;
const { distribution, etag } = await this.getDistributionDetails(distributionId);
const distributionId = arn.split('/').pop()!
const { distribution, etag } = await this.getDistributionDetails(distributionId)
const updatedConfig = {
...distribution.DistributionConfig,
@ -150,21 +148,21 @@ export class CloudFrontS3OriginAccessControlEnabled implements BPSet {
if (origin.S3OriginConfig) {
return {
...origin,
OriginAccessControlId: originAccessControlId,
};
OriginAccessControlId: originAccessControlId
}
}
return origin;
}),
},
};
return origin
})
}
}
await this.client.send(
new UpdateDistributionCommand({
Id: distributionId,
IfMatch: etag,
DistributionConfig: updatedConfig as any,
DistributionConfig: updatedConfig as unknown
})
);
)
}
};
}
}

View File

@ -2,35 +2,34 @@ import {
CloudFrontClient,
ListDistributionsCommand,
GetDistributionCommand,
UpdateDistributionCommand,
} from '@aws-sdk/client-cloudfront';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateDistributionCommand
} from '@aws-sdk/client-cloudfront'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class CloudFrontViewerPolicyHTTPS implements BPSet {
private readonly client = new CloudFrontClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new CloudFrontClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getDistributions = async () => {
const response = await this.memoClient.send(new ListDistributionsCommand({}));
return response.DistributionList?.Items || [];
};
const response = await this.memoClient.send(new ListDistributionsCommand({}))
return response.DistributionList?.Items || []
}
private readonly getDistributionDetails = async (distributionId: string) => {
const response = await this.memoClient.send(
new GetDistributionCommand({ Id: distributionId })
);
const response = await this.memoClient.send(new GetDistributionCommand({ Id: distributionId }))
return {
distribution: response.Distribution!,
etag: response.ETag!,
};
};
etag: response.ETag!
}
}
public readonly getMetadata = () => ({
name: 'CloudFrontViewerPolicyHTTPS',
description: 'Ensures that CloudFront distributions enforce HTTPS for viewer requests.',
priority: 1,
priorityReason: 'Enforcing HTTPS improves security by ensuring secure communication between viewers and CloudFront.',
priorityReason:
'Enforcing HTTPS improves security by ensuring secure communication between viewers and CloudFront.',
awsService: 'CloudFront',
awsServiceCategory: 'CDN',
bestPracticeCategory: 'Security',
@ -39,116 +38,116 @@ export class CloudFrontViewerPolicyHTTPS implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListDistributionsCommand',
reason: 'List all CloudFront distributions to check viewer protocol policies.',
reason: 'List all CloudFront distributions to check viewer protocol policies.'
},
{
name: 'GetDistributionCommand',
reason: 'Retrieve distribution details to verify viewer protocol policy settings.',
},
reason: 'Retrieve distribution details to verify viewer protocol policy settings.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateDistributionCommand',
reason: 'Update the viewer protocol policy to enforce HTTPS.',
},
reason: 'Update the viewer protocol policy to enforce HTTPS.'
}
],
adviseBeforeFixFunction: 'Ensure all origins and endpoints support HTTPS to prevent connectivity issues.',
});
adviseBeforeFixFunction: 'Ensure all origins and endpoints support HTTPS to prevent connectivity issues.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const distributions = await this.getDistributions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const distributions = await this.getDistributions()
for (const distribution of distributions) {
const { distribution: details } = await this.getDistributionDetails(distribution.Id!);
const { distribution: details } = await this.getDistributionDetails(distribution.Id!)
const hasNonCompliantViewerPolicy =
details.DistributionConfig?.DefaultCacheBehavior?.ViewerProtocolPolicy === 'allow-all' ||
details.DistributionConfig?.CacheBehaviors?.Items?.some(
(behavior) => behavior.ViewerProtocolPolicy === 'allow-all'
);
)
if (hasNonCompliantViewerPolicy) {
nonCompliantResources.push(details.ARN!);
nonCompliantResources.push(details.ARN!)
} else {
compliantResources.push(details.ARN!);
compliantResources.push(details.ARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const distributionId = arn.split('/').pop()!;
const { distribution, etag } = await this.getDistributionDetails(distributionId);
const distributionId = arn.split('/').pop()!
const { distribution, etag } = await this.getDistributionDetails(distributionId)
const updatedConfig = {
...distribution.DistributionConfig,
DefaultCacheBehavior: {
...distribution.DistributionConfig?.DefaultCacheBehavior,
ViewerProtocolPolicy: 'redirect-to-https',
ViewerProtocolPolicy: 'redirect-to-https'
},
CacheBehaviors: {
Items: distribution.DistributionConfig?.CacheBehaviors?.Items?.map((behavior) => ({
...behavior,
ViewerProtocolPolicy: 'redirect-to-https',
})),
},
};
ViewerProtocolPolicy: 'redirect-to-https'
}))
}
}
await this.client.send(
new UpdateDistributionCommand({
Id: distributionId,
IfMatch: etag,
DistributionConfig: updatedConfig as any,
DistributionConfig: updatedConfig as unknown
})
);
)
}
};
}
}

View File

@ -1,19 +1,19 @@
import {
CloudWatchLogsClient,
DescribeLogGroupsCommand,
PutRetentionPolicyCommand,
} from '@aws-sdk/client-cloudwatch-logs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
PutRetentionPolicyCommand
} from '@aws-sdk/client-cloudwatch-logs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class CWLogGroupRetentionPeriodCheck implements BPSet {
private readonly client = new CloudWatchLogsClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeLogGroupsCommand({}))
return response.logGroups || []
}
public readonly getMetadata = () => ({
name: 'CWLogGroupRetentionPeriodCheck',
@ -28,103 +28,101 @@ export class CWLogGroupRetentionPeriodCheck implements BPSet {
name: 'retention-period-days',
description: 'Retention period in days to apply to log groups.',
default: '',
example: '30',
},
example: '30'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'DescribeLogGroupsCommand',
reason: 'Retrieve all CloudWatch log groups to verify retention settings.',
},
reason: 'Retrieve all CloudWatch log groups to verify retention settings.'
}
],
commandUsedInFixFunction: [
{
name: 'PutRetentionPolicyCommand',
reason: 'Set the retention period for log groups without a defined retention policy.',
},
reason: 'Set the retention period for log groups without a defined retention policy.'
}
],
adviseBeforeFixFunction: 'Ensure the specified retention period meets your organizational compliance requirements.',
});
adviseBeforeFixFunction: 'Ensure the specified retention period meets your organizational compliance requirements.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const logGroups = await this.getLogGroups();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const logGroups = await this.getLogGroups()
for (const logGroup of logGroups) {
if (logGroup.retentionInDays) {
compliantResources.push(logGroup.logGroupArn!);
compliantResources.push(logGroup.logGroupArn!)
} else {
nonCompliantResources.push(logGroup.logGroupArn!);
nonCompliantResources.push(logGroup.logGroupArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const retentionPeriod = requiredParametersForFix.find(
(param) => param.name === 'retention-period-days'
)?.value;
const retentionPeriod = requiredParametersForFix.find((param) => param.name === 'retention-period-days')?.value
if (!retentionPeriod) {
throw new Error("Required parameter 'retention-period-days' is missing.");
throw new Error("Required parameter 'retention-period-days' is missing.")
}
for (const logGroupArn of nonCompliantResources) {
const logGroupName = logGroupArn.split(':').pop()!;
const logGroupName = logGroupArn.split(':').pop()!
await this.client.send(
new PutRetentionPolicyCommand({
logGroupName,
retentionInDays: parseInt(retentionPeriod, 10),
retentionInDays: parseInt(retentionPeriod, 10)
})
);
)
}
};
}
}

View File

@ -1,19 +1,15 @@
import {
CloudWatchClient,
DescribeAlarmsCommand,
PutMetricAlarmCommand,
} from '@aws-sdk/client-cloudwatch';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { CloudWatchClient, DescribeAlarmsCommand, PutMetricAlarmCommand } from '@aws-sdk/client-cloudwatch'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class CloudWatchAlarmSettingsCheck implements BPSet {
private readonly client = new CloudWatchClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeAlarmsCommand({}))
return response.MetricAlarms || []
}
public readonly getMetadata = () => ({
name: 'CloudWatchAlarmSettingsCheck',
@ -26,112 +22,120 @@ export class CloudWatchAlarmSettingsCheck implements BPSet {
requiredParametersForFix: [
{ name: 'metric-name', description: 'The metric name for the alarm.', default: '', example: 'CPUUtilization' },
{ name: 'threshold', description: 'The threshold for the alarm.', default: '', example: '80' },
{ name: 'evaluation-periods', description: 'Number of evaluation periods for the alarm.', default: '', example: '5' },
{
name: 'evaluation-periods',
description: 'Number of evaluation periods for the alarm.',
default: '',
example: '5'
},
{ name: 'period', description: 'The period in seconds for the metric evaluation.', default: '', example: '60' },
{ name: 'comparison-operator', description: 'Comparison operator for the threshold.', default: '', example: 'GreaterThanThreshold' },
{ name: 'statistic', description: 'Statistic to apply to the metric.', default: '', example: 'Average' },
{
name: 'comparison-operator',
description: 'Comparison operator for the threshold.',
default: '',
example: 'GreaterThanThreshold'
},
{ name: 'statistic', description: 'Statistic to apply to the metric.', default: '', example: 'Average' }
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'DescribeAlarmsCommand',
reason: 'Retrieve all CloudWatch alarms to verify their settings.',
},
reason: 'Retrieve all CloudWatch alarms to verify their settings.'
}
],
commandUsedInFixFunction: [
{
name: 'PutMetricAlarmCommand',
reason: 'Update or create alarms with the required settings.',
},
reason: 'Update or create alarms with the required settings.'
}
],
adviseBeforeFixFunction: 'Ensure the required settings are correctly configured for your monitoring needs.',
});
adviseBeforeFixFunction: 'Ensure the required settings are correctly configured for your monitoring needs.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const alarms = await this.getAlarms();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const alarms = await this.getAlarms()
const parameters = {
MetricName: '', // Required
Threshold: null,
EvaluationPeriods: null,
Period: null,
ComparisonOperator: null,
Statistic: null,
};
Statistic: null
}
for (const alarm of alarms) {
let isCompliant = true;
let isCompliant = true
for (const key of Object.keys(parameters).filter((k) => (parameters as any)[k] !== null)) {
for (const key of Object.keys(parameters).filter((k) => (parameters as unknown)[k] !== null)) {
if (alarm[key as keyof typeof alarm] !== parameters[key as keyof typeof parameters]) {
isCompliant = false;
break;
isCompliant = false
break
}
}
if (isCompliant) {
compliantResources.push(alarm.AlarmArn!);
compliantResources.push(alarm.AlarmArn!)
} else {
nonCompliantResources.push(alarm.AlarmArn!);
nonCompliantResources.push(alarm.AlarmArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const requiredSettings = Object.fromEntries(
requiredParametersForFix.map((param) => [param.name, param.value])
);
const requiredSettings = Object.fromEntries(requiredParametersForFix.map((param) => [param.name, param.value]))
for (const alarmArn of nonCompliantResources) {
const alarmName = alarmArn.split(':').pop()!;
const alarmName = alarmArn.split(':').pop()!
await this.client.send(
new PutMetricAlarmCommand({
@ -140,10 +144,10 @@ export class CloudWatchAlarmSettingsCheck implements BPSet {
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,
ComparisonOperator: requiredSettings['comparison-operator'] as unknown,
Statistic: requiredSettings['statistic'] as unknown
})
);
)
}
};
}
}

View File

@ -2,25 +2,23 @@ import {
CodeBuildClient,
ListProjectsCommand,
BatchGetProjectsCommand,
UpdateProjectCommand,
} from '@aws-sdk/client-codebuild';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateProjectCommand
} from '@aws-sdk/client-codebuild'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class CodeBuildProjectEnvironmentPrivilegedCheck implements BPSet {
private readonly client = new CodeBuildClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new CodeBuildClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getProjects = async () => {
const projectNames = await this.memoClient.send(new ListProjectsCommand({}));
const projectNames = await this.memoClient.send(new ListProjectsCommand({}))
if (!projectNames.projects?.length) {
return [];
return []
}
const response = await this.memoClient.send(
new BatchGetProjectsCommand({ names: projectNames.projects })
);
return response.projects || [];
};
const response = await this.memoClient.send(new BatchGetProjectsCommand({ names: projectNames.projects }))
return response.projects || []
}
public readonly getMetadata = () => ({
name: 'CodeBuildProjectEnvironmentPrivilegedCheck',
@ -35,92 +33,92 @@ export class CodeBuildProjectEnvironmentPrivilegedCheck implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListProjectsCommand',
reason: 'Retrieve all CodeBuild projects to verify environment settings.',
reason: 'Retrieve all CodeBuild projects to verify environment settings.'
},
{
name: 'BatchGetProjectsCommand',
reason: 'Fetch detailed configuration for each CodeBuild project.',
},
reason: 'Fetch detailed configuration for each CodeBuild project.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateProjectCommand',
reason: 'Update the project to disable privileged mode in the environment settings.',
},
reason: 'Update the project to disable privileged mode in the environment settings.'
}
],
adviseBeforeFixFunction: 'Ensure the privileged mode is not required for any valid use case before applying fixes.',
});
adviseBeforeFixFunction: 'Ensure the privileged mode is not required for any valid use case before applying fixes.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const projects = await this.getProjects();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const projects = await this.getProjects()
for (const project of projects) {
if (!project.environment?.privilegedMode) {
compliantResources.push(project.arn!);
compliantResources.push(project.arn!)
} else {
nonCompliantResources.push(project.arn!);
nonCompliantResources.push(project.arn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
const projects = await this.getProjects();
const projects = await this.getProjects()
for (const arn of nonCompliantResources) {
const projectName = arn.split(':').pop()!;
const projectToFix = projects.find((project) => project.arn === arn);
const projectName = arn.split(':').pop()!
const projectToFix = projects.find((project) => project.arn === arn)
if (!projectToFix) {
continue;
continue
}
await this.client.send(
@ -128,10 +126,10 @@ export class CodeBuildProjectEnvironmentPrivilegedCheck implements BPSet {
name: projectName,
environment: {
...projectToFix.environment,
privilegedMode: false,
} as any,
privilegedMode: false
} as unknown
})
);
)
}
};
}
}

View File

@ -2,25 +2,23 @@ import {
CodeBuildClient,
ListProjectsCommand,
BatchGetProjectsCommand,
UpdateProjectCommand,
} from '@aws-sdk/client-codebuild';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateProjectCommand
} from '@aws-sdk/client-codebuild'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class CodeBuildProjectLoggingEnabled implements BPSet {
private readonly client = new CodeBuildClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new CodeBuildClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getProjects = async () => {
const projectNames = await this.memoClient.send(new ListProjectsCommand({}));
const projectNames = await this.memoClient.send(new ListProjectsCommand({}))
if (!projectNames.projects?.length) {
return [];
return []
}
const response = await this.memoClient.send(
new BatchGetProjectsCommand({ names: projectNames.projects })
);
return response.projects || [];
};
const response = await this.memoClient.send(new BatchGetProjectsCommand({ names: projectNames.projects }))
return response.projects || []
}
public readonly getMetadata = () => ({
name: 'CodeBuildProjectLoggingEnabled',
@ -35,96 +33,93 @@ export class CodeBuildProjectLoggingEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListProjectsCommand',
reason: 'Retrieve all CodeBuild projects to verify logging settings.',
reason: 'Retrieve all CodeBuild projects to verify logging settings.'
},
{
name: 'BatchGetProjectsCommand',
reason: 'Fetch detailed configuration for each CodeBuild project.',
},
reason: 'Fetch detailed configuration for each CodeBuild project.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateProjectCommand',
reason: 'Enable logging for projects that have it disabled.',
},
reason: 'Enable logging for projects that have it disabled.'
}
],
adviseBeforeFixFunction: 'Ensure the default log group and stream names are suitable for your organization.',
});
adviseBeforeFixFunction: 'Ensure the default log group and stream names are suitable for your organization.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const projects = await this.getProjects();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
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!);
const logsConfig = project.logsConfig
if (logsConfig?.cloudWatchLogs?.status === 'ENABLED' || logsConfig?.s3Logs?.status === 'ENABLED') {
compliantResources.push(project.arn!)
} else {
nonCompliantResources.push(project.arn!);
nonCompliantResources.push(project.arn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
const projects = await this.getProjects();
const projects = await this.getProjects()
for (const arn of nonCompliantResources) {
const projectName = arn.split(':').pop()!;
const projectToFix = projects.find((project) => project.arn === arn);
const projectName = arn.split(':').pop()!
const projectToFix = projects.find((project) => project.arn === arn)
if (!projectToFix) {
continue;
continue
}
await this.client.send(
@ -135,11 +130,11 @@ export class CodeBuildProjectLoggingEnabled implements BPSet {
cloudWatchLogs: {
status: 'ENABLED',
groupName: 'default-cloudwatch-group',
streamName: 'default-stream',
},
},
streamName: 'default-stream'
}
}
})
);
)
}
};
}
}

View File

@ -3,43 +3,44 @@ import {
ListApplicationsCommand,
ListDeploymentGroupsCommand,
BatchGetDeploymentGroupsCommand,
UpdateDeploymentGroupCommand,
} from '@aws-sdk/client-codedeploy';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateDeploymentGroupCommand
} from '@aws-sdk/client-codedeploy'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class CodeDeployAutoRollbackMonitorEnabled implements BPSet {
private readonly client = new CodeDeployClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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;
continue
}
const batchResponse = await this.memoClient.send(
new BatchGetDeploymentGroupsCommand({
applicationName: application,
deploymentGroupNames: deploymentGroups.deploymentGroups,
deploymentGroupNames: deploymentGroups.deploymentGroups
})
);
deploymentGroupsInfo.push(...(batchResponse.deploymentGroupsInfo || []));
)
deploymentGroupsInfo.push(...(batchResponse.deploymentGroupsInfo || []))
}
return deploymentGroupsInfo;
};
return deploymentGroupsInfo
}
public readonly getMetadata = () => ({
name: 'CodeDeployAutoRollbackMonitorEnabled',
description: 'Ensures that auto-rollback and alarm monitoring are enabled for CodeDeploy deployment groups.',
priority: 2,
priorityReason: 'Enabling auto-rollback and alarms helps prevent deployment issues and allows for automated recovery.',
priorityReason:
'Enabling auto-rollback and alarms helps prevent deployment issues and allows for automated recovery.',
awsService: 'CodeDeploy',
awsServiceCategory: 'Deployment',
bestPracticeCategory: 'Resilience',
@ -48,101 +49,96 @@ export class CodeDeployAutoRollbackMonitorEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListApplicationsCommand',
reason: 'Retrieve all CodeDeploy applications to analyze their deployment groups.',
reason: 'Retrieve all CodeDeploy applications to analyze their deployment groups.'
},
{
name: 'ListDeploymentGroupsCommand',
reason: 'Fetch deployment groups for each application.',
reason: 'Fetch deployment groups for each application.'
},
{
name: 'BatchGetDeploymentGroupsCommand',
reason: 'Get detailed information about deployment groups to check configurations.',
},
reason: 'Get detailed information about deployment groups to check configurations.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateDeploymentGroupCommand',
reason: 'Enable alarm monitoring and auto-rollback configurations for deployment groups.',
},
reason: 'Enable alarm monitoring and auto-rollback configurations for deployment groups.'
}
],
adviseBeforeFixFunction:
'Ensure your alarms and configurations align with organizational standards before enabling auto-rollback and monitoring.',
});
'Ensure your alarms and configurations align with organizational standards before enabling auto-rollback and monitoring.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const deploymentGroups = await this.getDeploymentGroups();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const deploymentGroups = await this.getDeploymentGroups()
for (const deploymentGroup of deploymentGroups) {
if (
deploymentGroup.alarmConfiguration?.enabled &&
deploymentGroup.autoRollbackConfiguration?.enabled
) {
compliantResources.push(deploymentGroup.deploymentGroupId!);
if (deploymentGroup.alarmConfiguration?.enabled && deploymentGroup.autoRollbackConfiguration?.enabled) {
compliantResources.push(deploymentGroup.deploymentGroupId!)
} else {
nonCompliantResources.push(deploymentGroup.deploymentGroupId!);
nonCompliantResources.push(deploymentGroup.deploymentGroupId!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
const deploymentGroups = await this.getDeploymentGroups();
const deploymentGroups = await this.getDeploymentGroups()
for (const groupId of nonCompliantResources) {
const deploymentGroupToFix = deploymentGroups.find(
(group) => group.deploymentGroupId === groupId
);
const deploymentGroupToFix = deploymentGroups.find((group) => group.deploymentGroupId === groupId)
if (!deploymentGroupToFix) {
continue;
continue
}
await this.client.send(
@ -151,14 +147,14 @@ export class CodeDeployAutoRollbackMonitorEnabled implements BPSet {
currentDeploymentGroupName: deploymentGroupToFix.deploymentGroupName!,
alarmConfiguration: {
...deploymentGroupToFix.alarmConfiguration,
enabled: true,
enabled: true
},
autoRollbackConfiguration: {
...deploymentGroupToFix.autoRollbackConfiguration,
enabled: true,
},
enabled: true
}
})
);
)
}
};
}
}

View File

@ -1,39 +1,34 @@
import {
DynamoDBClient,
ListTablesCommand,
DescribeTableCommand,
} from '@aws-sdk/client-dynamodb';
import { DynamoDBClient, ListTablesCommand, DescribeTableCommand } from '@aws-sdk/client-dynamodb'
import {
ApplicationAutoScalingClient,
RegisterScalableTargetCommand,
PutScalingPolicyCommand,
DescribeScalingPoliciesCommand,
} from '@aws-sdk/client-application-auto-scaling';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
DescribeScalingPoliciesCommand
} from '@aws-sdk/client-application-auto-scaling'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
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 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 = [];
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!);
const tableDetails = await this.memoClient.send(new DescribeTableCommand({ TableName: tableName }))
tables.push(tableDetails.Table!)
}
return tables;
};
return tables
}
public readonly getMetadata = () => ({
name: 'DynamoDBAutoscalingEnabled',
description: 'Ensures DynamoDB tables have autoscaling enabled for both read and write capacity.',
priority: 2,
priorityReason: 'Autoscaling ensures DynamoDB tables dynamically adjust capacity to meet demand, reducing costs and preventing throttling.',
priorityReason:
'Autoscaling ensures DynamoDB tables dynamically adjust capacity to meet demand, reducing costs and preventing throttling.',
awsService: 'DynamoDB',
awsServiceCategory: 'Database',
bestPracticeCategory: 'Scalability',
@ -42,113 +37,111 @@ export class DynamoDBAutoscalingEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListTablesCommand',
reason: 'List all DynamoDB tables to check their configurations.',
reason: 'List all DynamoDB tables to check their configurations.'
},
{
name: 'DescribeTableCommand',
reason: 'Retrieve details of DynamoDB tables to analyze billing mode and autoscaling.',
reason: 'Retrieve details of DynamoDB tables to analyze billing mode and autoscaling.'
},
{
name: 'DescribeScalingPoliciesCommand',
reason: 'Fetch scaling policies for each table to check autoscaling settings.',
},
reason: 'Fetch scaling policies for each table to check autoscaling settings.'
}
],
commandUsedInFixFunction: [
{
name: 'RegisterScalableTargetCommand',
reason: 'Register read and write capacity units for autoscaling.',
reason: 'Register read and write capacity units for autoscaling.'
},
{
name: 'PutScalingPolicyCommand',
reason: 'Configure target tracking scaling policies for read and write capacity.',
},
reason: 'Configure target tracking scaling policies for read and write capacity.'
}
],
adviseBeforeFixFunction:
'Ensure the tables read and write workloads are predictable to configure appropriate scaling limits.',
});
'Ensure the tables read and write workloads are predictable to configure appropriate scaling limits.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const tables = await this.getTables();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const tables = await this.getTables()
for (const table of tables) {
if (table.BillingModeSummary?.BillingMode === 'PAY_PER_REQUEST') {
compliantResources.push(table.TableArn!);
continue;
compliantResources.push(table.TableArn!)
continue
}
const scalingPolicies = await this.autoScalingClient.send(
new DescribeScalingPoliciesCommand({
ServiceNamespace: 'dynamodb',
ResourceId: `table/${table.TableName}`,
ResourceId: `table/${table.TableName}`
})
);
const scalingPolicyDimensions = scalingPolicies.ScalingPolicies?.map(
(policy) => policy.ScalableDimension
);
)
const scalingPolicyDimensions = scalingPolicies.ScalingPolicies?.map((policy) => policy.ScalableDimension)
if (
scalingPolicyDimensions?.includes('dynamodb:table:ReadCapacityUnits') &&
scalingPolicyDimensions?.includes('dynamodb:table:WriteCapacityUnits')
) {
compliantResources.push(table.TableArn!);
compliantResources.push(table.TableArn!)
} else {
nonCompliantResources.push(table.TableArn!);
nonCompliantResources.push(table.TableArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const tableName = arn.split('/').pop()!;
const tableName = arn.split('/').pop()!
// Register scalable targets for read and write capacity
await this.autoScalingClient.send(
@ -157,9 +150,9 @@ export class DynamoDBAutoscalingEnabled implements BPSet {
ResourceId: `table/${tableName}`,
ScalableDimension: 'dynamodb:table:ReadCapacityUnits',
MinCapacity: 1,
MaxCapacity: 100,
MaxCapacity: 100
})
);
)
await this.autoScalingClient.send(
new RegisterScalableTargetCommand({
@ -167,9 +160,9 @@ export class DynamoDBAutoscalingEnabled implements BPSet {
ResourceId: `table/${tableName}`,
ScalableDimension: 'dynamodb:table:WriteCapacityUnits',
MinCapacity: 1,
MaxCapacity: 100,
MaxCapacity: 100
})
);
)
// Put scaling policies for read and write capacity
await this.autoScalingClient.send(
@ -184,11 +177,11 @@ export class DynamoDBAutoscalingEnabled implements BPSet {
ScaleInCooldown: 60,
ScaleOutCooldown: 60,
PredefinedMetricSpecification: {
PredefinedMetricType: 'DynamoDBReadCapacityUtilization',
},
},
PredefinedMetricType: 'DynamoDBReadCapacityUtilization'
}
}
})
);
)
await this.autoScalingClient.send(
new PutScalingPolicyCommand({
@ -202,11 +195,11 @@ export class DynamoDBAutoscalingEnabled implements BPSet {
ScaleInCooldown: 60,
ScaleOutCooldown: 60,
PredefinedMetricSpecification: {
PredefinedMetricType: 'DynamoDBWriteCapacityUtilization',
},
},
PredefinedMetricType: 'DynamoDBWriteCapacityUtilization'
}
}
})
);
)
}
};
}
}

View File

@ -1,44 +1,34 @@
import {
DynamoDBClient,
ListTablesCommand,
DescribeTableCommand,
} from '@aws-sdk/client-dynamodb';
import {
BackupClient,
ListRecoveryPointsByResourceCommand,
StartBackupJobCommand,
} from '@aws-sdk/client-backup';
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { DynamoDBClient, ListTablesCommand, DescribeTableCommand } from '@aws-sdk/client-dynamodb'
import { BackupClient, ListRecoveryPointsByResourceCommand, StartBackupJobCommand } from '@aws-sdk/client-backup'
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class DynamoDBLastBackupRecoveryPointCreated implements BPSet {
private readonly client = new DynamoDBClient({});
private readonly backupClient = new BackupClient({});
private readonly stsClient = new STSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new DynamoDBClient({})
private readonly backupClient = new BackupClient({})
private readonly stsClient = new STSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private accountId: string | undefined;
private accountId: string | undefined
private readonly fetchAccountId = async () => {
if (!this.accountId) {
const identity = await this.stsClient.send(new GetCallerIdentityCommand({}));
this.accountId = identity.Account!;
const identity = await this.stsClient.send(new GetCallerIdentityCommand({}))
this.accountId = identity.Account!
}
return this.accountId;
};
return this.accountId
}
private readonly getTables = async () => {
const tableNames = await this.memoClient.send(new ListTablesCommand({}));
const tables = [];
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!);
const tableDetails = await this.memoClient.send(new DescribeTableCommand({ TableName: tableName }))
tables.push(tableDetails.Table!)
}
return tables;
};
return tables
}
public readonly getMetadata = () => ({
name: 'DynamoDBLastBackupRecoveryPointCreated',
@ -53,115 +43,114 @@ export class DynamoDBLastBackupRecoveryPointCreated implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListTablesCommand',
reason: 'Retrieve the list of DynamoDB tables to check for backups.',
reason: 'Retrieve the list of DynamoDB tables to check for backups.'
},
{
name: 'DescribeTableCommand',
reason: 'Fetch details of each DynamoDB table.',
reason: 'Fetch details of each DynamoDB table.'
},
{
name: 'ListRecoveryPointsByResourceCommand',
reason: 'Check recovery points for DynamoDB tables in AWS Backup.',
},
reason: 'Check recovery points for DynamoDB tables in AWS Backup.'
}
],
commandUsedInFixFunction: [
{
name: 'StartBackupJobCommand',
reason: 'Initiate a backup job for non-compliant DynamoDB tables.',
},
reason: 'Initiate a backup job for non-compliant DynamoDB tables.'
}
],
adviseBeforeFixFunction:
'Ensure the backup vault and IAM role are properly configured for your backup strategy.',
});
adviseBeforeFixFunction: 'Ensure the backup vault and IAM role are properly configured for your backup strategy.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const tables = await this.getTables();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const tables = await this.getTables()
for (const table of tables) {
const recoveryPointsResponse = await this.backupClient.send(
new ListRecoveryPointsByResourceCommand({
ResourceArn: table.TableArn,
ResourceArn: table.TableArn
})
);
const recoveryPoints = recoveryPointsResponse.RecoveryPoints || [];
)
const recoveryPoints = recoveryPointsResponse.RecoveryPoints || []
if (recoveryPoints.length === 0) {
nonCompliantResources.push(table.TableArn!);
continue;
nonCompliantResources.push(table.TableArn!)
continue
}
const latestRecoveryPoint = recoveryPoints
.map((point) => new Date(point.CreationDate!))
.sort((a, b) => b.getTime() - a.getTime())[0];
.sort((a, b) => b.getTime() - a.getTime())[0]
if (new Date().getTime() - latestRecoveryPoint.getTime() > 86400000) {
nonCompliantResources.push(table.TableArn!);
nonCompliantResources.push(table.TableArn!)
} else {
compliantResources.push(table.TableArn!);
compliantResources.push(table.TableArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
const accountId = await this.fetchAccountId();
const accountId = await this.fetchAccountId()
for (const arn of nonCompliantResources) {
await this.backupClient.send(
new StartBackupJobCommand({
ResourceArn: arn,
BackupVaultName: 'Default',
IamRoleArn: `arn:aws:iam::${accountId}:role/service-role/BackupDefaultServiceRole`,
IamRoleArn: `arn:aws:iam::${accountId}:role/service-role/BackupDefaultServiceRole`
})
);
)
}
};
}
}

View File

@ -3,32 +3,31 @@ import {
ListTablesCommand,
DescribeTableCommand,
DescribeContinuousBackupsCommand,
UpdateContinuousBackupsCommand,
} from '@aws-sdk/client-dynamodb';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateContinuousBackupsCommand
} from '@aws-sdk/client-dynamodb'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class DynamoDBPITREnabled implements BPSet {
private readonly client = new DynamoDBClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const tableDetails = await this.memoClient.send(new DescribeTableCommand({ TableName: tableName }))
tables.push(tableDetails.Table!)
}
return tables;
};
return tables
}
public readonly getMetadata = () => ({
name: 'DynamoDBPITREnabled',
description: 'Ensures that Point-In-Time Recovery (PITR) is enabled for DynamoDB tables.',
priority: 1,
priorityReason: 'PITR provides continuous backups of DynamoDB tables, enabling recovery to any second within the last 35 days.',
priorityReason:
'PITR provides continuous backups of DynamoDB tables, enabling recovery to any second within the last 35 days.',
awsService: 'DynamoDB',
awsServiceCategory: 'Database',
bestPracticeCategory: 'Backup and Recovery',
@ -37,108 +36,109 @@ export class DynamoDBPITREnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListTablesCommand',
reason: 'Retrieve the list of DynamoDB tables to verify PITR settings.',
reason: 'Retrieve the list of DynamoDB tables to verify PITR settings.'
},
{
name: 'DescribeTableCommand',
reason: 'Fetch details of each DynamoDB table.',
reason: 'Fetch details of each DynamoDB table.'
},
{
name: 'DescribeContinuousBackupsCommand',
reason: 'Check if PITR is enabled for each table.',
},
reason: 'Check if PITR is enabled for each table.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateContinuousBackupsCommand',
reason: 'Enable PITR for non-compliant DynamoDB tables.',
},
reason: 'Enable PITR for non-compliant DynamoDB tables.'
}
],
adviseBeforeFixFunction: 'Ensure enabling PITR aligns with organizational backup policies and compliance requirements.',
});
adviseBeforeFixFunction:
'Ensure enabling PITR aligns with organizational backup policies and compliance requirements.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const tables = await this.getTables();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const tables = await this.getTables()
for (const table of tables) {
const backupStatus = await this.memoClient.send(
new DescribeContinuousBackupsCommand({
TableName: table.TableName!,
TableName: table.TableName!
})
);
)
if (
backupStatus.ContinuousBackupsDescription?.PointInTimeRecoveryDescription
?.PointInTimeRecoveryStatus === 'ENABLED'
backupStatus.ContinuousBackupsDescription?.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus ===
'ENABLED'
) {
compliantResources.push(table.TableArn!);
compliantResources.push(table.TableArn!)
} else {
nonCompliantResources.push(table.TableArn!);
nonCompliantResources.push(table.TableArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const tableName = arn.split('/').pop()!;
const tableName = arn.split('/').pop()!
await this.client.send(
new UpdateContinuousBackupsCommand({
TableName: tableName,
PointInTimeRecoverySpecification: {
PointInTimeRecoveryEnabled: true,
},
PointInTimeRecoveryEnabled: true
}
})
);
)
}
};
}
}

View File

@ -1,27 +1,20 @@
import {
DynamoDBClient,
ListTablesCommand,
DescribeTableCommand,
UpdateTableCommand,
} from '@aws-sdk/client-dynamodb';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { DynamoDBClient, ListTablesCommand, DescribeTableCommand, UpdateTableCommand } from '@aws-sdk/client-dynamodb'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class DynamoDBTableDeletionProtectionEnabled implements BPSet {
private readonly client = new DynamoDBClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const tableDetails = await this.memoClient.send(new DescribeTableCommand({ TableName: tableName }))
tables.push(tableDetails.Table!)
}
return tables;
};
return tables
}
public readonly getMetadata = () => ({
name: 'DynamoDBTableDeletionProtectionEnabled',
@ -36,93 +29,93 @@ export class DynamoDBTableDeletionProtectionEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListTablesCommand',
reason: 'Retrieve the list of DynamoDB tables to verify deletion protection.',
reason: 'Retrieve the list of DynamoDB tables to verify deletion protection.'
},
{
name: 'DescribeTableCommand',
reason: 'Fetch details of each DynamoDB table, including deletion protection settings.',
},
reason: 'Fetch details of each DynamoDB table, including deletion protection settings.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateTableCommand',
reason: 'Enable deletion protection for non-compliant DynamoDB tables.',
},
reason: 'Enable deletion protection for non-compliant DynamoDB tables.'
}
],
adviseBeforeFixFunction: 'Ensure enabling deletion protection aligns with operational and compliance requirements.',
});
adviseBeforeFixFunction: 'Ensure enabling deletion protection aligns with operational and compliance requirements.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const tables = await this.getTables();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const tables = await this.getTables()
for (const table of tables) {
if (table.DeletionProtectionEnabled) {
compliantResources.push(table.TableArn!);
compliantResources.push(table.TableArn!)
} else {
nonCompliantResources.push(table.TableArn!);
nonCompliantResources.push(table.TableArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const tableName = arn.split('/').pop()!;
const tableName = arn.split('/').pop()!
await this.client.send(
new UpdateTableCommand({
TableName: tableName,
DeletionProtectionEnabled: true,
DeletionProtectionEnabled: true
})
);
)
}
};
}
}

View File

@ -1,27 +1,20 @@
import {
DynamoDBClient,
ListTablesCommand,
DescribeTableCommand,
UpdateTableCommand,
} from '@aws-sdk/client-dynamodb';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { DynamoDBClient, ListTablesCommand, DescribeTableCommand, UpdateTableCommand } from '@aws-sdk/client-dynamodb'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class DynamoDBTableEncryptedKMS implements BPSet {
private readonly client = new DynamoDBClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const tableDetails = await this.memoClient.send(new DescribeTableCommand({ TableName: tableName }))
tables.push(tableDetails.Table!)
}
return tables;
};
return tables
}
public readonly getMetadata = () => ({
name: 'DynamoDBTableEncryptedKMS',
@ -36,102 +29,99 @@ export class DynamoDBTableEncryptedKMS implements BPSet {
name: 'kms-key-id',
description: 'The ID of the KMS key used to encrypt the DynamoDB table.',
default: '',
example: 'arn:aws:kms:us-east-1:123456789012:key/abcd1234-5678-90ef-ghij-klmnopqrstuv',
},
example: 'arn:aws:kms:us-east-1:123456789012:key/abcd1234-5678-90ef-ghij-klmnopqrstuv'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'ListTablesCommand',
reason: 'Retrieve the list of DynamoDB tables to verify encryption settings.',
reason: 'Retrieve the list of DynamoDB tables to verify encryption settings.'
},
{
name: 'DescribeTableCommand',
reason: 'Fetch details of each DynamoDB table, including SSE configuration.',
},
reason: 'Fetch details of each DynamoDB table, including SSE configuration.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateTableCommand',
reason: 'Enable KMS encryption for non-compliant DynamoDB tables.',
},
reason: 'Enable KMS encryption for non-compliant DynamoDB tables.'
}
],
adviseBeforeFixFunction: 'Ensure the specified KMS key is accessible and meets your security policies.',
});
adviseBeforeFixFunction: 'Ensure the specified KMS key is accessible and meets your security policies.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const tables = await this.getTables();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const tables = await this.getTables()
for (const table of tables) {
if (
table.SSEDescription?.Status === 'ENABLED' &&
table.SSEDescription?.SSEType === 'KMS'
) {
compliantResources.push(table.TableArn!);
if (table.SSEDescription?.Status === 'ENABLED' && table.SSEDescription?.SSEType === 'KMS') {
compliantResources.push(table.TableArn!)
} else {
nonCompliantResources.push(table.TableArn!);
nonCompliantResources.push(table.TableArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const kmsKeyId = requiredParametersForFix.find((param) => param.name === 'kms-key-id')?.value;
const kmsKeyId = requiredParametersForFix.find((param) => param.name === 'kms-key-id')?.value
if (!kmsKeyId) {
throw new Error("Required parameter 'kms-key-id' is missing.");
throw new Error("Required parameter 'kms-key-id' is missing.")
}
for (const arn of nonCompliantResources) {
const tableName = arn.split('/').pop()!;
const tableName = arn.split('/').pop()!
await this.client.send(
new UpdateTableCommand({
@ -139,10 +129,10 @@ export class DynamoDBTableEncryptedKMS implements BPSet {
SSESpecification: {
Enabled: true,
SSEType: 'KMS',
KMSMasterKeyId: kmsKeyId,
},
KMSMasterKeyId: kmsKeyId
}
})
);
)
}
};
}
}

View File

@ -1,33 +1,27 @@
import {
DynamoDBClient,
ListTablesCommand,
DescribeTableCommand,
UpdateTableCommand,
} from '@aws-sdk/client-dynamodb';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { DynamoDBClient, ListTablesCommand, DescribeTableCommand, UpdateTableCommand } from '@aws-sdk/client-dynamodb'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class DynamoDBTableEncryptionEnabled implements BPSet {
private readonly client = new DynamoDBClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const tableDetails = await this.memoClient.send(new DescribeTableCommand({ TableName: tableName }))
tables.push(tableDetails.Table!)
}
return tables;
};
return tables
}
public readonly getMetadata = () => ({
name: 'DynamoDBTableEncryptionEnabled',
description: 'Ensures that DynamoDB tables have server-side encryption enabled.',
priority: 3,
priorityReason: 'Enabling server-side encryption ensures data security and compliance with organizational policies.',
priorityReason:
'Enabling server-side encryption ensures data security and compliance with organizational policies.',
awsService: 'DynamoDB',
awsServiceCategory: 'Database',
bestPracticeCategory: 'Security',
@ -36,95 +30,95 @@ export class DynamoDBTableEncryptionEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListTablesCommand',
reason: 'Retrieve the list of DynamoDB tables to verify encryption settings.',
reason: 'Retrieve the list of DynamoDB tables to verify encryption settings.'
},
{
name: 'DescribeTableCommand',
reason: 'Fetch details of each DynamoDB table, including encryption settings.',
},
reason: 'Fetch details of each DynamoDB table, including encryption settings.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateTableCommand',
reason: 'Enable server-side encryption for non-compliant DynamoDB tables.',
},
reason: 'Enable server-side encryption for non-compliant DynamoDB tables.'
}
],
adviseBeforeFixFunction: 'Ensure enabling encryption aligns with organizational data security policies.',
});
adviseBeforeFixFunction: 'Ensure enabling encryption aligns with organizational data security policies.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const tables = await this.getTables();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const tables = await this.getTables()
for (const table of tables) {
if (table.SSEDescription?.Status === 'ENABLED') {
compliantResources.push(table.TableArn!);
compliantResources.push(table.TableArn!)
} else {
nonCompliantResources.push(table.TableArn!);
nonCompliantResources.push(table.TableArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const tableName = arn.split('/').pop()!;
const tableName = arn.split('/').pop()!
await this.client.send(
new UpdateTableCommand({
TableName: tableName,
SSESpecification: {
Enabled: true,
},
Enabled: true
}
})
);
)
}
};
}
}

View File

@ -1,21 +1,17 @@
import {
EC2Client,
DescribeVolumesCommand,
GetEbsEncryptionByDefaultCommand,
EnableEbsEncryptionByDefaultCommand,
} from '@aws-sdk/client-ec2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { EC2Client, GetEbsEncryptionByDefaultCommand, EnableEbsEncryptionByDefaultCommand } from '@aws-sdk/client-ec2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EC2EbsEncryptionByDefault implements BPSet {
private readonly client = new EC2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly getMetadata = () => ({
name: 'EC2EbsEncryptionByDefault',
description: 'Ensures that EBS encryption is enabled by default for all volumes in the AWS account.',
priority: 1,
priorityReason: 'Enabling EBS encryption by default ensures data at rest is encrypted, enhancing security and compliance.',
priorityReason:
'Enabling EBS encryption by default ensures data at rest is encrypted, enhancing security and compliance.',
awsService: 'EC2',
awsServiceCategory: 'Compute',
bestPracticeCategory: 'Security',
@ -24,79 +20,79 @@ export class EC2EbsEncryptionByDefault implements BPSet {
commandUsedInCheckFunction: [
{
name: 'GetEbsEncryptionByDefaultCommand',
reason: 'Verify if EBS encryption by default is enabled in the AWS account.',
},
reason: 'Verify if EBS encryption by default is enabled in the AWS account.'
}
],
commandUsedInFixFunction: [
{
name: 'EnableEbsEncryptionByDefaultCommand',
reason: 'Enable EBS encryption by default for the account.',
},
reason: 'Enable EBS encryption by default for the account.'
}
],
adviseBeforeFixFunction:
'Ensure enabling EBS encryption by default aligns with your organizations security policies.',
});
'Ensure enabling EBS encryption by default aligns with your organizations security policies.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const response = await this.client.send(new GetEbsEncryptionByDefaultCommand({}));
const response = await this.client.send(new GetEbsEncryptionByDefaultCommand({}))
if (response.EbsEncryptionByDefault) {
compliantResources.push('EBS Encryption By Default');
compliantResources.push('EBS Encryption By Default')
} else {
nonCompliantResources.push('EBS Encryption By Default');
nonCompliantResources.push('EBS Encryption By Default')
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async () => {
await this.client.send(new EnableEbsEncryptionByDefaultCommand({}));
};
await this.client.send(new EnableEbsEncryptionByDefaultCommand({}))
}
}

View File

@ -1,14 +1,10 @@
import {
DescribeInstancesCommand,
EC2Client,
ModifyInstanceMetadataOptionsCommand,
} from '@aws-sdk/client-ec2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { DescribeInstancesCommand, EC2Client, ModifyInstanceMetadataOptionsCommand } from '@aws-sdk/client-ec2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EC2Imdsv2Check implements BPSet {
private readonly client = new EC2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly getMetadata = () => ({
name: 'EC2Imdsv2Check',
@ -23,92 +19,91 @@ export class EC2Imdsv2Check implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeInstancesCommand',
reason: 'Retrieve all EC2 instances and check their metadata options.',
},
reason: 'Retrieve all EC2 instances and check their metadata options.'
}
],
commandUsedInFixFunction: [
{
name: 'ModifyInstanceMetadataOptionsCommand',
reason: 'Update EC2 instance metadata options to enforce IMDSv2.',
},
reason: 'Update EC2 instance metadata options to enforce IMDSv2.'
}
],
adviseBeforeFixFunction: 'Ensure modifying metadata options aligns with operational policies.',
});
adviseBeforeFixFunction: 'Ensure modifying metadata options aligns with operational policies.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
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.State?.Name === 'terminated')
continue
if (instance.State?.Name === 'terminated') continue
if (instance.MetadataOptions?.HttpTokens === 'required') {
compliantResources.push(instance.InstanceId!);
compliantResources.push(instance.InstanceId!)
} else {
nonCompliantResources.push(instance.InstanceId!);
nonCompliantResources.push(instance.InstanceId!)
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const instanceId of nonCompliantResources) {
await this.client.send(
new ModifyInstanceMetadataOptionsCommand({
InstanceId: instanceId,
HttpTokens: 'required',
HttpTokens: 'required'
})
);
)
}
};
}
}

View File

@ -1,14 +1,10 @@
import {
DescribeInstancesCommand,
EC2Client,
MonitorInstancesCommand,
} from '@aws-sdk/client-ec2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { DescribeInstancesCommand, EC2Client, MonitorInstancesCommand } from '@aws-sdk/client-ec2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EC2InstanceDetailedMonitoringEnabled implements BPSet {
private readonly client = new EC2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly getMetadata = () => ({
name: 'EC2InstanceDetailedMonitoringEnabled',
@ -23,92 +19,91 @@ export class EC2InstanceDetailedMonitoringEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeInstancesCommand',
reason: 'Retrieve the list of all EC2 instances and their monitoring state.',
},
reason: 'Retrieve the list of all EC2 instances and their monitoring state.'
}
],
commandUsedInFixFunction: [
{
name: 'MonitorInstancesCommand',
reason: 'Enable detailed monitoring for non-compliant EC2 instances.',
},
reason: 'Enable detailed monitoring for non-compliant EC2 instances.'
}
],
adviseBeforeFixFunction:
'Ensure enabling detailed monitoring aligns with organizational policies and cost considerations.',
});
'Ensure enabling detailed monitoring aligns with organizational policies and cost considerations.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
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.State?.Name === 'terminated')
continue
if (instance.State?.Name === 'terminated') continue
if (instance.Monitoring?.State === 'enabled') {
compliantResources.push(instance.InstanceId!);
compliantResources.push(instance.InstanceId!)
} else {
nonCompliantResources.push(instance.InstanceId!);
nonCompliantResources.push(instance.InstanceId!)
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
if (nonCompliantResources.length > 0) {
await this.client.send(
new MonitorInstancesCommand({
InstanceIds: nonCompliantResources,
InstanceIds: nonCompliantResources
})
);
)
}
};
}
}

View File

@ -1,26 +1,21 @@
import {
EC2Client,
DescribeInstancesCommand,
} from '@aws-sdk/client-ec2';
import {
SSMClient,
DescribeInstanceInformationCommand,
} from '@aws-sdk/client-ssm';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { EC2Client, DescribeInstancesCommand } from '@aws-sdk/client-ec2'
import { SSMClient, DescribeInstanceInformationCommand } from '@aws-sdk/client-ssm'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
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);
private readonly client = new EC2Client({})
private readonly ssmClient = new SSMClient({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly getMetadata = () => ({
name: 'EC2InstanceManagedBySystemsManager',
description: 'Ensures that EC2 instances are managed by AWS Systems Manager.',
priority: 2,
priorityReason: 'Management through Systems Manager ensures efficient and secure configuration and operation of EC2 instances.',
priorityReason:
'Management through Systems Manager ensures efficient and secure configuration and operation of EC2 instances.',
awsService: 'EC2',
awsServiceCategory: 'Compute',
awsServiceCategory: 'Compute',
bestPracticeCategory: 'Management and Governance',
requiredParametersForFix: [],
isFixFunctionUsesDestructiveCommand: false,
@ -28,80 +23,75 @@ export class EC2InstanceManagedBySystemsManager implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeInstancesCommand',
reason: 'Retrieve the list of all EC2 instances.',
reason: 'Retrieve the list of all EC2 instances.'
},
{
name: 'DescribeInstanceInformationCommand',
reason: 'Retrieve information about instances managed by Systems Manager.',
},
reason: 'Retrieve information about instances managed by Systems Manager.'
}
],
adviseBeforeFixFunction:
'Ensure Systems Manager Agent (SSM Agent) is installed and configured properly on non-compliant instances.',
});
'Ensure Systems Manager Agent (SSM Agent) is installed and configured properly on non-compliant instances.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
const ssmResponse = await this.ssmClient.send(
new DescribeInstanceInformationCommand({})
);
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
const ssmResponse = await this.ssmClient.send(new DescribeInstanceInformationCommand({}))
const managedInstanceIds = ssmResponse.InstanceInformationList?.map(
(info) => info.InstanceId
);
const managedInstanceIds = ssmResponse.InstanceInformationList?.map((info) => info.InstanceId)
for (const reservation of response.Reservations || []) {
for (const instance of reservation.Instances || []) {
if (instance.State?.Name === 'terminated')
continue
if (instance.State?.Name === 'terminated') continue
if (managedInstanceIds?.includes(instance.InstanceId!)) {
compliantResources.push(instance.InstanceId!);
compliantResources.push(instance.InstanceId!)
} else {
nonCompliantResources.push(instance.InstanceId!);
nonCompliantResources.push(instance.InstanceId!)
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async () => {
throw new Error(
'Fix logic for EC2InstanceManagedBySystemsManager is not directly applicable. Systems Manager Agent setup requires manual intervention.'
);
};
)
}
}

View File

@ -1,14 +1,10 @@
import {
EC2Client,
DescribeInstancesCommand,
AssociateIamInstanceProfileCommand,
} from '@aws-sdk/client-ec2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { EC2Client, DescribeInstancesCommand, AssociateIamInstanceProfileCommand } from '@aws-sdk/client-ec2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EC2InstanceProfileAttached implements BPSet {
private readonly client = new EC2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly getMetadata = () => ({
name: 'EC2InstanceProfileAttached',
@ -23,108 +19,105 @@ export class EC2InstanceProfileAttached implements BPSet {
name: 'iam-instance-profile',
description: 'The name of the IAM instance profile to attach.',
default: '',
example: 'EC2InstanceProfile',
},
example: 'EC2InstanceProfile'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'DescribeInstancesCommand',
reason: 'Retrieve all EC2 instances and their associated IAM instance profiles.',
},
reason: 'Retrieve all EC2 instances and their associated IAM instance profiles.'
}
],
commandUsedInFixFunction: [
{
name: 'AssociateIamInstanceProfileCommand',
reason: 'Attach an IAM instance profile to non-compliant EC2 instances.',
},
reason: 'Attach an IAM instance profile to non-compliant EC2 instances.'
}
],
adviseBeforeFixFunction:
'Ensure the specified IAM instance profile exists and aligns with your access control policies.',
});
'Ensure the specified IAM instance profile exists and aligns with your access control policies.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
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.State?.Name === 'terminated')
continue
if (instance.State?.Name === 'terminated') continue
if (instance.IamInstanceProfile) {
compliantResources.push(instance.InstanceId!);
compliantResources.push(instance.InstanceId!)
} else {
nonCompliantResources.push(instance.InstanceId!);
nonCompliantResources.push(instance.InstanceId!)
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const iamInstanceProfile = requiredParametersForFix.find(
(param) => param.name === 'iam-instance-profile'
)?.value;
const iamInstanceProfile = requiredParametersForFix.find((param) => param.name === 'iam-instance-profile')?.value
if (!iamInstanceProfile) {
throw new Error("Required parameter 'iam-instance-profile' is missing.");
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 },
IamInstanceProfile: { Name: iamInstanceProfile }
})
);
)
}
};
}
}

View File

@ -1,13 +1,10 @@
import {
EC2Client,
DescribeInstancesCommand,
} from '@aws-sdk/client-ec2';
import { BPSet, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { EC2Client, DescribeInstancesCommand } from '@aws-sdk/client-ec2'
import { BPSet, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EC2NoAmazonKeyPair implements BPSet {
private readonly client = new EC2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly getMetadata = () => ({
name: 'EC2NoAmazonKeyPair',
@ -22,70 +19,69 @@ export class EC2NoAmazonKeyPair implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeInstancesCommand',
reason: 'Retrieve all EC2 instances and verify if an Amazon Key Pair is used.',
},
reason: 'Retrieve all EC2 instances and verify if an Amazon Key Pair is used.'
}
],
commandUsedInFixFunction: [],
adviseBeforeFixFunction:
'Ensure instances are launched without a Key Pair or configure SSH access using alternative mechanisms like Systems Manager Session Manager.',
});
'Ensure instances are launched without a Key Pair or configure SSH access using alternative mechanisms like Systems Manager Session Manager.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
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.State?.Name === 'terminated')
continue
if (instance.State?.Name === 'terminated') continue
if (instance.KeyName) {
nonCompliantResources.push(instance.InstanceId!);
nonCompliantResources.push(instance.InstanceId!)
} else {
compliantResources.push(instance.InstanceId!);
compliantResources.push(instance.InstanceId!)
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async () => {
throw new Error(
'Fix logic for EC2NoAmazonKeyPair is not applicable. Key pairs must be removed manually or during instance creation.'
);
};
)
}
}

View File

@ -1,21 +1,16 @@
import {
EC2Client,
DescribeInstancesCommand,
TerminateInstancesCommand,
} from '@aws-sdk/client-ec2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { EC2Client, DescribeInstancesCommand, TerminateInstancesCommand } from '@aws-sdk/client-ec2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EC2StoppedInstance implements BPSet {
private readonly client = new EC2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly getMetadata = () => ({
name: 'EC2StoppedInstance',
description: 'Ensures that stopped EC2 instances are identified and terminated if necessary.',
priority: 3,
priorityReason:
'Stopped instances can incur costs for storage and IP addresses without being in use.',
priorityReason: 'Stopped instances can incur costs for storage and IP addresses without being in use.',
awsService: 'EC2',
awsServiceCategory: 'Compute',
bestPracticeCategory: 'Cost Optimization',
@ -24,91 +19,91 @@ export class EC2StoppedInstance implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeInstancesCommand',
reason: 'Retrieve EC2 instances to check their state.',
},
reason: 'Retrieve EC2 instances to check their state.'
}
],
commandUsedInFixFunction: [
{
name: 'TerminateInstancesCommand',
reason: 'Terminate stopped EC2 instances to prevent unnecessary costs.',
},
reason: 'Terminate stopped EC2 instances to prevent unnecessary costs.'
}
],
adviseBeforeFixFunction:
'Ensure terminated instances do not contain any critical data or configurations before proceeding.',
});
'Ensure terminated instances do not contain any critical data or configurations before proceeding.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
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.State?.Name === 'stopped') {
nonCompliantResources.push(instance.InstanceId!);
nonCompliantResources.push(instance.InstanceId!)
} else {
compliantResources.push(instance.InstanceId!);
compliantResources.push(instance.InstanceId!)
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
if (nonCompliantResources.length === 0) {
return; // No stopped instances to terminate
return // No stopped instances to terminate
}
await this.client.send(
new TerminateInstancesCommand({
InstanceIds: nonCompliantResources,
InstanceIds: nonCompliantResources
})
);
};
)
}
}

View File

@ -1,21 +1,16 @@
import {
EC2Client,
DescribeInstancesCommand,
ModifyInstanceMetadataOptionsCommand,
} from '@aws-sdk/client-ec2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { EC2Client, DescribeInstancesCommand, ModifyInstanceMetadataOptionsCommand } from '@aws-sdk/client-ec2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EC2TokenHopLimitCheck implements BPSet {
private readonly client = new EC2Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly getMetadata = () => ({
name: 'EC2TokenHopLimitCheck',
description: 'Ensures that EC2 instances have a Metadata Options HttpPutResponseHopLimit of 1.',
priority: 3,
priorityReason:
'Setting the HttpPutResponseHopLimit to 1 ensures secure access to the instance metadata.',
priorityReason: 'Setting the HttpPutResponseHopLimit to 1 ensures secure access to the instance metadata.',
awsService: 'EC2',
awsServiceCategory: 'Compute',
bestPracticeCategory: 'Security',
@ -24,96 +19,91 @@ export class EC2TokenHopLimitCheck implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeInstancesCommand',
reason: 'Retrieve EC2 instances and check the metadata options for HttpPutResponseHopLimit.',
},
reason: 'Retrieve EC2 instances and check the metadata options for HttpPutResponseHopLimit.'
}
],
commandUsedInFixFunction: [
{
name: 'ModifyInstanceMetadataOptionsCommand',
reason: 'Update the HttpPutResponseHopLimit to enforce secure metadata access.',
},
reason: 'Update the HttpPutResponseHopLimit to enforce secure metadata access.'
}
],
adviseBeforeFixFunction:
'Ensure modifying instance metadata options aligns with your operational policies.',
});
adviseBeforeFixFunction: 'Ensure modifying instance metadata options aligns with your operational policies.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
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.State?.Name === 'terminated')
continue
if (instance.State?.Name === 'terminated') continue
if (
instance.MetadataOptions?.HttpPutResponseHopLimit &&
instance.MetadataOptions.HttpPutResponseHopLimit < 2
) {
compliantResources.push(instance.InstanceId!);
if (instance.MetadataOptions?.HttpPutResponseHopLimit && instance.MetadataOptions.HttpPutResponseHopLimit < 2) {
compliantResources.push(instance.InstanceId!)
} else {
nonCompliantResources.push(instance.InstanceId!);
nonCompliantResources.push(instance.InstanceId!)
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const instanceId of nonCompliantResources) {
await this.client.send(
new ModifyInstanceMetadataOptionsCommand({
InstanceId: instanceId,
HttpPutResponseHopLimit: 1,
HttpPutResponseHopLimit: 1
})
);
)
}
};
}
}

View File

@ -5,19 +5,19 @@ import {
ListImagesCommand,
BatchGetImageCommand,
PutImageCommand,
DeleteRepositoryCommand,
} from '@aws-sdk/client-ecr';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
DeleteRepositoryCommand
} from '@aws-sdk/client-ecr'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ECRKmsEncryption1 implements BPSet {
private readonly client = new ECRClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}))
return response.repositories || []
}
public readonly getMetadata = () => ({
name: 'ECRKmsEncryption1',
@ -32,135 +32,131 @@ export class ECRKmsEncryption1 implements BPSet {
name: 'kms-key-id',
description: 'The ID of the KMS key used to encrypt the ECR repository.',
default: '',
example: 'arn:aws:kms:us-east-1:123456789012:key/abcd1234-5678-90ef-ghij-klmnopqrstuv',
},
example: 'arn:aws:kms:us-east-1:123456789012:key/abcd1234-5678-90ef-ghij-klmnopqrstuv'
}
],
isFixFunctionUsesDestructiveCommand: true,
commandUsedInCheckFunction: [
{
name: 'DescribeRepositoriesCommand',
reason: 'Retrieve the list of ECR repositories to verify encryption settings.',
},
reason: 'Retrieve the list of ECR repositories to verify encryption settings.'
}
],
commandUsedInFixFunction: [
{
name: 'CreateRepositoryCommand',
reason: 'Create a new repository with KMS encryption.',
reason: 'Create a new repository with KMS encryption.'
},
{
name: 'ListImagesCommand',
reason: 'List all images in the existing repository for migration.',
reason: 'List all images in the existing repository for migration.'
},
{
name: 'BatchGetImageCommand',
reason: 'Retrieve image manifests for migration to the new repository.',
reason: 'Retrieve image manifests for migration to the new repository.'
},
{
name: 'PutImageCommand',
reason: 'Push images to the newly created repository.',
reason: 'Push images to the newly created repository.'
},
{
name: 'DeleteRepositoryCommand',
reason: 'Delete the old repository after migration.',
},
reason: 'Delete the old repository after migration.'
}
],
adviseBeforeFixFunction:
'Ensure the specified KMS key is accessible, and deleting the old repository aligns with operational policies.',
});
'Ensure the specified KMS key is accessible, and deleting the old repository aligns with operational policies.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const repositories = await this.getRepositories();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const repositories = await this.getRepositories()
for (const repository of repositories) {
if (repository.encryptionConfiguration?.encryptionType === 'KMS') {
compliantResources.push(repository.repositoryArn!);
compliantResources.push(repository.repositoryArn!)
} else {
nonCompliantResources.push(repository.repositoryArn!);
nonCompliantResources.push(repository.repositoryArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const kmsKeyId = requiredParametersForFix.find((param) => param.name === 'kms-key-id')?.value;
const kmsKeyId = requiredParametersForFix.find((param) => param.name === 'kms-key-id')?.value
if (!kmsKeyId) {
throw new Error("Required parameter 'kms-key-id' is missing.");
throw new Error("Required parameter 'kms-key-id' is missing.")
}
for (const arn of nonCompliantResources) {
const repositoryName = arn.split('/').pop()!;
const repositoryName = arn.split('/').pop()!
// Create a new repository with KMS encryption
const newRepositoryName = `${repositoryName}-kms`;
const newRepositoryName = `${repositoryName}-kms`
await this.client.send(
new CreateRepositoryCommand({
repositoryName: newRepositoryName,
encryptionConfiguration: {
encryptionType: 'KMS',
kmsKey: kmsKeyId,
},
kmsKey: kmsKeyId
}
})
);
)
// Get all images in the existing repository
const listImagesResponse = await this.client.send(
new ListImagesCommand({ repositoryName })
);
const imageIds = listImagesResponse.imageIds || [];
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 })
);
const batchGetImageResponse = await this.client.send(new BatchGetImageCommand({ repositoryName, imageIds }))
// Push images to the new repository
for (const image of batchGetImageResponse.images || []) {
@ -168,9 +164,9 @@ export class ECRKmsEncryption1 implements BPSet {
new PutImageCommand({
repositoryName: newRepositoryName,
imageManifest: image.imageManifest,
imageTag: image.imageId?.imageTag,
imageTag: image.imageId?.imageTag
})
);
)
}
}
@ -178,9 +174,9 @@ export class ECRKmsEncryption1 implements BPSet {
await this.client.send(
new DeleteRepositoryCommand({
repositoryName,
force: true,
force: true
})
);
)
}
};
}
}

View File

@ -1,26 +1,21 @@
import {
ECRClient,
DescribeRepositoriesCommand,
PutImageScanningConfigurationCommand,
} from '@aws-sdk/client-ecr';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { ECRClient, DescribeRepositoriesCommand, PutImageScanningConfigurationCommand } from '@aws-sdk/client-ecr'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ECRPrivateImageScanningEnabled implements BPSet {
private readonly client = new ECRClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}))
return response.repositories || []
}
public readonly getMetadata = () => ({
name: 'ECRPrivateImageScanningEnabled',
description: 'Ensures that image scanning on push is enabled for private ECR repositories.',
priority: 2,
priorityReason:
'Enabling image scanning on push helps identify vulnerabilities in container images during upload.',
priorityReason: 'Enabling image scanning on push helps identify vulnerabilities in container images during upload.',
awsService: 'ECR',
awsServiceCategory: 'Container',
bestPracticeCategory: 'Security',
@ -29,90 +24,90 @@ export class ECRPrivateImageScanningEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeRepositoriesCommand',
reason: 'Retrieve all ECR repositories and check their image scanning configuration.',
},
reason: 'Retrieve all ECR repositories and check their image scanning configuration.'
}
],
commandUsedInFixFunction: [
{
name: 'PutImageScanningConfigurationCommand',
reason: 'Enable image scanning on push for non-compliant repositories.',
},
reason: 'Enable image scanning on push for non-compliant repositories.'
}
],
adviseBeforeFixFunction:
'Ensure enabling image scanning aligns with organizational policies and does not affect performance.',
});
'Ensure enabling image scanning aligns with organizational policies and does not affect performance.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const repositories = await this.getRepositories();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const repositories = await this.getRepositories()
for (const repository of repositories) {
if (repository.imageScanningConfiguration?.scanOnPush) {
compliantResources.push(repository.repositoryArn!);
compliantResources.push(repository.repositoryArn!)
} else {
nonCompliantResources.push(repository.repositoryArn!);
nonCompliantResources.push(repository.repositoryArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const repositoryName = arn.split('/').pop()!;
const repositoryName = arn.split('/').pop()!
await this.client.send(
new PutImageScanningConfigurationCommand({
repositoryName,
imageScanningConfiguration: { scanOnPush: true },
imageScanningConfiguration: { scanOnPush: true }
})
);
)
}
};
}
}

View File

@ -2,19 +2,19 @@ import {
ECRClient,
DescribeRepositoriesCommand,
PutLifecyclePolicyCommand,
GetLifecyclePolicyCommand,
} from '@aws-sdk/client-ecr';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
GetLifecyclePolicyCommand
} from '@aws-sdk/client-ecr'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ECRPrivateLifecyclePolicyConfigured implements BPSet {
private readonly client = new ECRClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}))
return response.repositories || []
}
public readonly getMetadata = () => ({
name: 'ECRPrivateLifecyclePolicyConfigured',
@ -30,119 +30,118 @@ export class ECRPrivateLifecyclePolicyConfigured implements BPSet {
name: 'lifecycle-policy',
description: 'The JSON-formatted lifecycle policy text to apply to the repositories.',
default: '',
example: '{"rules":[{"rulePriority":1,"description":"Expire untagged images older than 30 days","selection":{"tagStatus":"untagged","countType":"sinceImagePushed","countNumber":30,"countUnit":"days"},"action":{"type":"expire"}}]}',
},
example:
'{"rules":[{"rulePriority":1,"description":"Expire untagged images older than 30 days","selection":{"tagStatus":"untagged","countType":"sinceImagePushed","countNumber":30,"countUnit":"days"},"action":{"type":"expire"}}]}'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'DescribeRepositoriesCommand',
reason: 'Retrieve all ECR repositories to check their lifecycle policy status.',
reason: 'Retrieve all ECR repositories to check their lifecycle policy status.'
},
{
name: 'GetLifecyclePolicyCommand',
reason: 'Verify if a lifecycle policy exists for each repository.',
},
reason: 'Verify if a lifecycle policy exists for each repository.'
}
],
commandUsedInFixFunction: [
{
name: 'PutLifecyclePolicyCommand',
reason: 'Apply a lifecycle policy to non-compliant ECR repositories.',
},
reason: 'Apply a lifecycle policy to non-compliant ECR repositories.'
}
],
adviseBeforeFixFunction:
'Ensure the provided lifecycle policy is well-tested and does not inadvertently remove critical images.',
});
'Ensure the provided lifecycle policy is well-tested and does not inadvertently remove critical images.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const repositories = await this.getRepositories();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const repositories = await this.getRepositories()
for (const repository of repositories) {
try {
await this.client.send(
new GetLifecyclePolicyCommand({
registryId: repository.registryId,
repositoryName: repository.repositoryName,
repositoryName: repository.repositoryName
})
);
compliantResources.push(repository.repositoryArn!);
} catch (error: any) {
)
compliantResources.push(repository.repositoryArn!)
} catch (error: unknown) {
if (error.name === 'LifecyclePolicyNotFoundException') {
nonCompliantResources.push(repository.repositoryArn!);
nonCompliantResources.push(repository.repositoryArn!)
} else {
throw error;
throw error
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const lifecyclePolicy = requiredParametersForFix.find(
(param) => param.name === 'lifecycle-policy'
)?.value;
const lifecyclePolicy = requiredParametersForFix.find((param) => param.name === 'lifecycle-policy')?.value
if (!lifecyclePolicy) {
throw new Error("Required parameter 'lifecycle-policy' is missing.");
throw new Error("Required parameter 'lifecycle-policy' is missing.")
}
for (const arn of nonCompliantResources) {
const repositoryName = arn.split('/').pop()!;
const repositoryName = arn.split('/').pop()!
await this.client.send(
new PutLifecyclePolicyCommand({
repositoryName,
lifecyclePolicyText: lifecyclePolicy,
lifecyclePolicyText: lifecyclePolicy
})
);
)
}
};
}
}

View File

@ -1,19 +1,15 @@
import {
ECRClient,
DescribeRepositoriesCommand,
PutImageTagMutabilityCommand,
} from '@aws-sdk/client-ecr';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { ECRClient, DescribeRepositoriesCommand, PutImageTagMutabilityCommand } from '@aws-sdk/client-ecr'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ECRPrivateTagImmutabilityEnabled implements BPSet {
private readonly client = new ECRClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}))
return response.repositories || []
}
public readonly getMetadata = () => ({
name: 'ECRPrivateTagImmutabilityEnabled',
@ -29,91 +25,90 @@ export class ECRPrivateTagImmutabilityEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeRepositoriesCommand',
reason: 'Retrieve all ECR repositories to check their tag mutability setting.',
},
reason: 'Retrieve all ECR repositories to check their tag mutability setting.'
}
],
commandUsedInFixFunction: [
{
name: 'PutImageTagMutabilityCommand',
reason: 'Set image tag immutability for non-compliant repositories.',
},
reason: 'Set image tag immutability for non-compliant repositories.'
}
],
adviseBeforeFixFunction:
'Ensure that enabling tag immutability aligns with your development and deployment processes.',
});
'Ensure that enabling tag immutability aligns with your development and deployment processes.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const repositories = await this.getRepositories();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const repositories = await this.getRepositories()
for (const repository of repositories) {
if (repository.imageTagMutability === 'IMMUTABLE') {
compliantResources.push(repository.repositoryArn!);
compliantResources.push(repository.repositoryArn!)
} else {
nonCompliantResources.push(repository.repositoryArn!);
nonCompliantResources.push(repository.repositoryArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const repositoryName = arn.split('/').pop()!;
const repositoryName = arn.split('/').pop()!
await this.client.send(
new PutImageTagMutabilityCommand({
repositoryName,
imageTagMutability: 'IMMUTABLE',
imageTagMutability: 'IMMUTABLE'
})
);
)
}
};
}
}

View File

@ -2,35 +2,30 @@ import {
ECSClient,
ListTaskDefinitionsCommand,
DescribeTaskDefinitionCommand,
RegisterTaskDefinitionCommand,
} from '@aws-sdk/client-ecs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
RegisterTaskDefinitionCommand
} from '@aws-sdk/client-ecs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ECSAwsVpcNetworkingEnabled implements BPSet {
private readonly client = new ECSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
taskDefinitions.push(taskDefinition.taskDefinition!)
}
return taskDefinitions;
};
return taskDefinitions
}
public readonly getMetadata = () => ({
name: 'ECSAwsVpcNetworkingEnabled',
description: 'Ensures that ECS task definitions are configured to use the awsvpc network mode.',
priority: 3,
priorityReason:
'Using the awsvpc network mode provides enhanced security and networking capabilities.',
priorityReason: 'Using the awsvpc network mode provides enhanced security and networking capabilities.',
awsService: 'ECS',
awsServiceCategory: 'Container',
bestPracticeCategory: 'Security',
@ -39,90 +34,88 @@ export class ECSAwsVpcNetworkingEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListTaskDefinitionsCommand',
reason: 'Retrieve all active ECS task definitions.',
reason: 'Retrieve all active ECS task definitions.'
},
{
name: 'DescribeTaskDefinitionCommand',
reason: 'Describe details of each ECS task definition.',
},
reason: 'Describe details of each ECS task definition.'
}
],
commandUsedInFixFunction: [
{
name: 'RegisterTaskDefinitionCommand',
reason: 'Re-register ECS task definitions with awsvpc network mode.',
},
reason: 'Re-register ECS task definitions with awsvpc network mode.'
}
],
adviseBeforeFixFunction:
'Ensure that the awsvpc network mode is supported by your ECS setup and compatible with your workloads.',
});
'Ensure that the awsvpc network mode is supported by your ECS setup and compatible with your workloads.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const taskDefinitions = await this.getTaskDefinitions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const taskDefinitions = await this.getTaskDefinitions()
for (const taskDefinition of taskDefinitions) {
if (taskDefinition.networkMode === 'awsvpc') {
compliantResources.push(taskDefinition.taskDefinitionArn!);
compliantResources.push(taskDefinition.taskDefinitionArn!)
} else {
nonCompliantResources.push(taskDefinition.taskDefinitionArn!);
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const taskDefinition = await this.memoClient.send(
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
);
const family = taskDefinition.taskDefinition?.family;
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
const family = taskDefinition.taskDefinition?.family
await this.client.send(
new RegisterTaskDefinitionCommand({
@ -131,9 +124,9 @@ export class ECSAwsVpcNetworkingEnabled implements BPSet {
networkMode: 'awsvpc',
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
cpu: taskDefinition.taskDefinition?.cpu,
memory: taskDefinition.taskDefinition?.memory,
memory: taskDefinition.taskDefinition?.memory
})
);
)
}
};
}
}

View File

@ -1,28 +1,21 @@
import {
ECSClient,
DescribeClustersCommand,
UpdateClusterSettingsCommand,
} from '@aws-sdk/client-ecs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { ECSClient, DescribeClustersCommand, UpdateClusterSettingsCommand } from '@aws-sdk/client-ecs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ECSContainerInsightsEnabled implements BPSet {
private readonly client = new ECSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeClustersCommand({ include: ['SETTINGS'] }))
return response.clusters || []
}
public readonly getMetadata = () => ({
name: 'ECSContainerInsightsEnabled',
description: 'Ensures that ECS clusters have Container Insights enabled.',
priority: 3,
priorityReason:
'Enabling Container Insights provides enhanced monitoring and diagnostics for ECS clusters.',
priorityReason: 'Enabling Container Insights provides enhanced monitoring and diagnostics for ECS clusters.',
awsService: 'ECS',
awsServiceCategory: 'Container',
bestPracticeCategory: 'Monitoring',
@ -31,94 +24,91 @@ export class ECSContainerInsightsEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeClustersCommand',
reason: 'Retrieve ECS clusters and their settings to check if Container Insights is enabled.',
},
reason: 'Retrieve ECS clusters and their settings to check if Container Insights is enabled.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateClusterSettingsCommand',
reason: 'Enable Container Insights for non-compliant ECS clusters.',
},
reason: 'Enable Container Insights for non-compliant ECS clusters.'
}
],
adviseBeforeFixFunction:
'Ensure enabling Container Insights aligns with your monitoring strategy and does not incur unexpected costs.',
});
'Ensure enabling Container Insights aligns with your monitoring strategy and does not incur unexpected costs.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const clusters = await this.getClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const clusters = await this.getClusters()
for (const cluster of clusters) {
if (cluster.clusterName === 'default')
continue
if (cluster.clusterName === 'default') continue
const containerInsightsSetting = cluster.settings?.find(
(setting) => setting.name === 'containerInsights'
);
const containerInsightsSetting = cluster.settings?.find((setting) => setting.name === 'containerInsights')
if (containerInsightsSetting?.value === 'enabled') {
compliantResources.push(cluster.clusterArn!);
compliantResources.push(cluster.clusterArn!)
} else {
nonCompliantResources.push(cluster.clusterArn!);
nonCompliantResources.push(cluster.clusterArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
await this.client.send(
new UpdateClusterSettingsCommand({
cluster: arn,
settings: [{ name: 'containerInsights', value: 'enabled' }],
settings: [{ name: 'containerInsights', value: 'enabled' }]
})
);
)
}
};
}
}

View File

@ -2,35 +2,30 @@ import {
ECSClient,
DescribeTaskDefinitionCommand,
RegisterTaskDefinitionCommand,
ListTaskDefinitionsCommand,
} from '@aws-sdk/client-ecs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
ListTaskDefinitionsCommand
} from '@aws-sdk/client-ecs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ECSContainersNonPrivileged implements BPSet {
private readonly client = new ECSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
taskDefinitions.push(taskDefinition.taskDefinition!)
}
return taskDefinitions;
};
return taskDefinitions
}
public readonly getMetadata = () => ({
name: 'ECSContainersNonPrivileged',
description: 'Ensures that containers in ECS task definitions are not running in privileged mode.',
priority: 1,
priorityReason:
'Running containers in privileged mode poses significant security risks.',
priorityReason: 'Running containers in privileged mode poses significant security risks.',
awsService: 'ECS',
awsServiceCategory: 'Container',
bestPracticeCategory: 'Security',
@ -39,100 +34,93 @@ export class ECSContainersNonPrivileged implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListTaskDefinitionsCommand',
reason: 'Retrieve all active ECS task definitions.',
reason: 'Retrieve all active ECS task definitions.'
},
{
name: 'DescribeTaskDefinitionCommand',
reason: 'Check the container configurations in ECS task definitions.',
},
reason: 'Check the container configurations in ECS task definitions.'
}
],
commandUsedInFixFunction: [
{
name: 'RegisterTaskDefinitionCommand',
reason: 'Re-register ECS task definitions with privileged mode disabled.',
},
reason: 'Re-register ECS task definitions with privileged mode disabled.'
}
],
adviseBeforeFixFunction:
'Ensure that containers do not rely on privileged mode for their functionality.',
});
adviseBeforeFixFunction: 'Ensure that containers do not rely on privileged mode for their functionality.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const taskDefinitions = await this.getTaskDefinitions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const taskDefinitions = await this.getTaskDefinitions()
for (const taskDefinition of taskDefinitions) {
const privilegedContainers = taskDefinition.containerDefinitions?.filter(
(container) => container.privileged
);
const privilegedContainers = taskDefinition.containerDefinitions?.filter((container) => container.privileged)
if (privilegedContainers?.length) {
nonCompliantResources.push(taskDefinition.taskDefinitionArn!);
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
} else {
compliantResources.push(taskDefinition.taskDefinitionArn!);
compliantResources.push(taskDefinition.taskDefinitionArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const taskDefinition = await this.memoClient.send(
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
);
const family = taskDefinition.taskDefinition?.family;
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,
})
);
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map((container) => ({
...container,
privileged: false
}))
await this.client.send(
new RegisterTaskDefinitionCommand({
@ -141,9 +129,9 @@ export class ECSContainersNonPrivileged implements BPSet {
networkMode: taskDefinition.taskDefinition?.networkMode,
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
cpu: taskDefinition.taskDefinition?.cpu,
memory: taskDefinition.taskDefinition?.memory,
memory: taskDefinition.taskDefinition?.memory
})
);
)
}
};
}
}

View File

@ -2,28 +2,24 @@ import {
ECSClient,
DescribeTaskDefinitionCommand,
RegisterTaskDefinitionCommand,
ListTaskDefinitionsCommand,
} from '@aws-sdk/client-ecs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
ListTaskDefinitionsCommand
} from '@aws-sdk/client-ecs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ECSContainersReadonlyAccess implements BPSet {
private readonly client = new ECSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
taskDefinitions.push(taskDefinition.taskDefinition!)
}
return taskDefinitions;
};
return taskDefinitions
}
public readonly getMetadata = () => ({
name: 'ECSContainersReadonlyAccess',
@ -39,100 +35,96 @@ export class ECSContainersReadonlyAccess implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListTaskDefinitionsCommand',
reason: 'Retrieve all active ECS task definitions.',
reason: 'Retrieve all active ECS task definitions.'
},
{
name: 'DescribeTaskDefinitionCommand',
reason: 'Check the container configurations in ECS task definitions.',
},
reason: 'Check the container configurations in ECS task definitions.'
}
],
commandUsedInFixFunction: [
{
name: 'RegisterTaskDefinitionCommand',
reason: 'Re-register ECS task definitions with readonly root filesystems enabled.',
},
reason: 'Re-register ECS task definitions with readonly root filesystems enabled.'
}
],
adviseBeforeFixFunction:
'Ensure enabling readonly root filesystems does not interfere with the functionality of your containers.',
});
'Ensure enabling readonly root filesystems does not interfere with the functionality of your containers.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const taskDefinitions = await this.getTaskDefinitions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
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!);
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
} else {
compliantResources.push(taskDefinition.taskDefinitionArn!);
compliantResources.push(taskDefinition.taskDefinitionArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const taskDefinition = await this.memoClient.send(
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
);
const family = taskDefinition.taskDefinition?.family;
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,
})
);
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map((container) => ({
...container,
readonlyRootFilesystem: true
}))
await this.client.send(
new RegisterTaskDefinitionCommand({
@ -141,9 +133,9 @@ export class ECSContainersReadonlyAccess implements BPSet {
networkMode: taskDefinition.taskDefinition?.networkMode,
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
cpu: taskDefinition.taskDefinition?.cpu,
memory: taskDefinition.taskDefinition?.memory,
memory: taskDefinition.taskDefinition?.memory
})
);
)
}
};
}
}

View File

@ -3,31 +3,29 @@ import {
ListClustersCommand,
ListServicesCommand,
DescribeServicesCommand,
UpdateServiceCommand,
} from '@aws-sdk/client-ecs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
UpdateServiceCommand
} from '@aws-sdk/client-ecs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ECSFargateLatestPlatformVersion implements BPSet {
private readonly client = new ECSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 }[] = [];
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 })
);
const servicesResponse = await this.memoClient.send(new ListServicesCommand({ cluster: clusterArn }))
for (const serviceArn of servicesResponse.serviceArns || []) {
services.push({ clusterArn, serviceArn });
services.push({ clusterArn, serviceArn })
}
}
return services;
};
return services
}
public readonly getMetadata = () => ({
name: 'ECSFargateLatestPlatformVersion',
@ -43,104 +41,106 @@ export class ECSFargateLatestPlatformVersion implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListClustersCommand',
reason: 'Retrieve ECS clusters to identify associated services.',
reason: 'Retrieve ECS clusters to identify associated services.'
},
{
name: 'ListServicesCommand',
reason: 'Retrieve services associated with each ECS cluster.',
reason: 'Retrieve services associated with each ECS cluster.'
},
{
name: 'DescribeServicesCommand',
reason: 'Check the platform version of each ECS service.',
},
reason: 'Check the platform version of each ECS service.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateServiceCommand',
reason: 'Update ECS services to use the latest platform version.',
},
reason: 'Update ECS services to use the latest platform version.'
}
],
adviseBeforeFixFunction:
'Ensure that updating to the latest platform version aligns with your workload requirements.',
});
'Ensure that updating to the latest platform version aligns with your workload requirements.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const services = await this.getServices();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
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];
const service = serviceResponse.services?.[0]
if (service?.platformVersion === 'LATEST') {
compliantResources.push(service.serviceArn!);
compliantResources.push(service.serviceArn!)
} else {
nonCompliantResources.push(service?.serviceArn!);
if (service?.serviceArn) {
nonCompliantResources.push(service.serviceArn)
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const serviceArn of nonCompliantResources) {
const clusterArn = serviceArn.split(':cluster/')[1].split(':service/')[0];
const clusterArn = serviceArn.split(':cluster/')[1].split(':service/')[0]
await this.client.send(
new UpdateServiceCommand({
cluster: clusterArn,
service: serviceArn,
platformVersion: 'LATEST',
platformVersion: 'LATEST'
})
);
)
}
};
}
}

View File

@ -2,35 +2,30 @@ import {
ECSClient,
ListTaskDefinitionsCommand,
DescribeTaskDefinitionCommand,
RegisterTaskDefinitionCommand,
} from '@aws-sdk/client-ecs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
RegisterTaskDefinitionCommand
} from '@aws-sdk/client-ecs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ECSTaskDefinitionLogConfiguration implements BPSet {
private readonly client = new ECSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
taskDefinitions.push(taskDefinition.taskDefinition!)
}
return taskDefinitions;
};
return taskDefinitions
}
public readonly getMetadata = () => ({
name: 'ECSTaskDefinitionLogConfiguration',
description: 'Ensures that ECS task definitions have log configuration enabled.',
priority: 1,
priorityReason:
'Enabling log configuration is critical for monitoring and debugging container workloads.',
priorityReason: 'Enabling log configuration is critical for monitoring and debugging container workloads.',
awsService: 'ECS',
awsServiceCategory: 'Container',
bestPracticeCategory: 'Monitoring',
@ -39,118 +34,109 @@ export class ECSTaskDefinitionLogConfiguration implements BPSet {
name: 'log-configuration',
description: 'The log configuration to apply to non-compliant task definitions.',
default: '{}',
example: '{"logDriver": "awslogs", "options": {"awslogs-group": "/ecs/logs"}}',
},
example: '{"logDriver": "awslogs", "options": {"awslogs-group": "/ecs/logs"}}'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'ListTaskDefinitionsCommand',
reason: 'Retrieve all active ECS task definitions.',
reason: 'Retrieve all active ECS task definitions.'
},
{
name: 'DescribeTaskDefinitionCommand',
reason: 'Check the container configurations in ECS task definitions.',
},
reason: 'Check the container configurations in ECS task definitions.'
}
],
commandUsedInFixFunction: [
{
name: 'RegisterTaskDefinitionCommand',
reason: 'Re-register ECS task definitions with updated log configuration.',
},
reason: 'Re-register ECS task definitions with updated log configuration.'
}
],
adviseBeforeFixFunction:
'Ensure the provided log configuration aligns with your logging strategy and infrastructure.',
});
'Ensure the provided log configuration aligns with your logging strategy and infrastructure.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const taskDefinitions = await this.getTaskDefinitions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
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!);
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
} else {
compliantResources.push(taskDefinition.taskDefinitionArn!);
compliantResources.push(taskDefinition.taskDefinitionArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (
nonCompliantResources,
requiredParametersForFix
) => {
const logConfiguration = requiredParametersForFix.find(
(param) => param.name === 'log-configuration'
)?.value;
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const logConfiguration = requiredParametersForFix.find((param) => param.name === 'log-configuration')?.value
if (!logConfiguration) {
throw new Error("Required parameter 'log-configuration' is missing.");
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 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),
})
);
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map((container) => ({
...container,
logConfiguration: JSON.parse(logConfiguration)
}))
await this.client.send(
new RegisterTaskDefinitionCommand({
@ -159,9 +145,9 @@ export class ECSTaskDefinitionLogConfiguration implements BPSet {
networkMode: taskDefinition.taskDefinition?.networkMode,
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
cpu: taskDefinition.taskDefinition?.cpu,
memory: taskDefinition.taskDefinition?.memory,
memory: taskDefinition.taskDefinition?.memory
})
);
)
}
};
}
}

View File

@ -2,28 +2,24 @@ import {
ECSClient,
ListTaskDefinitionsCommand,
DescribeTaskDefinitionCommand,
RegisterTaskDefinitionCommand,
} from '@aws-sdk/client-ecs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
RegisterTaskDefinitionCommand
} from '@aws-sdk/client-ecs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ECSTaskDefinitionMemoryHardLimit implements BPSet {
private readonly client = new ECSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
taskDefinitions.push(taskDefinition.taskDefinition!)
}
return taskDefinitions;
};
return taskDefinitions
}
public readonly getMetadata = () => ({
name: 'ECSTaskDefinitionMemoryHardLimit',
@ -39,115 +35,105 @@ export class ECSTaskDefinitionMemoryHardLimit implements BPSet {
name: 'default-memory-limit',
description: 'The default memory hard limit to set for containers without a memory limit.',
default: '512',
example: '1024',
},
example: '1024'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'ListTaskDefinitionsCommand',
reason: 'Retrieve all active ECS task definitions.',
reason: 'Retrieve all active ECS task definitions.'
},
{
name: 'DescribeTaskDefinitionCommand',
reason: 'Check container configurations in ECS task definitions for memory limits.',
},
reason: 'Check container configurations in ECS task definitions for memory limits.'
}
],
commandUsedInFixFunction: [
{
name: 'RegisterTaskDefinitionCommand',
reason: 'Re-register ECS task definitions with updated memory limits.',
},
reason: 'Re-register ECS task definitions with updated memory limits.'
}
],
adviseBeforeFixFunction:
'Ensure the default memory limit aligns with your container resource requirements.',
});
adviseBeforeFixFunction: 'Ensure the default memory limit aligns with your container resource requirements.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const taskDefinitions = await this.getTaskDefinitions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const taskDefinitions = await this.getTaskDefinitions()
for (const taskDefinition of taskDefinitions) {
const containersWithoutMemoryLimit = taskDefinition.containerDefinitions?.filter(
(container) => !container.memory
);
const containersWithoutMemoryLimit = taskDefinition.containerDefinitions?.filter((container) => !container.memory)
if (containersWithoutMemoryLimit?.length) {
nonCompliantResources.push(taskDefinition.taskDefinitionArn!);
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
} else {
compliantResources.push(taskDefinition.taskDefinitionArn!);
compliantResources.push(taskDefinition.taskDefinitionArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (
nonCompliantResources,
requiredParametersForFix
) => {
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const defaultMemoryLimit = parseInt(
requiredParametersForFix.find((param) => param.name === 'default-memory-limit')?.value || '512',
10
);
)
for (const arn of nonCompliantResources) {
const taskDefinition = await this.memoClient.send(
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
);
const family = taskDefinition.taskDefinition?.family;
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 || defaultMemoryLimit,
})
);
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map((container) => ({
...container,
memory: container.memory || defaultMemoryLimit
}))
await this.client.send(
new RegisterTaskDefinitionCommand({
@ -156,9 +142,9 @@ export class ECSTaskDefinitionMemoryHardLimit implements BPSet {
networkMode: taskDefinition.taskDefinition?.networkMode,
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
cpu: taskDefinition.taskDefinition?.cpu,
memory: taskDefinition.taskDefinition?.memory,
memory: taskDefinition.taskDefinition?.memory
})
);
)
}
};
}
}

View File

@ -2,28 +2,24 @@ import {
ECSClient,
ListTaskDefinitionsCommand,
DescribeTaskDefinitionCommand,
RegisterTaskDefinitionCommand,
} from '@aws-sdk/client-ecs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
RegisterTaskDefinitionCommand
} from '@aws-sdk/client-ecs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ECSTaskDefinitionNonRootUser implements BPSet {
private readonly client = new ECSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
taskDefinitions.push(taskDefinition.taskDefinition!)
}
return taskDefinitions;
};
return taskDefinitions
}
public readonly getMetadata = () => ({
name: 'ECSTaskDefinitionNonRootUser',
@ -39,114 +35,106 @@ export class ECSTaskDefinitionNonRootUser implements BPSet {
name: 'default-non-root-user',
description: 'The default non-root user to assign for containers without a specified user.',
default: 'ecs-user',
example: 'app-user',
},
example: 'app-user'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'ListTaskDefinitionsCommand',
reason: 'Retrieve all active ECS task definitions.',
reason: 'Retrieve all active ECS task definitions.'
},
{
name: 'DescribeTaskDefinitionCommand',
reason: 'Check container configurations in ECS task definitions for user settings.',
},
reason: 'Check container configurations in ECS task definitions for user settings.'
}
],
commandUsedInFixFunction: [
{
name: 'RegisterTaskDefinitionCommand',
reason: 'Re-register ECS task definitions with non-root user configurations.',
},
reason: 'Re-register ECS task definitions with non-root user configurations.'
}
],
adviseBeforeFixFunction:
'Ensure the default non-root user has sufficient permissions to execute the container workload.',
});
'Ensure the default non-root user has sufficient permissions to execute the container workload.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const taskDefinitions = await this.getTaskDefinitions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
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!);
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
} else {
compliantResources.push(taskDefinition.taskDefinitionArn!);
compliantResources.push(taskDefinition.taskDefinitionArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (
nonCompliantResources,
requiredParametersForFix
) => {
const defaultNonRootUser = requiredParametersForFix.find(
(param) => param.name === 'default-non-root-user'
)?.value || 'ecs-user';
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const defaultNonRootUser =
requiredParametersForFix.find((param) => param.name === 'default-non-root-user')?.value || 'ecs-user'
for (const arn of nonCompliantResources) {
const taskDefinition = await this.memoClient.send(
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
);
const family = taskDefinition.taskDefinition?.family;
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 || defaultNonRootUser,
})
);
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map((container) => ({
...container,
user: container.user || defaultNonRootUser
}))
await this.client.send(
new RegisterTaskDefinitionCommand({
@ -155,9 +143,9 @@ export class ECSTaskDefinitionNonRootUser implements BPSet {
networkMode: taskDefinition.taskDefinition?.networkMode,
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
cpu: taskDefinition.taskDefinition?.cpu,
memory: taskDefinition.taskDefinition?.memory,
memory: taskDefinition.taskDefinition?.memory
})
);
)
}
};
}
}

View File

@ -2,19 +2,19 @@ import {
EFSClient,
DescribeAccessPointsCommand,
DeleteAccessPointCommand,
CreateAccessPointCommand,
} from '@aws-sdk/client-efs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
CreateAccessPointCommand
} from '@aws-sdk/client-efs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EFSAccessPointEnforceRootDirectory implements BPSet {
private readonly client = new EFSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeAccessPointsCommand({}))
return response.AccessPoints || []
}
public readonly getMetadata = () => ({
name: 'EFSAccessPointEnforceRootDirectory',
@ -30,121 +30,116 @@ export class EFSAccessPointEnforceRootDirectory implements BPSet {
name: 'root-directory-path',
description: 'The root directory path to enforce for EFS Access Points.',
default: '/',
example: '/data',
},
example: '/data'
}
],
isFixFunctionUsesDestructiveCommand: true,
commandUsedInCheckFunction: [
{
name: 'DescribeAccessPointsCommand',
reason: 'Retrieve all existing EFS Access Points and their configurations.',
},
reason: 'Retrieve all existing EFS Access Points and their configurations.'
}
],
commandUsedInFixFunction: [
{
name: 'DeleteAccessPointCommand',
reason: 'Delete non-compliant EFS Access Points.',
reason: 'Delete non-compliant EFS Access Points.'
},
{
name: 'CreateAccessPointCommand',
reason: 'Recreate EFS Access Points with the enforced root directory.',
},
reason: 'Recreate EFS Access Points with the enforced root directory.'
}
],
adviseBeforeFixFunction:
'Ensure no active workloads are using the access points before applying fixes as it involves deletion and recreation.',
});
'Ensure no active workloads are using the access points before applying fixes as it involves deletion and recreation.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const accessPoints = await this.getAccessPoints();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const accessPoints = await this.getAccessPoints()
for (const accessPoint of accessPoints) {
if (accessPoint.RootDirectory?.Path === '/') {
compliantResources.push(accessPoint.AccessPointArn!);
compliantResources.push(accessPoint.AccessPointArn!)
} else {
nonCompliantResources.push(accessPoint.AccessPointArn!);
nonCompliantResources.push(accessPoint.AccessPointArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (
nonCompliantResources,
requiredParametersForFix
) => {
const rootDirectoryPath = requiredParametersForFix.find(
(param) => param.name === 'root-directory-path'
)?.value;
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const rootDirectoryPath = requiredParametersForFix.find((param) => param.name === 'root-directory-path')?.value
if (!rootDirectoryPath) {
throw new Error("Required parameter 'root-directory-path' is missing.");
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];
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,
AccessPointId: accessPointId
})
);
)
// Recreate the access point with the desired root directory
await this.client.send(
new CreateAccessPointCommand({
FileSystemId: fileSystemId,
RootDirectory: { Path: rootDirectoryPath },
RootDirectory: { Path: rootDirectoryPath }
})
);
)
}
};
}
}

View File

@ -2,19 +2,19 @@ import {
EFSClient,
DescribeAccessPointsCommand,
DeleteAccessPointCommand,
CreateAccessPointCommand,
} from '@aws-sdk/client-efs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
CreateAccessPointCommand
} from '@aws-sdk/client-efs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EFSAccessPointEnforceUserIdentity implements BPSet {
private readonly client = new EFSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeAccessPointsCommand({}))
return response.AccessPoints || []
}
public readonly getMetadata = () => ({
name: 'EFSAccessPointEnforceUserIdentity',
@ -30,119 +30,116 @@ export class EFSAccessPointEnforceUserIdentity implements BPSet {
name: 'posix-user',
description: 'The PosixUser configuration to enforce for EFS Access Points.',
default: '{"Uid": "1000", "Gid": "1000"}',
example: '{"Uid": "1234", "Gid": "1234"}',
},
example: '{"Uid": "1234", "Gid": "1234"}'
}
],
isFixFunctionUsesDestructiveCommand: true,
commandUsedInCheckFunction: [
{
name: 'DescribeAccessPointsCommand',
reason: 'Retrieve all existing EFS Access Points and their configurations.',
},
reason: 'Retrieve all existing EFS Access Points and their configurations.'
}
],
commandUsedInFixFunction: [
{
name: 'DeleteAccessPointCommand',
reason: 'Delete non-compliant EFS Access Points.',
reason: 'Delete non-compliant EFS Access Points.'
},
{
name: 'CreateAccessPointCommand',
reason: 'Recreate EFS Access Points with the enforced PosixUser.',
},
reason: 'Recreate EFS Access Points with the enforced PosixUser.'
}
],
adviseBeforeFixFunction:
'Ensure no active workloads are using the access points before applying fixes as it involves deletion and recreation.',
});
'Ensure no active workloads are using the access points before applying fixes as it involves deletion and recreation.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const accessPoints = await this.getAccessPoints();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const accessPoints = await this.getAccessPoints()
for (const accessPoint of accessPoints) {
if (accessPoint.PosixUser) {
compliantResources.push(accessPoint.AccessPointArn!);
compliantResources.push(accessPoint.AccessPointArn!)
} else {
nonCompliantResources.push(accessPoint.AccessPointArn!);
nonCompliantResources.push(accessPoint.AccessPointArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (
nonCompliantResources,
requiredParametersForFix
) => {
const posixUser = requiredParametersForFix.find((param) => param.name === 'posix-user')?.value;
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const posixUser = requiredParametersForFix.find((param) => param.name === 'posix-user')?.value
if (!posixUser) {
throw new Error("Required parameter 'posix-user' is missing.");
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];
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,
AccessPointId: accessPointId
})
);
)
// Recreate the access point with the desired PosixUser
await this.client.send(
new CreateAccessPointCommand({
FileSystemId: fileSystemId,
PosixUser: JSON.parse(posixUser),
PosixUser: JSON.parse(posixUser)
})
);
)
}
};
}
}

View File

@ -2,19 +2,19 @@ import {
EFSClient,
DescribeFileSystemsCommand,
PutBackupPolicyCommand,
DescribeBackupPolicyCommand,
} from '@aws-sdk/client-efs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
DescribeBackupPolicyCommand
} from '@aws-sdk/client-efs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EFSAutomaticBackupsEnabled implements BPSet {
private readonly client = new EFSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeFileSystemsCommand({}))
return response.FileSystems || []
}
public readonly getMetadata = () => ({
name: 'EFSAutomaticBackupsEnabled',
@ -30,101 +30,97 @@ export class EFSAutomaticBackupsEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeFileSystemsCommand',
reason: 'Retrieve the list of EFS file systems.',
reason: 'Retrieve the list of EFS file systems.'
},
{
name: 'DescribeBackupPolicyCommand',
reason: 'Check if a backup policy is enabled for the file system.',
},
reason: 'Check if a backup policy is enabled for the file system.'
}
],
commandUsedInFixFunction: [
{
name: 'PutBackupPolicyCommand',
reason: 'Enable automatic backups for the file system.',
},
reason: 'Enable automatic backups for the file system.'
}
],
adviseBeforeFixFunction:
'Ensure that enabling backups aligns with the organizations cost and recovery objectives.',
});
adviseBeforeFixFunction: 'Ensure that enabling backups aligns with the organizations cost and recovery objectives.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const fileSystems = await this.getFileSystems();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
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!);
compliantResources.push(fileSystem.FileSystemArn!)
} else {
nonCompliantResources.push(fileSystem.FileSystemArn!);
nonCompliantResources.push(fileSystem.FileSystemArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (
nonCompliantResources,
requiredParametersForFix
) => {
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const fileSystemId = arn.split('/').pop()!;
const fileSystemId = arn.split('/').pop()!
await this.client.send(
new PutBackupPolicyCommand({
FileSystemId: fileSystemId,
BackupPolicy: { Status: 'ENABLED' },
BackupPolicy: { Status: 'ENABLED' }
})
);
)
}
};
}
}

View File

@ -2,19 +2,19 @@ import {
EFSClient,
DescribeFileSystemsCommand,
CreateFileSystemCommand,
DeleteFileSystemCommand,
} from '@aws-sdk/client-efs';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
DeleteFileSystemCommand
} from '@aws-sdk/client-efs'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EFSEncryptedCheck implements BPSet {
private readonly client = new EFSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeFileSystemsCommand({}))
return response.FileSystems || []
}
public readonly getMetadata = () => ({
name: 'EFSEncryptedCheck',
@ -30,100 +30,95 @@ export class EFSEncryptedCheck implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeFileSystemsCommand',
reason: 'Retrieve all existing EFS file systems and their encryption status.',
},
reason: 'Retrieve all existing EFS file systems and their encryption status.'
}
],
commandUsedInFixFunction: [
{
name: 'DeleteFileSystemCommand',
reason: 'Delete non-compliant EFS file systems.',
reason: 'Delete non-compliant EFS file systems.'
},
{
name: 'CreateFileSystemCommand',
reason: 'Recreate EFS file systems with encryption enabled.',
},
reason: 'Recreate EFS file systems with encryption enabled.'
}
],
adviseBeforeFixFunction:
'Ensure that backups are taken and data migration plans are in place, as the fix involves deletion and recreation of file systems.',
});
'Ensure that backups are taken and data migration plans are in place, as the fix involves deletion and recreation of file systems.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const fileSystems = await this.getFileSystems();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const fileSystems = await this.getFileSystems()
for (const fileSystem of fileSystems) {
if (fileSystem.Encrypted) {
compliantResources.push(fileSystem.FileSystemArn!);
compliantResources.push(fileSystem.FileSystemArn!)
} else {
nonCompliantResources.push(fileSystem.FileSystemArn!);
nonCompliantResources.push(fileSystem.FileSystemArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (...args) => {
await this.fixImpl(...args).then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
public readonly fixImpl: BPSetFixFn = async (
nonCompliantResources,
requiredParametersForFix
) => {
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const fileSystemId = arn.split('/').pop()!;
const fileSystem = await this.memoClient.send(
new DescribeFileSystemsCommand({ FileSystemId: fileSystemId })
);
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,
FileSystemId: fileSystemId
})
);
)
// Recreate the file system with encryption enabled
await this.client.send(
@ -131,9 +126,9 @@ export class EFSEncryptedCheck implements BPSet {
Encrypted: true,
PerformanceMode: fileSystem.FileSystems?.[0]?.PerformanceMode,
ThroughputMode: fileSystem.FileSystems?.[0]?.ThroughputMode,
ProvisionedThroughputInMibps: fileSystem.FileSystems?.[0]?.ProvisionedThroughputInMibps,
ProvisionedThroughputInMibps: fileSystem.FileSystems?.[0]?.ProvisionedThroughputInMibps
})
);
)
}
};
}
}

View File

@ -1,31 +1,27 @@
import {
EFSClient,
DescribeFileSystemsCommand,
DescribeMountTargetsCommand,
} from '@aws-sdk/client-efs';
import { EC2Client, DescribeRouteTablesCommand } from '@aws-sdk/client-ec2';
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { EFSClient, DescribeFileSystemsCommand, DescribeMountTargetsCommand } from '@aws-sdk/client-efs'
import { EC2Client, DescribeRouteTablesCommand } from '@aws-sdk/client-ec2'
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
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 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 || [];
};
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] }],
Filters: [{ Name: 'association.subnet-id', Values: [subnetId] }]
})
);
return response.RouteTables?.[0]?.Routes || [];
};
)
return response.RouteTables?.[0]?.Routes || []
}
public readonly getMetadata = () => ({
name: 'EFSMountTargetPublicAccessible',
@ -41,92 +37,85 @@ export class EFSMountTargetPublicAccessible implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeFileSystemsCommand',
reason: 'Retrieve the list of EFS file systems.',
reason: 'Retrieve the list of EFS file systems.'
},
{
name: 'DescribeMountTargetsCommand',
reason: 'Retrieve the list of mount targets for a file system.',
reason: 'Retrieve the list of mount targets for a file system.'
},
{
name: 'DescribeRouteTablesCommand',
reason: 'Check route tables associated with the mount target subnets.',
},
reason: 'Check route tables associated with the mount target subnets.'
}
],
commandUsedInFixFunction: [],
adviseBeforeFixFunction:
'Ensure that the network configurations are reviewed carefully to avoid breaking application connectivity.',
});
'Ensure that the network configurations are reviewed carefully to avoid breaking application connectivity.'
})
private readonly stats: BPSetStats = {
nonCompliantResources: [],
compliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const fileSystems = await this.getFileSystems();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const fileSystems = await this.getFileSystems()
for (const fileSystem of fileSystems) {
const mountTargets = await this.memoEFSClient.send(
new DescribeMountTargetsCommand({ FileSystemId: fileSystem.FileSystemId! })
);
)
let isNonCompliant = false;
let isNonCompliant = false
for (const mountTarget of mountTargets.MountTargets || []) {
const routes = await this.getRoutesForSubnet(mountTarget.SubnetId!);
const routes = await this.getRoutesForSubnet(mountTarget.SubnetId!)
if (
routes.some(
(route) =>
route.DestinationCidrBlock === '0.0.0.0/0' && route.GatewayId?.startsWith('igw-')
)
) {
nonCompliantResources.push(fileSystem.FileSystemArn!);
isNonCompliant = true;
break;
if (routes.some((route) => route.DestinationCidrBlock === '0.0.0.0/0' && route.GatewayId?.startsWith('igw-'))) {
nonCompliantResources.push(fileSystem.FileSystemArn!)
isNonCompliant = true
break
}
}
if (!isNonCompliant) {
compliantResources.push(fileSystem.FileSystemArn!);
compliantResources.push(fileSystem.FileSystemArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async () => {
throw new Error(
'Fixing public accessibility for mount targets requires manual network reconfiguration.'
);
};
throw new Error('Fixing public accessibility for mount targets requires manual network reconfiguration.')
}
}

View File

@ -1,35 +1,27 @@
import {
EKSClient,
ListClustersCommand,
DescribeClusterCommand,
UpdateClusterConfigCommand,
} from '@aws-sdk/client-eks';
import { BPSet, BPSetStats, BPSetFixFn } from '../../types';
import { Memorizer } from '../../Memorizer';
import { EKSClient, ListClustersCommand, DescribeClusterCommand, UpdateClusterConfigCommand } from '@aws-sdk/client-eks'
import { BPSet, BPSetStats, BPSetFixFn } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EKSClusterLoggingEnabled implements BPSet {
private readonly client = new EKSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const cluster = await this.memoClient.send(new DescribeClusterCommand({ name: clusterName }))
clusters.push(cluster.cluster!)
}
return clusters;
};
return clusters
}
public readonly getMetadata = () => ({
name: 'EKSClusterLoggingEnabled',
description: 'Ensures that all EKS clusters have full logging enabled.',
priority: 1,
priorityReason:
'Cluster logging is essential for monitoring, debugging, and auditing purposes.',
priorityReason: 'Cluster logging is essential for monitoring, debugging, and auditing purposes.',
awsService: 'EKS',
awsServiceCategory: 'Kubernetes Service',
bestPracticeCategory: 'Observability',
@ -38,75 +30,74 @@ export class EKSClusterLoggingEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListClustersCommand',
reason: 'Retrieve the list of EKS clusters.',
reason: 'Retrieve the list of EKS clusters.'
},
{
name: 'DescribeClusterCommand',
reason: 'Fetch details about the EKS cluster, including logging configuration.',
},
reason: 'Fetch details about the EKS cluster, including logging configuration.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateClusterConfigCommand',
reason: 'Enable all logging types for the EKS cluster.',
},
reason: 'Enable all logging types for the EKS cluster.'
}
],
adviseBeforeFixFunction:
'Ensure that enabling full logging does not generate excessive costs or logs.',
});
adviseBeforeFixFunction: 'Ensure that enabling full logging does not generate excessive costs or logs.'
})
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const clusters = await this.getClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const clusters = await this.getClusters()
for (const cluster of clusters) {
const clusterLogging = cluster.logging?.clusterLogging?.[0];
const clusterLogging = cluster.logging?.clusterLogging?.[0]
if (clusterLogging?.enabled && clusterLogging.types?.length === 5) {
compliantResources.push(cluster.arn!);
compliantResources.push(cluster.arn!)
} else {
nonCompliantResources.push(cluster.arn!);
nonCompliantResources.push(cluster.arn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const clusterName = arn.split(':cluster/')[1];
const clusterName = arn.split(':cluster/')[1]
await this.client.send(
new UpdateClusterConfigCommand({
@ -115,12 +106,12 @@ export class EKSClusterLoggingEnabled implements BPSet {
clusterLogging: [
{
enabled: true,
types: ['api', 'audit', 'authenticator', 'controllerManager', 'scheduler'],
},
],
},
types: ['api', 'audit', 'authenticator', 'controllerManager', 'scheduler']
}
]
}
})
);
)
}
};
}
}

View File

@ -2,34 +2,31 @@ import {
EKSClient,
ListClustersCommand,
DescribeClusterCommand,
AssociateEncryptionConfigCommand,
} from '@aws-sdk/client-eks';
import { BPSet, BPSetStats, BPSetFixFn } from '../../types';
import { Memorizer } from '../../Memorizer';
AssociateEncryptionConfigCommand
} from '@aws-sdk/client-eks'
import { BPSet, BPSetStats, BPSetFixFn } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EKSClusterSecretsEncrypted implements BPSet {
private readonly client = new EKSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const cluster = await this.memoClient.send(new DescribeClusterCommand({ name: clusterName }))
clusters.push(cluster.cluster!)
}
return clusters;
};
return clusters
}
public readonly getMetadata = () => ({
name: 'EKSClusterSecretsEncrypted',
description: 'Ensures that all EKS clusters have secrets encrypted with a KMS key.',
priority: 1,
priorityReason:
'Encrypting secrets ensures the security and compliance of sensitive data in EKS clusters.',
priorityReason: 'Encrypting secrets ensures the security and compliance of sensitive data in EKS clusters.',
awsService: 'EKS',
awsServiceCategory: 'Kubernetes Service',
bestPracticeCategory: 'Security',
@ -38,88 +35,87 @@ export class EKSClusterSecretsEncrypted implements BPSet {
name: 'kms-key-id',
description: 'The KMS key ARN to enable encryption for EKS secrets.',
default: '',
example: 'arn:aws:kms:us-east-1:123456789012:key/example-key-id',
},
example: 'arn:aws:kms:us-east-1:123456789012:key/example-key-id'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'ListClustersCommand',
reason: 'Retrieve the list of EKS clusters.',
reason: 'Retrieve the list of EKS clusters.'
},
{
name: 'DescribeClusterCommand',
reason: 'Fetch details about the EKS cluster, including encryption configuration.',
},
reason: 'Fetch details about the EKS cluster, including encryption configuration.'
}
],
commandUsedInFixFunction: [
{
name: 'AssociateEncryptionConfigCommand',
reason: 'Enable encryption for EKS secrets using the provided KMS key.',
},
reason: 'Enable encryption for EKS secrets using the provided KMS key.'
}
],
adviseBeforeFixFunction:
'Ensure that the specified KMS key is accessible to the EKS service and cluster.',
});
adviseBeforeFixFunction: 'Ensure that the specified KMS key is accessible to the EKS service and cluster.'
})
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const clusters = await this.getClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const clusters = await this.getClusters()
for (const cluster of clusters) {
const encryptionConfig = cluster.encryptionConfig?.[0];
const encryptionConfig = cluster.encryptionConfig?.[0]
if (encryptionConfig?.resources?.includes('secrets')) {
compliantResources.push(cluster.arn!);
compliantResources.push(cluster.arn!)
} else {
nonCompliantResources.push(cluster.arn!);
nonCompliantResources.push(cluster.arn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const kmsKeyId = requiredParametersForFix.find((param) => param.name === 'kms-key-id')?.value;
const kmsKeyId = requiredParametersForFix.find((param) => param.name === 'kms-key-id')?.value
if (!kmsKeyId) {
throw new Error("Required parameter 'kms-key-id' is missing.");
throw new Error("Required parameter 'kms-key-id' is missing.")
}
for (const arn of nonCompliantResources) {
const clusterName = arn.split(':cluster/')[1];
const clusterName = arn.split(':cluster/')[1]
await this.client.send(
new AssociateEncryptionConfigCommand({
@ -127,11 +123,11 @@ export class EKSClusterSecretsEncrypted implements BPSet {
encryptionConfig: [
{
resources: ['secrets'],
provider: { keyArn: kmsKeyId },
},
],
provider: { keyArn: kmsKeyId }
}
]
})
);
)
}
};
}
}

View File

@ -1,34 +1,28 @@
import {
EKSClient,
ListClustersCommand,
DescribeClusterCommand,
UpdateClusterConfigCommand,
} from '@aws-sdk/client-eks';
import { BPSet, BPSetStats, BPSetFixFn } from '../../types';
import { Memorizer } from '../../Memorizer';
import { EKSClient, ListClustersCommand, DescribeClusterCommand, UpdateClusterConfigCommand } from '@aws-sdk/client-eks'
import { BPSet, BPSetStats, BPSetFixFn } from '../../types'
import { Memorizer } from '../../Memorizer'
export class EKSEndpointNoPublicAccess implements BPSet {
private readonly client = new EKSClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 = [];
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!);
const cluster = await this.memoClient.send(new DescribeClusterCommand({ name: clusterName }))
clusters.push(cluster.cluster!)
}
return clusters;
};
return clusters
}
public readonly getMetadata = () => ({
name: 'EKSEndpointNoPublicAccess',
description: 'Ensures EKS cluster endpoint does not have public access enabled.',
priority: 1,
priorityReason: 'Disabling public access to the cluster endpoint enhances security by limiting exposure to public networks.',
priorityReason:
'Disabling public access to the cluster endpoint enhances security by limiting exposure to public networks.',
awsService: 'EKS',
awsServiceCategory: 'Kubernetes Service',
bestPracticeCategory: 'Security',
@ -37,83 +31,84 @@ export class EKSEndpointNoPublicAccess implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListClustersCommand',
reason: 'Retrieves the list of EKS clusters.',
reason: 'Retrieves the list of EKS clusters.'
},
{
name: 'DescribeClusterCommand',
reason: 'Fetches detailed configuration of each EKS cluster.',
},
reason: 'Fetches detailed configuration of each EKS cluster.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateClusterConfigCommand',
reason: 'Updates the EKS cluster configuration to disable public endpoint access.',
},
reason: 'Updates the EKS cluster configuration to disable public endpoint access.'
}
],
adviseBeforeFixFunction: 'Ensure the private endpoint is properly configured and accessible before disabling public access.',
});
adviseBeforeFixFunction:
'Ensure the private endpoint is properly configured and accessible before disabling public access.'
})
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const clusters = await this.getClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const clusters = await this.getClusters()
for (const cluster of clusters) {
const endpointPublicAccess = cluster.resourcesVpcConfig?.endpointPublicAccess;
const endpointPublicAccess = cluster.resourcesVpcConfig?.endpointPublicAccess
if (endpointPublicAccess) {
nonCompliantResources.push(cluster.arn!);
nonCompliantResources.push(cluster.arn!)
} else {
compliantResources.push(cluster.arn!);
compliantResources.push(cluster.arn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const clusterName = arn.split(':cluster/')[1];
const clusterName = arn.split(':cluster/')[1]
await this.client.send(
new UpdateClusterConfigCommand({
name: clusterName,
resourcesVpcConfig: {
endpointPublicAccess: false,
},
endpointPublicAccess: false
}
})
);
)
}
};
}
}

View File

@ -1,25 +1,22 @@
import {
ElastiCacheClient,
DescribeCacheClustersCommand,
ModifyCacheClusterCommand,
} from '@aws-sdk/client-elasticache';
import { BPSet, BPSetStats, BPSetFixFn } from '../../types';
import { Memorizer } from '../../Memorizer';
import { ElastiCacheClient, DescribeCacheClustersCommand, ModifyCacheClusterCommand } from '@aws-sdk/client-elasticache'
import { BPSet, BPSetStats, BPSetFixFn } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ElastiCacheAutoMinorVersionUpgradeCheck implements BPSet {
private readonly client = new ElastiCacheClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeCacheClustersCommand({}))
return response.CacheClusters || []
}
public readonly getMetadata = () => ({
name: 'ElastiCacheAutoMinorVersionUpgradeCheck',
description: 'Ensures that ElastiCache clusters have auto minor version upgrade enabled.',
priority: 2,
priorityReason: 'Auto minor version upgrades help ensure clusters stay up-to-date with the latest security and bug fixes.',
priorityReason:
'Auto minor version upgrades help ensure clusters stay up-to-date with the latest security and bug fixes.',
awsService: 'ElastiCache',
awsServiceCategory: 'Cache Service',
bestPracticeCategory: 'Reliability',
@ -28,75 +25,76 @@ export class ElastiCacheAutoMinorVersionUpgradeCheck implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeCacheClustersCommand',
reason: 'Fetches the list and configurations of ElastiCache clusters.',
},
reason: 'Fetches the list and configurations of ElastiCache clusters.'
}
],
commandUsedInFixFunction: [
{
name: 'ModifyCacheClusterCommand',
reason: 'Enables auto minor version upgrade on ElastiCache clusters.',
},
reason: 'Enables auto minor version upgrade on ElastiCache clusters.'
}
],
adviseBeforeFixFunction: 'Ensure application compatibility with updated ElastiCache versions before enabling this setting.',
});
adviseBeforeFixFunction:
'Ensure application compatibility with updated ElastiCache versions before enabling this setting.'
})
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const clusters = await this.getClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const clusters = await this.getClusters()
for (const cluster of clusters) {
if (cluster.AutoMinorVersionUpgrade) {
compliantResources.push(cluster.ARN!);
compliantResources.push(cluster.ARN!)
} else {
nonCompliantResources.push(cluster.ARN!);
nonCompliantResources.push(cluster.ARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const clusterId = arn.split(':cluster:')[1];
const clusterId = arn.split(':cluster:')[1]
await this.client.send(
new ModifyCacheClusterCommand({
CacheClusterId: clusterId,
AutoMinorVersionUpgrade: true,
AutoMinorVersionUpgrade: true
})
);
)
}
};
}
}

View File

@ -1,19 +1,19 @@
import {
ElastiCacheClient,
DescribeReplicationGroupsCommand,
ModifyReplicationGroupCommand,
} from '@aws-sdk/client-elasticache';
import { BPSet, BPSetStats, BPSetFixFn } from '../../types';
import { Memorizer } from '../../Memorizer';
ModifyReplicationGroupCommand
} from '@aws-sdk/client-elasticache'
import { BPSet, BPSetStats, BPSetFixFn } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ElastiCacheRedisClusterAutomaticBackupCheck implements BPSet {
private readonly client = new ElastiCacheClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}))
return response.ReplicationGroups || []
}
public readonly getMetadata = () => ({
name: 'ElastiCacheRedisClusterAutomaticBackupCheck',
@ -28,90 +28,89 @@ export class ElastiCacheRedisClusterAutomaticBackupCheck implements BPSet {
name: 'snapshot-retention-period',
description: 'Number of days to retain automatic snapshots.',
default: '7',
example: '7',
},
example: '7'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'DescribeReplicationGroupsCommand',
reason: 'Fetches details of replication groups to verify backup settings.',
},
reason: 'Fetches details of replication groups to verify backup settings.'
}
],
commandUsedInFixFunction: [
{
name: 'ModifyReplicationGroupCommand',
reason: 'Enables automatic snapshots and sets the retention period for Redis clusters.',
},
reason: 'Enables automatic snapshots and sets the retention period for Redis clusters.'
}
],
adviseBeforeFixFunction: 'Ensure that enabling snapshots does not conflict with operational or compliance requirements.',
});
adviseBeforeFixFunction:
'Ensure that enabling snapshots does not conflict with operational or compliance requirements.'
})
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const replicationGroups = await this.getReplicationGroups();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const replicationGroups = await this.getReplicationGroups()
for (const group of replicationGroups) {
if (group.SnapshottingClusterId) {
compliantResources.push(group.ARN!);
compliantResources.push(group.ARN!)
} else {
nonCompliantResources.push(group.ARN!);
nonCompliantResources.push(group.ARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
const retentionPeriod = requiredParametersForFix.find(
(param) => param.name === 'snapshot-retention-period'
)?.value;
const retentionPeriod = requiredParametersForFix.find((param) => param.name === 'snapshot-retention-period')?.value
if (!retentionPeriod) {
throw new Error("Required parameter 'snapshot-retention-period' is missing.");
throw new Error("Required parameter 'snapshot-retention-period' is missing.")
}
for (const arn of nonCompliantResources) {
const groupId = arn.split(':replication-group:')[1];
const groupId = arn.split(':replication-group:')[1]
await this.client.send(
new ModifyReplicationGroupCommand({
ReplicationGroupId: groupId,
SnapshotRetentionLimit: parseInt(retentionPeriod, 10),
SnapshotRetentionLimit: parseInt(retentionPeriod, 10)
})
);
)
}
};
}
}

View File

@ -1,19 +1,19 @@
import {
ElastiCacheClient,
DescribeReplicationGroupsCommand,
ModifyReplicationGroupCommand,
} from '@aws-sdk/client-elasticache';
import { BPSet, BPSetStats, BPSetFixFn } from '../../types';
import { Memorizer } from '../../Memorizer';
ModifyReplicationGroupCommand
} from '@aws-sdk/client-elasticache'
import { BPSet, BPSetStats, BPSetFixFn } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ElastiCacheReplGrpAutoFailoverEnabled implements BPSet {
private readonly client = new ElastiCacheClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}))
return response.ReplicationGroups || []
}
public readonly getMetadata = () => ({
name: 'ElastiCacheReplGrpAutoFailoverEnabled',
@ -28,75 +28,76 @@ export class ElastiCacheReplGrpAutoFailoverEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeReplicationGroupsCommand',
reason: 'Fetches replication group details to verify automatic failover settings.',
},
reason: 'Fetches replication group details to verify automatic failover settings.'
}
],
commandUsedInFixFunction: [
{
name: 'ModifyReplicationGroupCommand',
reason: 'Enables automatic failover for replication groups.',
},
reason: 'Enables automatic failover for replication groups.'
}
],
adviseBeforeFixFunction: 'Ensure the environment supports multi-AZ configurations before enabling automatic failover.',
});
adviseBeforeFixFunction:
'Ensure the environment supports multi-AZ configurations before enabling automatic failover.'
})
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const replicationGroups = await this.getReplicationGroups();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const replicationGroups = await this.getReplicationGroups()
for (const group of replicationGroups) {
if (group.AutomaticFailover === 'enabled') {
compliantResources.push(group.ARN!);
compliantResources.push(group.ARN!)
} else {
nonCompliantResources.push(group.ARN!);
nonCompliantResources.push(group.ARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix: BPSetFixFn = async (nonCompliantResources) => {
for (const arn of nonCompliantResources) {
const groupId = arn.split(':replication-group:')[1];
const groupId = arn.split(':replication-group:')[1]
await this.client.send(
new ModifyReplicationGroupCommand({
ReplicationGroupId: groupId,
AutomaticFailoverEnabled: true,
AutomaticFailoverEnabled: true
})
);
)
}
};
}
}

View File

@ -1,18 +1,15 @@
import {
ElastiCacheClient,
DescribeReplicationGroupsCommand,
} from '@aws-sdk/client-elasticache';
import { BPSet, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { ElastiCacheClient, DescribeReplicationGroupsCommand } from '@aws-sdk/client-elasticache'
import { BPSet, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ElastiCacheReplGrpEncryptedAtRest implements BPSet {
private readonly client = new ElastiCacheClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}))
return response.ReplicationGroups || []
}
public readonly getMetadata = () => ({
name: 'ElastiCacheReplGrpEncryptedAtRest',
@ -27,64 +24,65 @@ export class ElastiCacheReplGrpEncryptedAtRest implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeReplicationGroupsCommand',
reason: 'Fetches replication group details to verify encryption settings.',
},
reason: 'Fetches replication group details to verify encryption settings.'
}
],
commandUsedInFixFunction: [],
adviseBeforeFixFunction: 'Recreation of the replication group is required for encryption. Ensure data backups are available.',
});
adviseBeforeFixFunction:
'Recreation of the replication group is required for encryption. Ensure data backups are available.'
})
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const replicationGroups = await this.getReplicationGroups();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const replicationGroups = await this.getReplicationGroups()
for (const group of replicationGroups) {
if (group.AtRestEncryptionEnabled) {
compliantResources.push(group.ARN!);
compliantResources.push(group.ARN!)
} else {
nonCompliantResources.push(group.ARN!);
nonCompliantResources.push(group.ARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async () => {
throw new Error(
'Fixing encryption at rest for replication groups requires recreation. Please create a new replication group with AtRestEncryptionEnabled set to true.'
);
};
)
}
}

View File

@ -1,18 +1,15 @@
import {
ElastiCacheClient,
DescribeReplicationGroupsCommand,
} from '@aws-sdk/client-elasticache';
import { BPSet, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { ElastiCacheClient, DescribeReplicationGroupsCommand } from '@aws-sdk/client-elasticache'
import { BPSet, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ElastiCacheReplGrpEncryptedInTransit implements BPSet {
private readonly client = new ElastiCacheClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}))
return response.ReplicationGroups || []
}
public readonly getMetadata = () => ({
name: 'ElastiCacheReplGrpEncryptedInTransit',
@ -27,64 +24,65 @@ export class ElastiCacheReplGrpEncryptedInTransit implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeReplicationGroupsCommand',
reason: 'Fetches replication group details to verify in-transit encryption settings.',
},
reason: 'Fetches replication group details to verify in-transit encryption settings.'
}
],
commandUsedInFixFunction: [],
adviseBeforeFixFunction: 'Recreation of the replication group is required for enabling in-transit encryption. Ensure data backups are available.',
});
adviseBeforeFixFunction:
'Recreation of the replication group is required for enabling in-transit encryption. Ensure data backups are available.'
})
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const replicationGroups = await this.getReplicationGroups();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const replicationGroups = await this.getReplicationGroups()
for (const group of replicationGroups) {
if (group.TransitEncryptionEnabled) {
compliantResources.push(group.ARN!);
compliantResources.push(group.ARN!)
} else {
nonCompliantResources.push(group.ARN!);
nonCompliantResources.push(group.ARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async () => {
throw new Error(
'Fixing in-transit encryption for replication groups requires recreation. Please create a new replication group with TransitEncryptionEnabled set to true.'
);
};
)
}
}

View File

@ -3,18 +3,18 @@ import {
DescribeCacheClustersCommand,
DeleteCacheClusterCommand,
CreateCacheClusterCommand
} from '@aws-sdk/client-elasticache';
import { BPSet, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
} from '@aws-sdk/client-elasticache'
import { BPSet, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class ElastiCacheSubnetGroupCheck implements BPSet {
private readonly client = new ElastiCacheClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
const response = await this.memoClient.send(new DescribeCacheClustersCommand({}))
return response.CacheClusters || []
}
public readonly getMetadata = () => ({
name: 'ElastiCacheSubnetGroupCheck',
@ -29,106 +29,103 @@ export class ElastiCacheSubnetGroupCheck implements BPSet {
name: 'subnet-group-name',
description: 'The name of the desired subnet group to associate with the cluster.',
default: '',
example: 'custom-subnet-group',
example: 'custom-subnet-group'
}
],
isFixFunctionUsesDestructiveCommand: true,
commandUsedInCheckFunction: [
{
name: 'DescribeCacheClustersCommand',
reason: 'Fetches the details of all ElastiCache clusters to check their subnet group.',
reason: 'Fetches the details of all ElastiCache clusters to check their subnet group.'
}
],
commandUsedInFixFunction: [
{
name: 'DeleteCacheClusterCommand',
reason: 'Deletes non-compliant ElastiCache clusters.',
reason: 'Deletes non-compliant ElastiCache clusters.'
},
{
name: 'CreateCacheClusterCommand',
reason: 'Recreates ElastiCache clusters with the desired subnet group.',
reason: 'Recreates ElastiCache clusters with the desired subnet group.'
}
],
adviseBeforeFixFunction: 'Ensure data backups are available before fixing as clusters will be deleted and recreated.',
});
adviseBeforeFixFunction:
'Ensure data backups are available before fixing as clusters will be deleted and recreated.'
})
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const clusters = await this.getClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const clusters = await this.getClusters()
for (const cluster of clusters) {
if (cluster.CacheSubnetGroupName !== 'default') {
compliantResources.push(cluster.ARN!);
compliantResources.push(cluster.ARN!)
} else {
nonCompliantResources.push(cluster.ARN!);
nonCompliantResources.push(cluster.ARN!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const subnetGroupName = requiredParametersForFix.find(
(param) => param.name === 'subnet-group-name'
)?.value;
const subnetGroupName = requiredParametersForFix.find((param) => param.name === 'subnet-group-name')?.value
if (!subnetGroupName) {
throw new Error("Required parameter 'subnet-group-name' is missing.");
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];
const clusterId = arn.split(':cluster:')[1]
const cluster = await this.memoClient.send(new DescribeCacheClustersCommand({ CacheClusterId: clusterId }))
const clusterDetails = cluster.CacheClusters?.[0]
if (!clusterDetails) {
continue;
continue
}
// Delete the non-compliant cluster
await this.client.send(
new DeleteCacheClusterCommand({
CacheClusterId: clusterId,
CacheClusterId: clusterId
})
);
)
// Recreate the cluster with the desired subnet group
await this.client.send(
@ -138,13 +135,11 @@ export class ElastiCacheSubnetGroupCheck implements BPSet {
CacheNodeType: clusterDetails.CacheNodeType!,
NumCacheNodes: clusterDetails.NumCacheNodes!,
CacheSubnetGroupName: subnetGroupName,
SecurityGroupIds: clusterDetails.SecurityGroups?.map(
(group) => group.SecurityGroupId
) as string[],
SecurityGroupIds: clusterDetails.SecurityGroups?.map((group) => group.SecurityGroupId) as string[],
PreferredMaintenanceWindow: clusterDetails.PreferredMaintenanceWindow,
EngineVersion: clusterDetails.EngineVersion,
EngineVersion: clusterDetails.EngineVersion
})
);
)
}
};
}
}

View File

@ -1,27 +1,22 @@
import {
IAMClient,
ListPoliciesCommand,
GetPolicyVersionCommand,
DeletePolicyCommand
} from '@aws-sdk/client-iam';
import { BPSet, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { IAMClient, ListPoliciesCommand, GetPolicyVersionCommand, DeletePolicyCommand } from '@aws-sdk/client-iam'
import { BPSet, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class IAMPolicyNoStatementsWithAdminAccess implements BPSet {
private readonly client = new IAMClient({});
private readonly memoClient = Memorizer.memo(this.client);
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 || [];
};
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!;
};
)
return response.PolicyVersion!
}
public readonly getMetadata = () => ({
name: 'IAMPolicyNoStatementsWithAdminAccess',
@ -36,89 +31,83 @@ export class IAMPolicyNoStatementsWithAdminAccess implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListPoliciesCommand',
reason: 'Fetches all local IAM policies.',
reason: 'Fetches all local IAM policies.'
},
{
name: 'GetPolicyVersionCommand',
reason: 'Retrieves the default version of each policy.',
},
reason: 'Retrieves the default version of each policy.'
}
],
commandUsedInFixFunction: [
{
name: 'DeletePolicyCommand',
reason: 'Deletes non-compliant IAM policies.',
},
reason: 'Deletes non-compliant IAM policies.'
}
],
adviseBeforeFixFunction: 'Deleting policies is irreversible. Verify policies before applying fixes.',
});
adviseBeforeFixFunction: 'Deleting policies is irreversible. Verify policies before applying fixes.'
})
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl().then(
() => (this.stats.status = 'FINISHED'),
(err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
message: err.message
})
}
);
};
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const policies = await this.getPolicies();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const policies = await this.getPolicies()
for (const policy of policies) {
const policyVersion = await this.getPolicyDefaultVersions(policy.Arn!, policy.DefaultVersionId!);
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];
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 (statement?.Action === '*' && statement?.Resource === '*' && statement?.Effect === 'Allow') {
nonCompliantResources.push(policy.Arn!)
break
}
}
if (!nonCompliantResources.includes(policy.Arn!)) {
compliantResources.push(policy.Arn!);
compliantResources.push(policy.Arn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const arn of nonCompliantResources) {
await this.client.send(new DeletePolicyCommand({ PolicyArn: arn }));
await this.client.send(new DeletePolicyCommand({ PolicyArn: arn }))
}
};
}
}

View File

@ -3,185 +3,170 @@ import {
ListPoliciesCommand,
GetPolicyVersionCommand,
CreatePolicyVersionCommand,
DeletePolicyVersionCommand,
} from "@aws-sdk/client-iam";
import { BPSet, BPSetMetadata, BPSetStats } from "../../types";
import { Memorizer } from "../../Memorizer";
DeletePolicyVersionCommand
} from '@aws-sdk/client-iam'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class IAMPolicyNoStatementsWithFullAccess implements BPSet {
private readonly client = new IAMClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new IAMClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: "LOADED",
errorMessage: [],
};
status: 'LOADED',
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: "IAMPolicyNoStatementsWithFullAccess",
description: "Ensures IAM policies do not have statements granting full access.",
name: 'IAMPolicyNoStatementsWithFullAccess',
description: 'Ensures IAM policies do not have statements granting full access.',
priority: 2,
priorityReason: "Granting full access poses a significant security risk.",
awsService: "IAM",
awsServiceCategory: "Access Management",
bestPracticeCategory: "Security",
priorityReason: 'Granting full access poses a significant security risk.',
awsService: 'IAM',
awsServiceCategory: 'Access Management',
bestPracticeCategory: 'Security',
requiredParametersForFix: [
{
name: "policy-revision-strategy",
description: "Strategy to revise policies (e.g., remove, restrict actions)",
default: "remove",
example: "remove",
},
name: 'policy-revision-strategy',
description: 'Strategy to revise policies (e.g., remove, restrict actions)',
default: 'remove',
example: 'remove'
}
],
isFixFunctionUsesDestructiveCommand: true,
commandUsedInCheckFunction: [
{ name: "ListPoliciesCommand", reason: "Fetch all customer-managed policies." },
{ name: "GetPolicyVersionCommand", reason: "Retrieve the default version of the policy." },
{ name: 'ListPoliciesCommand', reason: 'Fetch all customer-managed policies.' },
{ name: 'GetPolicyVersionCommand', reason: 'Retrieve the default version of the policy.' }
],
commandUsedInFixFunction: [
{ name: "CreatePolicyVersionCommand", reason: "Create a new policy version." },
{ name: "DeletePolicyVersionCommand", reason: "Delete outdated policy versions." },
{ name: 'CreatePolicyVersionCommand', reason: 'Create a new policy version.' },
{ name: 'DeletePolicyVersionCommand', reason: 'Delete outdated policy versions.' }
],
adviseBeforeFixFunction:
"Ensure revised policies meet the organization's security and access requirements.",
});
adviseBeforeFixFunction: "Ensure revised policies meet the organization's security and access requirements."
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = "LOADED";
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = "CHECKING";
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(
() => {
this.stats.status = "FINISHED";
},
(err) => {
this.stats.status = "ERROR";
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
}
);
};
await this.checkImpl().then(
() => {
this.stats.status = 'FINISHED'
},
(err) => {
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
})
}
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const policiesResponse = await this.memoClient.send(
new ListPoliciesCommand({ Scope: "Local" })
);
const policies = policiesResponse.Policies || [];
const policiesResponse = await this.memoClient.send(new ListPoliciesCommand({ Scope: 'Local' }))
const policies = policiesResponse.Policies || []
for (const policy of policies) {
const policyVersionResponse = await this.memoClient.send(
new GetPolicyVersionCommand({
PolicyArn: policy.Arn!,
VersionId: policy.DefaultVersionId!,
VersionId: policy.DefaultVersionId!
})
);
)
const policyDocument = JSON.parse(
decodeURIComponent(policyVersionResponse.PolicyVersion!.Document as string)
);
const policyDocument = JSON.parse(decodeURIComponent(policyVersionResponse.PolicyVersion!.Document as string))
const hasFullAccess = policyDocument.Statement.some((statement: any) => {
if (statement.Effect === "Deny") return false;
const actions = Array.isArray(statement.Action)
? statement.Action
: [statement.Action];
return actions.some((action: string) => action.endsWith(":*"));
});
const hasFullAccess = policyDocument.Statement.some((statement: unknown) => {
if (statement.Effect === 'Deny') return false
const actions = Array.isArray(statement.Action) ? statement.Action : [statement.Action]
return actions.some((action: string) => action.endsWith(':*'))
})
if (hasFullAccess) {
nonCompliantResources.push(policy.Arn!);
nonCompliantResources.push(policy.Arn!)
} else {
compliantResources.push(policy.Arn!);
compliantResources.push(policy.Arn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(
() => {
this.stats.status = "FINISHED";
},
(err) => {
this.stats.status = "ERROR";
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
}
);
};
await this.fixImpl(nonCompliantResources, requiredParametersForFix).then(
() => {
this.stats.status = 'FINISHED'
},
(err) => {
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
})
}
)
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const strategy = requiredParametersForFix.find(
(param) => param.name === "policy-revision-strategy"
)?.value;
const strategy = requiredParametersForFix.find((param) => param.name === 'policy-revision-strategy')?.value
if (!strategy) {
throw new Error("Required parameter 'policy-revision-strategy' is missing.");
throw new Error("Required parameter 'policy-revision-strategy' is missing.")
}
for (const policyArn of nonCompliantResources) {
const policyVersionResponse = await this.memoClient.send(
new GetPolicyVersionCommand({
PolicyArn: policyArn,
VersionId: "v1",
VersionId: 'v1'
})
);
)
const policyDocument = JSON.parse(
decodeURIComponent(policyVersionResponse.PolicyVersion!.Document as string)
);
const policyDocument = JSON.parse(decodeURIComponent(policyVersionResponse.PolicyVersion!.Document as string))
policyDocument.Statement = policyDocument.Statement.filter((statement: any) => {
if (statement.Effect === "Deny") return true;
const actions = Array.isArray(statement.Action)
? statement.Action
: [statement.Action];
return !actions.some((action: string) => action.endsWith(":*"));
});
policyDocument.Statement = policyDocument.Statement.filter((statement: unknown) => {
if (statement.Effect === 'Deny') return true
const actions = Array.isArray(statement.Action) ? statement.Action : [statement.Action]
return !actions.some((action: string) => action.endsWith(':*'))
})
const createVersionResponse = await this.client.send(
new CreatePolicyVersionCommand({
PolicyArn: policyArn,
PolicyDocument: JSON.stringify(policyDocument),
SetAsDefault: true,
SetAsDefault: true
})
);
)
if (createVersionResponse.PolicyVersion?.VersionId) {
await this.client.send(
new DeletePolicyVersionCommand({
PolicyArn: policyArn,
VersionId: policyVersionResponse.PolicyVersion!.VersionId,
VersionId: policyVersionResponse.PolicyVersion!.VersionId
})
);
)
}
}
};
}
}

View File

@ -1,27 +1,24 @@
import {
IAMClient,
ListPoliciesCommand,
ListEntitiesForPolicyCommand,
} from '@aws-sdk/client-iam';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { IAMClient, ListPoliciesCommand, ListEntitiesForPolicyCommand } from '@aws-sdk/client-iam'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class IAMRoleManagedPolicyCheck implements BPSet {
private readonly client = new IAMClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new IAMClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'IAMRoleManagedPolicyCheck',
description: 'Checks whether managed IAM policies are attached to any entities (roles, users, or groups).',
priority: 3,
priorityReason: 'Orphaned managed policies may indicate unused resources that can be removed for better security and resource management.',
priorityReason:
'Orphaned managed policies may indicate unused resources that can be removed for better security and resource management.',
awsService: 'IAM',
awsServiceCategory: 'Access Management',
bestPracticeCategory: 'Security',
@ -30,86 +27,77 @@ export class IAMRoleManagedPolicyCheck implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListPoliciesCommand',
reason: 'Retrieve all customer-managed IAM policies.',
reason: 'Retrieve all customer-managed IAM policies.'
},
{
name: 'ListEntitiesForPolicyCommand',
reason: 'Check if policies are attached to any users, roles, or groups.',
},
reason: 'Check if policies are attached to any users, roles, or groups.'
}
],
commandUsedInFixFunction: [],
adviseBeforeFixFunction: 'Ensure orphaned policies are no longer required before removing them.',
});
adviseBeforeFixFunction: 'Ensure orphaned policies are no longer required before removing them.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(
() => {
this.stats.status = 'FINISHED';
},
(err) => {
this.stats.status = 'ERROR';
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
}
);
};
await this.checkImpl().then(
() => {
this.stats.status = 'FINISHED'
},
(err) => {
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
})
}
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const policies = await this.getPolicies();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const policies = await this.getPolicies()
for (const policy of policies) {
const { attached } = await this.checkEntitiesForPolicy(policy.Arn!);
const { attached } = await this.checkEntitiesForPolicy(policy.Arn!)
if (attached) {
compliantResources.push(policy.Arn!);
compliantResources.push(policy.Arn!)
} else {
nonCompliantResources.push(policy.Arn!);
nonCompliantResources.push(policy.Arn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async () => {
throw new Error(
'Fixing orphaned managed policies requires manual review and removal. Ensure these policies are no longer needed.'
);
};
)
}
private readonly getPolicies = async () => {
const response = await this.memoClient.send(
new ListPoliciesCommand({ Scope: 'Local' })
);
return response.Policies || [];
};
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 })
);
const response = await this.memoClient.send(new ListEntitiesForPolicyCommand({ PolicyArn: policyArn }))
return {
attached: Boolean(
response.PolicyGroups?.length ||
response.PolicyUsers?.length ||
response.PolicyRoles?.length
),
};
};
attached: Boolean(response.PolicyGroups?.length || response.PolicyUsers?.length || response.PolicyRoles?.length)
}
}
}

View File

@ -1,17 +1,17 @@
import { LambdaClient, ListFunctionsCommand, UpdateFunctionConfigurationCommand } from '@aws-sdk/client-lambda';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { LambdaClient, ListFunctionsCommand, UpdateFunctionConfigurationCommand } from '@aws-sdk/client-lambda'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class LambdaDLQCheck implements BPSet {
private readonly client = new LambdaClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new LambdaClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'LambdaDLQCheck',
@ -26,111 +26,109 @@ export class LambdaDLQCheck implements BPSet {
name: 'dlq-arn',
description: 'The ARN of the Dead Letter Queue to associate with the Lambda function.',
default: '',
example: 'arn:aws:sqs:us-east-1:123456789012:example-queue',
},
example: 'arn:aws:sqs:us-east-1:123456789012:example-queue'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'ListFunctionsCommand',
reason: 'Retrieve all Lambda functions in the account.',
},
reason: 'Retrieve all Lambda functions in the account.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateFunctionConfigurationCommand',
reason: 'Update the DLQ configuration for Lambda functions.',
},
reason: 'Update the DLQ configuration for Lambda functions.'
}
],
adviseBeforeFixFunction: 'Ensure that the specified DLQ exists and is correctly configured to handle failed events.',
});
adviseBeforeFixFunction: 'Ensure that the specified DLQ exists and is correctly configured to handle failed events.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(
() => {
this.stats.status = 'FINISHED';
},
(err) => {
this.stats.status = 'ERROR';
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
}
);
};
await this.checkImpl().then(
() => {
this.stats.status = 'FINISHED'
},
(err) => {
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
})
}
)
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const functions = await this.getFunctions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const functions = await this.getFunctions()
for (const func of functions) {
if (func.DeadLetterConfig) {
compliantResources.push(func.FunctionArn!);
compliantResources.push(func.FunctionArn!)
} else {
nonCompliantResources.push(func.FunctionArn!);
nonCompliantResources.push(func.FunctionArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(
() => {
this.stats.status = 'FINISHED';
},
(err) => {
this.stats.status = 'ERROR';
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
}
);
};
await this.fixImpl(nonCompliantResources, requiredParametersForFix).then(
() => {
this.stats.status = 'FINISHED'
},
(err) => {
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
})
}
)
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const dlqArn = requiredParametersForFix.find((param) => param.name === 'dlq-arn')?.value;
const dlqArn = requiredParametersForFix.find((param) => param.name === 'dlq-arn')?.value
if (!dlqArn) {
throw new Error("Required parameter 'dlq-arn' is missing.");
throw new Error("Required parameter 'dlq-arn' is missing.")
}
for (const functionArn of nonCompliantResources) {
const functionName = functionArn.split(':').pop()!;
const functionName = functionArn.split(':').pop()!
await this.client.send(
new UpdateFunctionConfigurationCommand({
FunctionName: functionName,
DeadLetterConfig: { TargetArn: dlqArn },
DeadLetterConfig: { TargetArn: dlqArn }
})
);
)
}
};
}
private readonly getFunctions = async () => {
const response = await this.memoClient.send(new ListFunctionsCommand({}));
return response.Functions || [];
};
const response = await this.memoClient.send(new ListFunctionsCommand({}))
return response.Functions || []
}
}

View File

@ -1,22 +1,17 @@
import {
LambdaClient,
ListFunctionsCommand,
GetPolicyCommand,
RemovePermissionCommand,
} from '@aws-sdk/client-lambda';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { LambdaClient, ListFunctionsCommand, GetPolicyCommand, RemovePermissionCommand } from '@aws-sdk/client-lambda'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class LambdaFunctionPublicAccessProhibited implements BPSet {
private readonly client = new LambdaClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new LambdaClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'LambdaFunctionPublicAccessProhibited',
@ -31,124 +26,121 @@ export class LambdaFunctionPublicAccessProhibited implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListFunctionsCommand',
reason: 'Retrieve all Lambda functions in the account.',
reason: 'Retrieve all Lambda functions in the account.'
},
{
name: 'GetPolicyCommand',
reason: 'Fetch the resource-based policy of a Lambda function.',
},
reason: 'Fetch the resource-based policy of a Lambda function.'
}
],
commandUsedInFixFunction: [
{
name: 'RemovePermissionCommand',
reason: 'Remove public access permissions from a Lambda function.',
},
reason: 'Remove public access permissions from a Lambda function.'
}
],
adviseBeforeFixFunction: 'Ensure that removing permissions does not disrupt legitimate use of the Lambda function.',
});
adviseBeforeFixFunction: 'Ensure that removing permissions does not disrupt legitimate use of the Lambda function.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const functions = await this.getFunctions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const functions = await this.getFunctions()
for (const func of functions) {
try {
const response = await this.memoClient.send(new GetPolicyCommand({ FunctionName: func.FunctionName! }));
const policy = JSON.parse(response.Policy!);
const response = await this.memoClient.send(new GetPolicyCommand({ FunctionName: func.FunctionName! }))
const policy = JSON.parse(response.Policy!)
const hasPublicAccess = policy.Statement.some(
(statement: any) => statement.Principal === '*' || statement.Principal?.AWS === '*'
);
(statement: unknown) => statement.Principal === '*' || statement.Principal?.AWS === '*'
)
if (hasPublicAccess) {
nonCompliantResources.push(func.FunctionArn!);
nonCompliantResources.push(func.FunctionArn!)
} else {
compliantResources.push(func.FunctionArn!);
compliantResources.push(func.FunctionArn!)
}
} catch (error) {
if ((error as any).name === 'ResourceNotFoundException') {
compliantResources.push(func.FunctionArn!);
if ((error as unknown).name === 'ResourceNotFoundException') {
compliantResources.push(func.FunctionArn!)
} else {
throw error;
throw error
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
public readonly fix = async (nonCompliantResources: string[]) => {
await this.fixImpl(nonCompliantResources)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly fixImpl = async (nonCompliantResources: string[]) => {
for (const functionArn of nonCompliantResources) {
const functionName = functionArn.split(':').pop()!;
const functionName = functionArn.split(':').pop()!
try {
const response = await this.memoClient.send(new GetPolicyCommand({ FunctionName: functionName }));
const policy = JSON.parse(response.Policy!);
const response = await this.memoClient.send(new GetPolicyCommand({ FunctionName: functionName }))
const policy = JSON.parse(response.Policy!)
for (const statement of policy.Statement) {
if (statement.Principal === '*' || statement.Principal?.AWS === '*') {
await this.client.send(
new RemovePermissionCommand({
FunctionName: functionName,
StatementId: statement.Sid, // Use the actual StatementId from the policy
StatementId: statement.Sid // Use the actual StatementId from the policy
})
);
)
}
}
} catch (error) {
if ((error as any).name !== 'ResourceNotFoundException') {
throw error;
if ((error as unknown).name !== 'ResourceNotFoundException') {
throw error
}
}
}
};
}
private readonly getFunctions = async () => {
const response = await this.memoClient.send(new ListFunctionsCommand({}));
return response.Functions || [];
};
const response = await this.memoClient.send(new ListFunctionsCommand({}))
return response.Functions || []
}
}

View File

@ -1,21 +1,17 @@
import {
LambdaClient,
ListFunctionsCommand,
UpdateFunctionConfigurationCommand
} from '@aws-sdk/client-lambda';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { LambdaClient, ListFunctionsCommand, UpdateFunctionConfigurationCommand } from '@aws-sdk/client-lambda'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class LambdaFunctionSettingsCheck implements BPSet {
private readonly client = new LambdaClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new LambdaClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'LambdaFunctionSettingsCheck',
@ -30,75 +26,76 @@ export class LambdaFunctionSettingsCheck implements BPSet {
name: 'timeout',
description: 'Timeout value in seconds for the Lambda function.',
default: '3',
example: '30',
example: '30'
},
{
name: 'memory-size',
description: 'Memory size in MB for the Lambda function.',
default: '128',
example: '256',
},
example: '256'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'ListFunctionsCommand',
reason: 'Retrieve all Lambda functions in the account.',
},
reason: 'Retrieve all Lambda functions in the account.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateFunctionConfigurationCommand',
reason: 'Update the timeout and memory size settings for non-compliant Lambda functions.',
},
reason: 'Update the timeout and memory size settings for non-compliant Lambda functions.'
}
],
adviseBeforeFixFunction: 'Ensure that the timeout and memory size changes are suitable for the function\'s requirements.',
});
adviseBeforeFixFunction:
"Ensure that the timeout and memory size changes are suitable for the function's requirements."
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const defaultTimeout = 3;
const defaultMemorySize = 128;
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const defaultTimeout = 3
const defaultMemorySize = 128
const functions = await this.getFunctions();
const functions = await this.getFunctions()
for (const func of functions) {
if (func.Timeout === defaultTimeout || func.MemorySize === defaultMemorySize) {
nonCompliantResources.push(func.FunctionArn!);
nonCompliantResources.push(func.FunctionArn!)
} else {
compliantResources.push(func.FunctionArn!);
compliantResources.push(func.FunctionArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
@ -106,42 +103,42 @@ export class LambdaFunctionSettingsCheck implements BPSet {
) => {
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const timeout = requiredParametersForFix.find(param => param.name === 'timeout')?.value;
const memorySize = requiredParametersForFix.find(param => param.name === 'memory-size')?.value;
const timeout = requiredParametersForFix.find((param) => param.name === 'timeout')?.value
const memorySize = requiredParametersForFix.find((param) => param.name === 'memory-size')?.value
if (!timeout || !memorySize) {
throw new Error("Required parameters 'timeout' and/or 'memory-size' are missing.");
throw new Error("Required parameters 'timeout' and/or 'memory-size' are missing.")
}
for (const functionArn of nonCompliantResources) {
const functionName = functionArn.split(':').pop()!;
const functionName = functionArn.split(':').pop()!
await this.client.send(
new UpdateFunctionConfigurationCommand({
FunctionName: functionName,
Timeout: parseInt(timeout, 10),
MemorySize: parseInt(memorySize, 10),
MemorySize: parseInt(memorySize, 10)
})
);
)
}
};
}
private readonly getFunctions = async () => {
const response = await this.memoClient.send(new ListFunctionsCommand({}));
return response.Functions || [];
};
const response = await this.memoClient.send(new ListFunctionsCommand({}))
return response.Functions || []
}
}

View File

@ -1,21 +1,17 @@
import {
LambdaClient,
ListFunctionsCommand,
UpdateFunctionConfigurationCommand
} from '@aws-sdk/client-lambda';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { LambdaClient, ListFunctionsCommand, UpdateFunctionConfigurationCommand } from '@aws-sdk/client-lambda'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class LambdaInsideVPC implements BPSet {
private readonly client = new LambdaClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new LambdaClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'LambdaInsideVPC',
@ -30,72 +26,73 @@ export class LambdaInsideVPC implements BPSet {
name: 'subnet-ids',
description: 'Comma-separated list of VPC subnet IDs.',
default: '',
example: 'subnet-abc123,subnet-def456',
example: 'subnet-abc123,subnet-def456'
},
{
name: 'security-group-ids',
description: 'Comma-separated list of VPC security group IDs.',
default: '',
example: 'sg-abc123,sg-def456',
},
example: 'sg-abc123,sg-def456'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'ListFunctionsCommand',
reason: 'Retrieve all Lambda functions in the account.',
},
reason: 'Retrieve all Lambda functions in the account.'
}
],
commandUsedInFixFunction: [
{
name: 'UpdateFunctionConfigurationCommand',
reason: 'Update the VPC configuration for non-compliant Lambda functions.',
},
reason: 'Update the VPC configuration for non-compliant Lambda functions.'
}
],
adviseBeforeFixFunction: 'Ensure the provided subnet and security group IDs are correct and appropriate for the Lambda function\'s requirements.',
});
adviseBeforeFixFunction:
"Ensure the provided subnet and security group IDs are correct and appropriate for the Lambda function's requirements."
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const functions = await this.getFunctions();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const functions = await this.getFunctions()
for (const func of functions) {
if (func.VpcConfig && Object.keys(func.VpcConfig).length > 0) {
compliantResources.push(func.FunctionArn!);
compliantResources.push(func.FunctionArn!)
} else {
nonCompliantResources.push(func.FunctionArn!);
nonCompliantResources.push(func.FunctionArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
@ -103,30 +100,30 @@ export class LambdaInsideVPC implements BPSet {
) => {
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const subnetIds = requiredParametersForFix.find(param => param.name === 'subnet-ids')?.value;
const securityGroupIds = requiredParametersForFix.find(param => param.name === 'security-group-ids')?.value;
const subnetIds = requiredParametersForFix.find((param) => param.name === 'subnet-ids')?.value
const securityGroupIds = requiredParametersForFix.find((param) => param.name === 'security-group-ids')?.value
if (!subnetIds || !securityGroupIds) {
throw new Error("Required parameters 'subnet-ids' and/or 'security-group-ids' are missing.");
throw new Error("Required parameters 'subnet-ids' and/or 'security-group-ids' are missing.")
}
for (const functionArn of nonCompliantResources) {
const functionName = functionArn.split(':').pop()!;
const functionName = functionArn.split(':').pop()!
await this.client.send(
new UpdateFunctionConfigurationCommand({
FunctionName: functionName,
@ -135,12 +132,12 @@ export class LambdaInsideVPC implements BPSet {
SecurityGroupIds: securityGroupIds.split(',')
}
})
);
)
}
};
}
private readonly getFunctions = async () => {
const response = await this.memoClient.send(new ListFunctionsCommand({}));
return response.Functions || [];
};
const response = await this.memoClient.send(new ListFunctionsCommand({}))
return response.Functions || []
}
}

View File

@ -1,26 +1,19 @@
import {
RDSClient,
DescribeDBClustersCommand,
ModifyDBClusterCommand
} from '@aws-sdk/client-rds';
import {
BackupClient,
ListRecoveryPointsByResourceCommand
} from '@aws-sdk/client-backup';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
import { BackupClient, ListRecoveryPointsByResourceCommand } from '@aws-sdk/client-backup'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
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 rdsClient = new RDSClient({})
private readonly backupClient = new BackupClient({})
private readonly memoRdsClient = Memorizer.memo(this.rdsClient)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'AuroraLastBackupRecoveryPointCreated',
@ -35,77 +28,75 @@ export class AuroraLastBackupRecoveryPointCreated implements BPSet {
name: 'backup-retention-period',
description: 'The number of days to retain backups for the DB cluster.',
default: '7',
example: '7',
example: '7'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'DescribeDBClustersCommand',
reason: 'Retrieve information about Aurora DB clusters.',
reason: 'Retrieve information about Aurora DB clusters.'
},
{
name: 'ListRecoveryPointsByResourceCommand',
reason: 'Check the recovery points associated with the DB cluster.',
reason: 'Check the recovery points associated with the DB cluster.'
}
],
commandUsedInFixFunction: [
{
name: 'ModifyDBClusterCommand',
reason: 'Update the backup retention period for the DB cluster.',
reason: 'Update the backup retention period for the DB cluster.'
}
],
adviseBeforeFixFunction: 'Ensure that extending the backup retention period aligns with your data protection policies.',
});
adviseBeforeFixFunction:
'Ensure that extending the backup retention period aligns with your data protection policies.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const dbClusters = await this.getDBClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
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());
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!);
if (recoveryDates.length > 0 && new Date().getTime() - recoveryDates[0].getTime() < 24 * 60 * 60 * 1000) {
compliantResources.push(cluster.DBClusterArn!)
} else {
nonCompliantResources.push(cluster.DBClusterArn!);
nonCompliantResources.push(cluster.DBClusterArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
@ -113,50 +104,46 @@ export class AuroraLastBackupRecoveryPointCreated implements BPSet {
) => {
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const retentionPeriod = requiredParametersForFix.find(
param => param.name === 'backup-retention-period'
)?.value;
const retentionPeriod = requiredParametersForFix.find((param) => param.name === 'backup-retention-period')?.value
if (!retentionPeriod) {
throw new Error("Required parameter 'backup-retention-period' is missing.");
throw new Error("Required parameter 'backup-retention-period' is missing.")
}
for (const arn of nonCompliantResources) {
const clusterId = arn.split(':cluster/')[1];
const clusterId = arn.split(':cluster/')[1]
await this.rdsClient.send(
new ModifyDBClusterCommand({
DBClusterIdentifier: clusterId,
BackupRetentionPeriod: parseInt(retentionPeriod, 10)
})
);
)
}
};
}
private readonly getDBClusters = async () => {
const response = await this.memoRdsClient.send(new DescribeDBClustersCommand({}));
return response.DBClusters || [];
};
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 || [];
};
const response = await this.backupClient.send(new ListRecoveryPointsByResourceCommand({ ResourceArn: resourceArn }))
return response.RecoveryPoints || []
}
}

View File

@ -1,21 +1,17 @@
import {
RDSClient,
DescribeDBClustersCommand,
ModifyDBClusterCommand
} from '@aws-sdk/client-rds';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class AuroraMySQLBacktrackingEnabled implements BPSet {
private readonly client = new RDSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new RDSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'AuroraMySQLBacktrackingEnabled',
@ -30,94 +26,91 @@ export class AuroraMySQLBacktrackingEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'DescribeDBClustersCommand',
reason: 'Fetch Aurora MySQL DB clusters and check backtracking configuration.',
},
reason: 'Fetch Aurora MySQL DB clusters and check backtracking configuration.'
}
],
commandUsedInFixFunction: [
{
name: 'ModifyDBClusterCommand',
reason: 'Enable backtracking for non-compliant Aurora MySQL DB clusters.',
},
reason: 'Enable backtracking for non-compliant Aurora MySQL DB clusters.'
}
],
adviseBeforeFixFunction: 'Ensure that enabling backtracking aligns with your application requirements.',
});
adviseBeforeFixFunction: 'Ensure that enabling backtracking aligns with your application requirements.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const dbClusters = await this.getDBClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const dbClusters = await this.getDBClusters()
for (const cluster of dbClusters) {
if (
cluster.Engine === 'aurora-mysql' &&
(!cluster.EarliestBacktrackTime || cluster.EarliestBacktrackTime === null)
) {
nonCompliantResources.push(cluster.DBClusterArn!);
nonCompliantResources.push(cluster.DBClusterArn!)
} else {
compliantResources.push(cluster.DBClusterArn!);
compliantResources.push(cluster.DBClusterArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
public readonly fix = async (nonCompliantResources: string[]) => {
await this.fixImpl(nonCompliantResources)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly fixImpl = async (nonCompliantResources: string[]) => {
for (const arn of nonCompliantResources) {
const clusterId = arn.split(':cluster/')[1];
const clusterId = arn.split(':cluster/')[1]
await this.client.send(
new ModifyDBClusterCommand({
DBClusterIdentifier: clusterId,
BacktrackWindow: 3600, // Set backtracking window to 1 hour
BacktrackWindow: 3600 // Set backtracking window to 1 hour
})
);
)
}
};
}
private readonly getDBClusters = async () => {
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
return response.DBClusters || [];
};
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
return response.DBClusters || []
}
}

View File

@ -1,21 +1,17 @@
import {
RDSClient,
DescribeDBInstancesCommand,
ModifyDBInstanceCommand
} from '@aws-sdk/client-rds';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBInstancesCommand, ModifyDBInstanceCommand } from '@aws-sdk/client-rds'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class DBInstanceBackupEnabled implements BPSet {
private readonly client = new RDSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new RDSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'DBInstanceBackupEnabled',
@ -47,49 +43,49 @@ export class DBInstanceBackupEnabled implements BPSet {
}
],
adviseBeforeFixFunction: 'Ensure the retention period aligns with your organizations backup policy.'
});
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const dbInstances = await this.getDBInstances();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const dbInstances = await this.getDBInstances()
for (const instance of dbInstances) {
if (instance.BackupRetentionPeriod && instance.BackupRetentionPeriod > 0) {
compliantResources.push(instance.DBInstanceArn!);
compliantResources.push(instance.DBInstanceArn!)
} else {
nonCompliantResources.push(instance.DBInstanceArn!);
nonCompliantResources.push(instance.DBInstanceArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
@ -97,42 +93,40 @@ export class DBInstanceBackupEnabled implements BPSet {
) => {
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const retentionPeriod = requiredParametersForFix.find(
(param) => param.name === 'retention-period'
)?.value;
const retentionPeriod = requiredParametersForFix.find((param) => param.name === 'retention-period')?.value
if (!retentionPeriod) {
throw new Error("Required parameter 'retention-period' is missing.");
throw new Error("Required parameter 'retention-period' is missing.")
}
for (const arn of nonCompliantResources) {
const instanceId = arn.split(':instance/')[1];
const instanceId = arn.split(':instance/')[1]
await this.client.send(
new ModifyDBInstanceCommand({
DBInstanceIdentifier: instanceId,
BackupRetentionPeriod: parseInt(retentionPeriod, 10)
})
);
)
}
};
}
private readonly getDBInstances = async () => {
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}));
return response.DBInstances || [];
};
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}))
return response.DBInstances || []
}
}

View File

@ -1,27 +1,24 @@
import {
RDSClient,
DescribeDBClustersCommand,
ModifyDBClusterCommand
} from '@aws-sdk/client-rds';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class RDSClusterAutoMinorVersionUpgradeEnabled implements BPSet {
private readonly client = new RDSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new RDSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'RDSClusterAutoMinorVersionUpgradeEnabled',
description: 'Ensures Auto Minor Version Upgrade is enabled for RDS clusters.',
priority: 1,
priorityReason: 'Auto minor version upgrades help keep the database engine updated with minimal effort, ensuring security and performance improvements.',
priorityReason:
'Auto minor version upgrades help keep the database engine updated with minimal effort, ensuring security and performance improvements.',
awsService: 'RDS',
awsServiceCategory: 'Database',
bestPracticeCategory: 'Configuration Management',
@ -39,83 +36,81 @@ export class RDSClusterAutoMinorVersionUpgradeEnabled implements BPSet {
reason: 'Enable auto minor version upgrade for non-compliant RDS clusters.'
}
],
adviseBeforeFixFunction: 'Ensure that enabling auto minor version upgrades aligns with your organizations change management policies.'
});
adviseBeforeFixFunction:
'Ensure that enabling auto minor version upgrades aligns with your organizations change management policies.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const dbClusters = await this.getDBClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const dbClusters = await this.getDBClusters()
for (const cluster of dbClusters) {
if (cluster.Engine === 'docdb' || cluster.AutoMinorVersionUpgrade) {
compliantResources.push(cluster.DBClusterArn!);
compliantResources.push(cluster.DBClusterArn!)
} else {
nonCompliantResources.push(cluster.DBClusterArn!);
nonCompliantResources.push(cluster.DBClusterArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
public readonly fix = async (nonCompliantResources: string[]) => {
await this.fixImpl(nonCompliantResources)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (nonCompliantResources: string[]) => {
for (const arn of nonCompliantResources) {
const clusterId = arn.split(':cluster/')[1];
const clusterId = arn.split(':cluster/')[1]
await this.client.send(
new ModifyDBClusterCommand({
DBClusterIdentifier: clusterId,
AutoMinorVersionUpgrade: true
})
);
)
}
};
}
private readonly getDBClusters = async () => {
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
return response.DBClusters || [];
};
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
return response.DBClusters || []
}
}

View File

@ -1,21 +1,17 @@
import {
RDSClient,
DescribeDBClustersCommand,
ModifyDBClusterCommand
} from '@aws-sdk/client-rds';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class RDSClusterDefaultAdminCheck implements BPSet {
private readonly client = new RDSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new RDSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'RDSClusterDefaultAdminCheck',
@ -53,49 +49,49 @@ export class RDSClusterDefaultAdminCheck implements BPSet {
}
],
adviseBeforeFixFunction: 'Ensure that the new master username and password comply with your security policies.'
});
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const dbClusters = await this.getDBClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const dbClusters = await this.getDBClusters()
for (const cluster of dbClusters) {
if (!['admin', 'postgres'].includes(cluster.MasterUsername!)) {
compliantResources.push(cluster.DBClusterArn!);
compliantResources.push(cluster.DBClusterArn!)
} else {
nonCompliantResources.push(cluster.DBClusterArn!);
nonCompliantResources.push(cluster.DBClusterArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
@ -103,46 +99,42 @@ export class RDSClusterDefaultAdminCheck implements BPSet {
) => {
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = 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;
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.");
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];
const clusterId = arn.split(':cluster/')[1]
await this.client.send(
new ModifyDBClusterCommand({
DBClusterIdentifier: clusterId,
MasterUserPassword: newMasterPassword
})
);
)
}
};
}
private readonly getDBClusters = async () => {
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
return response.DBClusters || [];
};
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
return response.DBClusters || []
}
}

View File

@ -1,21 +1,17 @@
import {
RDSClient,
DescribeDBClustersCommand,
ModifyDBClusterCommand
} from '@aws-sdk/client-rds';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class RDSClusterDeletionProtectionEnabled implements BPSet {
private readonly client = new RDSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new RDSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'RDSClusterDeletionProtectionEnabled',
@ -40,82 +36,79 @@ export class RDSClusterDeletionProtectionEnabled implements BPSet {
}
],
adviseBeforeFixFunction: 'Ensure that enabling deletion protection aligns with your operational policies.'
});
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const dbClusters = await this.getDBClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const dbClusters = await this.getDBClusters()
for (const cluster of dbClusters) {
if (cluster.DeletionProtection) {
compliantResources.push(cluster.DBClusterArn!);
compliantResources.push(cluster.DBClusterArn!)
} else {
nonCompliantResources.push(cluster.DBClusterArn!);
nonCompliantResources.push(cluster.DBClusterArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
public readonly fix = async (nonCompliantResources: string[]) => {
await this.fixImpl(nonCompliantResources)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (nonCompliantResources: string[]) => {
for (const arn of nonCompliantResources) {
const clusterId = arn.split(':cluster/')[1];
const clusterId = arn.split(':cluster/')[1]
await this.client.send(
new ModifyDBClusterCommand({
DBClusterIdentifier: clusterId,
DeletionProtection: true
})
);
)
}
};
}
private readonly getDBClusters = async () => {
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
return response.DBClusters || [];
};
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
return response.DBClusters || []
}
}

View File

@ -1,20 +1,17 @@
import {
RDSClient,
DescribeDBClustersCommand
} from '@aws-sdk/client-rds';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBClustersCommand } from '@aws-sdk/client-rds'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class RDSClusterEncryptedAtRest implements BPSet {
private readonly client = new RDSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new RDSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'RDSClusterEncryptedAtRest',
@ -33,67 +30,66 @@ export class RDSClusterEncryptedAtRest implements BPSet {
}
],
commandUsedInFixFunction: [],
adviseBeforeFixFunction: 'Manually recreate the RDS cluster with encryption at rest enabled, as fixing this requires destructive operations.'
});
adviseBeforeFixFunction:
'Manually recreate the RDS cluster with encryption at rest enabled, as fixing this requires destructive operations.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const dbClusters = await this.getDBClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const dbClusters = await this.getDBClusters()
for (const cluster of dbClusters) {
if (cluster.StorageEncrypted) {
compliantResources.push(cluster.DBClusterArn!);
compliantResources.push(cluster.DBClusterArn!)
} else {
nonCompliantResources.push(cluster.DBClusterArn!);
nonCompliantResources.push(cluster.DBClusterArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'ERROR';
public readonly fix = async () => {
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: 'Fixing encryption at rest requires recreating the cluster. Please manually recreate the cluster with encryption enabled.'
});
message:
'Fixing encryption at rest requires recreating the cluster. Please manually recreate the cluster with encryption enabled.'
})
throw new Error(
'Fixing encryption at rest requires recreating the cluster. Please manually recreate the cluster with encryption enabled.'
);
};
)
}
private readonly getDBClusters = async () => {
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
return response.DBClusters || [];
};
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
return response.DBClusters || []
}
}

View File

@ -1,21 +1,17 @@
import {
RDSClient,
DescribeDBClustersCommand,
ModifyDBClusterCommand
} from '@aws-sdk/client-rds';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class RDSClusterIAMAuthenticationEnabled implements BPSet {
private readonly client = new RDSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new RDSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'RDSClusterIAMAuthenticationEnabled',
@ -39,91 +35,86 @@ export class RDSClusterIAMAuthenticationEnabled implements BPSet {
reason: 'To enable IAM Database Authentication for non-compliant clusters.'
}
],
adviseBeforeFixFunction: 'Ensure that enabling IAM Database Authentication aligns with your security and application needs.'
});
adviseBeforeFixFunction:
'Ensure that enabling IAM Database Authentication aligns with your security and application needs.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const dbClusters = await this.getDBClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const dbClusters = await this.getDBClusters()
for (const cluster of dbClusters) {
if (
cluster.Engine === 'docdb' ||
cluster.IAMDatabaseAuthenticationEnabled
) {
compliantResources.push(cluster.DBClusterArn!);
if (cluster.Engine === 'docdb' || cluster.IAMDatabaseAuthenticationEnabled) {
compliantResources.push(cluster.DBClusterArn!)
} else {
nonCompliantResources.push(cluster.DBClusterArn!);
nonCompliantResources.push(cluster.DBClusterArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
private readonly fixImpl = async (nonCompliantResources: string[]) => {
for (const arn of nonCompliantResources) {
const clusterId = arn.split(':cluster/')[1];
const clusterId = arn.split(':cluster/')[1]
await this.client.send(
new ModifyDBClusterCommand({
DBClusterIdentifier: clusterId,
EnableIAMDatabaseAuthentication: true
})
);
)
}
};
}
private readonly getDBClusters = async () => {
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
return response.DBClusters || [];
};
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
return response.DBClusters || []
}
}

View File

@ -1,20 +1,17 @@
import {
RDSClient,
DescribeDBClustersCommand
} from '@aws-sdk/client-rds';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBClustersCommand } from '@aws-sdk/client-rds'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class RDSClusterMultiAZEnabled implements BPSet {
private readonly client = new RDSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new RDSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'RDSClusterMultiAZEnabled',
@ -42,80 +39,75 @@ export class RDSClusterMultiAZEnabled implements BPSet {
commandUsedInFixFunction: [],
adviseBeforeFixFunction:
'Enabling Multi-AZ for an existing cluster may require significant reconfiguration and potential downtime. Proceed with caution.'
});
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const dbClusters = await this.getDBClusters();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const dbClusters = await this.getDBClusters()
for (const cluster of dbClusters) {
if ((cluster.AvailabilityZones || []).length > 1) {
compliantResources.push(cluster.DBClusterArn!);
compliantResources.push(cluster.DBClusterArn!)
} else {
nonCompliantResources.push(cluster.DBClusterArn!);
nonCompliantResources.push(cluster.DBClusterArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
throw new Error(
'Enabling Multi-AZ requires cluster reconfiguration. This must be performed manually.'
);
};
private readonly fixImpl = async () => {
throw new Error('Enabling Multi-AZ requires cluster reconfiguration. This must be performed manually.')
}
private readonly getDBClusters = async () => {
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
return response.DBClusters || [];
};
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
return response.DBClusters || []
}
}

View File

@ -1,24 +1,20 @@
import {
RDSClient,
DescribeDBClustersCommand,
ModifyDBClusterCommand
} from '@aws-sdk/client-rds';
import { EC2Client, DescribeSecurityGroupsCommand } from '@aws-sdk/client-ec2';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
import { EC2Client, DescribeSecurityGroupsCommand } from '@aws-sdk/client-ec2'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
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 rdsClient = new RDSClient({})
private readonly ec2Client = new EC2Client({})
private readonly memoRdsClient = Memorizer.memo(this.rdsClient)
private readonly memoEc2Client = Memorizer.memo(this.ec2Client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'RDSDBSecurityGroupNotAllowed',
@ -48,94 +44,91 @@ export class RDSDBSecurityGroupNotAllowed implements BPSet {
],
adviseBeforeFixFunction:
'Ensure valid non-default security groups are associated with the clusters before applying the fix.'
});
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const dbClusters = await this.getDBClusters();
const defaultSecurityGroupIds = (await this.getDefaultSecurityGroups()).map(sg => sg.GroupId!);
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
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') || [];
const activeSecurityGroups = cluster.VpcSecurityGroups?.filter((sg) => sg.Status === 'active') || []
if (activeSecurityGroups.some(sg => defaultSecurityGroupIds.includes(sg.VpcSecurityGroupId!))) {
nonCompliantResources.push(cluster.DBClusterArn!);
if (activeSecurityGroups.some((sg) => defaultSecurityGroupIds.includes(sg.VpcSecurityGroupId!))) {
nonCompliantResources.push(cluster.DBClusterArn!)
} else {
compliantResources.push(cluster.DBClusterArn!);
compliantResources.push(cluster.DBClusterArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
public readonly fix = async (nonCompliantResources: string[]) => {
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (nonCompliantResources: string[]) => {
for (const arn of nonCompliantResources) {
const clusterId = arn.split(':cluster/')[1];
const clusterId = arn.split(':cluster/')[1]
await this.rdsClient.send(
new ModifyDBClusterCommand({
DBClusterIdentifier: clusterId,
VpcSecurityGroupIds: [] // Ensure valid non-default security groups are used here
})
);
)
}
};
}
private readonly getDBClusters = async () => {
const response = await this.memoRdsClient.send(new DescribeDBClustersCommand({}));
return response.DBClusters || [];
};
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 || [];
};
)
return response.SecurityGroups || []
}
}

View File

@ -1,21 +1,17 @@
import {
RDSClient,
DescribeDBInstancesCommand,
ModifyDBInstanceCommand
} from '@aws-sdk/client-rds';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBInstancesCommand, ModifyDBInstanceCommand } from '@aws-sdk/client-rds'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class RDSEnhancedMonitoringEnabled implements BPSet {
private readonly client = new RDSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new RDSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'RDSEnhancedMonitoringEnabled',
@ -47,95 +43,93 @@ export class RDSEnhancedMonitoringEnabled implements BPSet {
}
],
adviseBeforeFixFunction: 'Ensure that enabling Enhanced Monitoring does not conflict with existing configurations.'
});
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const dbInstances = await this.getDBInstances();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const dbInstances = await this.getDBInstances()
for (const instance of dbInstances) {
if (instance.MonitoringInterval && instance.MonitoringInterval > 0) {
compliantResources.push(instance.DBInstanceArn!);
compliantResources.push(instance.DBInstanceArn!)
} else {
nonCompliantResources.push(instance.DBInstanceArn!);
nonCompliantResources.push(instance.DBInstanceArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const monitoringInterval = requiredParametersForFix.find(
(param) => param.name === 'monitoring-interval'
)?.value;
const monitoringInterval = requiredParametersForFix.find((param) => param.name === 'monitoring-interval')?.value
if (!monitoringInterval) {
throw new Error("Required parameter 'monitoring-interval' is missing.");
throw new Error("Required parameter 'monitoring-interval' is missing.")
}
for (const arn of nonCompliantResources) {
const instanceId = arn.split(':instance/')[1];
const instanceId = arn.split(':instance/')[1]
await this.client.send(
new ModifyDBInstanceCommand({
DBInstanceIdentifier: instanceId,
MonitoringInterval: parseInt(monitoringInterval, 10)
})
);
)
}
};
}
private readonly getDBInstances = async () => {
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}));
return response.DBInstances || [];
};
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}))
return response.DBInstances || []
}
}

View File

@ -1,21 +1,17 @@
import {
RDSClient,
DescribeDBInstancesCommand,
ModifyDBInstanceCommand
} from '@aws-sdk/client-rds';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBInstancesCommand, ModifyDBInstanceCommand } from '@aws-sdk/client-rds'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class RDSInstancePublicAccessCheck implements BPSet {
private readonly client = new RDSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new RDSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'RDSInstancePublicAccessCheck',
@ -39,88 +35,86 @@ export class RDSInstancePublicAccessCheck implements BPSet {
reason: 'Disables public access for non-compliant RDS instances.'
}
],
adviseBeforeFixFunction: 'Ensure there are valid private network configurations in place before disabling public access.'
});
adviseBeforeFixFunction:
'Ensure there are valid private network configurations in place before disabling public access.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const dbInstances = await this.getDBInstances();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const dbInstances = await this.getDBInstances()
for (const instance of dbInstances) {
if (instance.PubliclyAccessible) {
nonCompliantResources.push(instance.DBInstanceArn!);
nonCompliantResources.push(instance.DBInstanceArn!)
} else {
compliantResources.push(instance.DBInstanceArn!);
compliantResources.push(instance.DBInstanceArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
private readonly fixImpl = async (nonCompliantResources: string[]) => {
for (const arn of nonCompliantResources) {
const instanceId = arn.split(':instance/')[1];
const instanceId = arn.split(':instance/')[1]
await this.client.send(
new ModifyDBInstanceCommand({
DBInstanceIdentifier: instanceId,
PubliclyAccessible: false
})
);
)
}
};
}
private readonly getDBInstances = async () => {
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}));
return response.DBInstances || [];
};
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}))
return response.DBInstances || []
}
}

View File

@ -1,21 +1,17 @@
import {
RDSClient,
DescribeDBClustersCommand,
ModifyDBClusterCommand
} from '@aws-sdk/client-rds';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class RDSLoggingEnabled implements BPSet {
private readonly client = new RDSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new RDSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'RDSLoggingEnabled',
@ -40,111 +36,108 @@ export class RDSLoggingEnabled implements BPSet {
}
],
adviseBeforeFixFunction: 'Ensure that enabling logs does not negatively impact application performance.'
});
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const logsForEngine: Record<string, string[]> = {
'aurora-mysql': ['audit', 'error', 'general', 'slowquery'],
'aurora-postgresql': ['postgresql'],
'docdb': ['audit', 'profiler'],
'mysql': ['audit', 'error', 'general', 'slowquery'],
'postgresql': ['postgresql', 'upgrade'],
};
docdb: ['audit', 'profiler'],
mysql: ['audit', 'error', 'general', 'slowquery'],
postgresql: ['postgresql', 'upgrade']
}
const dbClusters = await this.getDBClusters();
const dbClusters = await this.getDBClusters()
for (const cluster of dbClusters) {
const requiredLogs = logsForEngine[cluster.Engine!] || [];
const enabledLogs = cluster.EnabledCloudwatchLogsExports || [];
const requiredLogs = logsForEngine[cluster.Engine!] || []
const enabledLogs = cluster.EnabledCloudwatchLogsExports || []
if (JSON.stringify(enabledLogs) === JSON.stringify(requiredLogs)) {
compliantResources.push(cluster.DBClusterArn!);
compliantResources.push(cluster.DBClusterArn!)
} else {
nonCompliantResources.push(cluster.DBClusterArn!);
nonCompliantResources.push(cluster.DBClusterArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
private readonly fixImpl = async (nonCompliantResources: string[]) => {
const logsForEngine: Record<string, string[]> = {
'aurora-mysql': ['audit', 'error', 'general', 'slowquery'],
'aurora-postgresql': ['postgresql'],
'docdb': ['audit', 'profiler']
};
docdb: ['audit', 'profiler']
}
const dbClusters = await this.getDBClusters();
const dbClusters = await this.getDBClusters()
for (const arn of nonCompliantResources) {
const clusterId = arn.split(':cluster/')[1];
const cluster = dbClusters.find((c) => c.DBClusterArn === arn);
const clusterId = arn.split(':cluster/')[1]
const cluster = dbClusters.find((c) => c.DBClusterArn === arn)
if (cluster) {
const logsToEnable = logsForEngine[cluster.Engine!] || [];
const logsToEnable = logsForEngine[cluster.Engine!] || []
await this.client.send(
new ModifyDBClusterCommand({
DBClusterIdentifier: clusterId,
CloudwatchLogsExportConfiguration: { EnableLogTypes: logsToEnable }
})
);
)
}
}
};
}
private readonly getDBClusters = async () => {
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
return response.DBClusters || [];
};
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
return response.DBClusters || []
}
}

View File

@ -1,21 +1,17 @@
import {
RDSClient,
DescribeDBClusterSnapshotsCommand,
CopyDBClusterSnapshotCommand
} from '@aws-sdk/client-rds';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { RDSClient, DescribeDBClusterSnapshotsCommand, CopyDBClusterSnapshotCommand } from '@aws-sdk/client-rds'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class RDSSnapshotEncrypted implements BPSet {
private readonly client = new RDSClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new RDSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'RDSSnapshotEncrypted',
@ -47,83 +43,81 @@ export class RDSSnapshotEncrypted implements BPSet {
}
],
adviseBeforeFixFunction: 'Ensure that the KMS key is properly configured and accessible.'
});
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const snapshots = await this.getDBClusterSnapshots();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const snapshots = await this.getDBClusterSnapshots()
for (const snapshot of snapshots) {
if (snapshot.StorageEncrypted) {
compliantResources.push(snapshot.DBClusterSnapshotArn!);
compliantResources.push(snapshot.DBClusterSnapshotArn!)
} else {
nonCompliantResources.push(snapshot.DBClusterSnapshotArn!);
nonCompliantResources.push(snapshot.DBClusterSnapshotArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const kmsKeyId = requiredParametersForFix.find(
(param) => param.name === 'kms-key-id'
)?.value;
const kmsKeyId = requiredParametersForFix.find((param) => param.name === 'kms-key-id')?.value
if (!kmsKeyId) {
throw new Error("Required parameter 'kms-key-id' is missing.");
throw new Error("Required parameter 'kms-key-id' is missing.")
}
for (const arn of nonCompliantResources) {
const snapshotId = arn.split(':snapshot:')[1];
const snapshotId = arn.split(':snapshot:')[1]
await this.client.send(
new CopyDBClusterSnapshotCommand({
@ -131,12 +125,12 @@ export class RDSSnapshotEncrypted implements BPSet {
TargetDBClusterSnapshotIdentifier: `${snapshotId}-encrypted`,
KmsKeyId: kmsKeyId
})
);
)
}
};
}
private readonly getDBClusterSnapshots = async () => {
const response = await this.memoClient.send(new DescribeDBClusterSnapshotsCommand({}));
return response.DBClusterSnapshots || [];
};
const response = await this.memoClient.send(new DescribeDBClusterSnapshotsCommand({}))
return response.DBClusterSnapshots || []
}
}

View File

@ -3,22 +3,22 @@ import {
ListAccessPointsCommand,
DeleteAccessPointCommand,
CreateAccessPointCommand
} from '@aws-sdk/client-s3-control';
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
} from '@aws-sdk/client-s3-control'
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class S3AccessPointInVpcOnly implements BPSet {
private readonly client = new S3ControlClient({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly stsClient = Memorizer.memo(new STSClient({}));
private readonly client = new S3ControlClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stsClient = Memorizer.memo(new STSClient({}))
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'S3AccessPointInVpcOnly',
@ -54,94 +54,92 @@ export class S3AccessPointInVpcOnly implements BPSet {
}
],
adviseBeforeFixFunction: 'Ensure that the specified VPC ID is correct and accessible.'
});
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const accountId = await this.getAccountId();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const accountId = await this.getAccountId()
const response = await this.memoClient.send(
new ListAccessPointsCommand({ AccountId: accountId })
);
const response = await this.memoClient.send(new ListAccessPointsCommand({ AccountId: accountId }))
for (const accessPoint of response.AccessPointList || []) {
if (accessPoint.NetworkOrigin === 'VPC') {
compliantResources.push(accessPoint.AccessPointArn!);
compliantResources.push(accessPoint.AccessPointArn!)
} else {
nonCompliantResources.push(accessPoint.AccessPointArn!);
nonCompliantResources.push(accessPoint.AccessPointArn!)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const accountId = await this.getAccountId();
const vpcId = requiredParametersForFix.find(param => param.name === 'your-vpc-id')?.value;
const accountId = await this.getAccountId()
const vpcId = requiredParametersForFix.find((param) => param.name === 'your-vpc-id')?.value
if (!vpcId) {
throw new Error("Required parameter 'your-vpc-id' is missing.");
throw new Error("Required parameter 'your-vpc-id' is missing.")
}
for (const accessPointArn of nonCompliantResources) {
const accessPointName = accessPointArn.split(':').pop()!;
const bucketName = accessPointArn.split('/')[1]!;
const accessPointName = accessPointArn.split(':').pop()!
const bucketName = accessPointArn.split('/')[1]!
await this.client.send(
new DeleteAccessPointCommand({
AccountId: accountId,
Name: accessPointName
})
);
)
await this.client.send(
new CreateAccessPointCommand({
@ -152,12 +150,12 @@ export class S3AccessPointInVpcOnly implements BPSet {
VpcId: vpcId
}
})
);
)
}
};
}
private readonly getAccountId = async (): Promise<string> => {
const response = await this.stsClient.send(new GetCallerIdentityCommand({}));
return response.Account!;
};
const response = await this.stsClient.send(new GetCallerIdentityCommand({}))
return response.Account!
}
}

View File

@ -3,26 +3,27 @@ import {
ListBucketsCommand,
GetObjectLockConfigurationCommand,
PutObjectLockConfigurationCommand
} from '@aws-sdk/client-s3';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
} from '@aws-sdk/client-s3'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class S3BucketDefaultLockEnabled implements BPSet {
private readonly client = new S3Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new S3Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'S3BucketDefaultLockEnabled',
description: 'Ensures that all S3 buckets have default object lock configuration enabled.',
priority: 2,
priorityReason: 'Object lock configuration ensures immutability of bucket objects, protecting them from unintended deletions or modifications.',
priorityReason:
'Object lock configuration ensures immutability of bucket objects, protecting them from unintended deletions or modifications.',
awsService: 'S3',
awsServiceCategory: 'Buckets',
bestPracticeCategory: 'Data Protection',
@ -40,83 +41,79 @@ export class S3BucketDefaultLockEnabled implements BPSet {
reason: 'Enables object lock configuration with a default retention rule.'
}
],
adviseBeforeFixFunction: 'Ensure that the S3 bucket has object lock enabled before running the fix function, as this operation cannot be undone.'
});
adviseBeforeFixFunction:
'Ensure that the S3 bucket has object lock enabled before running the fix function, as this operation cannot be undone.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const buckets = await this.getBuckets();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const buckets = await this.getBuckets()
for (const bucket of buckets) {
try {
await this.memoClient.send(
new GetObjectLockConfigurationCommand({ Bucket: bucket.Name! })
);
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
await this.memoClient.send(new GetObjectLockConfigurationCommand({ Bucket: bucket.Name! }))
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
} catch (error) {
if ((error as any).name === 'ObjectLockConfigurationNotFoundError') {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
if ((error as unknown).name === 'ObjectLockConfigurationNotFoundError') {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
} else {
throw error;
throw error
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
private readonly fixImpl = async (nonCompliantResources: string[]) => {
for (const bucketArn of nonCompliantResources) {
const bucketName = bucketArn.split(':::')[1]!;
const bucketName = bucketArn.split(':::')[1]!
await this.client.send(
new PutObjectLockConfigurationCommand({
Bucket: bucketName,
@ -130,12 +127,12 @@ export class S3BucketDefaultLockEnabled implements BPSet {
}
}
})
);
)
}
};
}
private readonly getBuckets = async () => {
const response = await this.memoClient.send(new ListBucketsCommand({}));
return response.Buckets || [];
};
const response = await this.memoClient.send(new ListBucketsCommand({}))
return response.Buckets || []
}
}

View File

@ -3,20 +3,20 @@ import {
ListBucketsCommand,
GetPublicAccessBlockCommand,
PutPublicAccessBlockCommand
} from '@aws-sdk/client-s3';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
} from '@aws-sdk/client-s3'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class S3BucketLevelPublicAccessProhibited implements BPSet {
private readonly client = new S3Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new S3Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: []
};
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'S3BucketLevelPublicAccessProhibited',
@ -41,88 +41,83 @@ export class S3BucketLevelPublicAccessProhibited implements BPSet {
}
],
adviseBeforeFixFunction: 'Ensure no legitimate use cases require public access before applying this fix.'
});
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const buckets = await this.getBuckets();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const buckets = await this.getBuckets()
for (const bucket of buckets) {
try {
const response = await this.memoClient.send(
new GetPublicAccessBlockCommand({ Bucket: bucket.Name! })
);
const config = response.PublicAccessBlockConfiguration;
const response = await this.memoClient.send(new GetPublicAccessBlockCommand({ Bucket: bucket.Name! }))
const config = response.PublicAccessBlockConfiguration
if (
config?.BlockPublicAcls &&
config?.IgnorePublicAcls &&
config?.BlockPublicPolicy &&
config?.RestrictPublicBuckets
) {
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
} else {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
}
} catch {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message
});
});
};
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
private readonly fixImpl = async (nonCompliantResources: string[]) => {
for (const bucketArn of nonCompliantResources) {
const bucketName = bucketArn.split(':::')[1]!;
const bucketName = bucketArn.split(':::')[1]!
await this.client.send(
new PutPublicAccessBlockCommand({
Bucket: bucketName,
@ -133,12 +128,12 @@ export class S3BucketLevelPublicAccessProhibited implements BPSet {
RestrictPublicBuckets: true
}
})
);
)
}
};
}
private readonly getBuckets = async () => {
const response = await this.memoClient.send(new ListBucketsCommand({}));
return response.Buckets || [];
};
const response = await this.memoClient.send(new ListBucketsCommand({}))
return response.Buckets || []
}
}

View File

@ -1,29 +1,23 @@
import {
S3Client,
ListBucketsCommand,
GetBucketLoggingCommand,
PutBucketLoggingCommand,
} from '@aws-sdk/client-s3';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { S3Client, ListBucketsCommand, GetBucketLoggingCommand, PutBucketLoggingCommand } from '@aws-sdk/client-s3'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class S3BucketLoggingEnabled implements BPSet {
private readonly client = new S3Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new S3Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'S3BucketLoggingEnabled',
description: 'Ensures that S3 buckets have logging enabled.',
priority: 2,
priorityReason:
'Enabling logging on S3 buckets provides audit and security capabilities.',
priorityReason: 'Enabling logging on S3 buckets provides audit and security capabilities.',
awsService: 'S3',
awsServiceCategory: 'Buckets',
bestPracticeCategory: 'Logging',
@ -32,93 +26,90 @@ export class S3BucketLoggingEnabled implements BPSet {
name: 'log-destination-bucket',
description: 'The bucket where access logs should be stored.',
default: '',
example: 'my-log-bucket',
},
example: 'my-log-bucket'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'GetBucketLoggingCommand',
reason: 'Retrieves the logging configuration for the bucket.',
},
reason: 'Retrieves the logging configuration for the bucket.'
}
],
commandUsedInFixFunction: [
{
name: 'PutBucketLoggingCommand',
reason: 'Enables logging on the bucket.',
},
reason: 'Enables logging on the bucket.'
}
],
adviseBeforeFixFunction:
'Ensure the destination bucket for logs exists and has proper permissions for logging.',
});
adviseBeforeFixFunction: 'Ensure the destination bucket for logs exists and has proper permissions for logging.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const buckets = await this.getBuckets();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const buckets = await this.getBuckets()
for (const bucket of buckets) {
try {
const response = await this.memoClient.send(
new GetBucketLoggingCommand({ Bucket: bucket.Name! })
);
const response = await this.memoClient.send(new GetBucketLoggingCommand({ Bucket: bucket.Name! }))
if (response.LoggingEnabled) {
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
} else {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
}
} catch (error) {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
} catch {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
@ -126,30 +117,30 @@ export class S3BucketLoggingEnabled implements BPSet {
) => {
const logDestinationBucket = requiredParametersForFix.find(
(param) => param.name === 'log-destination-bucket'
)?.value;
)?.value
if (!logDestinationBucket) {
throw new Error("Required parameter 'log-destination-bucket' is missing.");
throw new Error("Required parameter 'log-destination-bucket' is missing.")
}
for (const bucketArn of nonCompliantResources) {
const bucketName = bucketArn.split(':::')[1]!;
const bucketName = bucketArn.split(':::')[1]!
await this.client.send(
new PutBucketLoggingCommand({
Bucket: bucketName,
BucketLoggingStatus: {
LoggingEnabled: {
TargetBucket: logDestinationBucket,
TargetPrefix: `${bucketName}/logs/`,
},
},
TargetPrefix: `${bucketName}/logs/`
}
}
})
);
)
}
};
}
private readonly getBuckets = async () => {
const response = await this.memoClient.send(new ListBucketsCommand({}));
return response.Buckets || [];
};
const response = await this.memoClient.send(new ListBucketsCommand({}))
return response.Buckets || []
}
}

View File

@ -1,22 +1,17 @@
import {
S3Client,
ListBucketsCommand,
GetBucketPolicyCommand,
PutBucketPolicyCommand,
} from '@aws-sdk/client-s3';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { S3Client, ListBucketsCommand, GetBucketPolicyCommand, PutBucketPolicyCommand } from '@aws-sdk/client-s3'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class S3BucketSSLRequestsOnly implements BPSet {
private readonly client = new S3Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new S3Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'S3BucketSSLRequestsOnly',
@ -31,115 +26,105 @@ export class S3BucketSSLRequestsOnly implements BPSet {
commandUsedInCheckFunction: [
{
name: 'GetBucketPolicyCommand',
reason: 'Retrieves the bucket policy to check for SSL conditions.',
},
reason: 'Retrieves the bucket policy to check for SSL conditions.'
}
],
commandUsedInFixFunction: [
{
name: 'PutBucketPolicyCommand',
reason: 'Updates the bucket policy to enforce SSL requests.',
},
reason: 'Updates the bucket policy to enforce SSL requests.'
}
],
adviseBeforeFixFunction:
'Ensure existing bucket policies will not conflict with the SSL-only policy.',
});
adviseBeforeFixFunction: 'Ensure existing bucket policies will not conflict with the SSL-only policy.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const buckets = await this.getBuckets();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const buckets = await this.getBuckets()
for (const bucket of buckets) {
try {
const response = await this.memoClient.send(
new GetBucketPolicyCommand({ Bucket: bucket.Name! })
);
const policy = JSON.parse(response.Policy!);
const response = await this.memoClient.send(new GetBucketPolicyCommand({ Bucket: bucket.Name! }))
const policy = JSON.parse(response.Policy!)
const hasSSLCondition = policy.Statement.some(
(stmt: any) =>
stmt.Condition &&
stmt.Condition.Bool &&
stmt.Condition.Bool['aws:SecureTransport'] === 'false'
);
(stmt: unknown) =>
stmt.Condition && stmt.Condition.Bool && stmt.Condition.Bool['aws:SecureTransport'] === 'false'
)
if (hasSSLCondition) {
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
} else {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
}
} catch (error) {
if ((error as any).name === 'NoSuchBucketPolicy') {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
if ((error as unknown).name === 'NoSuchBucketPolicy') {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
} else {
throw error;
throw error
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
private readonly fixImpl = async (nonCompliantResources: string[]) => {
for (const bucketArn of nonCompliantResources) {
const bucketName = bucketArn.split(':::')[1]!;
let existingPolicy: any;
const bucketName = bucketArn.split(':::')[1]!
let existingPolicy: unknown
try {
const response = await this.memoClient.send(
new GetBucketPolicyCommand({ Bucket: bucketName })
);
existingPolicy = JSON.parse(response.Policy!);
const response = await this.memoClient.send(new GetBucketPolicyCommand({ Bucket: bucketName }))
existingPolicy = JSON.parse(response.Policy!)
} catch (error) {
if ((error as any).name !== 'NoSuchBucketPolicy') {
throw error;
if ((error as unknown).name !== 'NoSuchBucketPolicy') {
throw error
}
}
@ -151,27 +136,27 @@ export class S3BucketSSLRequestsOnly implements BPSet {
Resource: [`arn:aws:s3:::${bucketName}/*`, `arn:aws:s3:::${bucketName}`],
Condition: {
Bool: {
'aws:SecureTransport': 'false',
},
},
};
'aws:SecureTransport': 'false'
}
}
}
let updatedPolicy;
let updatedPolicy
if (existingPolicy) {
existingPolicy.Statement.push(sslPolicyStatement);
updatedPolicy = JSON.stringify(existingPolicy);
existingPolicy.Statement.push(sslPolicyStatement)
updatedPolicy = JSON.stringify(existingPolicy)
} else {
updatedPolicy = this.createSSLOnlyPolicy(bucketName);
updatedPolicy = this.createSSLOnlyPolicy(bucketName)
}
await this.client.send(
new PutBucketPolicyCommand({
Bucket: bucketName,
Policy: updatedPolicy,
Policy: updatedPolicy
})
);
)
}
};
}
private readonly createSSLOnlyPolicy = (bucketName: string): string => {
return JSON.stringify({
@ -185,16 +170,16 @@ export class S3BucketSSLRequestsOnly implements BPSet {
Resource: [`arn:aws:s3:::${bucketName}/*`, `arn:aws:s3:::${bucketName}`],
Condition: {
Bool: {
'aws:SecureTransport': 'false',
},
},
},
],
});
};
'aws:SecureTransport': 'false'
}
}
}
]
})
}
private readonly getBuckets = async () => {
const response = await this.memoClient.send(new ListBucketsCommand({}));
return response.Buckets || [];
};
const response = await this.memoClient.send(new ListBucketsCommand({}))
return response.Buckets || []
}
}

View File

@ -2,27 +2,28 @@ import {
S3Client,
ListBucketsCommand,
GetBucketVersioningCommand,
PutBucketVersioningCommand,
} from '@aws-sdk/client-s3';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
PutBucketVersioningCommand
} from '@aws-sdk/client-s3'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class S3BucketVersioningEnabled implements BPSet {
private readonly client = new S3Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new S3Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'S3BucketVersioningEnabled',
description: 'Ensures that versioning is enabled on all S3 buckets.',
priority: 1,
priorityReason: 'Enabling versioning protects against accidental data loss and allows recovery of previous versions.',
priorityReason:
'Enabling versioning protects against accidental data loss and allows recovery of previous versions.',
awsService: 'S3',
awsServiceCategory: 'Buckets',
bestPracticeCategory: 'Data Protection',
@ -31,101 +32,96 @@ export class S3BucketVersioningEnabled implements BPSet {
commandUsedInCheckFunction: [
{
name: 'GetBucketVersioningCommand',
reason: 'Retrieve the current versioning status of the bucket.',
},
reason: 'Retrieve the current versioning status of the bucket.'
}
],
commandUsedInFixFunction: [
{
name: 'PutBucketVersioningCommand',
reason: 'Enable versioning on the bucket.',
},
reason: 'Enable versioning on the bucket.'
}
],
adviseBeforeFixFunction: 'Ensure that enabling versioning aligns with your data lifecycle and cost considerations.',
});
adviseBeforeFixFunction: 'Ensure that enabling versioning aligns with your data lifecycle and cost considerations.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const buckets = await this.getBuckets();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const buckets = await this.getBuckets()
for (const bucket of buckets) {
const response = await this.memoClient.send(
new GetBucketVersioningCommand({ Bucket: bucket.Name! })
);
const response = await this.memoClient.send(new GetBucketVersioningCommand({ Bucket: bucket.Name! }))
if (response.Status === 'Enabled') {
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
} else {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
private readonly fixImpl = async (nonCompliantResources: string[]) => {
for (const bucketArn of nonCompliantResources) {
const bucketName = bucketArn.split(':::')[1]!;
const bucketName = bucketArn.split(':::')[1]!
await this.client.send(
new PutBucketVersioningCommand({
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Enabled',
},
Status: 'Enabled'
}
})
);
)
}
};
}
private readonly getBuckets = async () => {
const response = await this.memoClient.send(new ListBucketsCommand({}));
return response.Buckets || [];
};
const response = await this.memoClient.send(new ListBucketsCommand({}))
return response.Buckets || []
}
}

View File

@ -2,21 +2,21 @@ import {
S3Client,
ListBucketsCommand,
GetBucketEncryptionCommand,
PutBucketEncryptionCommand,
} from '@aws-sdk/client-s3';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
PutBucketEncryptionCommand
} from '@aws-sdk/client-s3'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class S3DefaultEncryptionKMS implements BPSet {
private readonly client = new S3Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new S3Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'S3DefaultEncryptionKMS',
@ -31,117 +31,114 @@ export class S3DefaultEncryptionKMS implements BPSet {
name: 'kms-key-id',
description: 'The KMS Key ID used for bucket encryption.',
default: '',
example: 'arn:aws:kms:us-east-1:123456789012:key/abcd1234-5678-90ab-cdef-EXAMPLE12345',
},
example: 'arn:aws:kms:us-east-1:123456789012:key/abcd1234-5678-90ab-cdef-EXAMPLE12345'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'GetBucketEncryptionCommand',
reason: 'Retrieve the encryption configuration for a bucket.',
},
reason: 'Retrieve the encryption configuration for a bucket.'
}
],
commandUsedInFixFunction: [
{
name: 'PutBucketEncryptionCommand',
reason: 'Enable KMS encryption for the bucket.',
},
reason: 'Enable KMS encryption for the bucket.'
}
],
adviseBeforeFixFunction:
'Ensure the KMS key is properly configured with necessary permissions for S3 operations.',
});
adviseBeforeFixFunction: 'Ensure the KMS key is properly configured with necessary permissions for S3 operations.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const buckets = await this.getBuckets();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const buckets = await this.getBuckets()
for (const bucket of buckets) {
try {
const response = await this.memoClient.send(
new GetBucketEncryptionCommand({ Bucket: bucket.Name! })
);
const encryption = response.ServerSideEncryptionConfiguration!;
const response = await this.memoClient.send(new GetBucketEncryptionCommand({ Bucket: bucket.Name! }))
const encryption = response.ServerSideEncryptionConfiguration!
const isKmsEnabled = encryption.Rules?.some(
(rule) =>
rule.ApplyServerSideEncryptionByDefault &&
rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm === 'aws:kms'
);
)
if (isKmsEnabled) {
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
} else {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
}
} catch (error) {
if ((error as any).name === 'ServerSideEncryptionConfigurationNotFoundError') {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
if ((error as unknown).name === 'ServerSideEncryptionConfigurationNotFoundError') {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
} else {
throw error;
throw error
}
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const kmsKeyId = requiredParametersForFix.find((param) => param.name === 'kms-key-id')?.value;
const kmsKeyId = requiredParametersForFix.find((param) => param.name === 'kms-key-id')?.value
if (!kmsKeyId) {
throw new Error("Required parameter 'kms-key-id' is missing.");
throw new Error("Required parameter 'kms-key-id' is missing.")
}
for (const bucketArn of nonCompliantResources) {
const bucketName = bucketArn.split(':::')[1]!;
const bucketName = bucketArn.split(':::')[1]!
await this.client.send(
new PutBucketEncryptionCommand({
Bucket: bucketName,
@ -150,18 +147,18 @@ export class S3DefaultEncryptionKMS implements BPSet {
{
ApplyServerSideEncryptionByDefault: {
SSEAlgorithm: 'aws:kms',
KMSMasterKeyID: kmsKeyId,
},
},
],
},
KMSMasterKeyID: kmsKeyId
}
}
]
}
})
);
)
}
};
}
private readonly getBuckets = async () => {
const response = await this.memoClient.send(new ListBucketsCommand({}));
return response.Buckets || [];
};
const response = await this.memoClient.send(new ListBucketsCommand({}))
return response.Buckets || []
}
}

View File

@ -2,27 +2,28 @@ import {
S3Client,
ListBucketsCommand,
GetBucketNotificationConfigurationCommand,
PutBucketNotificationConfigurationCommand,
} from '@aws-sdk/client-s3';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
PutBucketNotificationConfigurationCommand
} from '@aws-sdk/client-s3'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class S3EventNotificationsEnabled implements BPSet {
private readonly client = new S3Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly client = new S3Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'S3EventNotificationsEnabled',
description: 'Ensures that S3 buckets have event notifications configured.',
priority: 2,
priorityReason: 'Event notifications facilitate automated responses to S3 events, enhancing automation and security.',
priorityReason:
'Event notifications facilitate automated responses to S3 events, enhancing automation and security.',
awsService: 'S3',
awsServiceCategory: 'Buckets',
bestPracticeCategory: 'Monitoring & Automation',
@ -31,118 +32,108 @@ export class S3EventNotificationsEnabled implements BPSet {
name: 'lambda-function-arn',
description: 'ARN of the Lambda function to invoke for bucket events.',
default: '',
example: 'arn:aws:lambda:us-east-1:123456789012:function:example-function',
example: 'arn:aws:lambda:us-east-1:123456789012:function:example-function'
},
{
name: 'event-type',
description: 'S3 event type to trigger the notification.',
default: 's3:ObjectCreated:*',
example: 's3:ObjectCreated:Put',
},
example: 's3:ObjectCreated:Put'
}
],
isFixFunctionUsesDestructiveCommand: false,
commandUsedInCheckFunction: [
{
name: 'GetBucketNotificationConfigurationCommand',
reason: 'Retrieve the current notification configuration for a bucket.',
},
reason: 'Retrieve the current notification configuration for a bucket.'
}
],
commandUsedInFixFunction: [
{
name: 'PutBucketNotificationConfigurationCommand',
reason: 'Add or update event notifications for the bucket.',
},
reason: 'Add or update event notifications for the bucket.'
}
],
adviseBeforeFixFunction: 'Ensure the Lambda function has necessary permissions to handle the S3 events.',
});
adviseBeforeFixFunction: 'Ensure the Lambda function has necessary permissions to handle the S3 events.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const buckets = await this.getBuckets();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const buckets = await this.getBuckets()
for (const bucket of buckets) {
const response = await this.memoClient.send(
new GetBucketNotificationConfigurationCommand({ Bucket: bucket.Name! })
);
if (
response.LambdaFunctionConfigurations ||
response.QueueConfigurations ||
response.TopicConfigurations
) {
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
)
if (response.LambdaFunctionConfigurations || response.QueueConfigurations || response.TopicConfigurations) {
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
} else {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly fixImpl = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const lambdaArn = requiredParametersForFix.find(
(param) => param.name === 'lambda-function-arn'
)?.value;
const eventType = requiredParametersForFix.find(
(param) => param.name === 'event-type'
)?.value;
const lambdaArn = requiredParametersForFix.find((param) => param.name === 'lambda-function-arn')?.value
const eventType = requiredParametersForFix.find((param) => param.name === 'event-type')?.value
if (!lambdaArn || !eventType) {
throw new Error(
"Required parameters 'lambda-function-arn' and/or 'event-type' are missing."
);
throw new Error("Required parameters 'lambda-function-arn' and/or 'event-type' are missing.")
}
for (const bucketArn of nonCompliantResources) {
const bucketName = bucketArn.split(':::')[1]!;
const bucketName = bucketArn.split(':::')[1]!
await this.client.send(
new PutBucketNotificationConfigurationCommand({
Bucket: bucketName,
@ -150,17 +141,17 @@ export class S3EventNotificationsEnabled implements BPSet {
LambdaFunctionConfigurations: [
{
LambdaFunctionArn: lambdaArn,
Events: [eventType as any],
},
],
},
Events: [eventType as unknown]
}
]
}
})
);
)
}
};
}
private readonly getBuckets = async () => {
const response = await this.memoClient.send(new ListBucketsCommand({}));
return response.Buckets || [];
};
const response = await this.memoClient.send(new ListBucketsCommand({}))
return response.Buckets || []
}
}

View File

@ -1,22 +1,19 @@
import {
S3Client,
ListBucketsCommand,
} from '@aws-sdk/client-s3';
import { BackupClient, ListRecoveryPointsByResourceCommand } from '@aws-sdk/client-backup';
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
import { Memorizer } from '../../Memorizer';
import { S3Client, ListBucketsCommand } from '@aws-sdk/client-s3'
import { BackupClient, ListRecoveryPointsByResourceCommand } from '@aws-sdk/client-backup'
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
import { Memorizer } from '../../Memorizer'
export class S3LastBackupRecoveryPointCreated implements BPSet {
private readonly client = new S3Client({});
private readonly memoClient = Memorizer.memo(this.client);
private readonly backupClient = Memorizer.memo(new BackupClient({}));
private readonly client = new S3Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly backupClient = Memorizer.memo(new BackupClient({}))
private readonly stats: BPSetStats = {
compliantResources: [],
nonCompliantResources: [],
status: 'LOADED',
errorMessage: [],
};
errorMessage: []
}
public readonly getMetadata = (): BPSetMetadata => ({
name: 'S3LastBackupRecoveryPointCreated',
@ -31,77 +28,72 @@ export class S3LastBackupRecoveryPointCreated implements BPSet {
commandUsedInCheckFunction: [
{
name: 'ListRecoveryPointsByResourceCommand',
reason: 'Checks for recent recovery points for the S3 bucket.',
},
reason: 'Checks for recent recovery points for the S3 bucket.'
}
],
commandUsedInFixFunction: [],
adviseBeforeFixFunction: 'Ensure the backup plan for S3 buckets is appropriately configured before proceeding.',
});
adviseBeforeFixFunction: 'Ensure the backup plan for S3 buckets is appropriately configured before proceeding.'
})
public readonly getStats = () => this.stats;
public readonly getStats = () => this.stats
public readonly clearStats = () => {
this.stats.compliantResources = [];
this.stats.nonCompliantResources = [];
this.stats.status = 'LOADED';
this.stats.errorMessage = [];
};
this.stats.compliantResources = []
this.stats.nonCompliantResources = []
this.stats.status = 'LOADED'
this.stats.errorMessage = []
}
public readonly check = async () => {
this.stats.status = 'CHECKING';
this.stats.status = 'CHECKING'
await this.checkImpl()
.then(() => {
this.stats.status = 'FINISHED';
this.stats.status = 'FINISHED'
})
.catch((err) => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: err.message,
});
});
};
message: err.message
})
})
}
private readonly checkImpl = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
const buckets = await this.getBuckets();
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const buckets = await this.getBuckets()
for (const bucket of buckets) {
const recoveryPoints = await this.backupClient.send(
new ListRecoveryPointsByResourceCommand({
ResourceArn: `arn:aws:s3:::${bucket.Name!}`,
ResourceArn: `arn:aws:s3:::${bucket.Name!}`
})
);
)
if (
recoveryPoints.RecoveryPoints &&
recoveryPoints.RecoveryPoints.length > 0
) {
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
if (recoveryPoints.RecoveryPoints && recoveryPoints.RecoveryPoints.length > 0) {
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
} else {
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
}
}
this.stats.compliantResources = compliantResources;
this.stats.nonCompliantResources = nonCompliantResources;
};
this.stats.compliantResources = compliantResources
this.stats.nonCompliantResources = nonCompliantResources
}
public readonly fix = async () => {
this.stats.status = 'ERROR';
this.stats.status = 'ERROR'
this.stats.errorMessage.push({
date: new Date(),
message: 'Fixing recovery points requires custom implementation for backup setup.',
});
throw new Error(
'Fixing recovery points requires custom implementation for backup setup.'
);
};
message: 'Fixing recovery points requires custom implementation for backup setup.'
})
throw new Error('Fixing recovery points requires custom implementation for backup setup.')
}
private readonly getBuckets = async () => {
const response = await this.memoClient.send(new ListBucketsCommand({}));
return response.Buckets || [];
};
const response = await this.memoClient.send(new ListBucketsCommand({}))
return response.Buckets || []
}
}

Some files were not shown because too many files have changed in this diff Show More